4

In a Swift 5.0 Playground, I was experimenting with extensions on CGPoint such that it treated points as integer values in terms of hashing and equality.

To my surprise, even after overriding hash() and == on CGPoint, a Set of CGPoints still held similar values as independent points even though two elements should have collided and kept only one. Is there some other method you would need to override on CGPoint to have this work as expected?

P.S. Probably best not to do this in practice as it might affect the system, better to provide some kind of wrapper to manage equality. But I would like to know why this does not work.

The Playground contents along with results given after execution:

// Extension to treat points as integer values (not reccomended approach)
extension CGPoint : Hashable {
    public func hash(into hasher: inout Hasher) {
                hasher.combine(Int(x))
        hasher.combine(Int(y))
    }

    static public func == (lhs: CGPoint, rhs: CGPoint) -> Bool {
        return Int(lhs.x) == Int(rhs.x) && Int(lhs.y) == Int(rhs.y)
    }
}

var pSet : Set<CGPoint> = []

let p1 = CGPoint.zero
let p2 =  CGPoint(x: 20.1, y: 30)
let p3 =  CGPoint(x:20, y: 30)

pSet.insert(p1) // inserted true
pSet.insert(p2) // inserted true
pSet.insert(p3) // inserted true(!), should be false

p2 == p3 // true

pSet.count // 3(!), should be two
p2.hashValue  // Same as p3
p3.hashValue  // Same as p2

pSet // shows all three values, all points defined, should be two values only
Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • 1
    1. Have you verified your `hash(into:)` is being called? 2. `CGPoint` already conforms to `Equatable` so your attempt to also implement your own `==` function may be conflicting with the existing implementation. It's undefined to do such a thing in an extension. – rmaddy Jul 09 '19 at 05:56
  • I'm not sure, but from the documentation of the Set's insert method I noticed this: "Returns (true, newMember) if newMember was not contained in the set. If an element equal to newMember was already contained in the set, the method returns (false, oldMember), where oldMember is the element that was equal to newMember. `In some cases, oldMember may be distinguishable from newMember by identity comparison or some other means."`. I think the last sentese has the answer to your question. – 3li Jul 09 '19 at 05:57
  • 2
    Your `== ` method is *not* called when the points are inserted into the set. – Martin R Jul 09 '19 at 06:05
  • 1
    Related: https://stackoverflow.com/q/38213286/1187415. – Martin R Jul 09 '19 at 06:06

1 Answers1

3

rmaddy and Martin R identified the problem, but here it is as an answer: Set doesn't use your == function. It uses the == function defined in the standard library, because Set is defined in the standard library. You can prove this by calling print is your == function. When you insert a point into Set, you will see that your print doesn't run.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848