12

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

rmaddy
319k44 gold badges548 silver badges590 bronze badges
asked Nov 22, 2015 at 22:29
2
  • By "pop" you mean remove? As in you want to remove duplicate objects from your array? Commented Nov 22, 2015 at 22:39
  • stackoverflow.com/a/33207005/2303865 Commented Nov 23, 2015 at 0:20

6 Answers 6

25

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

answered Jul 10, 2017 at 23:44

1 Comment

From the API design guidelines this method should probably be called 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/…
10

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
}
answered Nov 22, 2015 at 22:52

6 Comments

You should mention that using a set will not preserve the array order.
@Rob im using RLMObject (with is an NSObject, so conforms to Equatable, and Hashable). Tried to override var hashValue: Int { return subCategoria.hashValue } ` but im only getting the error Cannot invoke initializer for type 'Set<_>' with an argument list of type '([Product])'
Hmm. That's the error that you get if Product didn't conform to Hashable. You might try explicitly declaring it (or extending it) as conforming to Hashable.
@user1108474 Just tried it myself with just some super and subclass. override hasValue should work. Are you also implementing the Equatable functions?
@user1108474 - Correct, if it already conformed to 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).
|
2

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)
answered Nov 22, 2015 at 22:40

3 Comments

Was writing my answer and tried your sweet Set solution. It did not work in an extension of Array :/
Found it, for a Set based solution, the objects need to be Hashable
@RMenke Ah that's right, I assumed the object conformed to NSObject, which conforms to Equatable and Hashable.
2

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)
answered Jan 17, 2021 at 20:54

Comments

1

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
 }
}
answered Nov 23, 2015 at 0:21

Comments

1
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

answered Nov 22, 2015 at 23:13

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.