繁体   English   中英

将对象数组映射到 Swift 中的字典

[英]Map array of objects to Dictionary in Swift

我有一个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
}

Swift 是否有任何优雅的方式来使用函数式方法mapflatMap ... 或其他现代 Swift 风格来做到这一点

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"]

好的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 或更高版本中,请使用以下答案以获得更清晰的语法。

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

您可以为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)})

基于 KeyPath 的解决方案怎么样?

extension Array {

    func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
        return 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"),
]

let allHTTPHeaderFields = headers.dictionary(withKey: \.field, value: \.value)

// allHTTPHeaderFields == ["Accept": "application/json", "User-Agent": "Safari"]

这是我一直在使用的

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可以是一个letpeopleByPosition了。

我们可以对 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})

您可以使用减少功能。 首先,我为 Person 类创建了一个指定的初始化程序

class Person {
  var name:String
  var position:Int

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

后来,我初始化了一个值数组

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

最后,字典变量有结果:

let dictionary = myArray.reduce([Int: Person]()){
  (total, person) in
  var totalMutable = total
  totalMutable.updateValue(person, forKey: total.count)
  return totalMutable
}
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 })
extension Array {

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM