Skip to content

Instantly share code, notes, and snippets.

@phausler
Created May 19, 2017 02:05
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phausler/6c61343a609aeeb9a8f890f1fe2acc17 to your computer and use it in GitHub Desktop.
Save phausler/6c61343a609aeeb9a8f890f1fe2acc17 to your computer and use it in GitHub Desktop.
Hashability via Codability
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