webentwicklung-frage-antwort-db.com.de

Kann ich in Swift einen Tuple als Schlüssel in einem Wörterbuch verwenden?

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?

48
JuJoDi

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).

36
Lukas

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"
19
Marek Gregor

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)]!
4
Chris Felix

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] = ...
3
Peter Wagner

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.

1
SAKrisT

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/

1
norbDEV

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
}
1
Max Desiatov

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)."

0
Travis Griggs