kelan.io

Swift - Hashable for Sets

I wanted to create a Set from an Array of structs. But when I first tried, I got this error:

struct S {
    let i: Int
    let str: String
}

let a = S(i: 0, str: "zero")
let b = S(i: 1, str: "one")

let set = Set([ a, b ])  // <-- error: Cannot find an initializer for type 'Set<T>' that accepts an argument list of type '([S])'

To solve this, I first looked at the likely Set init() method that I wanted to use.

// from the Swift standard library generated interface:

struct Set<T : Hashable> : Hashable, CollectionType, ArrayLiteralConvertible {
    // …
    /// Create a `Set` from a finite sequence of items.
    init<S : SequenceType where T == T>(_ sequence: S)

Notice that the T from the init type constraint must be Hashable.

So, as previously, our structs need to conform to a protocol to work as we want – in this case, Hashable, which looks like this:

// from the Swift standard library generated interface:

/// Instances of conforming types provide an integer `hashValue` and
/// can be used as `Dictionary` keys.
protocol Hashable : Equatable {
    /// The hash value.
    ///
    /// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
    ///
    /// **Note:** the hash value is not guaranteed to be stable across
    /// different invocations of the same program.  Do not persist the
    /// hash value across program runs.
    var hashValue: Int { get }
}

Basically, this is the familiar -hash and -isEqual: pattern from Objective-C.

So, lets make our struct Hashable, which also requires Equatable.

extension S: Hashable, Equatable {
    var hashValue: Int { return i.hashValue ^ str.hashValue }
}
func ==(lhs: S, rhs: S) -> Bool {
    return lhs.i == rhs.i && lhs.str == rhs.str
}

And, now it works.

Putting it all together

struct S {
    let i: Int
    let str: String
}

extension S: Hashable, Equatable {
    var hashValue: Int { return i.hashValue ^ str.hashValue }
}
func ==(lhs: S, rhs: S) -> Bool {
    return lhs.i == rhs.i && lhs.str == rhs.str
}

let a = S(i: 0, str: "zero")
let b = S(i: 1, str: "one")
let c = S(i: 0, str: "zero")

let arr = [ a, b, c ]
let set = Set(arr)

print("arr.count=\(arr.count) set.count=\(set.count)")  // => arr.count=3 set.count=2