Ich frage mich, ob ich irgendwie ein x, y-Paar als Schlüssel zu meinem Wörterbuch verwenden kann
let activeSquares = Dictionary <(x: Int, y: Int), SKShapeNode>()
Aber ich bekomme den Fehler:
Cannot convert the expression's type '<<error type>>' to type '$T1'
und der Fehler:
Type '(x: Int, y: Int)?' does not conform to protocol 'Hashable'
Also ... wie können wir es konform machen?
Die Definition für Dictionary
ist struct Dictionary<KeyType : Hashable, ValueType> : ...
, d. H. Der Typ des Schlüssels muss dem Protokoll Hashable
entsprechen. Der Sprachführer sagt uns jedoch, dass Protokolle von Klassen, Strukturen und Aufzählungen übernommen werden können , d. H. Nicht von Tupeln. Daher können Tupel nicht als Dictionary
-Schlüssel verwendet werden.
Eine Problemumgehung wäre das Definieren eines hashbaren Strukturtyps, der zwei Ints enthält (oder was auch immer Sie in Ihr Tuple einfügen möchten).
Wie in der Antwort oben erwähnt, ist dies nicht möglich. Sie können Tuple jedoch mit dem Hashable-Protokoll in eine generische Struktur umwandeln:
struct Two<T:Hashable,U:Hashable> : Hashable {
let values : (T, U)
var hashValue : Int {
get {
let (a,b) = values
return a.hashValue &* 31 &+ b.hashValue
}
}
}
// comparison function for conforming to Equatable protocol
func ==<T:Hashable,U:Hashable>(lhs: Two<T,U>, rhs: Two<T,U>) -> Bool {
return lhs.values == rhs.values
}
// usage:
let pair = Two(values:("C","D"))
var pairMap = Dictionary<Two<String,String>,String>()
pairMap[pair] = "A"
Ich habe diesen Code in einer App erstellt:
struct Point2D: Hashable{
var x : CGFloat = 0.0
var y : CGFloat = 0.0
var hashValue: Int {
return "(\(x),\(y))".hashValue
}
static func == (lhs: Point2D, rhs: Point2D) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
struct Point3D: Hashable{
var x : CGFloat = 0.0
var y : CGFloat = 0.0
var z : CGFloat = 0.0
var hashValue: Int {
return "(\(x),\(y),\(z))".hashValue
}
static func == (lhs: Point3D, rhs: Point3D) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z
}
}
var map : [Point2D : Point3D] = [:]
map.updateValue(Point3D(x: 10.0, y: 20.0,z:0), forKey: Point2D(x: 10.0,
y: 20.0))
let p = map[Point2D(x: 10.0, y: 20.0)]!
Wenn es Ihnen nichts ausmacht, können Sie Ihren Tuple ganz einfach in einen String umwandeln und diesen dann für den Wörterbuchschlüssel verwenden ...
var dict = Dictionary<String, SKShapeNode>()
let tup = (3,4)
let key:String = "\(tup)"
dict[key] = ...
Ich empfehle, eine Struktur zu implementieren und eine Lösung ähnlich wie boost::hash_combine
zu verwenden.
Ich verwende Folgendes:
struct Point2: Hashable {
var x:Double
var y:Double
public var hashValue: Int {
var seed = UInt(0)
hash_combine(seed: &seed, value: UInt(bitPattern: x.hashValue))
hash_combine(seed: &seed, value: UInt(bitPattern: y.hashValue))
return Int(bitPattern: seed)
}
static func ==(lhs: Point2, rhs: Point2) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
func hash_combine(seed: inout UInt, value: UInt) {
let tmp = value &+ 0x9e3779b97f4a7c15 &+ (seed << 6) &+ (seed >> 2)
seed ^= tmp
}
Es ist viel schneller als die Verwendung von Zeichenfolge für den Hashwert.
Wenn Sie mehr über die magische Zahl erfahren möchten.
Sie benötigen keinen speziellen Code oder magische Zahlen, um Hashable zu implementieren
Hashable in Swift 4.2:
struct PairKey: Hashable {
let first: UInt
let second: UInt
func hash(into hasher: inout Hasher) {
hasher.combine(self.first)
hasher.combine(self.second)
}
static func ==(lhs: PairKey, rhs: PairKey) -> Bool {
return lhs.first == rhs.first && lhs.second == rhs.second
}
}
Weitere Informationen: https://nshipster.com/hashable/
Ab Swift 4.2 bietet die Standardbibliothek leider noch keine bedingte Konformität mit Hashable
für Tupel, und dies wird vom Compiler nicht als gültiger Code betrachtet:
extension (T1, T2): Hashable where T1: Hashable, T2: Hashable {
// potential generic `Hashable` implementation here..
}
Außerdem werden Strukturen, Klassen und Enumerationen mit Tupeln als Feldern nicht automatisch Hashable
synthetisiert.
Während andere Antworten die Verwendung von Arrays anstelle von Tupeln vorschlugen, würde dies zu Ineffizienzen führen. Ein Tuple ist eine sehr einfache Struktur, die leicht optimiert werden kann, da die Anzahl und die Art der Elemente zur Kompilierzeit bekannt sind. Eine Array
-Instanz weist fast immer mehr zusammenhängenden Speicher vor , um mögliche Elemente hinzuzufügen, die hinzugefügt werden sollen. Wenn Sie Array
type verwenden, müssen Sie außerdem Tuple-Typen gleich machen oder die Typlöschung verwenden. Das heißt, wenn Sie sich nicht für Ineffizienz interessieren, könnte (Int, Int)
in [Int]
gespeichert werden, aber (String, Int)
würde etwas wie [Any]
benötigen.
Die von mir gefundene Problemumgehung beruht auf der Tatsache, dass Hashable
automatisch für getrennt gespeicherte Felder synthetisiert. Daher funktioniert dieser Code auch ohne manuelles Hinzufügen von Hashable
- und Equatable
-Implementierungen wie Marek Gregors Antwort :
struct Pair<T: Hashable, U: Hashable>: Hashable {
let first: T
let second: U
}
Oder verwenden Sie stattdessen einfach Arrays. Ich habe versucht, den folgenden Code auszuführen:
let parsed:Dictionary<(Duration, Duration), [ValveSpan]> = Dictionary(grouping: cut) { span in (span.begin, span.end) }
Was mich zu diesem Beitrag geführt hat. Nachdem ich diese durchgelesen und enttäuscht hatte (weil sie, wenn sie Equatable
und Hashable
durch einfaches Anwenden des Protokolls synthetisieren können, ohne etwas zu tun, sollten sie es für Tupel tun können, nein?), Stellte ich plötzlich fest, verwende einfach Arrays. Keine Ahnung, wie effizient es ist, aber diese Änderung funktioniert gut:
let parsed:Dictionary<[Duration], [ValveSpan]> = Dictionary(grouping: cut) { span in [span.begin, span.end] }
Meine allgemeinere Frage lautet: "Warum sind Tupels also keine erstklassigen Strukturen wie Arrays? Python hat es abgelehnt (duck and run)."