【问题标题】:What's the cleanest way of applying map() to a dictionary in Swift?在 Swift 中将 map() 应用于字典的最简洁方法是什么?
【发布时间】:2014-07-29 18:09:50
【问题描述】:

我想在字典中的所有键上映射一个函数。我希望像下面这样的东西会起作用,但过滤器不能直接应用于字典。实现这一目标的最干净的方法是什么?

在此示例中,我尝试将每个值递增 1。但这对于示例来说是偶然的 - 主要目的是弄清楚如何将 map() 应用于字典。

var d = ["foo" : 1, "bar" : 2]

d.map() {
    $0.1 += 1
}

【问题讨论】:

  • 字典没有地图功能,所以不清楚你要做什么。
  • 是的 - 我知道 Dictionary 没有地图功能。这个问题可以改写为如何在不需要遍历整个字典的情况下通过闭包来完成。
  • 您仍然需要遍历整个字典,因为 map 就是这样做的。您要求的是一种将迭代隐藏在扩展/闭包后面的方法,这样您就不必每次使用它时都查看它。
  • 对于 Swift 4,请参阅 my answer,其中显示了多达 5 种不同的方法来解决您的问题。

标签: swift dictionary


【解决方案1】:

Swift 4+

好消息! Swift 4 包含一个mapValues(_:) 方法,该方法使用相同的键构造字典的副本,但值不同。它还包括一个filter(_:) 重载,它返回一个Dictionary,以及init(uniqueKeysWithValues:) 和init(_:uniquingKeysWith:) 初始化器,用于从任意元组序列创建一个Dictionary。这意味着,如果您想同时更改键和值,您可以这样说:

let newDict = Dictionary(uniqueKeysWithValues:
    oldDict.map { key, value in (key.uppercased(), value.lowercased()) })

还有新的 API 用于将字典合并在一起、用默认值替换缺失的元素、对值进行分组(将集合转换为数组字典,由将集合映射到某个函数的结果作为键)等等。

在讨论引入这些功能的提案SE-0165 期间,我多次提出了这个 Stack Overflow 答案,我认为支持的绝对数量有助于证明需求。感谢您帮助 Swift 变得更好!

【讨论】:

  • 当然是不完美的,但我们不能让完美成为美好的敌人。在许多情况下,可能产生冲突键的map 仍然是有用的map。 (另一方面,您可能会争辩说,仅映射值是字典上map 的自然解释——原始发帖人的示例和我的示例都只修改值,从概念上讲,数组上的map 只修改值,而不是索引。)
  • 您的最后一个代码块似乎缺少try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
  • 为 Swift 2.1 更新了吗?获取DictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
  • 使用 Swift 2.2 在实现最后一个扩展时,我得到了Argument labels '(_:)' do not match any available overloads。不太确定如何解决它:/
  • @Audioy 该代码 sn-p 取决于在前面的示例之一中添加的 Dictionary 初始化程序。确保将它们都包括在内。
【解决方案2】:

使用 Swift 5,您可以使用 以下五个 sn-ps 之一来解决您的问题。


#1。使用DictionarymapValues(_:)方法

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.mapValues { value in
    return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

#2。使用Dictionarymap方法和init(uniqueKeysWithValues:)初始化器

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let tupleArray = dictionary.map { (key: String, value: Int) in
    return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works

let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

#3。使用Dictionaryreduce(_:_:)方法或reduce(into:_:)方法

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], tuple: (key: String, value: Int)) in
    var result = partialResult
    result[tuple.key] = tuple.value + 1
    return result
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
    result[tuple.key] = tuple.value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

#4。使用Dictionarysubscript(_:default:)下标

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key, default: value] += 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

#5。使用Dictionarysubscript(_:)下标

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key] = value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

【讨论】:

  • 请举例说明更改键而不是值。谢谢它有帮助。
  • 很好的总结,谢谢!
【解决方案3】:

虽然这里的大多数答案都集中在如何映射整个字典(键 和 值),但问题实际上只想映射值。这是一个重要的区别,因为映射值允许您保证相同数量的条目,而映射键和值可能会导致重复键。

这是一个扩展,mapValues,它允许您仅映射值。请注意,它还使用来自一系列键/值对的init 扩展字典,这比从数组初始化它更通用:

extension Dictionary {
    init<S: SequenceType where S.Generator.Element == Element>
      (_ seq: S) {
        self.init()
        for (k,v) in seq {
            self[k] = v
        }
    }

    func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
        return Dictionary<Key,T>(zip(self.keys, self.values.map(transform)))
    }

}

【讨论】:

  • ..它是如何使用的?我是 swift 新手介意在这里举个例子吗?
  • 当我尝试 myDictionary.map 时,在闭包内,我不得不以常规方式制作另一张地图。这行得通,但这是它应该使用的方式吗?
  • 是否可以保证单独调用时键和值的顺序是成对的,因此适用于 zip?
  • 既然可以使用 func mapValues&lt;T&gt;(_ transform: (_ value: Value) -&gt; T) -&gt; [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result },为什么还要创建一个新的 init。我也觉得它读起来更好。是否存在性能问题?
【解决方案4】:

最简洁的方法是将map 添加到字典中:

extension Dictionary {
    mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
        for key in self.keys {
            var newValue = transform(key: key, value: self[key]!)
            self.updateValue(newValue, forKey: key)
        }
    }
}

检查它是否有效:

var dic = ["a": 50, "b": 60, "c": 70]

dic.map { $0.1 + 1 }

println(dic)

dic.map { (key, value) in
    if key == "a" {
        return value
    } else {
        return value * 2
    }
}

println(dic)

输出:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]

【讨论】:

  • 我看到这种方法的问题是SequenceType.map 不是一个变异函数。您的示例可以重写以遵循map 的现有合同,但这与接受的答案相同。
【解决方案5】:

您也可以使用reduce 代替地图。 reduce 可以做任何map 可以做的事情,甚至更多!

let oldDict = ["old1": 1, "old2":2]

let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
    var d = dict
    d["new\(pair.1)"] = pair.1
    return d
}

println(newDict)   //  ["new1": 1, "new2": 2]

将它封装在一个扩展中是相当容易的,但即使没有扩展,它也可以让你通过一个函数调用来做你想做的事情。

【讨论】:

  • 这种方法的缺点是每次添加新键时都会生成一个全新的字典副本,因此这个功能非常低效(优化器可能保存你)。
  • 这是一个很好的观点......我还不太了解的一件事是 Swift 内存管理的内部结构。我很欣赏像这样让我思考的 cmets :)
  • 不需要 temp var 和 reduce 现在是 Swift 2.0 中的一种方法:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
【解决方案6】:

事实证明你可以做到这一点。您需要做的是从字典keys 方法返回的MapCollectionView&lt;Dictionary&lt;KeyType, ValueType&gt;, KeyType&gt; 创建一个数组。 (Info here) 然后您可以映射此数组,并将更新后的值传回字典。

var dictionary = ["foo" : 1, "bar" : 2]

Array(dictionary.keys).map() {
    dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}

dictionary

【讨论】:

  • 您能否补充说明如何将 ["foo" : 1, "bar" : 2] 转换为 [("foo", 1), ("bar", 2)]
  • @MariaZverina 我不确定你的意思。
  • 如果可以进行上面的转换,过滤器可以直接应用到结果数组
  • @MariaZverina Gotcha。是的,不幸的是我不知道有什么方法可以做到这一点。
【解决方案7】:

斯威夫特 5

字典的map函数就是这种语法。

dictData.map(transform: ((key: String, value: String)) throws -&gt; T)

你可以设置闭包值

var dictData: [String: String] = [
    "key_1": "test 1",
    "key_2": "test 2",
    "key_3": "test 3",
    "key_4": "test 4",
    "key_5": "test 5",
]

dictData.map { (key, value) in
    print("Key :: \(key), Value :: \(value)")
}

输出将是::

Key :: key_5, Value :: test 5
Key :: key_1, Value :: test 1
Key :: key_3, Value :: test 3
Key :: key_2, Value :: test 2
Key :: key_4, Value :: test 4

可能会给出这个警告Result of call to 'map' is unused

然后在变量前设置_ =。

也许会对你有所帮助谢谢。

【讨论】:

  • 不要用.map来打印东西,用.map把A转成B。如果需要打印,用.foreach。这不是问题的答案
  • 问题询问如何将地图应用于字典。这回答了这个问题。示例中包含打印语句用于说明目的。
【解决方案8】:

根据 Swift 标准库参考,map 是数组的函数。不适用于字典。

但是你可以迭代你的字典来修改键:

var d = ["foo" : 1, "bar" : 2]

for (name, key) in d {
    d[name] = d[name]! + 1
}
【解决方案9】:

我正在寻找一种将字典直接映射到具有自定义对象的类型化数组的方法。在这个扩展中找到了解决方案:

extension Dictionary {
    func mapKeys<U> (transform: Key -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k))
        }
        return results
    }

    func mapValues<U> (transform: Value -> U) -> Array<U> {
        var results: Array<U> = []
        for v in self.values {
            results.append(transform(v))
        }
        return results
    }

    func map<U> (transform: Value -> U) -> Array<U> {
        return self.mapValues(transform)
    }

    func map<U> (transform: (Key, Value) -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k as Key, self[ k ]! as Value))
        }
        return results
    }

    func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
        var results: Dictionary<K, V> = [:]
        for k in self.keys {
            if let value = self[ k ] {
                let (u, w) = transform(k, value)
                results.updateValue(w, forKey: u)
            }
        }
        return results
    }
}

如下使用:

self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
    return VDLFilterValue(name: key, amount: value)
})

【讨论】:

    【解决方案10】:

    斯威夫特 3

    我在 Swift 3 中尝试了一种简单的方法。

    我想将 [String: String?] 映射到 [String : String],我使用 forEach 而不是 map 或 flat map。

        let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
        var newDict = [String: String]()
        oldDict.forEach { (source: (key: String, value: String?)) in
            if let value = source.value{
                newDict[source.key] = value
            }
        }
    

    【讨论】:

    • 不受欢迎,因为它创建了一个新数组并附加到它
    【解决方案11】:

    我只是想映射值(可能会改变它们的类型),包括这个扩展:

    extension Dictionary {
        func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
            var newDict = [Key: T]()
            for (key, value) in self {
                newDict[key] = transform(value)
            }
            return newDict
        }
    }
    

    假设你有这本字典:

    let intsDict = ["One": 1, "Two": 2, "Three": 3]
    

    单行值变换则如下所示:

    let stringsDict = intsDict.valuesMapped { String($0 * 2) }
    // => ["One": "2", "Three": "6", "Two": "4"]
    

    多线值变换则如下所示:

    let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
        let calculationResult = (value * 3 + 7) % 5
        return String("Complex number #\(calculationResult)")
    }
    // => ["One": "Complex number #0", "Three": ...
    

    【讨论】:

      【解决方案12】:

      我认为问题在于保留原始字典的键并以某种方式映射其所有值。

      让我们概括并说我们可能希望将所有值映射到另一种类型。

      所以我们想从一个字典和一个闭包(一个函数)开始,最后得到一个新字典。当然,问题在于 Swift 的严格类型会妨碍它。闭包需要指定输入什么类型以及输出什么类型,因此我们不能创建一个采用 general 闭包的方法——除非在泛型的上下文中。因此我们需要一个泛型函数。

      其他解决方案集中在 Dictionary 泛型结构本身的泛型世界中执行此操作,但我发现从顶级函数的角度考虑更容易。例如,我们可以这样写,其中 K 是键类型,V1 是起始字典的值类型,V2 是结束字典的值类型:

      func mapValues<K,V1,V2>(d1:[K:V1], closure:(V1)->V2) -> [K:V2] {
          var d2 = [K:V2]()
          for (key,value) in zip(d1.keys, d1.values.map(closure)) {
              d2.updateValue(value, forKey: key)
          }
          return d2
      }
      

      这是一个调用它的简单示例,只是为了证明泛型确实在编译时自行解析:

      let d : [String:Int] = ["one":1, "two":2]
      let result = mapValues(d) { (i : Int) -> String in String(i) }
      

      我们从[String:Int] 的字典开始,通过闭包转换第一个字典的值,最终得到[String:String] 的字典。

      (编辑:我现在看到这实际上与 AirspeedVelocity 的解决方案相同,只是我没有添加 Dictionary 初始化程序的额外优雅,它使字典脱离了 zip 序列。)

      【讨论】:

        【解决方案13】:

        斯威夫特 3

        用法:

        let bob = ["a": "AAA", "b": "BBB", "c": "CCC"]
        bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]
        

        扩展名:

        extension Dictionary {
            func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
                var dict = [Key: Value]()
                for key in keys {
                    guard let value = self[key], let keyValue = transform(key, value) else {
                        continue
                    }
        
                    dict[keyValue.0] = keyValue.1
                }
                return dict
            }
        }
        

        【讨论】:

          【解决方案14】:

          另一种方法是将map 指向字典和reduce,其中函数keyTransform 和valueTransform 是函数。

          let dictionary = ["a": 1, "b": 2, "c": 3]
          
          func keyTransform(key: String) -> Int {
              return Int(key.unicodeScalars.first!.value)
          }
          
          func valueTransform(value: Int) -> String {
              return String(value)
          }
          
          dictionary.map { (key, value) in
               [keyTransform(key): valueTransform(value)]
          }.reduce([Int:String]()) { memo, element in
              var m = memo
              for (k, v) in element {
                  m.updateValue(v, forKey: k)
              }
              return m
          }
          

          【讨论】:

          • 这更像是一个概念而不是实际代码,但尽管如此,我将其扩展为运行代码。
          【解决方案15】:

          Swift 3我用过这个,

          func mapDict(dict:[String:Any])->[String:String]{
              var updatedDict:[String:String] = [:]
              for key in dict.keys{
                  if let value = dict[key]{
                      updatedDict[key] = String(describing: value)
                  }
              }
          
             return updatedDict
          }
          

          用法:

          let dict:[String:Any] = ["MyKey":1]
          let mappedDict:[String:String] = mapDict(dict: dict)
          

          Ref

          【讨论】:

            猜你喜欢
            • 2011-04-10
            • 1970-01-01
            • 2015-04-11
            • 1970-01-01
            • 2015-09-09
            • 2017-02-02
            • 1970-01-01
            • 1970-01-01
            • 2010-10-05
            相关资源
            最近更新 更多