web-dev-qa-db-fra.com

Swift 3: Array to Dictionary?

J'ai un grand tableau et j'ai besoin d'y accéder par une clé (une recherche) donc j'ai besoin de créer un dictionnaire. Existe-t-il une fonction intégrée dans Swift 3.0), ou dois-je l'écrire moi-même?

D'abord, j'en aurai besoin pour une classe avec la clé "String" et plus tard, je pourrai peut-être écrire une version de modèle à usage général (tous les types de données et de clés).


Remarque pour 2019. Ceci est maintenant simplement intégré à Swift, uniqueKeysWithValues et des appels similaires.

33
Peter71

Je pense que vous cherchez quelque chose comme ça:

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

Vous pouvez maintenant faire:

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")]

Si vous souhaitez que votre code soit plus général, vous pouvez même ajouter cette extension sur Sequence au lieu de 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
    }
}

Notez que cela provoque l'itération de la séquence et peut avoir des effets secondaires dans certains cas.

39
overactor

Est-ce que c'est (in Swift 4)?

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


(Remarque - si vous craignez des clés en double, vous pouvez utiliser la variante
Dictionary#keysAndValues#uniquingKeysWith
qui vous permet de gérer les dupes.)

63
Kamil Nomtek.com

Sur Swift 4, vous pouvez y parvenir en utilisant l'initialiseur du dictionnaire grouping:by:

Par exemple: vous avez nommé la classe [~ # ~] a [~ # ~]

class A {

    var name: String

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

Ensuite, vous avez un tableau d'objets de 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]

Supposons que vous souhaitiez créer un dictionnaire en regroupant tous les noms par leur première lettre. Vous utilisez Swifts Dictionary(grouping:by:) pour y parvenir

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

source

Notez cependant que le dictionnaire "dictionnaire" résultant est de type

[String : [A]]

il n'est pas de type

[String : A]

comme vous pouvez vous attendre. (Utilisez #uniqueKeysWithValues Pour atteindre ce dernier point.)

37

Comme d'autres l'ont déjà dit, nous devons comprendre quelles sont les clés.

Cependant, j'essaie de fournir une solution à mon interprétation de votre question.

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

Ici, je suppose que 2 utilisateurs avec le même id ne peuvent pas exister

let users: [User] = ...

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

Maintenant dict est un dictionnaire où le key est le user id et le value est le user value.

Pour accéder à un utilisateur via id, vous pouvez maintenant écrire simplement

let user = dict["123"]

Mise à jour n ° 1: approche générale

Étant donné un tableau d'un type donné Element et une fermeture qui détermine le key d'un Element, la fonction générique suivante générera un Dictionary de 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
    }
}

Exemple

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"}]

Mise à jour # 2

Comme indiqué par Martin R, la réduction créera un nouveau dictionnaire pour chaque itération de la fermeture associée. Cela pourrait entraîner une consommation de mémoire énorme.

Voici une autre version de la fonction createIndex dans laquelle l'espace requis est O(n)) où n est la longueur de 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
}
11
Luca Angeletti

Cette extension fonctionne pour toutes les séquences (y compris les tableaux) et vous permet de sélectionner les deux clé et valeur:

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

Exemple:

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

Ce qui suit convertit un tableau en dictionnaire.

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

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

Faites-le simplement,

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 n'est pas très approprié,

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
}
2
DawnSong

Si je comprends bien votre question, vous voudriez convertir en Array en Dictionary.

Dans mon cas, je crée extension pour le Array et les clés du dictionnaire seront des index du Array.

Exemple:

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
1
Oleg Gordiichuk

Si vous voulez suivre le modèle défini par la carte et réduire en Swift vous pouvez faire quelque chose de joli et fonctionnel comme ceci:

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)]
0
Alex Pelletier