【问题标题】:Map array of objects to Dictionary in Swift将对象数组映射到 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 有什么优雅的方法可以通过函数式方法map、flatMap... 或其他现代 Swift 风格来做到这一点

【问题讨论】:

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

标签: ios arrays swift dictionary


【解决方案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.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})
          

          【讨论】:

            【解决方案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)})
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-08-06
                • 1970-01-01
                • 1970-01-01
                • 2023-03-29
                • 2015-10-17
                • 1970-01-01
                • 2018-07-18
                • 1970-01-01
                相关资源
                最近更新 更多