What is the cleanest way to apply map () to a dictionary in Swift?

I would like to map a function to all keys in a dictionary. I was hoping something like the following would work, but the filter could not be applied directly to the dictionary. What is the cleanest way to achieve this?

In this example, I am trying to increase each value by 1. However, itโ€™s random for the example - the main goal is to figure out how to apply map () to the dictionary.

var d = ["foo" : 1, "bar" : 2] d.map() { $0.1 += 1 } 
+127
dictionary swift
Jun 09 '14 at 8:19
source share
14 answers

Swift 4+

Good news! Swift 4 includes mapValues(_:) which creates a copy of the dictionary with the same keys but different values. It also includes an overload of filter(_:) which returns a Dictionary , and init(uniqueKeysWithValues:) and init(_:uniquingKeysWith:) to create a Dictionary from an arbitrary sequence of tuples. This means that if you want to change both the keys and the 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 combining dictionaries together, replacing the default value for missing elements, grouping values โ€‹โ€‹(converting a collection into an array dictionary, based on the result of matching the collection of a function) and much more.

During the discussion of proposal SE-0165 , in which these functions were presented, I raised this answer about stack overflows several times, and I think that a huge number of votes helped to demonstrate demand. So thanks for your help to make Swift better!

+245
Jun 14 '14 at 10:40
source share

With Swift 5, you can use one of the following five snippets to solve your problem.




# 1. Using Dictionary mapValues(_:) method

 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. Using the Dictionary map and init(uniqueKeysWithValues:) method 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. Using the Dictionary method reduce(_:_:) or reduce(into:_:) method 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. Using Dictionary subscript(_:default:) index

 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. Using Dictionary subscript(_:) 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] 
+35
Jun 17 '17 at 20:48
source share

While most of the answers here are focused on how to map the entire dictionary (keys and values), the question really only wanted to map the values. This is an important difference, since the display values โ€‹โ€‹guarantee the same number of records, while the display of both the key and the value can lead to duplicate keys.

There is an extension, mapValues , which allows only values โ€‹โ€‹to be displayed. Note that it also extends the dictionary with 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))) } } 
+18
Apr 05 '15 at 19:04
source share

The cleanest way is to simply add map to the 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) } } } 

Verification of work:

 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] 
+11
Jun 09 '14 at 9:52
source share

You can also use reduce instead of a map. reduce is capable of doing something map can and much 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 pretty easy to wrap this in an extension, but even without the extension, it allows you to do what you want with a single function call.

+7
Feb 13 '15 at 15:22
source share

Turns out you can do it. What you need to do is create an array from MapCollectionView<Dictionary<KeyType, ValueType>, KeyType> , returned from the keys dictionary. ( Information here ) Then you can 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 
+4
Jun 09 '14 at 8:47
source share

According to the Swift Standard Reference Library, a map is a function of arrays. Not for dictionaries.

But you can iterate over the dictionary to change keys:

 var d = ["foo" : 1, "bar" : 2] for (name, key) in d { d[name] = d[name]! + 1 } 
+3
Jun 09 '14 at 8:43
source share

I was looking for a way to map a dictionary directly in a typed array to user objects. Found a 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 it should:

 self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in return VDLFilterValue(name: key, amount: value) }) 
+3
Jan 26 '15 at
source share

Swift 3

I try easy in Swift 3.

I want to map [String: String?] To [String: String] , I use forEach instead of a 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 } } 
+3
Jan 04 '17 at 15:12
source share

I assume that the problem is to save the keys of our original dictionary and somehow display all of its values.

Let's summarize and say that we can match all values โ€‹โ€‹with another type.

So, we would like to start with a dictionary and closure (function) and end up with a new dictionary. The problem, of course, is that strong typing of Swift gets in the way. A closure should indicate which type enters and what type exits, and therefore we cannot make a method that accepts a general closure, except for the context of the general. So we need a common function.

Other solutions have focused on doing this in the general world of the most general dictionary generation, but itโ€™s easier for me to think in terms of top-level functions. For example, we could write this, where K is the key type, V1 is the value type of the start dictionary, and V2 is the type of the dictionary value:

 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 } 

Here is a simple example of calling it, just to prove that generic really resolves itself at compile time:

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

We started with the dictionary [String:Int] and ended with the dictionary [String:String] , converting the values โ€‹โ€‹of the first dictionary through closure.

(EDIT: now I see that it is actually the same as the AirspeedVelocity solution, except that I did not add the extra elegance of a dictionary initializer that makes the dictionary from a zip sequence.)

+1
May 2 '15 at 21:54
source share

Swift 3

Using:

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

Extension:

 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
Nov 05 '16 at 2:38
source share

Another approach is map in the dictionary and reduce , where the keyTransform and valueTransform functions 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 } 
0
Dec 01 '15 at 14:33
source share

I am only trying to match values (potentially changing their type), including 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 that you have this dictionary:

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

The conversion of the values โ€‹โ€‹of one row is as follows:

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

The conversion of the values โ€‹โ€‹of several lines is as follows:

 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
Feb 07 '17 at 12:54 on
source share

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 } 

Using:

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

Link

0
May 22 '17 at 11:46 a.m.
source share



All Articles