5

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

Confused
  • 6,048
  • 6
  • 34
  • 75
  • Related: [In Swift can I use a tuple as the key in a dictionary?](http://stackoverflow.com/q/24131323/2976878) – Hamish Dec 21 '16 at 00:42

1 Answers1

6

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.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • Excellent. About the floats, can I force use of the CGPoint variant that is based on Integers? – Confused Dec 21 '16 at 05:16
  • Added image to question as I can't add to a comment here – Confused Dec 21 '16 at 05:21
  • Even if you use the initializer of `CGPoint` taking `Int`, the values are internally converted to `CGFloat`. If you mean your own struct with _the CGPoint variant_, you can do it. You can write something like `struct MyPoint: Hashable {...}`, just that you need to define both `hashValue` property and `==` operator. – OOPer Dec 21 '16 at 05:22
  • Good to know about the use of CGFloats internally. Thank you!! Creating my own struct is too confusing, for me... I'm... – Confused Dec 21 '16 at 05:23
  • Added some example code for defining your own struct. Not too difficult, I think. Just practice. – OOPer Dec 21 '16 at 05:31
  • OH! Now I'm all giddy with power. I can shape the world. Of code ;). One question, and I apologise for my profuse lack of OOP grokkery. Why is this a class static func rather than a struct that works like: `let myStruct = MyPoint(x: 2, y: 3)` ? – Confused Dec 21 '16 at 05:36
  • Sorry, I'm afraid I cannot get what you mean with the last question. You are asking about the definition of `==`? Or something about initializer? Or just class or struct issue? – OOPer Dec 21 '16 at 05:41
  • Sorry. Let me try again. You have chosen to use a class static function, so the hashable "value" is `MyPoint[x: 1, y: 2]` since the class name is now part of that which is hashable. Is this because it's not possible to create struct instances that are hashable? Or some other reason? To be honest, this entire part confuses me deeply: `public static func == (lhs: MyPoint, rhs: MyPoint) -> Bool { return lhs.x == rhs.x && lhs.y == rhs.y` – Confused Dec 21 '16 at 05:45
  • Defining an operator with `public static` (`MyPoint` is not a class, so it should be called as "type static", not "class static"), is a requirement when writing an operator to conform to some protocol declaring the operator. The type of the arguments of the operator is `MyPoint`, so with that definition you can write something like this: `let p1 = MyPoint(x: 1, y: 2)` `let p2 = MyPoint(x: 1, y: 2)` `if p1 == p2 {...}`. When you compare two `MyPoint`s, you may want to compare both `x` and `y`, no? – OOPer Dec 21 '16 at 05:54
  • Maybe. I think this comes back to my problems of understanding how the conformance is achieved through this comparison. To my mind (designer viewpoint), that this is not somehow more automatic is a function of poor design of the language... but I have no height from which to make that claim. It just seems like a complete mess of boilerplate, arbitrariness and contrivance. And pisses me off. Sorry. – Confused Dec 21 '16 at 06:05
  • And, I'm absolutely infuriated that Ints, both of which are hashable, can't be used in a tuple as a key for a dictionary. That looks like it's deliberately designed to annoy, or a half arsed tuple implementation in the language. But... again, I am coming at this from a very naive point of view. And heading towards trying to learn NSCoding, which my previewing of makes me even more infuriated by the absolute lack of care in design and communication of what and how, where and when, and why. – Confused Dec 21 '16 at 06:09
  • Maybe your complaints about the current status of Swift is reasonable. Just that I (or many other users writing answers to Swift questions) are too accustomed to the current Swift. To improve Swift, some fresh point of view must be needed. Participating in a ML of swift.org, or writing a feature request with [Apple's bug reporter](http://developer.apple.com/bug-reporting/) would be a good starting point. – OOPer Dec 21 '16 at 06:14
  • My reactions are at a gut, instinctual and (sometimes) aesthetic and interpretive level. They're not borne out of reason or understanding of compilers and programming language paradigms of old, but a vision and dream of how they could be. No doubt a good dose of ignorance and naivety contributes to my views, too. – Confused Dec 21 '16 at 06:23
  • In older Swift, you cannot even [compare tuples](https://github.com/apple/swift-evolution/blob/master/proposals/0015-tuple-comparison-operators.md). Someone raised a simple, primitive opinion where Swift developers can see, and the opinion was refined to the proposal, and now it's a part of Swift. I would not say any more about this, but sharing sort of complaints is not always a bad thing. – OOPer Dec 21 '16 at 06:45
  • Is this the part that conforms CGPoint to hashable: `return x.hashValue ^ y.hashValue`? – Confused Dec 21 '16 at 17:36
  • I'm not sure what your `this` is addressing. I just have expressed that Swift is changing. In older Swift, this sort of comparison was invalid -- `if (x,y) == (1,2) {...}`, now you can do it in Swift 3. There may be some chance on making tuples or structs automatically `Hashable`, or at least on knowing why Swift Team would not do it. – OOPer Dec 22 '16 at 00:22