在Swift中将map()应用于字典的最干净的方法是什么?


154

我想在字典中的所有键上映射一个函数。我希望以下类似的方法可以工作,但是过滤器无法直接应用于字典。实现这一目标的最干净的方法是什么?

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

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

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

1
字典没有地图功能,因此不清楚您要做什么。
David Berry 2014年

7
是的-我知道Dictionary没有地图功能。这个问题可以改写为:如何用闭包来完成而不需要遍历整个字典。
Maria Zverina

2
您仍然需要遍历整个字典,因为那是这样map做的。您需要的是一种将迭代隐藏在扩展/闭包后面的方式,因此您不必每次使用它时都去查看它。
约瑟夫·马克

1
对于Swift 4,请参阅我的回答,其中显示了多达5种解决问题的方法。
Imanou Petit

Answers:


281

迅捷4+

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

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

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

在讨论介绍这些功能的建议SE-0165期间,我多次提出了这个Stack Overflow的答案,并且我认为大量的赞成票有助于证明需求。因此,感谢您的帮助,使Swift变得更好!


6
这当然是不完美的,但我们绝不能让完美成为商品的敌人。在许多情况下map,可能产生冲突密钥的A 仍然有用map。(另一方面,您可能会争辩说,仅映射值是map对字典的自然解释-原始发布者的示例和我的都仅修改值,并且从概念上讲,map在数组上仅修改值,而不是索引。)
布伦特皇家-戈登

2
您的最后一个代码块似乎丢失了tryreturn Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim,2015年

9
为Swift 2.1更新吗?上车DictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Per Eriksson 2015年

4
使用Swift 2.2,我可以Argument labels '(_:)' do not match any available overloads实现最后一个扩展。不太确定如何解决它:/
Erken

2
@Audioy该代码段取决于Dictionary前面示例之一中添加的初始化程序。确保同时包含它们。
布伦特皇家戈登

65

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


#1。使用Dictionary mapValues(_:)方法

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。使用Dictionary map方法和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。使用Dictionary reduce(_:_:)方法或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。使用Dictionary subscript(_: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。使用Dictionary subscript(_:)下标

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]

请举例说明更改键而不是值。谢谢它的帮助。
Yogesh Patel

18

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

这是扩展名,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)))
    }

}

..如何使用?我是迅速在头脑中树立榜样的新手吗?
FlowUI。SimpleUITesting.com 16-4-21

当我尝试在封闭内的myDictionary.map时,我不得不按常规方式制作另一个地图。这有效,但是应该采用这种方式吗?
FlowUI。SimpleUITesting.com

是否可以保证在单独调用时键和值的顺序是成对的,因此适合邮编?
亚伦·辛曼

当您可以使用时,为什么还要创建一个新的init func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }。我发现它的阅读效果也更好。是否存在性能问题?
Marcel

12

最干净的方法是只添加map到Dictionary中:

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]

1
我看到的这种方法的问题是,这SequenceType.map不是变异函数。您的示例可以重写为遵循的现有合同map,但是与接受的答案相同。
Christopher Pickslay 2015年

7

您也可以使用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]

将其包装在扩展中非常容易,但是即使没有扩展,它也允许您通过一个函数调用来完成所需的操作。


10
这种方法的缺点是,每次添加新键时都会创建字典的全新副本,因此此功能效率极低(优化程序可能为您节省了时间)。
空速速度2015年

这是一个好点...我还不太了解的一件事是Swift的内存管理内部。我感谢这样的评论,让我思考一下:)
Aaron Rasmussen

1
无需temp var和reduce现在是Swift 2.0中的一种方法:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

事实证明您可以做到这一点。您要做的是MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>根据字典keys方法返回的内容创建一个数组。(这里的信息)然后可以映射此数组,并将更新后的值传递回字典。

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)]
Maria Zverina 2014年

@MariaZverina我不确定你的意思。
Mick MacCallum 2014年

如果您可以执行上述转换,则可以将过滤器直接应用于结果数组
Maria Zverina 2014年

@MariaZverina Gotcha。是的,很遗憾,我不知道有任何方法可以做到这一点。
Mick MacCallum 2014年

3

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

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

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

for (name, key) in d {
    d[name] = d[name]! + 1
}

3

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

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

3

迅捷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
        }
    }

令人不快的是,它创建了一个新数组并追加到该数组
Steve Kuo

2

斯威夫特5

字典的map函数带有此语法。

dictData.map(transform: ((key: String, value: String)) throws -> 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

然后_ =在变量之前设置。

可能会帮助您,谢谢。


1

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

让我们概括地说,我们可能想将所有值映射到另一种类型

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

其他解决方案集中于在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的解决方案相同,只是我没有添加让字典从zip序列中提取出来的Dictionary初始化器的额外优雅。)


1

迅捷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
    }
}

1

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

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": ...

0

另一种方法是map创建字典和reduce,其中函数keyTransformvalueTransform是函数。

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
}

它比实际代码更多是一个概念,但尽管如此,我还是将其扩展为正在运行的代码。
NebulaFox 2015年

0

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)

参考

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.