将map()应用于Swift中的字典最简洁的方法是什么?

时间:2014-06-09 08:19:52

标签: swift dictionary

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

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

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

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

15 个答案:

答案 0 :(得分:238)

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,用缺省元素替换默认值,对值进行分组(将集合转换为数组字典,通过将集合映射到某个函数的结果来键入)等等。 / p>

在讨论引入这些功能的提案SE-0165时,我多次提出这个Stack Overflow的答案,我认为大量的upvotes有助于证明需求。谢谢你的帮助让Swift变得更好!

Swift 3

mapSequenceCollection的扩展方法。它的定义如下:

func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]

由于Dictionary符合Collection,并且其Element类型是(键,值)元组,这意味着您应该能够执行以下操作:

result = dict.map { (key, value) in (key, value.uppercaseString) }

但是,这实际上并不会分配给Dictionary类型的变量。 map方法被定义为始终返回一个数组([T]),即使对于其他类型的字典也是如此。如果你编写一个构造函数,将一个由两元组组成的数组转换为Dictionary,并且所有这些构造函数都适用于世界:

extension Dictionary {
    init(_ pairs: [Element]) {
        self.init()
        for (k, v) in pairs {
            self[k] = v
        }
    }
}

result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })

您甚至可能希望编写特定于字典的map版本,以避免显式调用构造函数。在这里,我还包括filter的实现:

extension Dictionary {
    func mapPairs<OutKey: Hashable, OutValue>(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
        return Dictionary<OutKey, OutValue>(uniqueKeysWithValues: try map(transform))
    }

    func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] {
        return Dictionary(uniqueKeysWithValues: try filter(includeElement))
    }
}

我使用了不同的名称,因为否则只有返回值会区分这两种方法,这会使代码变得非常模糊。

像这样使用:

result = dict.mapPairs { (key, value) in (key, value.uppercaseString) }

顺便提一下,出于多种目的,您可能只希望映射值:

extension Dictionary {
    func map<OutValue>(_ transform: (Value) throws -> OutValue) rethrows -> [Key: OutValue] {
        return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
    }
}

原因是如果两个最终映射到同一个键,mapPairs可能会丢失对。另一方面,map(Value -> OutValue)始终保留密钥,因此这不是危险。

答案 1 :(得分:34)

使用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]

答案 2 :(得分: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)))
    }

}

答案 3 :(得分:11)

最简洁的方法是将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]

答案 4 :(得分: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]

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

答案 5 :(得分:4)

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

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

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

dictionary

答案 6 :(得分:3)

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

但您可以迭代字典来修改密钥:

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

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

答案 7 :(得分:3)

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

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

答案 8 :(得分:3)

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

答案 9 :(得分: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

然后在变量前设置_ =

也许会帮助您谢谢。

答案 10 :(得分:1)

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

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

所以我们首先想要的是一个字典和一个闭包(一个函数),最后得到一个新的字典。当然,问题是斯威夫特的严格打字会妨碍他们。闭包需要指定输入什么类型以及输出什么类型,因此我们不能创建一个采用通用闭包的方法 - 除非在泛型的上下文中。因此,我们需要一个通用函数。

其他解决方案集中在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序列。)

答案 11 :(得分:1)

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

答案 12 :(得分: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
}

答案 13 :(得分:0)

我只是想映射值(可能会更改其类型),包含此扩展程序:

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

答案 14 :(得分: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)

Ref