web-dev-qa-db-fra.com

Quel est le moyen le plus propre d'appliquer map () à un dictionnaire dans Swift?

Je voudrais mapper une fonction sur toutes les clés du dictionnaire. J'espérais que quelque chose comme ce qui suit fonctionnerait, mais le filtre ne peut pas être appliqué directement au dictionnaire. Quel est le moyen le plus propre de réaliser cela?

Dans cet exemple, j'essaie d'incrémenter chaque valeur de 1. Toutefois, cela est accessoire pour l'exemple - l'objectif principal est de déterminer comment appliquer map () à un dictionnaire.

var d = ["foo" : 1, "bar" : 2]

d.map() {
    $0.1 += 1
}
123
Maria Zverina

Swift 4

Bonnes nouvelles! Swift 4 inclut une méthode mapValues(_:) qui construit une copie d’un dictionnaire avec les mêmes clés, mais des valeurs différentes. Il inclut également une surcharge filter(_:) qui renvoie un initialiseur Dictionary, et init(uniqueKeysWithValues:) et init(_:uniquingKeysWith:) afin de créer une Dictionary à partir d'une séquence arbitraire de tuples. Cela signifie que, si vous souhaitez modifier les clés et les valeurs, vous pouvez dire quelque chose comme:

let newDict = Dictionary(uniqueKeysWithValues:
    oldDict.map { key, value in (key.uppercased(), value.lowercased()) })

De nouvelles API permettent également de fusionner des dictionnaires, en substituant une valeur par défaut aux éléments manquants, en regroupant des valeurs (conversion d'une collection en dictionnaire de tableaux, indexées par le résultat du mappage de la collection sur une fonction), etc.

Lors de la discussion de la proposition, SE-0165 , qui présentait ces caractéristiques, j'ai évoqué cette réponse à plusieurs reprises, et je pense que le nombre élevé de votes positifs a contribué à démontrer la demande. Merci donc pour votre aide pour améliorer Swift!

Swift 3

map est une méthode d'extension sur Sequence et Collection. C'est défini comme ceci:

func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]

Puisque Dictionary est conforme à Collection et que son Element typealias est un tuple (clé, valeur), cela signifie que vous devriez pouvoir faire quelque chose comme ceci:

result = dict.map { (key, value) in (key, value.uppercaseString) }

Cependant, cela n'affectera pas réellement à une variable de type Dictionary-. La méthode map est définie pour toujours renvoyer un tableau (le [T]), même pour d'autres types comme les dictionnaires. Si vous écrivez un constructeur qui transformera un tableau de deux tuples en un Dictionary et que tout ira bien avec le monde:

extension Dictionary {
    init(_ pairs: [Element]) {
        self.init()
        for (k, v) in pairs {
            self[k] = v
        }
    }
}

result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })

Vous voudrez peut-être même écrire une version de map spécifique au dictionnaire afin d'éviter d'appeler explicitement le constructeur. Ici, j'ai également inclus une implémentation de filter:

extension Dictionary {
    func mapPairs<OutKey: Hashable, OutValue>(_ transform: (Element) throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
        return Dictionary<OutKey, OutValue>(uniqueKeysWithValues: try map(transform))
    }

    func filterPairs(_ includeElement: (Element) throws -> Bool) rethrows -> [Key: Value] {
        return Dictionary(uniqueKeysWithValues: try filter(includeElement))
    }
}

J'ai utilisé des noms différents car sinon, seules les valeurs de retour permettraient de distinguer les deux méthodes, ce qui peut rendre le code très ambigu.

Utilisez-le comme ceci:

result = dict.mapPairs { (key, value) in (key, value.uppercaseString) }

Incidemment, pour de nombreuses raisons, vous souhaiterez peut-être mapper uniquement les valeurs:

extension Dictionary {
    func map<OutValue>(_ transform: (Value) throws -> OutValue) rethrows -> [Key: OutValue] {
        return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
    }
}

La raison en est que mapPairs peut perdre des paires si deux finissent par être mappés sur la même clé. map(Value -> OutValue), d'autre part, conserve toujours les clés, donc ce n'est pas un danger.

231
Brent Royal-Gordon

Avec Swift 4, vous pouvez utiliser l’un des cinq extraits suivants pour résoudre votre problème.


#1. Utilisation de la méthode DictionarymapValues(_:)

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.mapValues { value in
    return value + 1
}
//let newDictionary = dictionary.mapValues { $0 + 1 } // also works

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 2. Utilisation de la méthode Dictionarymap et de l'initialiseur init(uniqueKeysWithValues:)

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let tupleArray = dictionary.map { (key: String, value: Int) in
    return (key, value + 1)
}
//let tupleArray = dictionary.map { ($0, $1 + 1) } // also works

let newDictionary = Dictionary(uniqueKeysWithValues: tupleArray)

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 3. Utilisation de la méthode Dictionaryreduce(_:_:)

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce([:]) { (partialResult: [String: Int], Tuple: (key: String, value: Int)) in
    var result = partialResult
    result[Tuple.key] = Tuple.value + 1
    return result
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 4. Utilisation de Dictionarysubscript(_:default:) indice

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key, default: value] += 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]

# 5. Utilisation de Dictionarysubscript(_:) indice

let dictionary = ["foo": 1, "bar": 2, "baz": 5]

var newDictionary = [String: Int]()
for (key, value) in dictionary {
    newDictionary[key] = value + 1
}

print(newDictionary) // prints: ["baz": 6, "foo": 2, "bar": 3]
27
Imanou Petit

Alors que la plupart des réponses ici concernent la manière de mapper l’ensemble du dictionnaire (clés et values), la question visait uniquement à mapper les valeurs. Il s'agit d'une distinction importante, car les valeurs de mappage vous permettent de garantir le même nombre d'entrées, alors que le mappage de clé et de valeur peut générer des clés en double.

Voici une extension, mapValues, qui vous permet de mapper uniquement les valeurs. Notez qu'il étend également le dictionnaire avec un init à partir d'une séquence de paires clé/valeur, ce qui est un peu plus général que l'initialiser à partir d'un tableau:

extension Dictionary {
    init<S: SequenceType where S.Generator.Element == Element>
      (_ seq: S) {
        self.init()
        for (k,v) in seq {
            self[k] = v
        }
    }

    func mapValues<T>(transform: Value->T) -> Dictionary<Key,T> {
        return Dictionary<Key,T>(Zip(self.keys, self.values.map(transform)))
    }

}
18

La méthode la plus propre consiste simplement à ajouter map au dictionnaire:

extension Dictionary {
    mutating func map(transform: (key:KeyType, value:ValueType) -> (newValue:ValueType)) {
        for key in self.keys {
            var newValue = transform(key: key, value: self[key]!)
            self.updateValue(newValue, forKey: key)
        }
    }
}

Vérifier que cela fonctionne:

var dic = ["a": 50, "b": 60, "c": 70]

dic.map { $0.1 + 1 }

println(dic)

dic.map { (key, value) in
    if key == "a" {
        return value
    } else {
        return value * 2
    }
}

println(dic)

Sortie:

[c: 71, a: 51, b: 61]
[c: 142, a: 51, b: 122]
11
Joseph Mark

Vous pouvez également utiliser reduce au lieu de map. reduce est capable de faire tout ce que map peut faire et plus encore!

let oldDict = ["old1": 1, "old2":2]

let newDict = reduce(oldDict, [String:Int]()) { dict, pair in
    var d = dict
    d["new\(pair.1)"] = pair.1
    return d
}

println(newDict)   //  ["new1": 1, "new2": 2]

Ce serait assez facile de placer cela dans une extension, mais même sans l'extension, cela vous permet de faire ce que vous voulez avec un seul appel de fonction.

7
Aaron Rasmussen

Il s'avère que vous pouvez le faire. Ce que vous devez faire est de créer un tableau à partir du MapCollectionView<Dictionary<KeyType, ValueType>, KeyType> renvoyé par la méthode des dictionnaires keys. ( Info here ) Vous pouvez ensuite mapper ce tableau et renvoyer les valeurs mises à jour au dictionnaire.

var dictionary = ["foo" : 1, "bar" : 2]

Array(dictionary.keys).map() {
    dictionary.updateValue(dictionary[$0]! + 1, forKey: $0)
}

dictionary
4
Mick MacCallum

Selon la référence de la bibliothèque Swift Standard Library, la carte est une fonction des tableaux. Pas pour les dictionnaires.

Mais vous pouvez utiliser votre dictionnaire pour modifier les clés:

var d = ["foo" : 1, "bar" : 2]

for (name, key) in d {
    d[name] = d[name]! + 1
}
3
Jens Wirth

Je cherchais un moyen de mapper un dictionnaire directement dans un tableau typé avec des objets personnalisés. Trouvé la solution dans cette extension:

extension Dictionary {
    func mapKeys<U> (transform: Key -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k))
        }
        return results
    }

    func mapValues<U> (transform: Value -> U) -> Array<U> {
        var results: Array<U> = []
        for v in self.values {
            results.append(transform(v))
        }
        return results
    }

    func map<U> (transform: Value -> U) -> Array<U> {
        return self.mapValues(transform)
    }

    func map<U> (transform: (Key, Value) -> U) -> Array<U> {
        var results: Array<U> = []
        for k in self.keys {
            results.append(transform(k as Key, self[ k ]! as Value))
        }
        return results
    }

    func map<K: Hashable, V> (transform: (Key, Value) -> (K, V)) -> Dictionary<K, V> {
        var results: Dictionary<K, V> = [:]
        for k in self.keys {
            if let value = self[ k ] {
                let (u, w) = transform(k, value)
                results.updateValue(w, forKey: u)
            }
        }
        return results
    }
}

En l'utilisant comme suit:

self.values = values.map({ (key:String, value:NSNumber) -> VDLFilterValue in
    return VDLFilterValue(name: key, amount: value)
})
3
Antoine

Swift 3

J'essaie un moyen facile dans Swift 3. 

Je souhaite mapper [String: String?] vers [String: String] , j'utilise forEach au lieu de map ou de carte plate.

    let oldDict = ["key0": "val0", "key1": nil, "key1": "val2","key2": nil]
    var newDict = [String: String]()
    oldDict.forEach { (source: (key: String, value: String?)) in
        if let value = source.value{
            newDict[source.key] = value
        }
    }
3
Mate

Swift 3

Usage:

let bob = ["a": "AAA", "b": "BBB", "c": "CCC"]
bob.mapDictionary { ($1, $0) } // ["BBB": "b", "CCC": "c", "AAA": "a"]

Extension:

extension Dictionary {
    func mapDictionary(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
        var dict = [Key: Value]()
        for key in keys {
            guard let value = self[key], let keyValue = transform(key, value) else {
                continue
            }

            dict[keyValue.0] = keyValue.1
        }
        return dict
    }
}
1
dimpiax

Je suppose que le problème est de conserver les clés de notre dictionnaire d'origine et de cartographier toutes ses valeurs d'une manière ou d'une autre.

Généralisons et disons que nous pouvons vouloir mapper toutes les valeurs vers un autre type .

Nous aimerions donc commencer par un dictionnaire et une fermeture (une fonction) et aboutir à un nouveau dictionnaire. Le problème est, bien sûr, que la frappe stricte de Swift est un obstacle. Une fermeture doit spécifier le type entrant et le type sortant. Nous ne pouvons donc pas créer une méthode qui prend un général fermeture - sauf dans le contexte d'un générique. Nous avons donc besoin d’une fonction générique.

D'autres solutions se sont concentrées sur cela dans le monde générique de la structure générique Dictionary elle-même, mais je trouve plus facile de penser en termes de fonction de niveau supérieur. Par exemple, nous pourrions écrire ceci, où K est le type de clé, V1 est le type de valeur du dictionnaire de départ et V2 est le type de valeur du dictionnaire de fin:

func mapValues<K,V1,V2>(d1:[K:V1], closure:(V1)->V2) -> [K:V2] {
    var d2 = [K:V2]()
    for (key,value) in Zip(d1.keys, d1.values.map(closure)) {
        d2.updateValue(value, forKey: key)
    }
    return d2
}

Voici un exemple simple d'appel, juste pour prouver que le générique se résout effectivement au moment de la compilation:

let d : [String:Int] = ["one":1, "two":2]
let result = mapValues(d) { (i : Int) -> String in String(i) }

Nous avons commencé avec un dictionnaire de [String:Int] et avons fini avec un dictionnaire de [String:String] en transformant les valeurs du premier dictionnaire par une fermeture.

(EDIT: Je vois maintenant que c'est en fait la même chose que la solution AirspeedVelocity, sauf que je n'ai pas ajouté l'élégance supplémentaire d'un initialiseur de dictionnaire qui crée un dictionnaire à partir d'une séquence Zip.)

1
matt

Swift 3 Je l'ai utilisé,

func mapDict(dict:[String:Any])->[String:String]{
    var updatedDict:[String:String] = [:]
    for key in dict.keys{
        if let value = dict[key]{
            updatedDict[key] = String(describing: value)
        }
    }

   return updatedDict
}

Usage:

let dict:[String:Any] = ["MyKey":1]
let mappedDict:[String:String] = mapDict(dict: dict)

Réf

0
Zaid Pathan

Si vous essayez uniquement de mapper les valeurs (en modifiant éventuellement leur type), incluez cette extension:

extension Dictionary {
    func valuesMapped<T>(_ transform: (Value) -> T) -> [Key: T] {
        var newDict = [Key: T]()
        for (key, value) in self {
            newDict[key] = transform(value)
        }
        return newDict
    }
}

Étant donné que vous avez ce dictionnaire:

let intsDict = ["One": 1, "Two": 2, "Three": 3]

Transformation de valeur sur une seule ligne alors, ressemble à ceci:

let stringsDict = intsDict.valuesMapped { String($0 * 2) }
// => ["One": "2", "Three": "6", "Two": "4"]

La transformation de valeur multiligne alors ressemble à ceci:

let complexStringsDict = intsDict.valuesMapped { (value: Int) -> String in
    let calculationResult = (value * 3 + 7) % 5
    return String("Complex number #\(calculationResult)")
}
// => ["One": "Complex number #0", "Three": ...
0
Dschee

Une autre approche consiste à map dans un dictionnaire et à reduce, où les fonctions keyTransform et valueTransform sont des fonctions.

let dictionary = ["a": 1, "b": 2, "c": 3]

func keyTransform(key: String) -> Int {
    return Int(key.unicodeScalars.first!.value)
}

func valueTransform(value: Int) -> String {
    return String(value)
}

dictionary.map { (key, value) in
     [keyTransform(key): valueTransform(value)]
}.reduce([Int:String]()) { memo, element in
    var m = memo
    for (k, v) in element {
        m.updateValue(v, forKey: k)
    }
    return m
}
0
NebulaFox