Comment ajouter un dictionnaire d'éléments dans un autre dictionnaire


172

Les tableaux dans Swift prennent en charge l'opérateur + = pour ajouter le contenu d'un tableau à un autre. Existe-t-il un moyen simple de le faire pour un dictionnaire?

par exemple:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var combinedDict = ... (some way of combining dict1 & dict2 without looping)


fromDict.forEach {intoDict[$0] = $1}
Sazzad Hissain Khan

Réponses:


171

Vous pouvez définir un +=opérateur pour Dictionary, par exemple,

func += <K, V> (left: inout [K:V], right: [K:V]) { 
    for (k, v) in right { 
        left[k] = v
    } 
}

1
Oh mec, j'ai tellement lutté pour trouver la déclaration générique appropriée pour cela, j'ai tout essayé sauf cela. Mais vous pouvez laisser tomber le @assignmentet return, vous êtes déjà en train de muter à gauche. Edit: en fait, même si je n'obtiens aucune erreur, je pense que cela @assignmentdevrait rester.
Roland

14
Plus de sucre de syntaxe: func +=<K, V> (inout left: [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Ivan Vavilov

48
@animal_chin Parce que nous devons implémenter la moitié du langage nous-mêmes? Oui. Impressionné. Ne vous méprenez pas, j'adore la surcharge des opérateurs. Je n'aime tout simplement pas devoir l'utiliser pour les fonctionnalités de base qui devraient être intégrées.
devios1

2
@devios Haha en fait ensuite une demande de tirage au repo Swift: D Puisque, de toute évidence, Apple ne peut pas être arsé
CommaToast

6
Tirant directement de la bibliothèque SwifterSwift :public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach({ lhs[$0] = $1}) }
Justin Oroz

99

Dans Swift 4, il faut utiliser merging(_:uniquingKeysWith:):

Exemple:

let dictA = ["x" : 1, "y": 2, "z": 3]
let dictB = ["x" : 11, "y": 22, "w": 0]

let resultA = dictA.merging(dictB, uniquingKeysWith: { (first, _) in first })
let resultB = dictA.merging(dictB, uniquingKeysWith: { (_, last) in last })

print(resultA) // ["x": 1, "y": 2, "z": 3, "w": 0]
print(resultB) // ["x": 11, "y": 22, "z": 3, "w": 0]

1
// mutable: var dictA = ["x": 1, "y": 2, "z": 3] var dictB = ["x": 11, "y": 22, "w": 0] dictA. merge (dictB, uniquingKeysWith: {(first, _) in first}) print (dictA) // ["x": 1, "y": 2, "z": 3, "w": 0]
muthukumar

1
Le deuxième exemple montré dans cette réponse est l'équivalent de [NSMutableDictionary addEntriesFromDictionary:].
orj

92

Que diriez-vous

dict2.forEach { (k,v) in dict1[k] = v }

Cela ajoute toutes les clés et valeurs de dict2 dans dict1.


43
Belle solution. Légèrement plus court: dict2.forEach {dict1 [$ 0] = $ 1}
Brett

1
C'est une excellente solution, mais pour Swift 4, vous obtiendrez probablement une erreur indiquant Closure tuple parameter '(key: _, value: _)' does not support destructuring(du moins au moment de la rédaction de cet article). Il faudrait restructurer la fermeture en fonction de [cette réponse stackoverflow] ( stackoverflow.com/questions/44945967/... ):
JonnyB

78

Actuellement, en regardant la référence de la bibliothèque standard Swift pour le dictionnaire, il n'y a aucun moyen de mettre à jour facilement un dictionnaire avec un autre.

Vous pouvez écrire une extension pour le faire

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

extension Dictionary {
    mutating func update(other:Dictionary) {
        for (key,value) in other {
            self.updateValue(value, forKey:key)
        }
    }
}

dict1.update(dict2)
// dict1 is now ["a" : "foo", "b" : "bar]

3
C'est une excellente utilisation de l'extension pour le dictionnaire!
Marc Attinasi

76

Swift 4 fournit merging(_:uniquingKeysWith:), donc pour votre cas:

let combinedDict = dict1.merging(dict2) { $1 }

La fermeture abrégée retourne $1, donc la valeur de dict2 sera utilisée en cas de conflit avec les clés.


1
Je voulais juste souligner que c'est le plus concis et le plus proche que j'ai trouvé de ce que la documentation Apple indique - (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;. En ce qui concerne ce qu'il faut faire avec les doublons, il indique que: "Si les deux dictionnaires contiennent la même clé, l'objet de valeur précédent du dictionnaire de réception pour cette clé reçoit un message de libération et le nouvel objet de valeur prend sa place.", Donc dans la version Swift, ou en merge (_: uniquingKeysWith :), renvoyant la deuxième valeur $1, est identique à ce que addEntriesFromDictionaryfait.
Tim Fuqua

31

Il n'est pas intégré à la bibliothèque Swift mais vous pouvez ajouter ce que vous voulez avec la surcharge d'opérateurs, par exemple:

func + <K,V>(left: Dictionary<K,V>, right: Dictionary<K,V>) 
    -> Dictionary<K,V> 
{
    var map = Dictionary<K,V>()
    for (k, v) in left {
        map[k] = v
    }
    for (k, v) in right {
        map[k] = v
    }
    return map
}

Cela surcharge l' +opérateur pour les dictionnaires que vous pouvez maintenant utiliser pour ajouter des dictionnaires avec l' +opérateur, par exemple:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var dict3 = dict1 + dict2 // ["a": "foo", "b": "bar"]

1
Vous pouvez également le faire pour + = pour mettre à jour en place un dict (selon la question op).
Rod

3
Vous pouvez supprimer mapet supprimer la première for (k, v)...boucle si vous déclarez le leftparamètre comme var, puis copiez simplement les valeurs à partir de rightcelui-ci.
Nate Cook

2
@NateCook qui muterait le dictionnaire, ce qui n'est pas un comportement attendu pour l' +opérateur infixe.
mythz

Merci pour ça. Votre réponse était probablement plus précise pour l'exemple de code que j'ai publié, tandis que l'autre correspondait davantage à ce que je voulais en fonction de ma question. Mon mauvais, de toute façon vous a donné un vote favorable;)
rustyshelf

2
@mythz Ce n'est pas vraiment une mutation, puisque la +surcharge d'opérateurs n'est pas une méthode non plus Dictionary, c'est une fonction simple. Les modifications que vous apportez à un leftparamètre variable ne seraient pas visibles en dehors de la fonction.
Nate Cook

28

Swift 3:

extension Dictionary {

    mutating func merge(with dictionary: Dictionary) {
        dictionary.forEach { updateValue($1, forKey: $0) }
    }

    func merged(with dictionary: Dictionary) -> Dictionary {
        var dict = self
        dict.merge(with: dictionary)
        return dict
    }
}

let a = ["a":"b"]
let b = ["1":"2"]
let c = a.merged(with: b)

print(c) //["a": "b", "1": "2"]

6
légèrement mieuxfunc merged(with dictionary: Dictionary<Key,Value>) -> Dictionary<Key,Value> { var copy = self dictionary.forEach { copy.updateValue($1, forKey: $0) } return copy }
Alexander Vasenin

16

Swift 2.0

extension Dictionary {

    mutating func unionInPlace(dictionary: Dictionary) {
        dictionary.forEach { self.updateValue($1, forKey: $0) }
    }

    func union(var dictionary: Dictionary) -> Dictionary {
        dictionary.unionInPlace(self)
        return dictionary
    }
}

impossible d'appeler une fonction mutante à partir d'une fonction non
mutante

La unionfonction a la valeur qui lui est passée var, ce qui signifie que le dictionnaire copié peut être muté. C'est un peu plus propre que func union(dictionary: Dictionary) -> Dictionary { var dict2 = dictionary; dict2.unionInPlace(self); return dict2 }, ne serait-ce que par une ligne.
MaddTheSane

2
var params sont dépréciées et seront enlevés à Swift 3. La façon préférée de faire est maintenant de déclarer un var dans le corps: var dictionary = dictionary. De là: github.com/apple/swift-evolution/blob/master/proposals
Daniel Wood

Pour rendre les choses plus sûres, ajoutez <Key, Value>à ces Dictionarys.
Raphael

12

Immuable

Je préfère combiner / unir des dictionnaires immuables avec un +opérateur, donc je l'ai implémenté comme:

// Swift 2
func + <K,V> (left: Dictionary<K,V>, right: Dictionary<K,V>?) -> Dictionary<K,V> {
    guard let right = right else { return left }
    return left.reduce(right) {
        var new = $0 as [K:V]
        new.updateValue($1.1, forKey: $1.0)
        return new
    }
}

let moreAttributes: [String:AnyObject] = ["Function":"authenticate"]
let attributes: [String:AnyObject] = ["File":"Auth.swift"]

attributes + moreAttributes + nil //["Function": "authenticate", "File": "Auth.swift"]    
attributes + moreAttributes //["Function": "authenticate", "File": "Auth.swift"]
attributes + nil //["File": "Auth.swift"]

Mutable

// Swift 2
func += <K,V> (inout left: Dictionary<K,V>, right: Dictionary<K,V>?) {
    guard let right = right else { return }
    right.forEach { key, value in
        left.updateValue(value, forKey: key)
    }
}

let moreAttributes: [String:AnyObject] = ["Function":"authenticate"]
var attributes: [String:AnyObject] = ["File":"Auth.swift"]

attributes += nil //["File": "Auth.swift"]
attributes += moreAttributes //["File": "Auth.swift", "Function": "authenticate"]

5
Je ne comprends pas pourquoi ce n'est pas intégré à swift par défaut?
ioquatix

1
voulez-vous que les valeurs de gauche remplacent la droite dans votre solution "Immuable"? Je pense que vous voulez avoir right.reduce(left), au moins c'est le comportement attendu imo (et c'est le comportement de votre deuxième exemple) - ie. ["A":1] + ["A":2]devrait sortir["A":2]
ccwasden

La sortie correspond au code. Je veux que la valeur initiale soit du bon côté, comme c'est le cas actuellement.
ricardopereira

12

Pas besoin d'avoir des extensions de dictionnaire maintenant. Le dictionnaire Swift (Xcode 9.0+) a une fonctionnalité pour cela. Jetez un œil ici . Ci-dessous, voici un exemple d'utilisation

  var oldDictionary = ["a": 1, "b": 2]
  var newDictionary = ["a": 10000, "b": 10000, "c": 4]

  oldDictionary.merge(newDictionary) { (oldValue, newValue) -> Int in
        // This closure return what value to consider if repeated keys are found
        return newValue 
  }
  print(oldDictionary) // Prints ["b": 10000, "a": 10000, "c": 4]

2
J'ajoute un style fonctionnel pour l'exemple ci-dessus:oldDictionary.merge(newDictionary) { $1 }
Andrej

11

Une variante plus lisible utilisant une extension.

extension Dictionary {    
    func merge(dict: Dictionary<Key,Value>) -> Dictionary<Key,Value> {
        var mutableCopy = self        
        for (key, value) in dict {
            // If both dictionaries have a value for same key, the value of the other dictionary is used.           
            mutableCopy[key] = value 
        }        
        return mutableCopy
    }    
}

3
solution très agréable et propre!
user3441734

11

Tu peux essayer ça

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var temp = NSMutableDictionary(dictionary: dict1);
temp.addEntriesFromDictionary(dict2)

10

Vous pouvez également utiliser réduire pour les fusionner. Essayez ceci dans la cour de récréation

let d1 = ["a":"foo","b":"bar"]
let d2 = ["c":"car","d":"door"]

let d3 = d1.reduce(d2) { (var d, p) in
   d[p.0] = p.1
   return d
}

Cela semble intéressant, mais que sont det p?
rob

1
d est le résultat persistant de chaque itération du bloc de réduction et p est l'élément de la collection qui est réduit.
farhadf

1
cela semble planter dans swift 3.0 beta
possen

Les paramètres var sont obsolètes dans swift 3
Dmitry Klochkov

C'est ma solution préférée parmi celles mentionnées ici. Filtrez / cartographiez / réduisez à nouveau les victoires pour des solutions concises.
gokeji

7

Je recommande la bibliothèque SwifterSwift . Cependant, si vous ne souhaitez pas utiliser toute la bibliothèque et tous ses excellents ajouts, vous pouvez simplement utiliser leur extension de Dictionary:

Swift 3+

public extension Dictionary {
    public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) {
        rhs.forEach({ lhs[$0] = $1})
    }
}

En fait, SE-110 a été rétabli, donc la version Swift 4 devrait être la même que la version Swift 3.
BallpointBen

5

Vous pouvez parcourir les combinaisons de valeurs clés par rapport à la valeur que vous souhaitez fusionner et les ajouter via la méthode updateValue (forKey :):

dictionaryTwo.forEach {
    dictionaryOne.updateValue($1, forKey: $0)
}

Maintenant, toutes les valeurs de dictionaryTwo ont été ajoutées à dictionaryOne.


4

Identique à la réponse de @ farhadf mais adoptée pour Swift 3:

let sourceDict1 = [1: "one", 2: "two"]
let sourceDict2 = [3: "three", 4: "four"]

let result = sourceDict1.reduce(sourceDict2) { (partialResult , pair) in
    var partialResult = partialResult //without this line we could not modify the dictionary
    partialResult[pair.0] = pair.1
    return partialResult
}

4

Swift 3, extension de dictionnaire:

public extension Dictionary {

    public static func +=(lhs: inout Dictionary, rhs: Dictionary) {
        for (k, v) in rhs {
            lhs[k] = v
        }
    }

}

4

Quelques surcharges encore plus simplifiées pour Swift 4:

extension Dictionary {
    static func += (lhs: inout [Key:Value], rhs: [Key:Value]) {
        lhs.merge(rhs){$1}
    }
    static func + (lhs: [Key:Value], rhs: [Key:Value]) -> [Key:Value] {
        return lhs.merging(rhs){$1}
    }
}

3

Vous pouvez ajouter une Dictionaryextension comme celle-ci:

extension Dictionary {
    func mergedWith(otherDictionary: [Key: Value]) -> [Key: Value] {
        var mergedDict: [Key: Value] = [:]
        [self, otherDictionary].forEach { dict in
            for (key, value) in dict {
                mergedDict[key] = value
            }
        }
        return mergedDict
    }
}

Ensuite, l' utilisation est aussi simple que ce qui suit:

var dict1 = ["a" : "foo"]
var dict2 = ["b" : "bar"]

var combinedDict = dict1.mergedWith(dict2)
// => ["a": "foo", "b": "bar"]

Si vous préférez un cadre qui comprend également quelques fonctionnalités plus pratiques , puis la caisse HandySwift . Importez-le simplement dans votre projet et vous pouvez utiliser le code ci-dessus sans ajouter vous-même d'extensions au projet.


Installer une bibliothèque pour utiliser une seule fonction est une mauvaise pratique
HackaZach

@HackaZach: Je viens de mettre à jour ma réponse pour inclure la partie appropriée du framework pour empêcher l'inclusion de la bibliothèque entière si seule cette petite partie est nécessaire. Je garde l'indice du cadre pour les personnes qui souhaitent utiliser plusieurs de ses fonctionnalités. J'espère que cela aide à maintenir les bonnes pratiques!
Jeehut

3

Il n'y a plus besoin d'extension ou de fonction supplémentaire. Vous pouvez écrire comme ça:

firstDictionary.merge(secondDictionary) { (value1, value2) -> AnyObject in
        return object2 // what you want to return if keys same.
    }

2

Vous pouvez utiliser,

func addAll(from: [String: Any], into: [String: Any]){
    from.forEach {into[$0] = $1}
}

1

Vous pouvez utiliser la fonction bridgeToObjectiveC () pour faire du dictionnaire un NSDictionary.

Sera comme le suivant:

var dict1 = ["a":"Foo"]
var dict2 = ["b":"Boo"]

var combinedDict = dict1.bridgeToObjectiveC()
var mutiDict1 : NSMutableDictionary! = combinedDict.mutableCopy() as NSMutableDictionary

var combineDict2 = dict2.bridgeToObjectiveC()

var combine = mutiDict1.addEntriesFromDictionary(combineDict2)

Ensuite, vous pouvez reconvertir le NSDictionary (combiner) ou faire quoi que ce soit.


Pardon que voulez-vous dire exactement?
Anton

Juste une préférence. Semble alambiqué pour faire le pont entre les langues. Mieux vaut rester dans les limites d'une seule langue, tout en laissant obj-c mourir plus rapidement.
TruMan1

2
Ouais, j'ai posté cette réponse littéralement le jour où Swift a annoncé ... Il y avait donc une raison
Anton

1
import Foundation

let x = ["a":1]
let y = ["b":2]

let out = NSMutableDictionary(dictionary: x)
out.addEntriesFromDictionary(y)

Le résultat est un NSMutableDictionary pas un dictionnaire typé Swift, mais la syntaxe pour l'utiliser est la même (out["a"] == 1 dans ce cas) donc vous n'auriez un problème que si vous utilisez un code tiers qui attend un dictionnaire Swift, ou vraiment besoin de la vérification de type.

La réponse courte ici est que vous devez en fait faire une boucle. Même si vous ne le saisissez pas explicitement, c'est ce que fera la méthode que vous appelez (addEntriesFromDictionary: here). Je suggérerais que si vous ne savez pas pourquoi cela serait le cas, vous devriez réfléchir à la façon dont vous fusionneriez les nœuds feuilles de deux arbres B.

Si vous avez vraiment besoin d'un type de dictionnaire natif Swift en retour, je vous suggère:

let x = ["a":1]
let y = ["b":2]

var out = x
for (k, v) in y {
    out[k] = v
}

L'inconvénient de cette approche est que l'index du dictionnaire - quelle que soit la méthode utilisée - peut être reconstruit plusieurs fois dans la boucle, donc en pratique, c'est environ 10 fois plus lent que l'approche NSMutableDictionary.


1

Toutes ces réponses sont compliquées. Voici ma solution pour swift 2.2:

    //get first dictionnary
    let finalDictionnary : NSMutableDictionary = self.getBasicDict()
    //cast second dictionnary as [NSObject : AnyObject]
    let secondDictionnary : [NSObject : AnyObject] = self.getOtherDict() as [NSObject : AnyObject]
    //merge dictionnary into the first one
    finalDictionnary.addEntriesFromDictionary(secondDictionnary) 

Cela ne fonctionne que sur NSMutableDictionary et non sur les dictionnaires Swift natifs, malheureusement. Je souhaite que cela soit ajouté à Swift de manière native.
Chris Paveglio

0

Mes besoins étaient différents, j'avais besoin de fusionner des ensembles de données imbriqués incomplets sans écraser.

merging:
    ["b": [1, 2], "s": Set([5, 6]), "a": 1, "d": ["x": 2]]
with
    ["b": [3, 4], "s": Set([6, 7]), "a": 2, "d": ["y": 4]]
yields:
    ["b": [1, 2, 3, 4], "s": Set([5, 6, 7]), "a": 2, "d": ["y": 4, "x": 2]]

C'était plus difficile que je ne le voulais. Le défi consistait à mapper du typage dynamique au typage statique, et j'ai utilisé des protocoles pour résoudre ce problème.

Il convient également de noter que lorsque vous utilisez la syntaxe littérale du dictionnaire, vous obtenez en fait les types de base, qui ne prennent pas les extensions de protocole. J'ai abandonné mes efforts pour les soutenir car je ne pouvais pas trouver un moyen facile de valider l'uniformité des éléments de la collection.

import UIKit


private protocol Mergable {
    func mergeWithSame<T>(right: T) -> T?
}



public extension Dictionary {

    /**
    Merge Dictionaries

    - Parameter left: Dictionary to update
    - Parameter right:  Source dictionary with values to be merged

    - Returns: Merged dictionay
    */


    func merge(right:Dictionary) -> Dictionary {
        var merged = self
        for (k, rv) in right {

            // case of existing left value
            if let lv = self[k] {

                if let lv = lv as? Mergable where lv.dynamicType == rv.dynamicType {
                    let m = lv.mergeWithSame(rv)
                    merged[k] = m
                }

                else if lv is Mergable {
                    assert(false, "Expected common type for matching keys!")
                }

                else if !(lv is Mergable), let _ = lv as? NSArray {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else if !(lv is Mergable), let _ = lv as? NSDictionary {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else {
                    merged[k] = rv
                }
            }

                // case of no existing value
            else {
                merged[k] = rv
            }
        }

        return merged
    }
}




extension Array: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Array {
            return (self + right) as? T
        }

        assert(false)
        return nil
    }
}


extension Dictionary: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Dictionary {
            return self.merge(right) as? T
        }

        assert(false)
        return nil
    }
}


extension Set: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Set {
            return self.union(right) as? T
        }

        assert(false)
        return nil
    }
}



var dsa12 = Dictionary<String, Any>()
dsa12["a"] = 1
dsa12["b"] = [1, 2]
dsa12["s"] = Set([5, 6])
dsa12["d"] = ["c":5, "x": 2]


var dsa34 = Dictionary<String, Any>()
dsa34["a"] = 2
dsa34["b"] = [3, 4]
dsa34["s"] = Set([6, 7])
dsa34["d"] = ["c":-5, "y": 4]


//let dsa2 = ["a": 1, "b":a34]
let mdsa3 = dsa12.merge(dsa34)
print("merging:\n\t\(dsa12)\nwith\n\t\(dsa34) \nyields: \n\t\(mdsa3)")

0

Swift 2.2

func + <K,V>(left: [K : V], right: [K : V]) -> [K : V] {
    var result = [K:V]()

    for (key,value) in left {
        result[key] = value
    }

    for (key,value) in right {
        result[key] = value
    }
    return result
}

si vous mettez cela, vous pouvez supprimer la première boucle: `var result = left`
NikeAlive

0

J'utiliserais simplement la bibliothèque Dollar .

https://github.com/ankurp/Dollar/#merge---merge-1

Fusionne tous les dictionnaires ensemble et le dernier dictionnaire remplace la valeur à une clé donnée

let dict: Dictionary<String, Int> = ["Dog": 1, "Cat": 2]
let dict2: Dictionary<String, Int> = ["Cow": 3]
let dict3: Dictionary<String, Int> = ["Sheep": 4]
$.merge(dict, dict2, dict3)
=> ["Dog": 1, "Cat": 2, "Cow": 3, "Sheep": 4]

5
jQuery est de retour yay!
Ben Sinclair

0

Voici une belle extension que j'ai écrite ...

extension Dictionary where Value: Any {
    public func mergeOnto(target: [Key: Value]?) -> [Key: Value] {
        guard let target = target else { return self }
        return self.merging(target) { current, _ in current }
    }
}

utiliser:

var dict1 = ["cat": 5, "dog": 6]
var dict2 = ["dog": 9, "rodent": 10]

dict1 = dict1.mergeOnto(target: dict2)

Ensuite, dict1 sera modifié en

["cat": 5, "dog": 6, "rodent": 10]
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.