有没有办法从数组中声明性地初始化字典是快速的? 我正在寻找这样的事情:
struct MyStruct {
var key: Int
var value: String
}
let array = [MyStruct(key: 0, value: "a"), MyStruct(key: 1, value: "b")]
let dict = array.someTransform { // Some arguments
// Some transformation
}
dict
的类型为[Int: String]
?
注意:我没有寻找包含forEach
的解决方案,因为从此任务的角度来看,它只是for
循环的更复杂版本。
答案 0 :(得分:8)
Dictionary
的序列初始化在Swift 4中,假设密钥保证是唯一的,您可以简单地说:
let array = [MyStruct(key: 0, value: "a"), MyStruct(key: 1, value: "b")]
let dict = Dictionary(uniqueKeysWithValues: array.lazy.map { ($0.key, $0.value) })
print(dict) // [0: "a", 1: "c"]
这是使用SE-0165中的init(uniqueKeysWithValues:)
初始化程序。它需要一系列键值元组,其中键保证是唯一的(如果不是,则会出现致命错误)。所以在这种情况下,我们将懒惰变换应用于数组中的元素,以获得一组懒惰的键值对集合。
如果密钥不是保证是唯一的,那么您需要某种方法来确定给定密钥使用哪些可能的值。为此,您可以使用init(_:uniquingKeysWith:)
初始化程序from the same proposal,并传递给定函数,以确定在出现重复键时给定键使用的值。
uniquingKeysWith:
函数的第一个参数是已经在字典中的值,第二个是尝试插入的值。
例如,这里我们每次在序列中出现重复键时都会覆盖该值:
let array = [MyStruct(key: 0, value: "a"), MyStruct(key: 0, value: "b"),
MyStruct(key: 1, value: "c")]
let keyValues = array.lazy.map { ($0.key, $0.value) }
let dict = Dictionary(keyValues, uniquingKeysWith: { _, latest in latest })
print(dict) // [0: "b", 1: "c"]
要保留给定密钥的第一个值,并忽略同一个密钥的任何后续值,您需要uniquingKeysWith:
关闭{ first, _ in first }
,结果为[0: "a", 1: "c"]
在这种情况下。
inout
累加器 Swift 4中的另一个可能选项,假设您希望通过在给定键的每次出现时覆盖值来合并任何重复键,则使用SE-0171中引入的reduce(into:_:)
。
与reduce(_:_:)
不同,此方法在组合函数中对累加器使用inout
参数。这允许它避免在填充字典累加器时在reduce(_:_:)
的每次迭代时发生的不必要的累加器复制。因此,这允许我们以线性而非二次时间填充它。
您可以像这样使用它:
let array = [MyStruct(key: 0, value: "a"), MyStruct(key: 0, value: "b"),
MyStruct(key: 1, value: "c")]
let dict = array.reduce(into: [:]) { $0[$1.key] = $1.value }
print(dict) // [0: "b", 1: "c"]
// with initial capacity to avoid resizing upon populating.
let dict2 = array.reduce(into: Dictionary(minimumCapacity: array.count)) { dict, element in
dict[element.key] = element.value
}
print(dict2) // [0: "b", 1: "c"]
答案 1 :(得分:1)
使用reduce
let dict = array.reduce([:]) { (d, s) -> [Int:String] in
var d = d
d[s.key] = s.value
return d
}
如 @Martin R 所述,这不是最佳表现者,但非常容易使用。 @Hamish 的扩展程序很不错,至少相同的性能给你一点点简单
var dict:[Int:String] = [:]
for s in array {
dict[s.key] = s.value
}
是的,我知道,您希望避免 forEach 版本,但实际上,它是一个强大而强大的解决方案。
var dict:[Int:String] = [:]
array.forEach {
dict[$0.key] = $0.value
}
让事情尽可能简单,减少产生不良副作用的机会(bug)
定义最低容量
var dict = Dictionary<Int,String>(minimumCapacity: array.count)
array.forEach {
dict[$0.key] = $0.value
}
你的表现最好。
比较解决方案
do {
let start = Date()
let dict = Dictionary(uniqueKeysWithValues: array.lazy.map { ($0.key, $0.value) })
let time = start.timeIntervalSince(Date())
print(1,time, dict.count)
}
do {
let start = Date()
var dict = Dictionary<Int,String>(minimumCapacity: array.count)
array.forEach {
dict[$0.key] = $0.value
}
let time = start.timeIntervalSince(Date())
print(2,time, dict.count)
}
它在我的电脑上打印
1 -1.93269997835159 10000000
2 -1.80712699890137 10000000
我喜欢 @Hamish 的概念,使用 inout参数作为累积功能。我用相同的数据集测试了它
do {
let start = Date()
let dict = array.reduce(into: Dictionary(minimumCapacity: array.count)) { dict, element in
dict[element.key] = element.value
}
let time = start.timeIntervalSince(Date())
print(3,time, dict.count)
}
我期望与上述其他产品具有相同的性能但不幸的是,它打印
3 -3.80046594142914 10000000
看起来它需要两次cc来执行相同的工作。
答案 2 :(得分:1)
您还可以扩展Dictionary类型以添加接受元组数组的初始值设定项:
extension Dictionary
{
init(_ keyValues:[(Key,Value)] )
{
self.init(minimumCapacity: keyValues.underestimatedCount) // self.init()
for (key,value) in keyValues { self[key] = value }
}
}
struct MyStruct {
var key: Int
var value: String
}
let array = [MyStruct(key: 0, value: "a"), MyStruct(key: 1, value: "b")]
let dict = Dictionary(array.map{($0.key,$0.value)})
for循环仍然存在,但只在Dictionary类型中,并且不需要样板代码在需要这样初始化的各个地方构建一个数组。
[EDIT]将init更改为使用3441734建议的最小容量。这应该与#1一样快。然而,我觉得这种优化牺牲了一些简单性,这是为了一个非常罕见的用例,这种初始化将是一个关键的性能因素。