web-dev-qa-db-fra.com

Existe-t-il un moyen déclaratif de transformer Array en dictionnaire?

Je veux obtenir de ce tableau de chaînes

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

pour ça

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

J'ai essayé d'utiliser map mais le problème semble être qu'une paire clé-valeur dans un dictionnaire n'est pas un type distinct, c'est juste dans mon esprit, mais pas dans le type Dictionnaire donc je ne pouvais pas vraiment fournir une fonction de transformation car il y a n'y a rien à transformer. De plus, map renvoie un tableau, donc rien ne va.

Des idées?

31
Earl Grey

Swift 4

Comme mentionné par fl034, ceci peut être simplifié avec Swift 4 où une version vérifiée par erreur ressemble à ceci:

let foo = entries
    .map { $0.components(separatedBy: "=") }
    .reduce(into: [String:Int64]()) { dict, pair in
        if pair.count == 2, let value = Int64(pair[1]) {
            dict[pair[0]] = value
        }
    }

Encore plus simple si vous ne voulez pas les valeurs en Ints:

let foo = entries
    .map { $0.components(separatedBy: "=") }
    .reduce(into: [String:String]()) { dict, pair in
        if pair.count == 2 {
            dict[pair[0]] = pair[1]
        }
    }

Ancien TL; DR

Moins vérification d'erreur, il ressemble à peu près à:

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

Utilisez map pour transformer le [String] en un [[String]] divisé, puis construisez le dictionnaire de [String:Int] à partir de celui-ci en utilisant une réduction.

Ou, en ajoutant une extension à Dictionary:

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

(C'est une extension très utile, vous pouvez l'utiliser pour beaucoup d'opérations map/filter sur les dictionnaires, vraiment dommage qu'il n'existe pas par défaut)

Cela devient encore plus simple:

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

Bien sûr, vous pouvez également combiner les deux appels de carte, mais je préfère diviser les transformations individuelles.

Si vous souhaitez ajouter une vérification d'erreur, flatMap peut être utilisé à la place de 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
        }})
)

De nouveau, si vous le souhaitez, vous pouvez évidemment fusionner la map dans la flatMap ou la scinder pour plus de simplicité.

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
    }}
)
52
David Berry

Une façon de le faire est en deux étapes avec map et reduce avec un tuple comme valeur intermédiaire, par exemple:

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

let dict = entries.map { (str) -> (String, String) in
    let elements = str.characters.split("=").map(String.init)
    return (elements[0], elements[1])
    }.reduce([String:String]()) { (var dict, kvpair) in
        dict[kvpair.0] = kvpair.1
        return dict
}

for key in dict.keys {
    print("Value for key '\(key)' is \(dict[key]).")
}

les sorties:

Value for key 'y' is Optional("7").
Value for key 'x' is Optional("5").
Value for key 'z' is Optional("10").

ou avec une seule reduce avec le même résultat:

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

let dict = entries.reduce([String:String]()) { (var dict, entry) in
    let elements = entry.characters.split("=").map(String.init)
    dict[elements[0]] = elements[1]
    return dict
}

for key in dict.keys {
    print("Value for key '\(key)' is \(dict[key]).")
}
9
Paul Griffiths

Utilisez la nouvelle méthode reduce(into: Result) de Swift 4 :

let keyValuePairs = entries.reduce(into: [String:String]()) { (dict, entry) in
    let key = String(entry.first!)
    let value = String(entry.last!)
    dict[entry.first!] = entry.last!
}

Bien sûr, la division de votre chaîne pourrait être améliorée.

8
fl034

Bonnes réponses déjà. Voici un moyen supplémentaire avec une extension de type collection. Vous pouvez convertir n'importe quel type de collection en dictionnaire, tableau ou ensemble.

extension CollectionType {
    func map2Dict<K, V>(@noescape map: ((Self.Generator.Element) -> (K,    V)?))  -> [K: V] {
        var d = [K: V]()
        for e in self {
            if let kV = map(e) {
                d[kV.0] = kV.1
            }
        }

        return d
    }

    func map2Array<T>(@noescape map: ((Self.Generator.Element) -> (T)?))  -> [T] {
        var a = [T]()
        for e in self {
            if let o = map(e) {
                a.append(o)
            }
        }

        return a
    }

    func map2Set<T>(@noescape map: ((Self.Generator.Element) -> (T)?))  -> Set<T> {
        return Set(map2Array(map))
    }
 }

Voici l'exemple d'utilisation.

let entries = ["x=5", "y=7", "z=10"]
let dict:[String: String]  = entries.map2Dict({
let components = $0.componentsSeparatedByString("=")
    return (components.first!, components.last!)
})

print("dict \(dict)")
3
user3255356

J'aime @paulgriffiths répondre, mais si vous avez une aversion extrême pour les erreurs d'exécution, vous pouvez aller encore plus loin pour vous assurer que chaque chaîne du tableau initial comporte réellement les deux éléments requis ...

La différence importante entre ce code et les autres est que je vérifie qu'il existe bien un "=" dans la chaîne avec des éléments des deux côtés. La flatMap filtre efficacement ceux qui échouent.

extension Dictionary {
    func withUpdate(key: Key, value: Value) -> Dictionary<Key, Value> {
        var result = self
        result[key] = value
        return result
    }
}

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

let keyValues = entries.flatMap { str -> (String, String)? in
    let split = str.characters.split("=").map(String.init)
    return split.count > 1 ? (split[0], split[1]) : nil
}

let keyValuePairs = keyValues.reduce([String: String]()) { $0.withUpdate($1.0, value: $1.1) }
1
Daniel T.

Manière simple: 

let array = ["key1", "key2", "key3"]
let dict =  array.flatMap({["\($0)" : "value"]})

Sortie:

[(clé: "clé1", valeur: "valeur"), (clé: "clé2", valeur: "valeur"), (clé: "clé3", valeur: "valeur")]


Manière un peu plus complexe: 

let array = ["key1", "key2", "key3"]
let dict =  array.enumerated().flatMap({["\($0.element)" : "value\($0.offset)"]})

Sortie: 

[(clé: "clé1", valeur: "valeur0"), (clé: "clé2", valeur: "valeur1"), (clé: "clé3", valeur: "valeur2")]

0
Dmitrii Z

Et pour tous ceux qui aiment la surcharge et les one-liners.

public func +<K, V>(lhs: [K:V], rhs: [K:V]) -> [K:V] {
    var lhs: [K:V] = lhs
    for (key, value) in rhs {
        lhs[key] = value
    }
    return lhs
}

let array = ["x=5", "y=7", "z=10"]
let dictionary = array.map({ $0.componentsSeparatedByString("=") }).reduce([:]) { $0 + [$1[0]: $1[1]] }

print(dictionary) // ["y": "7", "x": "5", "z": "10"]
0
Ian Bytchek

Si vous avez deux tableaux parallèles, vous pouvez utiliser Zip(), Array() et l'extension Dictionary de David Berry :

let numbers = [10, 9, 8]
let strings = ["one", "two", "three", "four"]

let dictionary = Dictionary(elements: Array(Zip(numbers, strings)))
// == [10: "one", 9: "two", 8: "three"]

N'oubliez pas d'ajouter l'extension:

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