What's the cleanest way of applying map() to a dictionary in Swift?
SwiftDictionarySwift Problem Overview
I'd like to map a function on all keys in the dictionary. I was hoping something like the following would work, but filter cannot be applied to dictionary directly. What's the cleanest way of achieving this?
In this example, I'm trying to increment each value by 1. However this is incidental for the example - the main purpose is to figure out how to apply map() to a dictionary.
var d = ["foo" : 1, "bar" : 2]
d.map() {
$0.1 += 1
}
Swift Solutions
Solution 1 - Swift
Swift 4+
Good news! Swift 4 includes a mapValues(_:)
method which constructs a copy of a dictionary with the same keys, but different values. It also includes a filter(_:)
overload which returns a Dictionary
, and init(uniqueKeysWithValues:)
and init(_:uniquingKeysWith:)
initializers to create a Dictionary
from an arbitrary sequence of tuples. That means that, if you want to change both the keys and values, you can say something like:
let newDict = Dictionary(uniqueKeysWithValues:
oldDict.map { key, value in (key.uppercased(), value.lowercased()) })
There are also new APIs for merging dictionaries together, substituting a default value for missing elements, grouping values (converting a collection into a dictionary of arrays, keyed by the result of mapping the collection over some function), and more.
During discussion of the proposal, SE-0165, that introduced these features, I brought up this Stack Overflow answer several times, and I think the sheer number of upvotes helped demonstrate the demand. So thanks for your help making Swift better!
Solution 2 - Swift
With Swift 5, you can use one of the five following snippets in order to solve your problem.
Dictionary
mapValues(_:)
method
#1. Using 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]
Dictionary
map
method and init(uniqueKeysWithValues:)
initializer
#2. Using 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]
Dictionary
reduce(_:_:)
method or reduce(into:_:)
method
#3. Using 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]
Dictionary
subscript(_:default:)
subscript
#4. Using 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]
Dictionary
subscript(_:)
subscript
#5. Using 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]
Solution 3 - Swift
While most of the answers here focus on how to map the entire dictionary (keys and values), the question really only wanted to map the values. This is an important distinction since mapping values allows you to guarantee the same number of entries, whereas mapping both key and value might result in duplicate keys.
Here’s an extension, mapValues
, that allows you to map just the values. Note it also extends dictionary with an init
from a sequence of key/value pairs, which is a bit more general than initializing it from an array:
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)))
}
}
Solution 4 - Swift
The cleanest way is to just add map
to 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)
}
}
}
Checking that it works:
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)
Output:
[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
Solution 5 - Swift
You can also use reduce
instead of map. reduce
is capable of doing anything map
can do and more!
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]
It would be fairly easy to wrap this in an extension, but even without the extension it lets you do what you want with one function call.
Solution 6 - Swift
Swift 5
map function of dictionary comes with this syntax.
dictData.map(transform: ((key: String, value: String)) throws -> T)
you can just set closure values to this
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)")
}
Output will be::
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
It may be give this warning Result of call to 'map' is unused
Then just set _ =
before variable.
May be it will help you Thank you.
Solution 7 - Swift
It turns out you can do this. What you have to do is create an array from the MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>
returned from the dictionaries keys
method. (Info here) You can then map this array, and pass the updated values back to the dictionary.
var dictionary = ["foo" : 1, "bar" : 2]
Array(dictionary.keys).map() {
dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}
dictionary
Solution 8 - Swift
According to the Swift Standard Library Reference, map is a function of arrays. Not for dictionaries.
But you could iterate your dictionary to modify the keys:
var d = ["foo" : 1, "bar" : 2]
for (name, key) in d {
d[name] = d[name]! + 1
}
Solution 9 - Swift
I was looking for a way to map a dictionary right into a typed Array with custom objects. Found the solution in this extension:
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
}
}
Using it as followed:
self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
return VDLFilterValue(name: key, amount: value)
})
Solution 10 - Swift
Swift 3
I try an easy way in Swift 3.
I want to map [String: String?] to [String : String], I use forEach instead of map or 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
}
}
Solution 11 - Swift
I you're only trying to map the values (potentially changing their type), include this extension:
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
}
}
Given you have this dictionary:
let intsDict = ["One": 1, "Two": 2, "Three": 3]
Single-line value transformation then looks like this:
let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]
Multi-line value transformation then looks like this:
let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
let calculationResult = (value * 3 + 7) % 5
return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...
Solution 12 - Swift
Another approach is to map
to a dictionary and reduce
, where functions keyTransform
and valueTransform
are functions.
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
}
Solution 13 - Swift
Swift 3
Usage:
let value = ["a": "AAA", "b": "BBB", "c": "CCC"]
value.transformed { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]
Extension:
extension Dictionary {
func transformed(closure: (Key, Value) -> (Key, Value)?) -> [Key: Value] {
var dict = [Key: Value]()
for key in keys {
guard
let value = self[key],
let keyValue = closure(key, value)
else { continue }
dict[keyValue.0] = keyValue.1
}
return dict
}
}
Solution 14 - Swift
Swift 3 I used this,
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
}
Usage:
let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)