web-dev-qa-db-fra.com

Dans Swift, puis-je utiliser un tuple comme clé dans un dictionnaire?

Je me demande si je peux en quelque sorte utiliser une paire x, y comme clé de mon dictionnaire

let activeSquares = Dictionary <(x: Int, y: Int), SKShapeNode>()

Mais je reçois l'erreur:

Cannot convert the expression's type '<<error type>>' to type '$T1'

et l'erreur:

Type '(x: Int, y: Int)?' does not conform to protocol 'Hashable'

Alors .. comment pouvons-nous le rendre conforme?

48
JuJoDi

La définition de Dictionary est struct Dictionary<KeyType : Hashable, ValueType> : ..., c’est-à-dire que le type de clé doit être conforme au protocole Hashable. Mais le guide de langue nous indique que les protocoles peuvent être adoptés par des classes, des structs et des enums , c’est-à-dire pas par des tuples. Par conséquent, les n-uplets ne peuvent pas être utilisés comme clés Dictionary.

Une solution de contournement consisterait à définir un type struct hashable contenant deux Ints (ou tout ce que vous voulez mettre dans votre tuple).

36
Lukas

Comme mentionné dans la réponse ci-dessus, ce n'est pas possible. Mais vous pouvez envelopper Tuple dans une structure générique avec le protocole Hashable comme solution palliative:

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

J'ai créé ce code dans une application:

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

Si cela ne vous dérange pas, vous pouvez facilement convertir votre Tuple en chaîne, puis l'utiliser pour la clé de dictionnaire ...

var dict = Dictionary<String, SKShapeNode>() 

let tup = (3,4)
let key:String = "\(tup)"
dict[key] = ...
3
Peter Wagner

Je suggère d’implémenter une structure et d’utiliser une solution similaire à boost::hash_combine

Voici ce que j'utilise:

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
}

C'est beaucoup plus rapide que d'utiliser string pour la valeur de hachage. 

Si vous voulez en savoir plus sur nombre magique .

1
SAKrisT

Pas besoin de code spécial ou de nombres magiques pour mettre en œuvre Hashable

Peut être lavé dans 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
    }
}

Plus d'infos: https://nshipster.com/hashable/

1
norbDEV

Malheureusement, à partir de Swift 4.2, la bibliothèque standard ne fournit toujours pas de conformité conditionnelle à Hashable pour les n-uplets et ceci n'est pas considéré comme un code valide par le compilateur:

extension (T1, T2): Hashable where T1: Hashable, T2: Hashable {
  // potential generic `Hashable` implementation here..
}

De plus, les structures, classes et enums ayant des nuplets comme champs ne seront pas automatiquement synthétisés par Hashable.

Alors que d’autres réponses suggéraient d’utiliser des tableaux au lieu de tuples, cela causerait des inefficacités. Un tuple est une structure très simple qui peut être facilement optimisée du fait que le nombre et les types d'éléments sont connus au moment de la compilation. Une instance Array presque toujours préalloue de la mémoire plus contiguë pour prendre en compte les éléments potentiels à ajouter. De plus, utiliser le type Array vous oblige à rendre les types Tuple identiques ou à utiliser le type effacement. Autrement dit, si vous ne vous souciez pas de l’inefficacité, (Int, Int) pourrait être stocké dans [Int], mais (String, Int) aurait besoin de quelque chose comme [Any].

La solution que j'ai trouvée repose sur le fait que Hashable synthétise automatiquement les champs stockés séparément. Ce code fonctionne donc même sans ajouter manuellement les implémentations Hashable et Equatable telles que La réponse de Marek Gregor :

struct Pair<T: Hashable, U: Hashable>: Hashable {
  let first: T
  let second: U
}
1
Max Desiatov

Ou utilisez simplement Arrays à la place. J'essayais de faire le code suivant:

let parsed:Dictionary<(Duration, Duration), [ValveSpan]> = Dictionary(grouping: cut) { span in (span.begin, span.end) }

Ce qui m'a amené à ce post. Après les avoir lus et avoir été déçus (car s’ils peuvent synthétiser Equatable et Hashable en adoptant simplement le protocole sans rien faire, ils devraient pouvoir le faire pour des n-uplets, non?), J’ai soudainement réalisé: utilisez simplement Arrays. Aucune idée de son efficacité, mais ce changement fonctionne très bien:

let parsed:Dictionary<[Duration], [ValveSpan]> = Dictionary(grouping: cut) { span in [span.begin, span.end] }

Ma question plus générale devient "alors pourquoi les structures de première classe en tuples comme les tableaux ne le sont-elles pas alors? Python l'a enlevée (canard et fuite)."

0
Travis Griggs