18

In Swift, I am trying to flatten an array of dictionaries into one dictionary i.e

let arrayOfDictionaries = [["key1": "value1"], ["key2": "value2"], ["key3": "value3", "key4": "value4"]]


//the end result will be:   
 flattenedArray = ["key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"]

I have tried using flatmap, but the type of the returned result is [(String, AnyObject)] and not [String, Object] ie

let flattenedArray = arrayOfDictionaries.flatMap { $0 }
// type is [(String, AnyObject)]

So I have 2 questions:

  • Why is type [(String, AnyObject)] returned? And what do the brackets mean?

  • How do I achieve the desired result?

Edit: I would prefer to use a functional approach with Swift's map/flatmap/reduce etc. instead of a for-loop

Lneuner
  • 1,090
  • 1
  • 9
  • 19

4 Answers4

19

what do the brackets mean?

This, along with a comma instead of a colon, should provide the first clue: brackets mean that you get an array of tuples. Since you are looking for a dictionary, not an array, this tells you that you need to convert the sequence of tuples (key-value pairs) to a single dictionary.

How do I achieve the desired result?

One way to do it would be using reduce, like this:

let flattenedDictionary = arrayOfDictionaries
    .flatMap { $0 }
    .reduce([String:String]()) { (var dict, tuple) in
        dict.updateValue(tuple.1, forKey: tuple.0)
        return dict
    }
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Sir you did little mistake i have fixed it :) Please check the Edit – O-mkar Feb 24 '16 at 09:48
  • Here there was problem "dict.updateValue(tuple.0, forKey: tuple.1)" i changed to dict.updateValue(tuple.1, forKey: tuple.0) – O-mkar Feb 24 '16 at 09:51
  • Thanks for this. I feel like there could be a slightly more elegant way though. Do you know why flatmap is mapping it to an array of tuples and not an array of dictionaries? – Lneuner Feb 24 '16 at 10:25
  • @Lneuner Yes, `(String, AnyObject)` in Swift's type notation denotes a tuple. A map would use brackets and a colon, i.e. `[String:AnyObject]`. – Sergey Kalinichenko Feb 24 '16 at 11:46
  • @dasblinkenlight but why is it mapping it to a tuple instead of dictionary? – Lneuner Feb 24 '16 at 11:47
  • 2
    @Lneuner Because map/flatMap iterate the dictionary. When a dictionary gets iterated, each item in the dictionary is returned as a `(key, value)` tuple. This is described [here](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html) at the bottom of the document. – Sergey Kalinichenko Feb 24 '16 at 11:54
  • @dasblinkenlight thank you! That answers my question – Lneuner Feb 24 '16 at 11:57
  • @dasblinkenlight one more thing. I am slightly confused as to what `[String:String]()) { (var dict, tuple)` actually does in the closure. Could you point me to a good resource where I could read more about it? :) – Lneuner Feb 25 '16 at 08:16
  • 1
    @Lneuner `reduce` takes two things - the "initial value" of the result, and the function that "adds" the current element to the result. In this case `reduce` starts with an empty dictionary, hence the `[String:String]()` constructor. `(var dict, tuple)` is the header of the lambda function that adds the current tuple to the dictionary. – Sergey Kalinichenko Feb 25 '16 at 10:38
  • @dasblinkenlight great explanation! Thanks! – Lneuner Feb 25 '16 at 11:25
16

With Swift 5, Dictionay has a init(_:uniquingKeysWith:) initializer. init(_:uniquingKeysWith:) has the following declaration:

init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

Creates a new dictionary from the key-value pairs in the given sequence, using a combining closure to determine the value for any duplicate keys.


The two following Playground sample codes show how to flatten an array of dictionaries into a new dictionary.

let dictionaryArray = [["key1": "value1"], ["key1": "value5", "key2": "value2"], ["key3": "value3"]]

let tupleArray: [(String, String)] = dictionaryArray.flatMap { $0 }
let dictonary = Dictionary(tupleArray, uniquingKeysWith: { (first, last) in last })

print(dictonary) // prints ["key2": "value2", "key3": "value3", "key1": "value5"]
let dictionaryArray = [["key1": 10], ["key1": 10, "key2": 2], ["key3": 3]]

let tupleArray: [(String, Int)] = dictionaryArray.flatMap { $0 }
let dictonary = Dictionary(tupleArray, uniquingKeysWith: { (first, last) in first + last })
//let dictonary = Dictionary(tupleArray, uniquingKeysWith: +) // also works

print(dictonary) // ["key2": 2, "key3": 3, "key1": 20]
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • 3
    Why were these Swift 4 features built for "initialization from a sequence of `(Key, Value)` tuples" rather (or also) than "a sequence of dictionaries"? That forces inclusion of the `tupleArray` step in your code, which is kind of annoying. – pkamb Feb 26 '18 at 08:59
9

Updating @dasblinkenlight's answer for Swift 3.

"var" in the parameters has been deprecated, but this approach worked fine for me.

let flattenedDictionary = arrayOfDictionaries
    .flatMap { $0 }
    .reduce([String:String]()) { (dict, tuple) in
        var nextDict = dict
        nextDict.updateValue(tuple.1, forKey: tuple.0)
        return nextDict
    }
Mars
  • 2,505
  • 17
  • 26
1

Here is the way to do

let arrayOfDictionaries = [["key1": "value1"], ["key2": "value2"], ["key3": "value3", "key4": "value4"]]
var dic = [String: String]()
for item in arrayOfDictionaries {
    for (kind, value) in item {
        print(kind)
        dic.updateValue(value, forKey: kind)
    }


}
print(dic)

print(dic["key1"]!)

OUTPUT

OUTPUT

O-mkar
  • 5,430
  • 8
  • 37
  • 61
  • Thanks for this. While this approach is correct, I would prefer to use a functional approach with Swift's map/flatmap/reduce etc. – Lneuner Feb 24 '16 at 10:23