5

Other languages such as Python let you use a dictionary comprehension to make a dict from an array, but I haven't figure out how to do this in Swift. I thought I could use something like this but it doesn't compile:

let x = ["a","b","c"]
let y = x.map( { ($0:"x") })
// expected y to be ["a":"x", "b":"x", "c":"x"]

What is the correct way to generate a dictionary from an array in swift?

hennes
  • 9,147
  • 4
  • 43
  • 63
Yusuf X
  • 14,513
  • 5
  • 35
  • 47

1 Answers1

5

The map method simply transforms each element of an array into a new element. The result is, however, still an array. To transform the array into a dictionary you can use the reduce method.

let x = ["a","b","c"]
let y = x.reduce([String: String]()) { (var dict, arrayElem) in
    dict[arrayElem] = "this is the value for \(arrayElem)"
    return dict
}

This will generate the dictionary

["a": "this is the value for a",
 "b": "this is the value for b",
 "c": "this is the value for c"]

Some explanation: The first argument of reduce is the initial value which in this case is the empty dictionary [String: String](). The second argument of reduce is a callback for combining each element of the array into the current value. In this case, the current value is the dictionary and we define a new key and value in it for every array element. The modified dictionary also needs to be returned in the callback.


Update: Since the reduce approach can be heavy on memory for large arrays (see comments) you could also define a custom comprehension function similar to the below snippet.

func dictionaryComprehension<T,K,V>(array: [T], map: (T) -> (key: K, value: V)?) -> [K: V] {
    var dict = [K: V]()
    for element in array {
        if let (key, value) = map(element) {
            dict[key] = value
        }
    }
    return dict
}

Calling that function would look like this.

let x = ["a","b","c"]
let y = dictionaryComprehension(x) { (element) -> (key: String, value: String)? in
    return (key: element, value: "this is the value for \(element)")
}

Update 2: Instead of a custom function you could also define an extension on Array which would make the code easier to reuse.

extension Array {
    func toDict<K,V>(map: (T) -> (key: K, value: V)?) -> [K: V] {
        var dict = [K: V]()
        for element in self {
            if let (key, value) = map(element) {
                dict[key] = value
            }
        }
        return dict
    }
}

Calling the above would look like this.

let x = ["a","b","c"]
let y = x.toDict { (element) -> (key: String, value: String)? in
    return (key: element, value: "this is the value for \(element)")
}
hennes
  • 9,147
  • 4
  • 43
  • 63
  • Just note that this creates a new dictionary in each reduction step. This might be a performance problem if applied to *large* arrays. – Martin R Aug 15 '15 at 07:17
  • @MartinR Is that because the dictionary is passed by value to the callback due to the fact that it's a struct internally? – hennes Aug 15 '15 at 07:18
  • Yes, exactly. See also this comment: http://stackoverflow.com/questions/24116271/whats-the-cleanest-way-of-applying-map-to-a-dictionary-in-swift#comment47086028_28502842. – Martin R Aug 15 '15 at 07:21
  • 3
    In Swift 4, `Dictionary` has an initializer taking tuples. If the tuples are known to have distinct keys, use `Dictionary(uniqueKeysWithValues: x.map { ($0, "x") })`. Otherwise, if there might be key collisions, use e.g., `Dictionary(x.map { ($0, "x") }, uniquingKeysWith: { (first,_) in first } )` – BallpointBen Jun 01 '17 at 16:41