I have an array, with custom objects.
I Would like to pop the repeated objects, with the repeated properties:
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
in this case, pop the product2 or product3
-
By "pop" you mean remove? As in you want to remove duplicate objects from your array?Duncan C– Duncan C2015年11月22日 22:39:26 +00:00Commented Nov 22, 2015 at 22:39
-
stackoverflow.com/a/33207005/2303865Leo Dabus– Leo Dabus2015年11月23日 00:20:30 +00:00Commented Nov 23, 2015 at 0:20
6 Answers 6
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
using this you can so this
let unique = [product,product2,product3].unique{0ドル.subCategory}
this has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination
1 Comment
uniqueing
, and a mutating variant unique
could me written. That seems a little odd to me so I'd push for removingDuplicates
and removeDuplicates
as the pair of method names. - swift.org/documentation/api-design-guidelines/… You can use Swift Set
:
let array = [product,product2,product3]
let set = Set(array)
You have to make Product
conform to Hashable
(and thus, Equatable
) though:
class Product : Hashable {
var subCategory = ""
var hashValue: Int { return subCategory.hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.subCategory == rhs.subCategory
}
And, if Product
was a NSObject
subclass, you have to override isEqual
:
override func isEqual(object: AnyObject?) -> Bool {
if let product = object as? Product {
return product == self
} else {
return false
}
}
Clearly, modify those to reflect other properties you might have in your class. For example:
class Product : Hashable {
var category = ""
var subCategory = ""
var hashValue: Int { return [category, subCategory].hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}
6 Comments
Cannot invoke initializer for type 'Set<_>' with an argument list of type '([Product])'
Product
didn't conform to Hashable
. You might try explicitly declaring it (or extending it) as conforming to Hashable
.override hasValue
should work. Are you also implementing the Equatable
functions?Hashable
, then you shouldn't declare it to conform again. It was just that a failure to conform to Hashable
was the only way I could reproduce the error you shared with us. Anyway, I tested this with RLMObject
based Product
, and it worked fine (though I had to override isEqual
, too, as shown in revised answer).If Product
conforms to Equatable
, where a product is equal based on it's subcategory (and you don't care about order), you can add the objects to a set, and take an array from that set:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects
or
let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)
3 Comments
Hashable
NSObject
, which conforms to Equatable
and Hashable
.Here is a KeyPath based version of the Ciprian Rarau' solution
extension Array {
func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
var set = Set<T>()
return self.reduce(into: [Element]()) { result, value in
guard !set.contains(value[keyPath: keyPath]) else {
return
}
set.insert(value[keyPath: keyPath])
result.append(value)
}
}
}
example usage:
let unique = [product, product2, product3].unique(by: \.subCategory)
Comments
If your class conforms to protocol Hashable and you would like to keep the original array order you can create an extension as follow:
extension Array where Element: Hashable {
var uniqueElements: [Element] {
var elements: [Element] = []
for element in self {
if let _ = elements.indexOf(element) {
print("item found")
} else {
print("item not found, add it")
elements.append(element)
}
}
return elements
}
}
Comments
class Product {
var subCategory: String = ""
}
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
extension Product : Hashable {
var hashValue: Int {
return subCategory.hashValue
}
}
func ==(lhs: Product, rhs: Product)->Bool {
return lhs.subCategory == rhs.subCategory
}
let set = Set(array)
set.forEach { (p) -> () in
print(p, p.subCategory)
}
/*
Product one
Product two
*/
if an item is part of set or not doesn't depends on hashValue, it depends on comparation. if your product conform to Hashable, it should conform to Equatable. if you need that the creation of the set depends solely on subCategory, the comparation should depends solely on subCategory. this can be a big trouble, if you need to compare your products some other way