web-dev-qa-db-ja.com

In Swift辞書でキーとしてタプルを使用できますか?

どういうわけか、x、yのペアを辞書のキーとして使用できるかどうか疑問に思っています

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

しかし、私はエラーを受け取ります:

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

そしてエラー:

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

それで..どうやって適合させることができますか?

50
JuJoDi

Dictionaryの定義はstruct Dictionary<KeyType : Hashable, ValueType> : ...、つまりキーのタイプはプロトコルHashableに準拠する必要があります。しかし、言語ガイドには、 プロトコルはクラス、構造体、および列挙型で採用できる 、つまりタプルではないことが示されています。したがって、タプルをDictionaryキーとして使用することはできません。

回避策は、2つのInt(またはTupleに入れたいもの)を含むハッシュ可能な構造体型を定義することです。

41
Lukas

上記の答えで述べたように、それは不可能です。ただし、回避策としてHashableプロトコルを使用してTupleを汎用構造にラップできます。

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

アプリでこのコードを作成しました:

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

非効率性を少しでも気にしない場合は、タプルを文字列に簡単に変換して、辞書キーに使用できます...

var dict = Dictionary<String, SKShapeNode>() 

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

残念ながら、Swift 4.2の時点で、標準ライブラリはタプルのHashableへの条件付き適合をまだ提供しておらず、これはコンパイラーによって有効なコードと見なされません。

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

さらに、フィールドとしてタプルを持つ構造体、クラス、および列挙型はHashableが自動的に合成されません。

他の答えはタプルの代わりに配列を使用することを提案しましたが、これは非効率を引​​き起こします。タプルは非常に単純な構造で、コンパイル時に要素の数と型がわかっているため、簡単に最適化できます。 Arrayインスタンスは、ほとんど常に 追加の潜在的な要素に対応するために、より連続したメモリを事前に割り当てます 。また、Array型を使用すると、タプル型を同じにするか、型消去を使用する必要があります。つまり、非効率性を気にしない場合は、(Int, Int)[Int]に格納できますが、(String, Int)[Any]のようなものを必要とします。

私が見つけた回避策は、Hashableが個別に格納されたフィールドに対して自動的に合成するという事実に依存しているため、このコードは Marek Gregor's answer のようなHashableおよびEquatable実装を手動で追加しなくても機能します:

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

Hashableの実装に特別なコードやマジックナンバーは不要

ハッシュ可能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
    }
}

詳細: https://nshipster.com/hashable/

1
norbDEV