Vinícius Albino
Vinícius Albino

Reputation: 547

Unique Objects inside a Array Swift

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

Upvotes: 12

Views: 14347

Answers (6)

Tomasz Pe
Tomasz Pe

Reputation: 716

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)

Upvotes: 2

Ciprian Rarau
Ciprian Rarau

Reputation: 3120

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

Upvotes: 25

user3441734
user3441734

Reputation: 17534

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

Upvotes: 1

Leo Dabus
Leo Dabus

Reputation: 236305

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
    }
}

Upvotes: 1

Rob
Rob

Reputation: 437432

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
}

Upvotes: 10

JAL
JAL

Reputation: 42449

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)

Upvotes: 2

Related Questions