What's the cleanest way of applying map() to a dictionary in Swift?

SwiftDictionary

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


#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 Dictionary map method and init(uniqueKeysWithValues:) initializer

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 Dictionary reduce(_:_:) method or reduce(into:_:) method

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:) subscript

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]

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)

Ref

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMaria ZverinaView Question on Stackoverflow
Solution 1 - SwiftBecca Royal-GordonView Answer on Stackoverflow
Solution 2 - SwiftImanou PetitView Answer on Stackoverflow
Solution 3 - SwiftAirspeed VelocityView Answer on Stackoverflow
Solution 4 - SwiftJoseph MarkView Answer on Stackoverflow
Solution 5 - SwiftAaron RasmussenView Answer on Stackoverflow
Solution 6 - SwiftNøBi MacView Answer on Stackoverflow
Solution 7 - SwiftMick MacCallumView Answer on Stackoverflow
Solution 8 - SwiftJens WirthView Answer on Stackoverflow
Solution 9 - SwiftAntoineView Answer on Stackoverflow
Solution 10 - SwiftMateView Answer on Stackoverflow
Solution 11 - SwiftJeehutView Answer on Stackoverflow
Solution 12 - SwiftNebulaFoxView Answer on Stackoverflow
Solution 13 - SwiftdimpiaxView Answer on Stackoverflow
Solution 14 - SwiftMohammad Zaid PathanView Answer on Stackoverflow