Skip to content

Instantly share code, notes, and snippets.

@inamiy
Last active May 5, 2020 06:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inamiy/0ec54da159f01a7215f4125f65d375ae to your computer and use it in GitHub Desktop.
Save inamiy/0ec54da159f01a7215f4125f65d375ae to your computer and use it in GitHub Desktop.
// https://twitter.com/inamiy/status/1257538502226358272
struct Hashable1: Hashable {
struct Hashable1a: Hashable {}
}
struct Hashable2: Hashable {}
Hashable1().hashValue
Hashable1.Hashable1a().hashValue
Hashable2().hashValue
// all same
AnyHashable(Hashable1()).hashValue
AnyHashable(Hashable1.Hashable1a()).hashValue
AnyHashable(Hashable2()).hashValue
// all same
var dict: [AnyHashable: Int] = [Hashable1(): 999]
//dict[Hashable1()] // 999
//dict[Hashable1.Hashable1a()] // nil
//dict[Hashable2()] // nil
//----------------------------------------
enum Hashables: Hashable {
case x1(Hashable1)
case x1a(Hashable1.Hashable1a)
case x2(Hashable2)
}
var dict2: [Hashables: Int] = [.x1(Hashable1()): 999]
dict2[.x1(Hashable1())] // 999
dict2[.x1a(Hashable1.Hashable1a())] // nil
dict2[.x2(Hashable2())] // nil
//----------------------------------------
struct MyAnyHashable: Equatable, Hashable { // NOTE: Ugly impl.
let _hashValue: Int
let _hash: (inout Hasher) -> Void
let _isEqual: (Any) -> Bool
let _value: Any
init<H: Hashable>(_ value: H) {
self._hashValue = value.hashValue
self._hash = value.hash(into:)
self._isEqual = {
if let x = $0 as? H {
return value == x
} else {
return false
}
}
self._value = value
}
var hashValue: Int { self._hashValue }
func hash(into hasher: inout Hasher) { _hash(&hasher) }
static func == (l: MyAnyHashable, r: MyAnyHashable) -> Bool {
l._isEqual(r._value)
}
}
var dict3: [MyAnyHashable: Int] = [MyAnyHashable(Hashable1()): 999]
dict3[MyAnyHashable(Hashable1())] // 999
dict3[MyAnyHashable(Hashable1.Hashable1a())] // nil
dict3[MyAnyHashable(Hashable2())] // nil
// P.S. Just to make sure `MyAnyHashable` works like `AnyHashable`...
AnyHashable(Hashable1()) == AnyHashable(Hashable1()) // true
AnyHashable(Hashable1()) == AnyHashable(Hashable2()) // false
MyAnyHashable(Hashable1()) == MyAnyHashable(Hashable1()) // true
MyAnyHashable(Hashable1()) == MyAnyHashable(Hashable2()) // false
//----------------------------------------
// https://twitter.com/inamiy/status/1257558273676328960
struct Foo: Hashable {
let value: String
init(_ value: String) { self.value = value }
// Manual impl
static func == (l: Foo, r: Foo) -> Bool {
return true
// return l.value == r.value
}
// Manual impl
func hash(into hasher: inout Hasher) {
hasher.combine(self.value)
}
}
Foo("ok") == Foo("not ok")
Foo("ok") == Foo("err")
var dict4: [Foo: Int] = [Foo("ok"): 999]
dict4[Foo("ok")] // 999
dict4[Foo("not ok")] // sometimes 999, sometimes not!
dict4[Foo("err")] // sometimes 999, sometimes not!
// Quiz: Try using / commenting-out "Manual impl"s to see behavior difference.
@inamiy
Copy link
Author

inamiy commented May 5, 2020

Explanation from @lorentey : https://twitter.com/lorentey/status/1257549455907160066

The enum case is encoded in the enum’s hash value, but hashing does not typically encode type information. (AnyHashable should do that, but it doesn’t yet — this leads to more frequent hash collisions.)

Swift’s Hashable works very hard to prevent reproducible hash collisions, but hashing maps an arbitrary amount of information into a single 64-bit (or 32-bit) value — so hash collisions are unavoidable, and the hash tables in Set and Dictionary must be able to handle them.

They do that by falling back to equality checking, so that they are 100% guaranteed never to return a false match. Hashing is really only used to (drastically) cut down the number of equality tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment