在 Swift 中,我可以使用元组作为字典中的键吗?
Posted
技术标签:
【中文标题】在 Swift 中,我可以使用元组作为字典中的键吗?【英文标题】:In Swift can I use a tuple as the key in a dictionary? 【发布时间】:2014-07-30 15:29:15 【问题描述】:我想知道是否可以以某种方式使用 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'
那么..我们怎样才能使它符合?
【问题讨论】:
我确定(x:Int,y:Int)
不是可哈希的东西:)
我认为你应该只创建一个以这种方式建模的结构类型并使其符合Hashable
。
@sjeohp 他们不能,你可以简单地使用结构体创建一个简单的值类型。
元组不能用作键(或者具体来说,不是hashable
)的原因是因为它们不是严格不可变的。如果您使用let
定义它,它是不可变的,但如果您使用var
定义它,它不是。在字典中用作键的散列必须根据散列表的定义是不可变的,并且由于任何类型的合理散列将直接依赖于容器内的值,因此不能对可变容器进行散列(同样,不可变容器包含可变对象不能被合理地散列)。
@aruisdante 元组的可变性是无关紧要的——它们是值类型,因此,抛开散列性不谈,它将是存储在字典中的元组的不可变副本。唯一的问题是元组不符合Hashable
并且不能扩展为这样做。元组并不比字符串或Int
s 更易变,它们就像字典键一样。
【参考方案1】:
Dictionary
的定义是struct Dictionary<KeyType : Hashable, ValueType> : ...
,即密钥的类型必须符合协议Hashable
。但是语言指南告诉我们protocols can be adopted by classes, structs and enums,即不是元组。因此,元组不能用作Dictionary
键。
一种解决方法是定义一个包含两个 Int 的可散列结构类型(或任何您想放入元组中的内容)。
【讨论】:
假设我在两个 Int 属性中创建了一个唯一的可散列结构。我应该如何将两个整数的组合映射到唯一的哈希? “x^y”? "x 哇,这似乎完全错过了本可以是一个很棒的语言功能。我的意思是,我没有理由认为他们不能通过组合其组件的哈希值来使元组隐式可哈希。这将是避免二维字典的一种非常快捷的方法! 完全应该是一个特性,如果更高版本的 Swift 没有添加它,我会感到惊讶(尽管 v4 仍然没有它)。在 Python 中,hashable 元组是自动可散列的。 该提案已被接受:github.com/apple/swift-evolution/blob/master/proposals/… @devios1 这也许是 C++ 使用比较优于散列的原因之一。它更容易为元组等数据结构组合可比较的操作。【参考方案2】:正如上面的答案所述,这是不可能的。但是您可以使用 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"
【讨论】:
您能解释一下&* 31 &+
部分的作用吗?
&* 和 &+ 与正常操作 * 和 + 类似,但具有溢出错误保护(因此在溢出的情况下不会引发错误)
在 Swift 3 中,==
已移至结构内的静态函数:static func ==<T: Hashable, U: Hashable>(...) -> Bool
很棒的答案。使用对是一种以一般方式处理此问题的好方法。
从 Swift 4.1 开始,现在可以在一个结构或枚举上为您合成哈希实现,其值都是可哈希的 - 只需通过扩展 Hashable 协议来选择加入。见github.com/apple/swift-evolution/blob/master/proposals/…。在 swift 4.2 中还有 hash(into:)
函数来处理组合哈希:github.com/apple/swift-evolution/blob/master/proposals/…。开发文档也已更新,请参阅“符合 Hashable 协议”。【参考方案3】:
不幸的是,从 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
确实会自动合成单独存储的字段这一事实,因此即使不手动添加 Hashable
和 Equatable
实现,如 Marek Gregor's answer 中那样,此代码也可以工作:
struct Pair<T: Hashable, U: Hashable>: Hashable
let first: T
let second: U
【讨论】:
【参考方案4】:我在一个应用程序中创建了这段代码:
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】:如果您不介意效率低下,您可以轻松地将元组转换为字符串,然后将其用作字典键...
var dict = Dictionary<String, SKShapeNode>()
let tup = (3,4)
let key:String = "\(tup)"
dict[key] = ...
【讨论】:
【参考方案6】:无需特殊代码或幻数即可实现 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/
【讨论】:
【参考方案7】:我建议实现结构并使用类似于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的信息。
【讨论】:
【参考方案8】:将扩展文件添加到项目(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()
【讨论】:
【参考方案9】:或者只是使用数组来代替。我正在尝试执行以下代码:
let parsed:Dictionary<(Duration, Duration), [ValveSpan]> = Dictionary(grouping: cut) span in (span.begin, span.end)
这让我看到了这篇文章。看完这些,失望了(因为如果他们可以通过只采用协议而不做任何事情来合成Equatable
和Hashable
,他们应该可以对元组做到这一点,不是吗?),我突然意识到,只需使用数组然后。不知道它的效率如何,但这种改变效果很好:
let parsed:Dictionary<[Duration], [ValveSpan]> = Dictionary(grouping: cut) span in [span.begin, span.end]
我更一般的问题变成了“那么为什么元组不是像数组这样的一流结构呢?Python 成功了(鸭子然后跑)。”
【讨论】:
【参考方案10】:你还不能使用Swift 5.3.2
,但是你可以使用Array
代替元组:
var dictionary: Dictionary<[Int], Any> = [:]
而且用法很简单:
dictionary[[1,2]] = "hi"
dictionary[[2,2]] = "bye"
它还支持任何尺寸:
dictionary[[1,2,3,4,5,6]] = "Interstellar"
【讨论】:
【参考方案11】:struct Pair<T:Hashable> : Hashable
let values : (T, T)
init(_ a: T, _ b: T)
values = (a, b)
static func == (lhs: Pair<T>, rhs: Pair<T>) -> Bool
return lhs.values == rhs.values
func hash(into hasher: inout Hasher)
let (a, b) = values
hasher.combine(a)
hasher.combine(b)
let myPair = Pair(3, 4)
let myPairs: Set<Pair<Int>> = set()
myPairs.update(myPair)
【讨论】:
以上是关于在 Swift 中,我可以使用元组作为字典中的键吗?的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用 NSPredicate 作为 NSDictionary 中的键吗
我可以使用对象(类的实例)作为 Python 中的字典键吗?