7

I want to do set operations on co-ordinate pair elements from an x-y grid.

E.g. {(0,0),(1,4),(1,5),(2,3)} union with {(2,3),(1,4),(2,6)} = {(0,0),(1,4),(1,5),(2,3),(2,6)}

Unfortunately I can't work out a way of inserting tuples into Swift's Set commands as it says that they do not conform to the 'hashable' protocol.

Error: type '(Int, Int)' does not conform to protocol 'Hashable'

I believe I've got a work around but it involves a lot of code. Is there a simple way that I'm missing before I hit the grindstone?

hippietrail
  • 15,848
  • 18
  • 99
  • 158
  • 1
    Why not use CGPoint instead of a tuple. – Abizern Oct 07 '15 at 09:36
  • 1
    Essentially the same problem as in [In Swift can I use a tuple as the key in a dictionary?](http://stackoverflow.com/questions/24131323/in-swift-can-i-use-a-tuple-as-the-key-in-a-dictionary). – Martin R Oct 07 '15 at 09:59

3 Answers3

3

Rather than using tuples to represent points, use the built in type CGPoint. You can extend CGPoint to be hashable by extending it:

import UIKit

extension CGPoint: Hashable {
    public var hashValue: Int {
        return self.x.hashValue << sizeof(CGFloat) ^ self.y.hashValue
    }
}

// Hashable requires Equatable, so define the equality function for CGPoints.
public func ==(lhs: CGPoint, rhs: CGPoint) -> Bool {
    return CGPointEqualToPoint(lhs, rhs)
}

Now that CGPoint is Hashable, you can use it in sets. For example:

let point1 = CGPoint(x: 0, y: 1)
let point2 = CGPoint(x: 0, y: 2)
let point3 = CGPoint(x: 1, y: 1)
let point4 = CGPoint(x: 3, y: 3)
let point5 = CGPoint(x: 3, y: 3)  // Intentionally the same as point4 to see the effect in union and difference.

let set1 = Set([point1, point2 , point5])
let set2 = Set([point4, point3])

let union = set1.union(set2) // -> {{x 0 y 2}, {x 3 y 3}, {x 0 y 1}, {x 1 y 1}}
let difference = set1.intersect(set2) // -> {{x 3 y 3}}
Abizern
  • 146,289
  • 39
  • 203
  • 257
  • 1
    Thanks Abizern. For those reading the above code who don’t immediately understand it ( I didn’t) try: Googling: CG Point – to find out about the Core Graphics framework And then check out the links provided in the initial question to: http://stackoverflow.com/questions/31438210/how-to-implement-the-hashable-protocol-in-swift-for-an-int-array-a-custom-strin?lq=1 Particularly the article referenced entitled “Implementing Swift’s Hashable Protocol” – Moonfaced Baboon Oct 08 '15 at 22:14
  • 1
    I'm glad you took the time to understand my example. It's easy for a beginner to just accept an answer and apply it. By learning how to learn for yourself you're on your way to becoming a better programmer.. – Abizern Oct 08 '15 at 22:17
  • 1
    `CGPoint` may not exactly be the right thing here. If we were to infer types from @MoonfacedBaboon's example as the compiler would, then we'd have `[(Int, Int)]`. `CGPoint`, however converts your integer arguments to `CGFloats`. That means there will be a mandatory cast back to `Int` after performing any set operations which is just noise. I'd also worry about the possibility for floating point error during set operations depending on the choices for input values. – mgadda Nov 22 '17 at 17:15
1

You could make a struct as a Hashable type:

struct Point: Hashable {
  let x: Int
  let y: Int
}

Now that you have a hashable tuple, normal Set operations can be used:

let set1 = Set([
  Point(x:0,y:0),
  Point(x:1,y:4),
  Point(x:1,y:5),
  Point(x:2,y:3)
]) 

let set2 = Set([
  Point(x:2,y:3),
  Point(x:1,y:4),
  Point(x:2,y:6)
])

let setUnion = set1.union(set2)

/*
setUnion = {
  Point(x: 1, y: 5), 
  Point(x: 0, y: 0), 
  Point(x: 1, y: 4), 
  Point(x: 2, y: 3), 
  Point(x: 2, y: 6)
}
*/
imnegan
  • 23
  • 6
0

Here you go:

class Pair {
    var x: Int
    var y: Int
    init(x: Int, y:Int){
        self.x = x
        self.y = y
    }

    func isExisted(inPairs pairs:[Pair]) -> Bool {
        for p in pairs {
            if p.y == self.y && p.x == self.x{
                return true
            }
        }
        return false
    }

    static func union (pairs1: [Pair], pairs2: [Pair]) -> [Pair] {
        var pairsFinal = [Pair]()

        for p in pairs1 {
            pairsFinal.append(p)
        }

        for p in pairs2 {
            if !p.isExisted(inPairs: pairsFinal){
                pairsFinal.append(p)
            }
        }
        return pairsFinal
    }
}

let pari1 = Pair(x: 4, y: 7)
let pair2 = Pair(x: 5, y: 2)
let pair3 = Pair(x: 4, y: 7)
let pair4  = Pair(x: 3, y: 9)

let pairs1 = [pari1, pair2]
let pairs2 = [pair3, pair4]

let f = Pair.union(pairs1, pairs2: pairs2)

And this is the result of the union:

enter image description here

William Kinaan
  • 28,059
  • 20
  • 85
  • 118
  • Thanks William. Yes this was the work-around (or at least similar to) that I had in mind - yours being more elegant. – Moonfaced Baboon Oct 08 '15 at 22:19
  • Given that `union` is already implemented by `Set` in the standard library, I don't think it makes good sense to reimplement it here. Far easier to implement a custom struct which implements Hashable. If you decided tomorrow that you also wanted intersection, you'd have to reimplement that as well. But if you just implement Hashable once, you get everything else for free. – mgadda Nov 22 '17 at 17:08