Quelle est la manière la plus propre d'appliquer map () à un dictionnaire dans Swift?


154

Je voudrais mapper une fonction sur toutes les touches du dictionnaire. J'espérais que quelque chose comme ce qui suit fonctionnerait, mais le filtre ne peut pas être appliqué directement au dictionnaire. Quelle est la manière la plus propre d'y parvenir?

Dans cet exemple, j'essaie d'incrémenter chaque valeur de 1. Cependant, c'est accessoire pour l'exemple - le but principal est de comprendre comment appliquer map () à un dictionnaire.

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

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

1
Le dictionnaire n'a pas de fonction de carte, ce que vous essayez de faire n'est donc pas clair.
David Berry

7
Oui, je sais que le dictionnaire n'a pas de fonction de carte. La question pourrait être reformulée comme comment cela peut être accompli avec des fermetures sans avoir besoin d'itérer sur tout le dictionnaire.
Maria Zverina

2
vous devez toujours parcourir l'ensemble du dictionnaire puisque c'est ce que mapfait a. ce que vous demandez, c'est un moyen de cacher l'itération derrière une extension / fermeture afin que vous n'ayez pas à la regarder à chaque fois que vous l'utilisez.
Joseph Mark

1
Pour Swift 4, consultez ma réponse qui montre jusqu'à 5 façons différentes de résoudre votre problème.
Imanou Petit

Réponses:


281

Swift 4+

Bonnes nouvelles! Swift 4 comprend une mapValues(_:)méthode qui construit une copie d'un dictionnaire avec les mêmes clés, mais des valeurs différentes. Il inclut également une filter(_:)surcharge qui renvoie a Dictionary, et init(uniqueKeysWithValues:)et des init(_:uniquingKeysWith:)initialiseurs pour créer un à Dictionarypartir d'une séquence arbitraire de tuples. Cela signifie que, si vous souhaitez modifier à la fois 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()) })

Il existe également de nouvelles API pour fusionner des dictionnaires, substituer une valeur par défaut aux éléments manquants, regrouper des valeurs (conversion d'une collection en un dictionnaire de tableaux, indexé par le résultat du mappage de la collection sur une fonction), etc.

Au cours de la discussion de la proposition, SE-0165 , qui a introduit ces fonctionnalités, j'ai évoqué cette réponse Stack Overflow à plusieurs reprises, et je pense que le grand nombre de votes positifs a aidé à démontrer la demande. Merci donc pour votre aide à améliorer Swift!


6
C'est certes imparfait, mais il ne faut pas laisser le parfait être l'ennemi du bien. Un mapqui pourrait produire des clés conflictuelles est toujours utile mapdans de nombreuses situations. (D'un autre côté, vous pourriez affirmer que mapper uniquement les valeurs est une interprétation naturelle des mapdictionnaires - à la fois l'exemple de l'affiche d'origine et le mien ne modifient que les valeurs, et conceptuellement, mapsur un tableau ne modifie que les valeurs, pas les indices.)
Brent Royal-Gordon

2
votre dernier bloc de code semble manquer a try:return Dictionary<Key, OutValue>(try map { (k, v) in (k, try transform(v)) })
jpsim

9
Mis à jour pour Swift 2.1? ObtenirDictionaryExtension.swift:13:16: Argument labels '(_:)' do not match any available overloads
Par Eriksson

4
Avec Swift 2.2, je reçois Argument labels '(_:)' do not match any available overloadslors de la mise en œuvre de cette dernière extension. Je ne sais pas vraiment comment le résoudre: /
Erken

2
@Audioy Cet extrait de code dépend de l' Dictionaryinitialiseur ajouté dans l'un des exemples précédents. Assurez-vous d'inclure les deux.
Brent Royal-Gordon

66

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


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

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 Dictionary mapméthode et de l' init(uniqueKeysWithValues:)initialiseur

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 d'une Dictionary reduce(_:_:)méthode ou d'une reduce(into:_:)méthode

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]
let dictionary = ["foo": 1, "bar": 2, "baz": 5]

let newDictionary = dictionary.reduce(into: [:]) { (result: inout [String: Int], tuple: (key: String, value: Int)) in
    result[tuple.key] = tuple.value + 1
}

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

# 4. Utilisation de l' Dictionary subscript(_: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 l' Dictionary subscript(_:)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]

Veuillez par exemple changer les clés et non les valeurs. Merci utile.
Yogesh Patel

18

Alors que la plupart des réponses ici se concentrent sur la façon de mapper l'ensemble du dictionnaire (clés et valeurs), la question ne voulait vraiment mapper que les valeurs. Il s'agit d'une distinction importante car le mappage des valeurs vous permet de garantir le même nombre d'entrées, tandis que le mappage à la fois de la clé et de la valeur peut entraîner des clés en double.

Voici une extension, mapValuesqui vous permet de mapper uniquement les valeurs. Notez qu'il étend également le dictionnaire avec une initséquence de paires clé / valeur, ce qui est un peu plus général que de 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)))
    }

}

..Comment est-ce utilisé? Je suis nouveau sur Swift Mind en mettant un exemple ici?
FlowUI. SimpleUITesting.com

quand j'ai essayé myDictionary.map, à l'intérieur de la fermeture, j'ai dû faire une autre carte de la manière habituelle. Cela fonctionne, mais est-ce la façon dont il est censé être utilisé?
FlowUI. SimpleUITesting.com

Y a-t-il une garantie quelque part que l'ordre des clés et des valeurs lorsqu'ils sont appelés séparément sont appariés et donc adaptés à zip?
Aaron Zinman

Pourquoi créer un nouveau init alors que vous pourriez simplement utiliser func mapValues<T>(_ transform: (_ value: Value) -> T) -> [Key: T] { var result = [Key: T]() for (key, val) in self { result[key] = transform(val) } return result }. Je trouve que ça se lit mieux aussi. Y a-t-il un problème de performances?
Marcel

12

Le moyen le plus simple est d'ajouter simplement mapau 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)

Production:

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

1
Le problème que je vois avec cette approche est qu'il SequenceType.mapne s'agit pas d'une fonction de mutation. Votre exemple pourrait être réécrit pour suivre le contrat existant pour map, mais c'est la même chose que la réponse acceptée.
Christopher Pickslay

7

Vous pouvez également utiliser à la reduceplace de la carte. reduceest capable de faire tout ce que vous mappouvez 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]

Il serait assez facile d'envelopper 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.


10
L'inconvénient de cette approche est qu'une toute nouvelle copie du dictionnaire est faite chaque fois que vous ajoutez une nouvelle clé, donc cette fonction est terriblement inefficace (l'optimiseur pourrait vous sauver).
Airspeed Velocity

C'est un bon point ... une chose que je ne comprends pas encore très bien, ce sont les éléments internes de la gestion de la mémoire de Swift. J'apprécie les commentaires comme celui-ci qui me font réfléchir :)
Aaron Rasmussen

1
Pas besoin de temp var et de réduire est désormais une méthode dans Swift 2.0:let newDict = oldDict.reduce([String:Int]()) { (var dict, pair) in dict["new\(pair.1)"] = pair.1; return dict }
Zmey

4

Il s'avère que vous pouvez le faire. Ce que vous devez faire est de créer un tableau à partir MapCollectionView<Dictionary<KeyType, ValueType>, KeyType>de la keysméthode renvoyée par les dictionnaires . ( Info ici ) 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

Pouvez-vous s'il vous plaît ajouter des explications sur la façon de convertir ["foo": 1, "bar": 2] en [("foo", 1), ("bar", 2)]
Maria Zverina

@MariaZverina Je ne sais pas ce que vous voulez dire.
Mick MacCallum

Si vous pouvez effectuer la conversion ci-dessus, le filtre peut être appliqué directement au tableau résultant
Maria Zverina

@MariaZverina Gotcha. Ouais, malheureusement, je ne connais aucun moyen de le faire.
Mick MacCallum

3

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

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

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

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

3

Je cherchais un moyen de mapper un dictionnaire directement dans un tableau typé avec des objets personnalisés. J'ai 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

Swift 3

J'essaye de manière simple dans Swift 3.

Je veux la carte [Chaîne: Chaîne] à [string: String] , j'utilise forEach au lieu de carte ou d'une carte plat.

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

Indésirable car il crée un nouveau tableau et y ajoute
Steve Kuo

2

Swift 5

La fonction map du dictionnaire est fournie avec cette syntaxe.

dictData.map(transform: ((key: String, value: String)) throws -> T)

vous pouvez simplement définir les valeurs de clôture sur ceci

var dictData: [String: String] = [
    "key_1": "test 1",
    "key_2": "test 2",
    "key_3": "test 3",
    "key_4": "test 4",
    "key_5": "test 5",
]

dictData.map { (key, value) in
    print("Key :: \(key), Value :: \(value)")
}

La sortie sera:

Key :: key_5, Value :: test 5
Key :: key_1, Value :: test 1
Key :: key_3, Value :: test 3
Key :: key_2, Value :: test 2
Key :: key_4, Value :: test 4

C'est peut-être donner cet avertissement Result of call to 'map' is unused

Ensuite, définissez juste _ =avant la variable.

Peut-être que cela vous aidera. Merci.


1

Je suppose que le problème est de conserver les clés de notre dictionnaire d'origine et de mapper 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 finir avec un nouveau dictionnaire. Le problème est, bien sûr, que le typage strict de Swift fait obstacle. Une fermeture doit spécifier quel type entre et quel type sort, et donc nous ne pouvons pas faire 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 du dictionnaire lui-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 de son 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 retrouvés avec un dictionnaire de [String:String]en transformant les valeurs du premier dictionnaire par une fermeture.

(EDIT: je vois maintenant que c'est effectivement 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

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

Si vous essayez uniquement de mapper les valeurs (en changeant potentiellement 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]

La transformation de valeur sur une seule ligne ressemble alors à ceci:

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

La transformation de valeur multiligne ressemble alors à 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

Une autre approche consiste à maptrouver un dictionnaire et reduce, où les fonctions keyTransformet valueTransformsont 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
}

C'était plus un concept que du code réel, mais je l'ai néanmoins développé en code d'exécution.
NebulaFox

0

Swift 3 j'ai utilisé ceci,

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.