Swift 3: Array to Dictionary?

ArraysDictionaryFilterSwift3

Arrays Problem Overview


I have a large array and need to access it by a key (a lookup) so I need to create Dictionary. Is there a built in function in Swift 3.0 to do so, or do I need to write it myself?

First I will need it for a class with key "String" and later on maybe I will be able to write a template version for general purpose (all types of data and key).


Note for 2019. This is now simply built-in to Swift 5, uniqueKeysWithValues and similar calls.

Arrays Solutions


Solution 1 - Arrays

Is that it (in Swift 4)?

let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })

Note: As mentioned in the comment, using uniqueKeysWithValues would give a fatal error (Fatal error: Duplicate values for key: 'your_key':) if you have duplicated keys.

If you fear that may be your case, then you can use init(_:uniquingKeysWith:) e.g.

let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] // or `let pairsWithDuplicateKeys = array.map{ ($0.key, $0) }`

let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })

print(firstValues)

//prints ["a": 1, "b": 2]

let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })

print(lastValues)

//prints ["a": 3, "b": 4]

Solution 2 - Arrays

I think you're looking for something like this:

extension Array {
    public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
        var dict = [Key:Element]()
        for element in self {
            dict[selectKey(element)] = element
        }
        return dict
    }
}

You can now do:

struct Person {
    var name: String
    var surname: String
    var identifier: String
}

let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),           Person(name: "Jane", surname: "Doe", identifier: "JAD")]
let dict = arr.toDictionary { $0.identifier }

print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]

If you'd like your code to be more general, you could even add this extension on Sequence instead of Array:

extension Sequence {
    public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
        var dict: [Key:Iterator.Element] = [:]
        for element in self {
            dict[selectKey(element)] = element
        }
        return dict
    }
}

Do note, that this causes the Sequence to be iterated over and could have side effects in some cases.

Solution 3 - Arrays

On Swift 4, you can achieve this by using Dictionary's grouping:by: initializer

For ex: You have class named A

class A {

    var name: String

    init(name: String) {
        self.name = name
    }
    // .
    // .
    // .
    // other declations and implementions
}

Next, you have an array of objects of type A

let a1 = A(name: "Joy")
let a2 = A(name: "Ben")
let a3 = A(name: "Boy")
let a4 = A(name: "Toy")
let a5 = A(name: "Tim")

let array = [a1, a2, a3, a4, a5]

Let's say you want to create a Dictionary by grouping all the names by their first letter. You use Swifts Dictionary(grouping:by:) to achieve this

let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
// this will give you a dictionary
// ["J": [a1], "B": [a2, a3], "T": [a4, a5]] 

source

Note however that the resulting Dictionary "dictionary" is of type

[String : [A]]

it is not of type

[String : A]

as you may expect. (Use #uniqueKeysWithValues to achieve the latter.)

Solution 4 - Arrays

As others already said, we need to understand which are the keys.

However I am trying to provide a solution to my interpretation of your question.

struct User {
    let id: String
    let firstName: String
    let lastName: String
}

> Here I am assuming that 2 users with the same id cannot exist

let users: [User] = ...

let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
    var result = result
    result[user.id] = user
    return result
}

Now dict is a dictionary where the key is the user id and the value is the user value.

To access a user via its id you can now simply write

let user = dict["123"]

Update #1: General approach

Given an array of a given type Element, and a closure that determine the key of an Element, the following generic function will generate a Dictionary of type [Key:Element]

func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
    return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
        var dict = dict
        dict[extractKey(elm)] = elm
        return dict
    }
}

Example

let users: [User] = [
    User(id: "a0", firstName: "a1", lastName: "a2"),
    User(id: "b0", firstName: "b1", lastName: "b2"),
    User(id: "c0", firstName: "c1", lastName: "c2")
 ]

let dict = createIndex(elms: users) { $0.id }
// ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]

Update #2

As noted by Martin R the reduce will create a new dictionary for each iteration of the related closure. This could lead to huge memory consumption.

Here's another version of the createIndex function where the space requirement is O(n) where n is the length of elms.

func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
    var dict = [Key:Element]()
    for elm in elms {
        dict[extractKey(elm)] = elm
    }
    return dict
}

Solution 5 - Arrays

The following converts an array to a dictionary.

let firstArray = [2,3,4,5,5] 

let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)

Solution 6 - Arrays

let pills = ["12", "34", "45", "67"]
let kk = Dictionary(uniqueKeysWithValues: pills.map{ ($0, "number") })

["12": "number", "67": "number", "34": "number", "45": "number"]

swift5 swift4

Solution 7 - Arrays

This extension works for all sequences (including arrays) and lets you select both key and value:

extension Sequence {
    public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
        var dict = [K: V]()
        for element in self {
            if let (key, value) = try selector(element) {
                dict[key] = value
            }
        }
        
        return dict
    }
}

Example:

let nameLookup = persons.toDictionary{($0.name, $0)}

Solution 8 - Arrays

Just do it simply,

let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!

var dic = [String: Any?]()
items.foreach {
    dic[$0.name] = $0.value
}

reduce is not very suitable,

let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
   var r = result
   r[item.name] = item.value // will create an copy of result!!!!!!
   return r
}

Solution 9 - Arrays

As i understand from you're question you would like to convert to Array to Dictionary.

In my case i create extension for the Array and keys for the dictionary will be indexes of the Array.

Example:

var intArray = [2, 3, 5, 3, 2, 1]

extension Array where Element: Any {

    var toDictionary: [Int:Element] {
        var dictionary: [Int:Element] = [:]
        for (index, element) in enumerate() {
            dictionary[index] = element
        }
        return dictionary
    }

}

let dic = intArray.toDictionary

Solution 10 - Arrays

Compatible with Swift 5 Standard Library (Xcode 10.2+ , iOS 12.2).

Here's an example of usage of an initializer init(uniqueKeysWithValues:)

The input let array: [String] = Locale.isoRegionCodes is an array of ISO31661-2 codes represented by a string.

let countryCodeAndName: [String: String] = Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? "")} )

Returned dictionary, will list all regions with ISO31661-2 code as a key and a localized region name as a value.

Output:

...
"PL":"Poland"
"DE":"Germany"
"FR":"France"
"ES":"Spain"
...

Example 2:

let dictionary: [String: String] = Dictionary(uniqueKeysWithValues: [ ("key1", "value1"), ("key2", "value2")] )

Output:

["key1": "value1", "key2": "value2"]

Important:

Precondition: The sequence must not have duplicate keys.

Code below will crash an app:

let digitWords = ["one", "two", "three", "four", "five", "five"]
let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...6))

with:

> Fatal error: Duplicate values for key: 'five'

Solution 11 - Arrays

Swift 5

extension Array {

    func toDictionary() -> [Int: Element] {
        self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
    }
    
}

Solution 12 - Arrays

If you want to follow the pattern set out by map and reduce in swift you could do something nice and functional like this:

extension Array {
    func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
        var ret = [Key: Element]()
        for item in self{
            ret[keyFor(item)] = item
        }
        return ret
    }
}

Usage:

struct Dog {
    let id: Int
}

let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
let dogsById = dogs.keyBy({ $0.id }) 
            // [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]

Solution 13 - Arrays

Swift way:

extension Sequence {
    func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key: Element] {
        reduce(into: [:]) { $0[selectKey($1)] = $1 }
    }
}

// let arr = [Person(id: 1, name: "Alan")]
// arr.toDictionary { $0.id }
// ==
// [1: Person(id: 1, name: "Alan")]

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
QuestionPeter71View Question on Stackoverflow
Solution 1 - ArraysKamil Nomtek.comView Answer on Stackoverflow
Solution 2 - ArraysoveractorView Answer on Stackoverflow
Solution 3 - ArraysBurhanuddin SunelwalaView Answer on Stackoverflow
Solution 4 - ArraysLuca AngelettiView Answer on Stackoverflow
Solution 5 - ArrayscasillasView Answer on Stackoverflow
Solution 6 - ArraysVinoth AnandanView Answer on Stackoverflow
Solution 7 - ArraysJohnView Answer on Stackoverflow
Solution 8 - ArraysDawnSongView Answer on Stackoverflow
Solution 9 - ArraysOleg GordiichukView Answer on Stackoverflow
Solution 10 - ArraysBlazej SLEBODAView Answer on Stackoverflow
Solution 11 - Arraysuser_View Answer on Stackoverflow
Solution 12 - ArraysAlex PelletierView Answer on Stackoverflow
Solution 13 - ArraysArtem SydorenkoView Answer on Stackoverflow