How do I make (Int,Int) Hashable?

I was surprised (Int,Int) isn't Hashable. How do I write the obvious extension to make it so?


If I try "extension (Int,Int)" the error is that non-nominal type (Int,Int) can't be extended. The word "nominal" appears nowhere in the Swift iBooks provided by Apple, so I can't be absolutely certain that I even know what Xcode is telling here... Using a typealias gives the same error.


How do I extend (Int,Int) to make it Hashable so I can use it for dictionary keys?


What is a "non-nominal" type, and where can I find that definition?

Answered by OOPer in 333321022

You cannot extend tuple types in Swift.


The term "nominal" is not clearly defined, but usually it is used when you mean `something identifiable by its name` in a context of speaking about programming language.


Another example of non-nominal type is function type. You cannot extend function types neither.


typealias CallbackType = (Int)->Void
extension CallbackType { //->Non-nominal type 'CallbackType' (aka '(Int) -> ()') cannot be extended
    //...
}

And nominal types are class, struct, enum and protocol.


One reason why you cannot extend tuple types is described in the Swift Book.


Tuples

NOTE

Tuples are useful for temporary groups of related values. They’re not suited to the creation of complex data structures. If your data structure is likely to persist beyond a temporary scope, model it as a class or structure, rather than as a tuple. For more information, see Structures and Classes.


Seems the designers of Swift want to limit tuples in very limited use cases and do not want to expand it.


There was once (maybe more) a discussion to make tuples Hashable, but the responses from the core members of Swift team are negative.

Synthesizing Equatable, Hashable, and Comparable for tuple types

(forums.swift.org/t/synthesizing-equatable-hashable-and-comparable-for-tuple-types/7111)

I think the following should work (it compiles at least).


As (Int, Int) can in fact represent a rational (set is called Q), that's where I selected the names.


The hash function is possible because Q is enumerable

It comes from Cantor polynom as described here (sorry, it is in french, but english page of wiki does not give this detail):

h ttps://fr.wikipedia.org/wiki/Ensemble_dénombrable


struct Q : Hashable {
     var pair : (p: Int, q: Int)

     var hashValue: Int {     // Need to prvide a hash function
          let cantorvalue = ((pair.p + pair.q) * (pair.p + pair.q)  + pair.p + 3 * pair.q ) / 2
          return Int(cantorvalue)
     }
}

func ==(left: Q, right: Q) -> Bool {          // Needed to be Equatable
     return left.pair == right.pair
}

You can now use Q type for dictionary key.


     var myDict = [Q : String]()
     let q = Q(pair: (1, 2))
     myDict[q] = "Hello word"


It is still a bit clumsy, should be improved, but hope that gives a direction to search for


Credit for the general scheme:

https://stackoverflow.com/questions/31438210/how-to-implement-the-hashable-protocol-in-swift-for-an-int-array-a-custom-s

Accepted Answer

You cannot extend tuple types in Swift.


The term "nominal" is not clearly defined, but usually it is used when you mean `something identifiable by its name` in a context of speaking about programming language.


Another example of non-nominal type is function type. You cannot extend function types neither.


typealias CallbackType = (Int)->Void
extension CallbackType { //->Non-nominal type 'CallbackType' (aka '(Int) -> ()') cannot be extended
    //...
}

And nominal types are class, struct, enum and protocol.


One reason why you cannot extend tuple types is described in the Swift Book.


Tuples

NOTE

Tuples are useful for temporary groups of related values. They’re not suited to the creation of complex data structures. If your data structure is likely to persist beyond a temporary scope, model it as a class or structure, rather than as a tuple. For more information, see Structures and Classes.


Seems the designers of Swift want to limit tuples in very limited use cases and do not want to expand it.


There was once (maybe more) a discussion to make tuples Hashable, but the responses from the core members of Swift team are negative.

Synthesizing Equatable, Hashable, and Comparable for tuple types

(forums.swift.org/t/synthesizing-equatable-hashable-and-comparable-for-tuple-types/7111)

A little hack to declare a Dictionary which accepts (Int, Int) as Key.


import Foundation

protocol IntPairConvertible {
    init(_ pair: (Int, Int))
}

struct IntPairStruct: Hashable, IntPairConvertible {
    let int0: Int
    let int1: Int
    
    init(_ pair: (Int, Int)) {
        self.int0 = pair.0
        self.int1 = pair.1
    }
}

extension Dictionary where Key: IntPairConvertible {
    subscript (key: (Int, Int)) -> Value? {
        get {
            return self[Key(key)]
        }
        set {
            self[Key(key)] = newValue
        }
    }
    subscript (key0: Int, key1: Int) -> Value? {
        get {
            return self[Key((key0, key1))]
        }
        set {
            self[Key((key0, key1))] = newValue
        }
    }
}

You can declare a Dictionary with Key as IntPairStruct,

var dict: [IntPairStruct: String] = [:]

And use it as:

dict[(1,2)] = "abc"
dict[(3,4)] = "def"
print(dict[(1,2)] as Any) //->Optional("abc")

Or like this (using the second definition of subscript):

print(dict[3,4] as Any) //->Optional("def")
I've modified OOPer's code to work for any pair of hashables:

Code Block Swift
private var foo: [PairStruct<Int, Int>: String] = [:]
func bar() {
foo[(1,2)] = "asdf"
}
protocol PairConvertible {
associatedtype X
associatedtype Y
init(_ pair: (X, Y))
}
struct PairStruct<X, Y>: Hashable, PairConvertible where X: Hashable, Y: Hashable {
let x: X
let y: Y
init(_ pair: (X, Y)) {
self.x = pair.0
self.y = pair.1
}
}
extension Dictionary where Key: PairConvertible {
subscript (key: (Key.X, Key.Y)) -> Value? {
get {
return self[Key(key)]
}
set {
self[Key(key)] = newValue
}
}
subscript (key0: Key.X, key1: Key.Y) -> Value? {
get {
return self[Key((key0, key1))]
}
set {
self[Key((key0, key1))] = newValue
}
}
}


How do I make (Int,Int) Hashable?
 
 
Q