Make simple tuple conform to Hashable, so can be a Dictionary Key

2.7k views Asked by At

I'd like to use a very simple tuple as a key:

(Int, Int)

Dictionary keys need to be Hashable. I've learnt.

But can't find how I make this simple tuple Hashable, and do struggle with protocol conformance at the best of times.

More profoundly, a CGPoint would solve my problems. It can be of this format, but is not hashable.

Is it possible to extend a CGPoint so it's hashable? If so, how?

EDIT: Image of Int variant of CGPoint choice.

enter image description here

1

There are 1 answers

16
OOPer On BEST ANSWER

Making conform to Hashable is not difficult for class, struct or enum. You just need to explicitly declare conformance to Hashable and define a property hashValue: Int. Practically, the hashValue needs to fulfill one simple axiom: if a == b then a.hashValue == b.hashValue.

(To conform Hashable, you also need to make the type Equatable. In case of CGPoint, it is already Equatable.)

An example to make CGPoint conform to Hashable:

extension CGPoint: Hashable {
    public var hashValue: Int {
        //This expression can be any of the arbitrary expression which fulfills the axiom above.
        return x.hashValue ^ y.hashValue
    }
}

var pointDict: [CGPoint: String] = [
    CGPoint(x: 1.0, y: 2.0): "PointA",
    CGPoint(x: 3.0, y: 4.0): "PointB",
    CGPoint(x: 5.0, y: 6.0): "PointC",
]
print(pointDict[CGPoint(x: 1.0, y: 2.0)]) //->Optional("PointA")

As CGPoint contains CGFloat values, so, CGPoint as Key of Dictionary may cause unexpected behavior based on the calculation error of binary floating-point system. You need to use it with extra caution.


ADDITION

If you want to avoid some calculation error issue and can accept that the structure can only contain Ints, you can define your own struct and make it conform to Hashable:

struct MyPoint {
    var x: Int
    var y: Int
}
extension MyPoint: Hashable {
    public var hashValue: Int {
        return x.hashValue ^ y.hashValue
    }

    public static func == (lhs: MyPoint, rhs: MyPoint) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}
var myPointDict: [MyPoint: String] = [
    MyPoint(x: 1, y: 2): "MyPointA",
    MyPoint(x: 3, y: 4): "MyPointB",
    MyPoint(x: 5, y: 6): "MyPointC",
]
print(myPointDict[MyPoint(x: 1, y: 2)]) //->Optional("MyPointA")

Not much more difficult than the code above, one more thing you need is just defining == operator for the struct. Please make it a try.