How to create a collection of Identifiable objects similar to Set?
Asked Answered
V

1

7

A Set is great for avoiding duplicates, unions, and other operations. However, objects shouldn't be Hashable because the changes in the object will cause duplicates in a Set.

There is a List in SwiftUI that uses the Identifiable protocol to manage the collection, but is geared towards views. Is there collection that operates the same way?

For example, for the following object, I'd like to manage a collection:

struct Parcel: Identifiable, Hashable {
    let id: String
    var location: Int?
}

var item = Parcel(id: "123")
var list: Set<Parcel> = [item]

Later, I mutate item's location and update the list:

item.location = 33435
list.update(with: item)

This adds a duplicate item to the list since the hash has changed, but isn't intended since it has the same identifier. Is there a good way to handle a collection of Identifiable objects?

Vesicate answered 19/5, 2020 at 13:25 Comment(8)
Asking the difficult questions I see. I'll give you an upvote for this one.Waterloo
I suspect you'll need to extend a set and manually check the elements based on a given predicate. So suppose you have a Set<Car> and you add Honda and Honda well typically the set wouldn't allow that but you might need to tell that Set<Car> that the second Honda only has 3x tires instead of 4. On the surface, it looks like a duplicate but it's really not. The other option would be to identify WHY does it allow duplicates if a hash is changed? Extend that feature and remove the unwanted behavior.Waterloo
It seems a dictionary with the identifier as the key is the best homegrown way to do this, but was hoping for something native and elegant like SwiftUI's List.Vesicate
That makes sense. A dictionary is a "Collection" of sorts and it only allows one key per entry. If your key remains the same then it doesn't matter what the value is, it will only ever allow one key. I'm a bit confused about how this isn't elegant. A dictionary seems fairly straightforward.Waterloo
I'd like to operate on it more like an array than a dictionary. You're right I could extend it and copy the API's for the Set to offer methods like union, update, insert, but surprised nothing native for this unless I'm missing something.Vesicate
Just to add.. staying in the world of arrays, I have to constantly do things like this: let ids = items.map(\.id); parcels.filter { !ids.contains($0.id) } + items where I'd just like to do just items.union(parcels).Vesicate
Why not override the default implementation of Hashable for your type? func hash(into hasher: inout Hasher) { hasher.combine(id) }Chadburn
@JoakimDanielson you are totally right, this is the correct way to handle it. I will accept your answer if you post it.Vesicate
C
1

Implement hash(into) (and ==) for your type using only the id property

func hash(into hasher: inout Hasher) { 
    hasher.combine(id) 
}

static func == (lhs: Parcel, rhs: Parcel) -> Bool {
    lhs.id == rhs.id
}
Chadburn answered 21/5, 2020 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.