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