在 Swift 中将对象数组映射到 Dictionary

我有一个 Person对象的数组:

class Person {
let name:String
let position:Int
}

数组是:

let myArray = [p1,p1,p3]

我想把 myArray映射成 [position:name]的字典经典的解决方案是:

var myDictionary =  [Int:String]()


for person in myArray {
myDictionary[person.position] = person.name
}

是否有任何优雅的方式做到这一点,由斯威夫特与功能方法 mapflatMap... 或其他现代斯威夫特风格

115121 次浏览

您可以为 Dictionary类型编写自定义初始值设定项,例如从元组编写:

extension Dictionary {
public init(keyValuePairs: [(Key, Value)]) {
self.init()
for pair in keyValuePairs {
self[pair.0] = pair.1
}
}
}

然后使用 map作为 Person的数组:

var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})

好的,map不是一个很好的例子,因为它和循环一样,你可以用 reduce来代替,它把你的每个对象合并成一个值:

let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
var dict = dict
dict[person.position] = person.name
return dict
}


//[2: "b", 3: "c", 1: "a"]

在 Swift 4或更高版本中,请使用下面的答案以获得更清晰的语法。

您可以使用 reduce 函数

class Person {
var name:String
var position:Int


init(_ n: String,_ p: Int) {
name = n
position = p
}
}

后来,我初始化了一个值的 Array

let myArray = [Person("Bill",1),
Person("Steve", 2),
Person("Woz", 3)]

最后,dictionary 变量的结果是:

let dictionary = myArray.reduce([Int: Person]()){
(total, person) in
var totalMutable = total
totalMutable.updateValue(person, forKey: total.count)
return totalMutable
}

因为 Swift 4,你可以使用 into版本的 reduce更干净、更有效地使用@Tj3n 的方法。它去掉了临时字典和返回值,因此它更快、更容易阅读。

示例代码设置:

struct Person {
let name: String
let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]

Into参数被传递结果类型的空字典:

let myDict = myArray.reduce(into: [Int: String]()) {
$0[$1.position] = $1.name
}

直接返回在 into中传递的类型的字典:

print(myDict) // [2: "c", 0: "h", 4: "b"]

由于 Swift 4,你可以很容易地做到这一点。有一些 新的初始化器,它们根据元组序列(键和值对)构建字典。如果密钥保证是唯一的,则可以执行以下操作:

let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]


Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })

= > [1: "Franz", 2: "Heinz", 3: "Hans"]

如果复制了任何键,这将失败并出现运行时错误。在这种情况下,您可以使用这个版本:

let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 1)]


Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }

= > [1: "Hans", 2: "Heinz"]

这表现为 for 循环。如果您不想“覆盖”值并坚持第一个映射,可以使用以下方法:

Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }

= > [1: "Franz", 2: "Heinz"]

Swift 4.2 添加了一个 第三初始化器,它将序列元素分组到字典中。字典键由闭包派生。具有相同键的元素按照与序列中相同的顺序放入数组中。这使您可以实现与上面类似的结果。例如:

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }

= > [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]

Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }

= > [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]

这是我一直在用的

struct Person {
let name:String
let position:Int
}
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]


var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}

如果有一种方法可以组合最后两行,使 peopleByPosition可以是 let,那就太好了。

我们可以做一个 Array 的扩展来实现这个功能!

extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}

然后我们就可以

let peopleByPosition = persons.mapToDict(by: {$0.position})
extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}

也许像这样?

myArray.forEach({ myDictionary[$0.position] = $0.name })

基于 KeyPath 的解决方案如何?

extension Array {
func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
reduce(into: [:]) { dictionary, element in
let key = element[keyPath: key]
let value = element[keyPath: value]
dictionary[key] = value
}
}
}

这就是你如何使用它:

struct HTTPHeader {
let field: String, value: String
}


let headers = [
HTTPHeader(field: "Accept", value: "application/json"),
HTTPHeader(field: "User-Agent", value: "Safari")
]


headers.dictionary(withKey: \.field, value: \.value) // ["Accept": "application/json", "User-Agent": "Safari"]
extension Array {


func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
    

}