13

I have an array of object. I want to get distinct elements in this array by comparing objects based on its name property

class Item {
var name: String
init(name: String) {
    self.name = name
}
}
let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]

result:

let items = [Item(name:"1"), Item(name:"2"),Item(name:"3"), Item(name:"4")]

how can I do this in swift?

Hashem Aboonajmi
  • 13,077
  • 8
  • 66
  • 75

5 Answers5

38

Here is an Array extension to return the unique list of objects based on a given key:

extension Array {
    func unique<T:Hashable>(by: ((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(by(value)) {
                set.insert(by(value))
                arrayOrdered.append(value)
            }
        }

        return arrayOrdered
    }
}

For your example you can do:

let uniqueBasedOnName = items.unique{$0.name}
Ciprian Rarau
  • 3,040
  • 1
  • 30
  • 27
  • Thanks for that used it in the project then decided I only need to append unique data not remove it afterwards :) Added the answer at the end. – palme Mar 22 '21 at 19:03
  • 1
    Note that `by(value)` is calculated twice. If performance matters then assign the value to a variable and reuse – ULazdins Jan 12 '22 at 08:43
  • Can be improved - use just `if set.insert(by(value)).inserted {` and avoid calling `by(value)` twice. – Palli Mar 07 '22 at 10:11
4

Hope this will help you:

class Item:Equatable, Hashable {
    var name: String
    init(name: String) {
        self.name = name
    }
    var hashValue: Int{
      return name.hashValue
    }

}

func ==(lhs: Item, rhs: Item) -> Bool {
    return lhs.name == rhs.name
}


let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]

var uniqueArray = Array(Set(items))
Muzahid
  • 5,072
  • 2
  • 24
  • 42
1

I used the sweet answer of @Ciprian Rarau and then realised I don't even need to add the elements in the first place if they are not unique. So I wrote a little extension for that (inspired by the answer).

extension Array {
    public mutating func appendIfUnique<T: Hashable>(_ newElement: Element, check property: ((Element) -> (T))) {
        for element in self {
            if property(element) == property(newElement) { return }
        }
        
        append(newElement)
    }
}

Append elements only if unique:

array.appendIfUnique(newElement, check: { $0.name })
palme
  • 2,499
  • 2
  • 21
  • 38
0

In Swift you can use Equatable protocol to distinguish unique element in an Array of object.

 struct Item:Equatable{
        var name:String
        var price:Double

        init(name:String,price:Double) {
            self.name = name
            self.price = price
        }

        static func ==(lhs: Item, rhs: Item) -> Bool{
            return lhs.name == rhs.name
        }
    }

    class ViewController: UIViewController {

       var books = [Item]()
        override func viewDidLoad() {
            super.viewDidLoad()
            items.append(Item(name: "Example 1", price: 250.0))
            items.append(Item(name: "Example 2", price: 150.0))
            items.append(Item(name: "Example 1", price: 150.0))
            items.append(Item(name: "Example 1", price: 150.0))
            items.append(Item(name: "Example 3", price: 100.0))
            items.unique().forEach { (item) in
                print(item.name)
            }
        }

    }

    extension Sequence where Iterator.Element: Equatable {
        func unique() -> [Iterator.Element] {
            return reduce([], { collection, element in collection.contains(element) ? collection : collection + [element] })
        }
    }
Krishna Kumar Thakur
  • 1,456
  • 12
  • 27
0

Solution that uses keypaths instead of closures:

extension Sequence {
    func uniqued<Type: Hashable>(by keyPath: KeyPath<Element, Type>) -> [Element] {
        var set = Set<Type>()
        return filter { set.insert($0[keyPath: keyPath]).inserted }
    }
}

Example for struct Mock { var uuid: UUID; var num: Int }

let uuid = UUID()
let arr = [
    Mock(uuid: uuid, num: 1),
    Mock(uuid: uuid, num: 2),
    Mock(uuid: UUID(), num: 3)
]

let unique = arr.uniqued(by: \.uuid)

unique array will contain first (num = 1) and last (num = 3) elements.

mikro098
  • 2,173
  • 2
  • 32
  • 48