【问题标题】:Swift 3: Array to Dictionary?Swift 3:数组到字典?
【发布时间】:2017-02-09 00:08:56
【问题描述】:

我有一个大数组,需要通过键(查找)来访问它,所以我需要创建 Dictionary。 Swift 3.0 中是否有内置函数可以这样做,还是我需要自己编写?

首先,我将需要它用于具有键“String”的类,然后也许我将能够编写通用模板版本(所有类型的数据和键)。


2019 年注意事项。现在,这只是 Swift 5 的内置uniqueKeysWithValues 和类似调用。

【问题讨论】:

  • 在数组中你只有字典中的值你有键值。应该是什么键?
  • So I need to create Dictionary 但关键是什么?
  • 你最初是如何得到一个数组的?如果有可以用作键的元素的严格格式,那应该不会太难。你能不能给我们看看数组的一部分?
  • 我想我需要一个闭包来传递密钥,例如班级的成员。为了简单起见,我的第一次尝试是字符串键。
  • 数组中所有元素的键是否唯一?如果是这样,它将与您的数组具有相同的计数,那么您为什么需要字典?

标签: arrays dictionary filter swift3


【解决方案1】:

斯威夫特 5

extension Array {

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

【讨论】:

    【解决方案2】:
    let pills = ["12", "34", "45", "67"]
    let kk = Dictionary(uniqueKeysWithValues: pills.map{ ($0, "number") })
    
    ["12": "number", "67": "number", "34": "number", "45": "number"]
    

    swift5 swift4

    【讨论】:

    • 完美解决方案!
    【解决方案3】:

    兼容 Swift 5 标准库(Xcode 10.2+,iOS 12.2)。

    这是一个使用初始化器init(uniqueKeysWithValues:)的示例

    输入 let array: [String] = Locale.isoRegionCodes 是一个由字符串表示的 ISO31661-2 代码数组。

    let countryCodeAndName: [String: String] = Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? "")} )
    

    返回的字典,将列出所有以 ISO31661-2 代码为键、以本地化区域名称为值的区域。

    输出:

    ...
    "PL":"Poland"
    "DE":"Germany"
    "FR":"France"
    "ES":"Spain"
    ...
    

    示例 2:

    let dictionary: [String: String] = Dictionary(uniqueKeysWithValues: [ ("key1", "value1"), ("key2", "value2")] )
    

    输出:

    ["key1": "value1", "key2": "value2"]
    

    重要:

    前提条件:序列不能有重复的键。

    下面的代码会使应用崩溃:

    let digitWords = ["one", "two", "three", "four", "five", "five"]
    let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...6))
    

    与:

    致命错误:键值重复:“五”

    【讨论】:

      【解决方案4】:

      是这样吗(在 Swift 4 中)?

      let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
      

      注意: 正如评论中提到的,如果您有重复的密钥,使用 uniqueKeysWithValues 会产生致命错误 (Fatal error: Duplicate values for key: 'your_key':)。

      如果您担心这可能是您的情况,那么您可以使用 init(_:uniquingKeysWith:) 例如

      let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)]
      let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
      let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })
      print(firstValues)
      
      //prints ["a": 1, "b": 2]
      
      print(lastValues)
      
      //prints ["a": 3, "b": 4]
      

      【讨论】:

      • @ShivamPokhriyal - 有一个变体 uniquingKeysWith 用于可能存在欺骗的情况!
      • @Fattie 感谢您提供信息。虽然,我现在不能尝试。无论如何,谢谢!
      • 你应该使用.lazy.map来防止中间复制和数组分配。
      【解决方案5】:

      在 Swift 4 上,您可以通过使用 Dictionary's grouping:by: initializer

      来实现这一点

      例如: 你有一个名为 A

      的类
      class A {
      
          var name: String
      
          init(name: String) {
              self.name = name
          }
          // .
          // .
          // .
          // other declations and implementions
      }
      

      接下来,您有一个 A

      类型的对象数组
      let a1 = A(name: "Joy")
      let a2 = A(name: "Ben")
      let a3 = A(name: "Boy")
      let a4 = A(name: "Toy")
      let a5 = A(name: "Tim")
      
      let array = [a1, a2, a3, a4, a5]
      

      假设您想通过将所有名称按首字母分组来创建字典。你使用 Swifts Dictionary(grouping:by:) 来实现这个

      let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
      // this will give you a dictionary
      // ["J": [a1], "B": [a2, a3], "T": [a4, a5]] 
      

      source

      但请注意,生成的 Dictionary "dictionary" 属于类型

      [String : [A]]
      

      不是类型的

      [String : A]
      

      如您所料。 (使用#uniqueKeysWithValues实现后者。)

      【讨论】:

      • 这是最好的正确答案。所有其他答案都没有抓住重点。当然,可以遍历数组并将值复制到字典中。关键是使用 functioal 构造来做到这一点,它可能具有懒惰的额外好处。
      • 完美答案。
      • @SteveKuo 它比其他垃圾答案要好,但这是错误的:) 您只需使用 uniqueKeysWithValues ,仅此而已。 grouping#by 为您提供 类的数组 作为每个值。您只需使用uniqueKeysWithValues 将数组转换为字典。
      【解决方案6】:

      如果你想遵循 map 设置的模式并在 swift 中减少,你可以做一些很好的功能,如下所示:

      extension Array {
          func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
              var ret = [Key: Element]()
              for item in self{
                  ret[keyFor(item)] = item
              }
              return ret
          }
      }
      

      用法:

      struct Dog {
          let id: Int
      }
      
      let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
      let dogsById = dogs.keyBy({ $0.id }) 
                  // [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]
      

      【讨论】:

        【解决方案7】:

        以下将数组转换为字典。

        let firstArray = [2,3,4,5,5] 
        
        let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
        

        【讨论】:

        • 仅当key 没有重复项时。在这个例子中有:5.
        【解决方案8】:

        简单地做,

        let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!
        
        var dic = [String: Any?]()
        items.foreach {
            dic[$0.name] = $0.value
        }
        

        reduce不太合适,

        let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
           var r = result
           r[item.name] = item.value // will create an copy of result!!!!!!
           return r
        }
        

        【讨论】:

          【解决方案9】:

          此扩展适用于所有序列(包括数组),并允许您同时选择键和值

          extension Sequence {
              public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
                  var dict = [K: V]()
                  for element in self {
                      if let (key, value) = try selector(element) {
                          dict[key] = value
                      }
                  }
          
                  return dict
              }
          }
          

          例子:

          let nameLookup = persons.toDictionary{($0.name, $0)}
          

          【讨论】:

            【解决方案10】:

            正如其他人已经说过的,我们需要了解哪些是关键。

            但是,我试图为我对您问题的解释提供一个解决方案。

            struct User {
                let id: String
                let firstName: String
                let lastName: String
            }
            

            这里我假设不能存在 2 个具有相同 id 的用户

            let users: [User] = ...
            
            let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
                var result = result
                result[user.id] = user
                return result
            }
            

            现在dict 是一个字典,其中keyuser idvalueuser value

            要通过id 访问用户,您现在只需编写

            let user = dict["123"]
            

            更新 #1:一般方法

            给定一个给定类型Element 的数组,以及一个确定Elementkey 的闭包,以下泛型函数将生成Dictionary 类型的[Key:Element]

            func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
                return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
                    var dict = dict
                    dict[extractKey(elm)] = elm
                    return dict
                }
            }
            

            例子

            let users: [User] = [
                User(id: "a0", firstName: "a1", lastName: "a2"),
                User(id: "b0", firstName: "b1", lastName: "b2"),
                User(id: "c0", firstName: "c1", lastName: "c2")
             ]
            
            let dict = createIndex(elms: users) { $0.id }
            // ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]
            

            更新 #2

            正如 Martin R 所述,reduce 将为相关闭包的每次迭代创建一个新字典。这可能会导致大量内存消耗。

            这是 createIndex 函数的另一个版本,其中空间要求为 O(n),其中 n 是榆树的长度。

            func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
                var dict = [Key:Element]()
                for elm in elms {
                    dict[extractKey(elm)] = elm
                }
                return dict
            }
            

            【讨论】:

            • 是的! :-) 这看起来很棒:使用 reduce。我会接受这个。为了改进,模板最好使用类类型和闭包从未知成员变量传递专家密钥。
            • 请注意,这会在每个迭代步骤中创建一个新字典,因此它可能不是大型数组的最佳性能解决方案。 – 有关使用reduce 进行映射操作的一些想法,请参阅airspeedvelocity.net/2015/08/03/…
            • @Peter71:我在Update#1 中添加了您要求的通用功能。
            • @MartinR:你是对的;我在Update#2 中添加了一种更经典的空间复杂度 O(n) 方法。谢谢。
            • 也谢谢你。它看起来有点类似于 overactors 解决方案,所以我认为我们从双方都达到了最佳代码。太好了!
            【解决方案11】:

            我认为您正在寻找这样的东西:

            extension Array {
                public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
                    var dict = [Key:Element]()
                    for element in self {
                        dict[selectKey(element)] = element
                    }
                    return dict
                }
            }
            

            您现在可以这样做:

            struct Person {
                var name: String
                var surname: String
                var identifier: String
            }
            
            let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),
                       Person(name: "Jane", surname: "Doe", identifier: "JAD")]
            let dict = arr.toDictionary { $0.identifier }
            
            print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]
            

            如果您希望您的代码更通用,您甚至可以在 Sequence 而不是 Array 上添加此扩展:

            extension Sequence {
                public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
                    var dict: [Key:Iterator.Element] = [:]
                    for element in self {
                        dict[selectKey(element)] = element
                    }
                    return dict
                }
            }
            

            请注意,这会导致序列被迭代,并且在某些情况下可能会产生副作用。

            【讨论】:

            • 是的,你可能是对的。 :-) 看起来很棒,但我是初学者,所以我必须通过它来了解这是如何完成的。非常感谢!
            • @Peter71 我已经添加了一些关于如何使用它的演示,如果您遇到任何问题,请随时提出任何具体问题。
            • 非常感谢。我想我明白了。现在我将测试它的速度。但是有什么副作用???
            • @Peter71 一些序列只能迭代一次,更多信息请查看Apple's documentation of Sequence。除非您的阵列很大,否则性能应该不是问题。而且很难击败一个好的老式 for 循环(在这种情况下,我认为带有 reduce 的版本无论如何都不是特别优雅)。
            • 数组很大(20.000 个元素),但是任何其他方式都很慢,所以我会尝试一下。而且 .reduce 不是最优的,我相信你。 :-) 我没有这方面的经验。
            【解决方案12】:

            据我了解,您想将Array 转换为Dictionary

            在我的例子中,我为Array 创建extension,字典的键将是Array 的索引。

            例子:

            var intArray = [2, 3, 5, 3, 2, 1]
            
            extension Array where Element: Any {
            
                var toDictionary: [Int:Element] {
                    var dictionary: [Int:Element] = [:]
                    for (index, element) in enumerate() {
                        dictionary[index] = element
                    }
                    return dictionary
                }
            
            }
            
            let dic = intArray.toDictionary
            

            【讨论】:

            • 是的,这可以完成工作,但是“手动”。一切都由单独的代码处理。我正在寻找使用内置函数的通用解决方案(如上所示.reduce)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-01-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-06-23
            • 1970-01-01
            相关资源
            最近更新 更多