0

I have a situation where I have 2 types, which must be structs for other reasons.

These two types are very similar, but are slightly different. However, their hashing & equality functions are exactly the same.

struct S1: Hashable {
    let data: String
    let otherData: String
    
    // ... Equality & Hash functions, both only use data
}
struct S2: Hashable {
    let data: String
    let otherData: Int
    
    // ... Equality & Hash functions, both only use data
}

Suppose I had a Set like this:

var set: Set<S1> = ...

How could I do a contains operation on set, but use a S2 object instead, whilst maintaining the O(1) time complexity?

let obj: S2 = ...
set.contains(obj)

Is there a way to provide a custom equality & hashing object, or have some equivalent behavior?

1 Answers1

0

You can make a protocol containing all the shared data, so both S1 and S2 go through the same hasher.

I created a SharedData protocol containing the shared properties (only data in this case) and gave it a Hashable implementation. Then S1 and S2 now conform to SharedData. It looks like this:

protocol SharedData: Hashable {
    var data: String { get }
}

extension SharedData {
    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
}

struct S1: SharedData {
    let data: String
    let otherData: String

    // ... Equality & Hash functions, both only use data
}
struct S2: SharedData {
    let data: String
    let otherData: Int

    // ... Equality & Hash functions, both only use data
}

And the comparison looks like this:

print(S1(data: "hi", otherData: "there").hashValue)
print(S2(data: "hi", otherData: 1).hashValue)

let set: Set<Int> = [S1(data: "Hello", otherData: "world!").hashValue, S1(data: "abc", otherData: "123").hashValue]

let obj2 = S2(data: "Hello", otherData: 0)
print(set.contains(obj2.hashValue))

Prints:

-5068166682035457964  // <- these change every program execution
-5068166682035457964
true

Unfortunately it's not possible to do the following, yet:

let set: Set<SharedData> = [S1(data: "Hello", otherData: "world!"), S1(data: "abc", otherData: "123")]

Error:

Protocol 'SharedData' as a type cannot conform to 'Hashable'

I believe SE-0309 (Unlock existentials for all protocols) could fix this.

George
  • 25,988
  • 10
  • 79
  • 133
  • Thanks for the answer! How would I keep `set` as a `Set` though? –  Aug 30 '21 at 01:55
  • @Joe I'll carry on having a look if there is a better solution - but right now protocols (which you need because you can only use `struct`) are relatively limited in situations like this. See edit on end of answer – George Aug 30 '21 at 01:58
  • That's too bad. I hope apple implements that soon! But thank you for your help –  Aug 30 '21 at 02:00
  • @Joe Yep, unfortunately this is the only way for now. If you still need `S1` (fast), create a dictionary like `let dict: [Int: S1] = [a.hashValue: a, ...]` then check if `obj2` is in there with `print(dict[obj2.hashValue] != nil)`. – George Aug 30 '21 at 02:08