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

Posted

技术标签:

【中文标题】将对象数组映射到 Swift 中的字典【英文标题】:Map array of objects to Dictionary in Swift 【发布时间】:2016-11-22 03:17:43 【问题描述】:

我有一组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 风格来做到这一点

【问题讨论】:

游戏有点晚了,但是您想要[position:name][position:[name]] 的字典吗?如果您有两个人处于同一位置,则您的字典将只保留循环中遇到的最后一个....我有一个类似的问题,我正在尝试寻找解决方案,但我希望结果像[1: [p1, p3], 2: [p2]] 它们是一个内置函数。见here。基本上是Dictionary(uniqueKeysWithValues: array.map ($0.key, $0) ) 【参考方案1】:

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

【讨论】:

【参考方案2】:
extension Array 

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

【讨论】:

【参考方案3】:

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

【讨论】:

我有点困惑,为什么它似乎只适用于位置 ($0, $1) 参数而不是我命名它们时。编辑:没关系,编译器可能出错了,因为我仍然试图返回结果。 这个答案中不清楚的是$0 代表什么:它是我们“返回”的inout 字典——虽然不是真的返回,因为修改它相当于返回到期给它inout-ness。【参考方案4】:

Swift 4 开始,您可以非常轻松地做到这一点。有 two new 初始化器从元组序列(键和值对)构建字典。如果保证密钥是唯一的,您可以执行以下操作:

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 添加了 third 初始化器,将序列元素分组到字典中。字典键是由闭包派生的。具有相同键的元素按照与序列中相同的顺序放入数组中。这使您可以实现与上述类似的结果。例如:

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

【讨论】:

事实上,根据 docs 'grouping' 初始值设定项会创建以 array 为值的字典(这是 'grouping' 的意思,对吗?),在上述情况下,它将是更像[1: [Person(name: "Franz", position: 1)], 2: [Person(name: "Heinz", position: 2)], 3: [Person(name: "Hans", position: 3)]]。不是预期的结果,但如果您愿意,它可以进一步映射到平面字典。 尤里卡!这就是我一直在寻找的机器人!这是完美的,并且大大简化了我的代码。 指向初始化程序的死链接。能否请您更新以使用永久链接? @MarqueIV 我修复了损坏的链接。不确定您在这种情况下使用 permalink 指的是什么? 永久链接是网站使用的永远不会改变的链接。因此,例如,大多数网站不会像“www.SomeSite.com/events/today/item1.htm”那样每天都在变化,而是会设置第二个“永久链接”,比如“www.SomeSite.com?eventid= 12345' 永远不会改变,因此在链接中使用是安全的。不是每个人都这样做,但大网站的大多数页面都会提供。【参考方案5】:

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

【讨论】:

我喜欢这种方法,但这比简单地创建一个循环来加载字典更有效吗?我担心性能,因为似乎每个项目都必须为更大的字典创建一个新的可变副本...... 其实是一个循环,这种方式是简化的写法,性能和普通循环一样或者好一些 Kendall,请参阅下面的答案,使用 Swift 4 时它可能比循环更快。虽然,我还没有对其进行基准测试以确认这一点。 不要使用这个!!!正如@KendallHelmstetterGelner 指出的那样,这种方法的问题在于每次迭代,它都在复制当前状态下的整个字典,所以在第一个循环中,它正在复制一个单项字典。第二次迭代它正在复制一个双项字典。这是一个巨大的性能消耗!更好的方法是在字典上编写一个方便的 init ,它接受你的数组并在其中添加所有项目。这样,它对于消费者来说仍然是单行的,但它消除了整个分配混乱。另外,您可以根据数组进行预分配。 我认为性能不会成为问题。 Swift 编译器将识别出字典的任何旧副本都不会被使用,甚至可能不会创建它们。记住优化自己代码的第一条规则:“不要这样做”。计算机科学的另一个格言是,高效算法仅在 N 很大时才有用,而 N 几乎永远不会很大。【参考方案6】:

也许是这样的?

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

【讨论】:

【参考方案7】:
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
    

【讨论】:

【参考方案8】:

这是我一直在使用的

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.forEachpeopleByPosition[$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)

【讨论】:

【参考方案9】:

您可以使用 reduce 函数。首先我为 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

【讨论】:

【参考方案10】:

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

【讨论】:

以上是关于将对象数组映射到 Swift 中的字典的主要内容,如果未能解决你的问题,请参考以下文章

将字典映射到 Swift 中的结构数组

从字典数组映射到 Swift 中的数字数组

swift:将传入的 json 数组转换为字典和对象

我可以将包含字典对象的数组传递给异步映射吗

数组的性能包括与映射到对象并在 JavaScript 中访问它

Swift 如何访问 Struct 对象字典中的特定变量