202

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
}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
Maria Zverina
  • 10,863
  • 3
  • 44
  • 61
  • 1
    Dictionary doesn't have a map function, so it's not clear what you're trying to do. – David Berry Jun 09 '14 at 08:39
  • 12
    Yes - I know that Dictionary doesn't have map function. The question could be rephrased as how can this be accomplished with closures without needing to iterate over the whole dictionary. – Maria Zverina Jun 09 '14 at 08:55
  • 2
    you still need to iterate over the whole dictionary since that's what a `map` does. what you're asking for is a way of hiding the iteration behind an extension / closure so you don't have to look at it every time you use it. – Joseph Mark Jun 09 '14 at 22:05
  • 2
    For Swift 4, see [my answer](https://stackoverflow.com/a/44609272/1966109) that shows up to 5 different ways to solve your problem. – Imanou Petit Jun 17 '17 at 21:08

14 Answers14

365

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!

Becca Royal-Gordon
  • 17,541
  • 7
  • 56
  • 91
  • `OutKey` must be an `Hashable` I suppose – DeFrenZ Nov 11 '14 at 14:06
  • Also, I realised only now why the Swift Team didn't put map in the `Dictionary` struct... there is no way to restrict the `transform` function to be injective, and if it is you would have to define a behaviour to manage more values with the same transformed key (in this implementation it is solved through overriding). – DeFrenZ Jan 16 '15 at 15:07
  • It could be implemented by transforming only the value and not the key but then it wouldn't be a real `map`. – DeFrenZ Jan 16 '15 at 15:13
  • 8
    It is certainly imperfect, but we mustn't let the perfect be the enemy of the good. A `map` that could produce conflicting keys is still a useful `map` in many situations. (On the other hand, you could argue that mapping only the values is a natural interpretation of `map` on dictionaries—both the original poster's example and mine only modify the values, and conceptually, `map` on an array only modifies the values, not the indices.) – Becca Royal-Gordon Jan 17 '15 at 01:42
  • 2
    your last code block seems to be missing a `try`: `return Dictionary(try map { (k, v) in (k, try transform(v)) })` – jpsim Nov 04 '15 at 00:19
  • 9
    Updated for Swift 2.1? Getting ```DictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads``` – Per Eriksson Nov 08 '15 at 14:20
  • Great... this is also useful for mapping an array to a Dictionary - e.g., `let squares = Dictionary(Array(0...10).map { ($0, $0 * $0) })` – johnpatrickmorgan Jan 07 '16 at 12:32
  • Agreed. An update for Swift 2.x would be awesome. Looks like I have a bit of studying to do if I want to be able to conjure these up like Mr. Brent Royal-Gordon. – Stephen Paul Jan 14 '16 at 01:48
  • @StephenPaul There was a very minor bug in the last extension (I was missing a `try`), but it's now fixed. All three extensions compile in Swift 2.1.1. – Becca Royal-Gordon Jan 15 '16 at 04:50
  • When using map() I can't lookup a value. The following throws an error "Cannot subscript a value of type '[(String, Int)]' with an index of type 'String' var testarr = ["foo" : 1, "bar" : 2] let result2 = testarr.map { (key, value) in (key, value * 2) } result2["foo"] – Daniel Mar 06 '16 at 08:35
  • 4
    With Swift 2.2 I am getting `Argument labels '(_:)' do not match any available overloads` when implementing that last extension. Not really sure how to solve it :/ – Erken May 20 '16 at 08:22
  • 2
    @Audioy That code snippet depends on the `Dictionary` initializer added in one of the previous examples. Make sure you include both of them. – Becca Royal-Gordon May 21 '16 at 23:49
  • 1
    There is a proposal for a `compactMapValues` (analogous to `compactMap` for e.g. arrays) that covers the case when transforming the value fails and you need to return `nil`. It seems it has'nt been approved yet, but the proposal page has the source code: https://github.com/apple/swift-evolution/blob/master/proposals/0218-introduce-compact-map-values.md – Nicolas Miari Aug 31 '18 at 06:22
  • 2
    @Nicolas Miari: `compactMapValues(_:)` has been accepted for Swift 5, so it’s not available in the Xcode version that’s currently in beta, but it’ll be in the next major release. – Becca Royal-Gordon Aug 31 '18 at 16:22
129

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]
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
17

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

}
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • ..how is it used? Im new to swift Mind putting an example here? – Just a coder Apr 21 '16 at 00:38
  • when i tried myDictionary.map, inside the closure, i had to do another map the regular way. This works, but is this the way its supposed to be used? – Just a coder Apr 21 '16 at 00:40
  • Is there a guarantee somewhere that the order of keys & values when called separately are paired, and thus suitable for zip? – Aaron Zinman Jan 31 '17 at 08:36
  • Why create a new init when you could just use `func mapValues(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }`. I find it reads better too. Is there a performance issue? – Marcel Jul 19 '17 at 13:09
12

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]
Joseph Mark
  • 9,298
  • 4
  • 29
  • 31
  • 1
    The problem I see with this approach is that `SequenceType.map` is not a mutating function. Your example could be rewritten to follow the existing contract for `map`, but that is the same as the accepted answer. – Christopher Pickslay Nov 25 '15 at 19:25
8

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.

Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • 10
    The downside of this approach is that a brand new copy of the dictionary is made every time you add a new key, so this function is horribly inefficient (the optimizer _might_ save you). – Airspeed Velocity Apr 05 '15 at 19:12
  • That is a good point...one thing I don't understand very well yet is the internals of Swift's memory management. I appreciate comments like this that make me think about it :) – Aaron Rasmussen Apr 06 '15 at 17:09
  • 2
    No need for temp var and reduce is now a method in Swift 2.0: `let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }` – Zmey Jul 23 '15 at 15:48
5

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
   // Operations on values or key
}

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.

NøBi Mac
  • 505
  • 5
  • 15
  • 10
    don't use .map to print something, use .map to transform A into B. If you need to print, use .foreach. And this is not an answer to a question – Zaporozhchenko Oleksandr Mar 15 '21 at 15:53
  • 1
    Question asks how to apply a map to a dictionary. This answers that question. print statements are included in the example for illustrative purposes. – Max MacLeod Jun 16 '21 at 14:18
  • Hello, @ZaporozhchenkoOleksandr I have just given the answer to a question. and this print statement is just for reference to the viewer that shows the result of the operation, so there is nothing wrong. Thank you!! – NøBi Mac Jun 10 '22 at 10:39
  • 1
    Hello, @NøBiMac, but no, if you want print something, u choose forEach instead. Everything is wrong with using map to print, ty :) – Zaporozhchenko Oleksandr Jun 11 '22 at 16:35
3

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
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
3

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)
})
Antoine
  • 23,526
  • 11
  • 88
  • 94
2

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
        }
    }
Mate
  • 109
  • 2
1

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": ...
Jeehut
  • 20,202
  • 8
  • 59
  • 80
0

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
}
Jens Wirth
  • 17,110
  • 4
  • 30
  • 33
  • https://developer.apple.com/documentation/swift/dictionary/3017871-map – Max MacLeod Jun 16 '21 at 14:08
  • This might have been true in 2014, when the answer was written, and fair enough. But since it's not true in 2022, I'm downvoting. – damd Nov 20 '22 at 09:37
0

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
  }
}
dimpiax
  • 12,093
  • 5
  • 62
  • 45
-1

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
}
NebulaFox
  • 7,813
  • 9
  • 47
  • 65
-1

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

Community
  • 1
  • 1
Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130