Earl Grey Earl Grey - 5 months ago 18
Swift Question

Is there a declarative way to transform Array to Dictionary?

I want to get from this array of strings

let entries = ["x=5", "y=7", "z=10"]


to this

let keyValuePairs = ["x" : "5", "y" : "7", "z" : "10"]


I tried to use
map
but the problem seems to be that a key - value pair in a dictionary is not a distinct type, it's just in my mind, but not in the Dictionary type so I couldn't really provide a transform function because there is nothing to transform to. Plus
map
return an array so it's a no go.

Any ideas?

Answer

Minus error checking, it looks pretty much like:

let foo = entries.map({ $0.componentsSeparatedByString("=") }).reduce([String:Int]()) { acc, comps in
    var ret = acc
    ret[comps[0]] = Int(comps[1])
    return ret
}

Use map to turn the [String] into a split up [[String]] and then build the dictionary of [String:Int] from that using reduce.

Or, by adding an extension to Dictionary:

extension Dictionary {
    init(elements:[(Key, Value)]) {
        self.init()
        for (key, value) in elements {
            updateValue(value, forKey: key)
        }
    }
}

(Quite a useful extension btw, you can use it for a lot of map/filter operations on Dictionaries, really kind of a shame it doesn't exist by default)

It becomes even simpler:

let dict = Dictionary(elements: entries
    .map({ $0.componentsSeparatedByString("=") })
    .map({ ($0[0], Int($0[1])!)})
)

Of course, you can also combine the two map calls, but I prefer to break up the individual transforms.

If you want to add some error checking, flatMap can be used instead of map:

let dict2 = [String:Int](elements: entries
    .map({ $0.componentsSeparatedByString("=") })
    .flatMap({
        if $0.count == 2, let value = Int($0[1]) {
            return ($0[0], value)
        } else {
            return nil
        }})
)

Again, if you want, you can obviously merge the map into the flatMap or split them for simplicity.

let dict2 = [String:Int](elements: entries.flatMap {
    let parts = $0.componentsSeparatedByString("=")
    if parts.count == 2, let value = Int(parts[1]) {
        return (parts[0], value)
    } else {
        return nil
    }}
)