Created
May 19, 2017 02:05
-
-
Save phausler/6c61343a609aeeb9a8f890f1fe2acc17 to your computer and use it in GitHub Desktop.
Hashability via Codability
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct HashingSingleValueEncodingContainer : SingleValueEncodingContainer { | |
var owner: HashingEncoder | |
mutating func combineHash<T>(of element: T?) where T: Hashable { | |
if let elt = element { | |
owner.combine(elt, hash: elt.hashValue) { (other: Any) -> Bool in | |
if let otherValue = other as? T { | |
return elt == otherValue | |
} | |
return false | |
} | |
} | |
} | |
mutating func encode(_ value: Bool) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Int) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Int8) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Int16) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Int32) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Int64) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: UInt) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: UInt8) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: UInt16) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: UInt32) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: UInt64) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Float) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: Double) throws { | |
combineHash(of: value) | |
} | |
mutating func encode(_ value: String) throws { | |
combineHash(of: value) | |
} | |
} | |
struct HashingUnkeyedEncodingContainer : UnkeyedEncodingContainer { | |
var codingPath: [CodingKey?] { return owner.codingPath } | |
var owner: HashingEncoder | |
mutating func encode<T>(_ value: T?) throws where T : Encodable { | |
if let elt = value { | |
let _owner = owner | |
owner.combine(elt, hash: owner.hash(elt)) { (other: Any) -> Bool in | |
if let otherValue = other as? T { | |
return _owner.compare(elt, otherValue) | |
} | |
return false | |
} | |
} | |
} | |
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> { | |
fatalError("An excersize for the reader") | |
} | |
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { | |
fatalError("An excersize for the reader") | |
} | |
mutating func superEncoder() -> Encoder { | |
fatalError("An excersize for the reader") | |
} | |
} | |
struct HashingKeyedEncodingContainer<EncodingKey: CodingKey> : KeyedEncodingContainerProtocol { | |
var owner: HashingEncoder | |
var codingPath: [CodingKey?] { return owner.codingPath } | |
mutating func combineHash<T>(of element: T?) where T: Hashable { | |
if let elt = element { | |
owner.combine(elt, hash: elt.hashValue) { (other: Any) -> Bool in | |
if let otherValue = other as? T { | |
return elt == otherValue | |
} | |
return false | |
} | |
} | |
} | |
public mutating func encode<T>(_ value: T?, forKey key: EncodingKey) throws where T : Encodable { | |
if let elt = value { | |
let _owner = owner | |
owner.combine(elt, hash: owner.hash(elt)) { (other: Any) -> Bool in | |
if let otherValue = other as? T { | |
return _owner.compare(elt, otherValue) | |
} | |
return false | |
} | |
} | |
} | |
public mutating func encode(_ value: Int?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Int8?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Int16?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Int32?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Int64?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: UInt?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: UInt8?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: UInt16?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: UInt32?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: UInt64?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Float?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: Double?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encode(_ value: String?, forKey key: EncodingKey) throws { | |
combineHash(of: value) | |
} | |
public mutating func encodeWeak<T>(_ object: T?, forKey key: EncodingKey) throws where T : AnyObject, T : Encodable { | |
guard let obj = object else { | |
combineHash(of: 0) | |
return | |
} | |
combineHash(of: ObjectIdentifier(obj)) | |
} | |
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: EncodingKey) -> KeyedEncodingContainer<NestedKey> { | |
fatalError("An excersize for the reader") | |
} | |
mutating func nestedUnkeyedContainer(forKey key: EncodingKey) -> UnkeyedEncodingContainer { | |
fatalError("An excersize for the reader") | |
} | |
mutating func superEncoder() -> Encoder { | |
fatalError("An excersize for the reader") | |
} | |
mutating func superEncoder(forKey key: EncodingKey) -> Encoder { | |
fatalError("An excersize for the reader") | |
} | |
} | |
class HashingEncoder : Encoder { | |
var codingPath = [CodingKey?]() | |
var userInfo = [CodingUserInfoKey : Any]() | |
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> { | |
return KeyedEncodingContainer(HashingKeyedEncodingContainer<Key>(owner: self)) | |
} | |
func unkeyedContainer() -> UnkeyedEncodingContainer { | |
return HashingUnkeyedEncodingContainer(owner: self) | |
} | |
func singleValueContainer() -> SingleValueEncodingContainer { | |
return HashingSingleValueEncodingContainer(owner: self) | |
} | |
var encoded = [(Any, Int, (Any) -> Bool)]() | |
func combine(_ value: Any, hash: Int, equal: @escaping (Any) -> Bool) { | |
encoded.append((value, hash, equal)) | |
} | |
func hash<T>(_ value: T) -> Int where T: Encodable { | |
do { | |
try value.encode(to: self) | |
} catch { | |
// ignore | |
} | |
return encoded.reduce(0) { (hashValue: Int, entry: (Any, Int, (Any) -> Bool)) -> Int in | |
return hashValue ^ entry.1 // perhaps better mixing here? | |
} | |
} | |
func compare<T>(_ lhs: T, _ rhs: T) -> Bool where T: Encodable { | |
var lhsEncoding: [(Any, Int, (Any) -> Bool)] | |
var rhsEncoding: [(Any, Int, (Any) -> Bool)] | |
do { | |
try lhs.encode(to: self) | |
lhsEncoding = encoded | |
encoded.removeAll(keepingCapacity: true) | |
try rhs.encode(to: self) | |
rhsEncoding = encoded | |
} catch { | |
return false | |
} | |
guard lhsEncoding.count == rhsEncoding.count else { return false } | |
for idx in 0..<lhsEncoding.count { | |
let comparator = lhsEncoding[idx].2 | |
let rhsValue = rhsEncoding[idx].0 | |
if comparator(rhsValue) == false { | |
return false | |
} | |
} | |
return true | |
} | |
} | |
extension Hashable where Self: Codable { | |
var hashValue: Int { | |
let encoder = HashingEncoder() | |
return encoder.hash(self) | |
} | |
static func ==(_ lhs: Self, _ rhs: Self) -> Bool { | |
let encoder = HashingEncoder() | |
return encoder.compare(lhs, rhs) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment