How do I create a Set of custom objects (Swift)?

26,801

Solution 1

To make set of Person you need to make it conform to Equatable and Hashable protocols:

class Person: Equatable, Hashable {
    var Id: Int
    var Name: String

    init(id: Int, name: String?) {
        self.Id = id
        self.Name = name ?? ""
    }

    var hashValue: Int {
        get {
            return Id.hashValue << 15 + Name.hashValue
        }
    }
}

func ==(lhs: Person, rhs: Person) -> Bool {
    return lhs.Id == rhs.Id && lhs.Name == rhs.Name
}

Then you can use set of persons like this:

var set = Set<Person>()
set.insert(Person(id: 1, name: "name"))

Solution 2

With Swift 2.0, Hashable and Equitable is a part of NSObject. All you need to do is to override "isEqual" and "var hash:" for the property of interest. In this case: "Id", Set will exclude Person-objects with identical Ids.

    class Person: NSObject {
        var Id: Int
        var Name: String

        init(id: Int, name: String?) {
            self.Id = id
            self.Name = name ?? ""
        }

        override var hash: Int {
        return Id.hashValue
   }

        override func isEqual(object: AnyObject?) -> Bool {
            guard let rhs = object as? Person else {
                return false
            }
            let lhs = self

            return lhs.Id == rhs.Id
        }


    }

          func mergeArrays(){
       let person1 = Person(id: 1, name: "Tom")
       let person2 = Person (id: 2, name: "John")
       let person3 = Person(id: 3, name: "Adam")


       let downloadedPeople  = [person1,person2] //[{NSObject, Id 1, Name "Tom"}, {NSObject, Id 2, Name "John"}] 

       let peopleStoredLocally = [person1,person3] //[{NSObject, Id 1, Name "Tom"}, {NSObject, Id 3, Name "Adam"}]

       let downloadedPeopleSet = Set(downloadedPeople) //{{NSObject, Id 2, Name "John"}, {NSObject, Id 1, Name "Tom"}}


       let mergedSet = downloadedPeopleSet.union(peopleStoredLocally) //{{NSObject, Id 2, Name "John"}, {NSObject, Id 3, Name "Adam"}, {NSObject, Id 1, Name "Tom"}}


       let mergedArray = Array(mergedSet)//[{NSObject, Id 2, Name "John"}, {NSObject, Id 3, Name "Adam"}, {NSObject, Id 1, Name "Tom"}]

    }

Solution 3

UPDATE

Depretation warning when using hashValue:

'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'Person' to 'Hashable' by implementing 'hash(into:)' instead

Following the object Person example, nowadays implementation would be:

class Person: Equatable, Hashable {

    let id: Int
    let countryId: Int
    var name: String
    
    init(id: Int, countryId: Int, name: String) {
        self.id = id
        self.countryId = countryId
        self.name = name
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(countryId)
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id &&  lhs.countryId == rhs.countryId
    }
    
}

Note: As per documentation the components used for hashing must be the same as the components compared in your type’s == operator implementation.

Share:
26,801
Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    For my iOS app I have a model something like

    class Person {
        var Id: Int
        var Name: String
    
        init(id: Int, name: String?) {
            self.Id = id
            self.Name = name ?? ""
        }
    }
    

    Then later on in my ViewController when I load data from the server I add some people to an array

    class ViewController: UIViewController {
        var people:[Person] = []
    
        override func viewDidLoad() {
            self.loadPeople()
        }
    
        func loadPeople() {
            // This data will be coming from a server request
            // so is just sample. It could have users which 
            // already exist in the people array
    
            self.people.append(Person(id: "1", name: "Josh"))
            self.people.append(Person(id: "2", name: "Ben"))
            self.people.append(Person(id: "3", name: "Adam"))
        }
    }
    

    What I am now trying todo is turn the people array into a Set<Person> so it will not add duplicates. Is this possible to do or do I need to change my logic?

  • Vatsal Manot
    Vatsal Manot almost 9 years
    I personally use XOR to combine hashes, but +1 for including the Hashable protocol in your answer.
  • Martin R
    Martin R almost 9 years
    Hashable inherits from Equatable, so declaring the class as class Person: Hashable { ... } would suffice. Of course, adding Equatable explicitly does no harm.
  • Admin
    Admin almost 9 years
    @egor.zhdan could you explain the shift bits 15 part and why you used 15 there?
  • egor.zhdan
    egor.zhdan almost 9 years
    @lennard I'm using bits shift because it (probably) reduces hash collisions. There is no strong reason why using 15 - you can use Id.hashValue << 30 + Name.hashValue and it will work.
  • herby
    herby almost 8 years
    You should probably not use objC types for a swift app unless you really have to. So i'd recommend using the standalone protocols instead of inheriting from NSObject.