在Swift中,我可以使用元组作为字典中的键吗?

时间:2014-06-10 00:59:12

标签: dictionary tuples 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'

那么..我们怎样才能使它符合要求?

10 个答案:

答案 0 :(得分:38)

Dictionary的定义是struct Dictionary<KeyType : Hashable, ValueType> : ...,即密钥的类型必须符合协议Hashable。但语言指南告诉我们protocols can be adopted by classes, structs and enums,即不是元组。因此,元组不能用作Dictionary个键。

一种解决方法是定义一个包含两个Ints的哈希结构类型(或者你想要放在元组中的任何内容)。

答案 1 :(得分:19)

如上面的答案所述,这是不可能的。但是你可以使用Hashable协议将元组包装成通用结构作为解决方法:

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"

答案 2 :(得分:5)

我在app中创建了这段代码:

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)]!

答案 3 :(得分:4)

如果您不介意一些低效率,您可以轻松地将您的元组转换为字符串,然后将其用于字典键......

var dict = Dictionary<String, SKShapeNode>() 

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

答案 4 :(得分:1)

我建议实现类似于boost::hash_combine的结构和使用解决方案。

以下是我使用的内容:

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
}

比使用字符串作为哈希值要快得多。

如果您想了解有关magic number的更多信息。

答案 5 :(得分:1)

不幸的是,从Swift 4.2开始,标准库仍然没有为元组提供符合Hashable的条件,并且编译器认为这不是有效的代码:

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

此外,以元组为字段的结构,类和枚举不会自动合成Hashable

虽然其他答案建议使用数组而不是元组,但这会导致效率低下。元组是一种非常简单的结构,由于在编译时就知道元素的数量和类型,因此可以很容易地对其进行优化。 Array实例几乎总是preallocates more contiguous memory,以适应可能添加的元素。此外,使用Array类型会强制您使元组类型相同或使用类型擦除。也就是说,如果您不关心效率低下的问题,可以将(Int, Int)存储在[Int]中,但是(String, Int)将需要类似[Any]的内容。

我发现的解决方法取决于Hashable会自动为单独存储的字段自动合成的事实​​,因此即使没有手动添加{{3}的HashableEquatable实现,此代码也能正常工作}}:

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

答案 6 :(得分:1)

无需特殊代码或魔术数字即可实现Hashable

可哈希于 Swift 4.2

<IfModule mod_rewrite.c>
<FilesMatch '\.(pdf|mp4)'>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_URI} !.*\/sites\/[0-9]+\/.* 
RewriteCond %{REQUEST_URI} ^.*/[0-9]{4}/[0-9]{2}.*$ 
RewriteRule ^([^?]*)$ /index.php?uamfiletype=attachment&uamgetfile=$1 [QSA,L]
RewriteRule ^(.*)\?(((?!uamfiletype).)*)$ /index.php?uamfiletype=attachment&uamgetfile=$1&$2 [QSA,L]
RewriteRule ^(.*)\?(.*)$ /index.php?uamgetfile=$1&$2 [QSA,L]
</FilesMatch>
</IfModule>

更多信息:https://nshipster.com/hashable/

答案 7 :(得分:1)

将扩展文件添加到项目(View on gist.github.com):

extension Dictionary where Key == Int64, Value == SKNode {
    func int64key(_ key: (Int32, Int32)) -> Int64 {
        return (Int64(key.0) << 32) | Int64(key.1)
    }

    subscript(_ key: (Int32, Int32)) -> SKNode? {
        get {
            return self[int64key(key)]
        }
        set(newValue) {
            self[int64key(key)] = newValue
        }
    }
}

声明:

var dictionary: [Int64 : SKNode] = [:]

使用:

var dictionary: [Int64 : SKNode] = [:]
dictionary[(0,1)] = SKNode()
dictionary[(1,0)] = SKNode()

答案 8 :(得分:0)

或者只是使用数组代替。我正在尝试执行以下代码:

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

是哪一位带我到这则帖子的。仔细阅读这些内容并感到失望之后(因为如果他们可以通过采用协议而合成EquatableHashable而无所事事,那么他们应该能够为元组做到这一点,不是吗?),我突然意识到,则只需使用数组即可。不知道它有多有效,但是这种更改可以正常工作:

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

我更笼统的问题是“那么为什么不像数组那样元组第一类结构呢?Python将其删除(运行)。”

答案 9 :(得分:0)

您还不能在 Array 中,但您可以使用 var dictionary: Dictionary<[Int], Any> = [:] 代替元组:

dictionary[[1,2]] = "hi"
dictionary[[2,2]] = "bye"

而且用法很简单:

dictionary[[1,2,3,4,5,6]] = "Interstellar"

它还支持任何维度:

{{1}}