【问题标题】:Is there a declarative way to transform Array to Dictionary?有没有一种将数组转换为字典的声明方式?
【发布时间】:2016-02-21 12:18:44
【问题描述】:

我想从这个字符串数组中获取

let entries = ["x=5", "y=7", "z=10"]

到这里

let keyValuePairs = ["x" : "5", "y" : "7", "z" : "10"]

我尝试使用map,但问题似乎是字典中的键值对不是一个独特的类型,它只是在我的脑海中,但不是在字典类型中,所以我无法真正提供一个转换函数,因为没有什么可以转换的。加上map 返回一个数组,所以这是不行的。

有什么想法吗?

【问题讨论】:

  • 如果数组中的字符串始终采用相同的格式,您可以解析键值= 和值并将所述数据分配给字典。
  • @Arc676 这是一种命令式方法,当然,我实际上已经拥有它....但我正在寻找一个声明性解决方案。

标签: swift declarative


【解决方案1】:

斯威夫特 4

正如 fl034 所提到的,这可以通过 Swift 4 进行一些简化,其中错误检查版本如下所示:

let foo = entries
    .map { $0.components(separatedBy: "=") }
    .reduce(into: [String:Int64]()) { dict, pair in
        if pair.count == 2, let value = Int64(pair[1]) {
            dict[pair[0]] = value
        }
    }

如果您不希望将值作为 Ints 则更简单:

let foo = entries
    .map { $0.components(separatedBy: "=") }
    .reduce(into: [String:String]()) { dict, pair in
        if pair.count == 2 {
            dict[pair[0]] = pair[1]
        }
    }

老 TL;DR

减去错误检查,它看起来很像:

let foo = entries.map({ $0.componentsSeparatedByString("=") })
    .reduce([String:Int]()) { acc, comps in
        var ret = acc
        ret[comps[0]] = Int(comps[1])
        return ret
    }

使用map将[String]拆分成[[String]],然后使用reduce构建[String:Int]的字典。

或者,通过向Dictionary 添加扩展名:

extension Dictionary {
    init(elements:[(Key, Value)]) {
        self.init()
        for (key, value) in elements {
            updateValue(value, forKey: key)
        }
    }
}

(顺便说一句,这是一个非常有用的扩展,您可以将它用于字典上的许多映射/过滤操作,真的很遗憾它默认不存在)

它变得更加简单:

let dict = Dictionary(elements: entries
    .map({ $0.componentsSeparatedByString("=") })
    .map({ ($0[0], Int($0[1])!)})
)

当然,您也可以将两个 map 调用结合起来,但我更喜欢将单独的变换分开。

如果要添加一些错误检查,可以使用flatMap代替map

let dict2 = [String:Int](elements: entries
    .map({ $0.componentsSeparatedByString("=") })
    .flatMap({
        if $0.count == 2, let value = Int($0[1]) {
            return ($0[0], value)
        } else {
            return nil
        }})
)

同样,如果您愿意,显然可以将 map 合并到 flatMap 中,或者为了简单起见将它们拆分。

let dict2 = [String:Int](elements: entries.flatMap {
    let parts = $0.componentsSeparatedByString("=")
    if parts.count == 2, let value = Int(parts[1]) {
        return (parts[0], value)
    } else {
        return nil
    }}
)

【讨论】:

  • 出于好奇,acccompsret 的缩写是什么?
  • 累加器、组件和回报
  • 看看我的回答,因为 Swift 4 让这一切都过时了
【解决方案2】:

一种方法是分两个阶段使用mapreduce,其中一个元组作为中间值,例如:

let entries = ["x=5", "y=7", "z=10"]

let dict = entries.map { (str) -> (String, String) in
    let elements = str.characters.split("=").map(String.init)
    return (elements[0], elements[1])
    }.reduce([String:String]()) { (var dict, kvpair) in
        dict[kvpair.0] = kvpair.1
        return dict
}

for key in dict.keys {
    print("Value for key '\(key)' is \(dict[key]).")
}

输出:

Value for key 'y' is Optional("7").
Value for key 'x' is Optional("5").
Value for key 'z' is Optional("10").

或使用具有相同输出的单个reduce

let entries = ["x=5", "y=7", "z=10"]

let dict = entries.reduce([String:String]()) { (var dict, entry) in
    let elements = entry.characters.split("=").map(String.init)
    dict[elements[0]] = elements[1]
    return dict
}

for key in dict.keys {
    print("Value for key '\(key)' is \(dict[key]).")
}

【讨论】:

  • 非常好。请注意str.characters.split{$0 == "="}也可以写成str.characters.split("=")
  • 同样在第一个 map 闭包中,你不需要声明 varstr 参数。它可以简单地是entries.map { (str) -> (String, String) in
【解决方案3】:

使用 Swift 4 的新 reduce(into: Result) 方法:

let keyValuePairs = entries.reduce(into: [String:String]()) { (dict, entry) in
    let key = String(entry.first!)
    let value = String(entry.last!)
    dict[entry.first!] = entry.last!
}

当然,你的字符串的拆分可以改进。

【讨论】:

    【解决方案4】:

    已经有了很好的答案。这是使用集合类型扩展的另一种方法。您可以将任何集合类型转换为字典、数组或集合。

    extension CollectionType {
        func map2Dict<K, V>(@noescape map: ((Self.Generator.Element) -> (K,    V)?))  -> [K: V] {
            var d = [K: V]()
            for e in self {
                if let kV = map(e) {
                    d[kV.0] = kV.1
                }
            }
    
            return d
        }
    
        func map2Array<T>(@noescape map: ((Self.Generator.Element) -> (T)?))  -> [T] {
            var a = [T]()
            for e in self {
                if let o = map(e) {
                    a.append(o)
                }
            }
    
            return a
        }
    
        func map2Set<T>(@noescape map: ((Self.Generator.Element) -> (T)?))  -> Set<T> {
            return Set(map2Array(map))
        }
     }
    

    这是示例用法。

    let entries = ["x=5", "y=7", "z=10"]
    let dict:[String: String]  = entries.map2Dict({
    let components = $0.componentsSeparatedByString("=")
        return (components.first!, components.last!)
    })
    
    print("dict \(dict)")
    

    【讨论】:

      【解决方案5】:

      我喜欢@paulgriffiths 的回答,但如果您极度厌恶运行时错误,您可以更进一步确保初始数组中的每个字符串实际上都具有两个必需的元素...

      与其他代码相比,此代码的重要区别在于我检查以确保字符串中确实存在一个“=”,并且两边都有元素。 flatMap 有效地过滤掉任何失败的。

      extension Dictionary {
          func withUpdate(key: Key, value: Value) -> Dictionary<Key, Value> {
              var result = self
              result[key] = value
              return result
          }
      }
      
      let entries = ["x=5", "y=7", "z=10"]
      
      let keyValues = entries.flatMap { str -> (String, String)? in
          let split = str.characters.split("=").map(String.init)
          return split.count > 1 ? (split[0], split[1]) : nil
      }
      
      let keyValuePairs = keyValues.reduce([String: String]()) { $0.withUpdate($1.0, value: $1.1) }
      

      【讨论】:

        【解决方案6】:

        也适用于所有喜欢超载和单线的人。

        public func +<K, V>(lhs: [K:V], rhs: [K:V]) -> [K:V] {
            var lhs: [K:V] = lhs
            for (key, value) in rhs {
                lhs[key] = value
            }
            return lhs
        }
        
        let array = ["x=5", "y=7", "z=10"]
        let dictionary = array.map({ $0.componentsSeparatedByString("=") }).reduce([:]) { $0 + [$1[0]: $1[1]] }
        
        print(dictionary) // ["y": "7", "x": "5", "z": "10"]
        

        【讨论】:

          【解决方案7】:

          如果有两个并行数组,可以使用zip()Array()David Berry's Dictionary extension

          let numbers = [10, 9, 8]
          let strings = ["one", "two", "three", "four"]
          
          let dictionary = Dictionary(elements: Array(zip(numbers, strings)))
          // == [10: "one", 9: "two", 8: "three"]
          

          不要忘记添加扩展名:

          extension Dictionary {
              init(elements:[(Key, Value)]) {
                  self.init()
                  for (key, value) in elements {
                      updateValue(value, forKey: key)
                  }
              }
          }
          

          【讨论】:

            【解决方案8】:

            简单的方法:

            let array = ["key1", "key2", "key3"]
            let dict =  array.flatMap({["\($0)" : "value"]})
            

            输出:

            [(key: "key1", value: "value"), (key: "key2", value: "value"), (key: "key3", value: "value")]


            更复杂一点的方式:

            let array = ["key1", "key2", "key3"]
            let dict =  array.enumerated().flatMap({["\($0.element)" : "value\($0.offset)"]})
            

            输出:

            [(key: "key1", value: "value0"), (key: "key2", value: "value1"), (key: "key3", 值: "value2")]

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-12-13
              • 1970-01-01
              • 2012-02-25
              • 2020-12-02
              • 1970-01-01
              • 2021-03-13
              • 2017-06-05
              相关资源
              最近更新 更多