从转换数组初始化字典

时间:2017-05-29 14:48:29

标签: swift

有没有办法从数组中声明性地初始化字典是快速的? 我正在寻找这样的事情:

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循环的更复杂版本。

3 个答案:

答案 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一样快。然而,我觉得这种优化牺牲了一些简单性,这是为了一个非常罕见的用例,这种初始化将是一个关键的性能因素。