Come si aggiunge un dizionario di elementi in un altro dizionario?


172

Le matrici in Swift supportano l'operatore + = per aggiungere il contenuto di una matrice a un'altra. C'è un modo semplice per farlo per un dizionario?

per esempio:

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,

Risposte:


171

È possibile definire l' +=operatore per Dictionary, ad es.

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

1
Oh amico, ho faticato tanto a trovare la dichiarazione generica adeguata per questo, ho provato di tutto tranne questo. Ma puoi rilasciare @assignmente return, stai già mutando a sinistra. Modifica: in realtà, anche se non ricevo errori, penso che @assignmentdovrebbe rimanere.
Roland,

14
Più zucchero di sintassi: func +=<K, V> (inout left: [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Ivan Vavilov,

48
@animal_chin Perché dobbiamo implementare noi stessi metà della lingua? Sì. Impressionato. Non fraintendetemi, adoro il sovraccarico dell'operatore. Semplicemente non mi piace doverlo utilizzare per le funzionalità di base che dovrebbero essere integrate.
Devios1

2
@devios Haha quindi una richiesta pull al repository Swift: D Dato che evidentemente Apple non può essere
risolta

6
Tirando direttamente dalla libreria SwifterSwift :public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach({ lhs[$0] = $1}) }
Justin Oroz il

99

In Swift 4, si dovrebbe usare merging(_:uniquingKeysWith:):

Esempio:

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
Il secondo esempio mostrato in questa risposta è l'equivalente di [NSMutableDictionary addEntriesFromDictionary:].
orj

92

Che ne dite di

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

Ciò aggiunge tutte le chiavi e i valori di dict2 in dict1.


43
Bella soluzione. Leggermente più breve: dict2.forEach {dict1 [$ 0] = $ 1}
Brett

1
Questa è un'ottima soluzione, ma per Swift 4 è molto probabile che venga visualizzato un errore Closure tuple parameter '(key: _, value: _)' does not support destructuring(almeno al momento in cui scrivo). Uno avrebbe bisogno di ristrutturare la chiusura in base alla [questo StackOverflow risposta] ( stackoverflow.com/questions/44945967/... ):
JonnyB

78

Attualmente, guardando il riferimento alla libreria standard di Swift per Dizionario, non è possibile aggiornare facilmente un dizionario con un altro.

È possibile scrivere un'estensione per farlo

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
Questo è un ottimo uso dell'estensione per il Dizionario!
Marc Attinasi,

76

Swift 4 fornisce merging(_:uniquingKeysWith:), quindi per il tuo caso:

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

La chiusura abbreviata ritorna $1, pertanto il valore di dict2 verrà utilizzato in caso di conflitto con le chiavi.


1
Volevo solo sottolineare che questo è il più conciso e il più vicino che ho trovato per ciò che afferma la documentazione di Apple - (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;. Per quanto riguarda cosa fare con i duplicati, afferma che: "Se entrambi i dizionari contengono la stessa chiave, l'oggetto valore precedente del dizionario ricevente per quella chiave viene inviato un messaggio di rilascio e il nuovo oggetto valore prende il suo posto.", Quindi in la versione Swift o in merge (_: uniquingKeysWith :), che restituisce il secondo valore $1, è la stessa cosa addEntriesFromDictionary.
Tim Fuqua,

31

Non è integrato nella libreria Swift ma è possibile aggiungere ciò che si desidera con il sovraccarico dell'operatore, ad esempio:

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
}

Ciò sovraccarica l' +operatore per i dizionari che ora è possibile utilizzare per aggiungere dizionari con l' +operatore, ad esempio:

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

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

1
Puoi anche farlo per + = per aggiornare sul posto un dict (come da domanda operativa).
Rod,

3
È possibile eliminare mape rilasciare il primo for (k, v)...ciclo se si dichiara il leftparametro come vare quindi si copiano i valori da rightesso.
Nate Cook,

2
@NateCook che cambierebbe il dizionario, che non è un comportamento previsto per l' +operatore infix.
mythz,

Grazie per quello La tua risposta è stata probabilmente più precisa rispetto al codice di esempio che ho pubblicato, mentre l'altro era più quello che volevo in base alla mia domanda. Mio male, comunque hai dato a entrambi un voto;)
scaffale arrugginito

2
@mythz Non sta realmente mutando, poiché il +sovraccarico dell'operatore non è neanche un metodo Dictionary, è una semplice funzione. Le modifiche apportate a un leftparametro variabile non sarebbero visibili al di fuori della funzione.
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
leggermente megliofunc 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
    }
}

non posso chiamare una funzione mutante da una funzione non mutante come quella
njzk2

La unionfunzione ha il valore passato in essere a var, il che significa che il dizionario copiato può essere mutato. È un po 'più pulito di func union(dictionary: Dictionary) -> Dictionary { var dict2 = dictionary; dict2.unionInPlace(self); return dict2 }, anche se solo di una riga.
MaddTheSane,

2
params var sono obsoleti e verranno eliminate a Swift 3. Il modo preferito per farlo è ora dichiarare una var nel corpo: var dictionary = dictionary. Da qui: github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Wood,

Per rendere le cose più sicure per i tipi, aggiungi <Key, Value>quelle Dictionary.
Raffaello,

12

Immutabile

Preferisco combinare / unire dizionari immutabili con +operatore, quindi l'ho implementato come:

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

Mutevole

// 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
Non capisco perché questo non sia integrato in rapido per impostazione predefinita?
ioquatix,

1
intendi valori da sinistra per sovrascrivere la destra nella tua soluzione "Immutabile"? Penso che intendi avere right.reduce(left), almeno quello è il comportamento atteso imo (ed è il comportamento del tuo secondo esempio) - cioè. ["A":1] + ["A":2]dovrebbe produrre["A":2]
ccwasden il

L'output corrisponde al codice. Voglio che il valore iniziale sia il lato giusto, come è adesso.
ricardopereira,

12

Non è necessario disporre di estensioni del dizionario ora. Il dizionario Swift (Xcode 9.0+) ha una funzionalità per questo. Dai un'occhiata qui . Di seguito ecco un esempio su come usarlo

  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
Sto aggiungendo uno stile funzionale per l'esempio sopra:oldDictionary.merge(newDictionary) { $1 }
Andrej,

11

Una variante più leggibile usando un'estensione.

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
soluzione molto bella e pulita!
user3441734,

11

Puoi provare questo

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

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

10

Puoi anche usare riduci per unirli. Prova questo nel parco giochi

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
}

Sembra interessante, ma cosa sono de p?
rapina il

1
d è il risultato persistente di ogni iterazione del blocco di riduzione e p è l'elemento della raccolta che viene ridotto.
farhadf,

1
questo sembra andare in crash nella rapida beta 3.0
posseggibile il

i parametri var sono deprecati in rapido 3
Dmitry Klochkov

Questa è la mia soluzione preferita tra quelle menzionate qui. Filtra / mappa / riduci nuovamente le vincite per ottime soluzioni concise.
Gokeji,

7

Raccomando la libreria SwifterSwift . Tuttavia, se non vuoi usare l'intera libreria e tutte le sue fantastiche aggiunte, puoi semplicemente usare la loro estensione di Dictionary:

Swift 3+

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

In realtà, SE-110 è stato ripristinato, quindi la versione di Swift 4 dovrebbe essere la stessa della versione di Swift 3.
BallpointBen,

5

Puoi scorrere le combinazioni di valori chiave sul valore che desideri unire e aggiungerle tramite il metodo updateValue (forKey :):

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

Ora tutti i valori del dizionario Due sono stati aggiunti al dizionario Uno.


4

Lo stesso della risposta di @ farhadf ma adottata per 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, estensione del dizionario:

public extension Dictionary {

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

}

4

Alcuni sovraccarichi ancora più snelli per 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

Puoi aggiungere Dictionaryun'estensione come questa:

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

Quindi l' utilizzo è semplice come il seguente:

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

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

Se si preferisce un quadro che comprende anche alcune caratteristiche più a portata di mano quindi checkout HandySwift . Basta importarlo nel tuo progetto e puoi usare il codice sopra senza aggiungere tu stesso estensioni al progetto.


Installare una libreria per usare una singola funzione è una cattiva pratica
HackaZach,

@HackaZach: ho appena aggiornato la mia risposta per includere la parte appropriata del framework per impedire l'inclusione dell'intera libreria se è necessaria solo questa piccola parte. Sto mantenendo il suggerimento per il framework per le persone che vogliono utilizzare molteplici delle sue funzionalità. Spero che questo aiuti a mantenere le buone pratiche!
Jeehut,

3

Non è più necessaria l'estensione o alcuna funzione aggiuntiva. Puoi scrivere così:

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

2

Puoi usare,

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

1

È possibile utilizzare la funzione bridgeToObjectiveC () per rendere il dizionario un NSDictionary.

Sarà come il seguente:

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)

Quindi è possibile riconvertire NSDictionary (combinare) o fare qualunque cosa.


Scusa, cosa intendi esattamente?
Anton

Solo una preferenza. Sembra contorto per creare un ponte tra le lingue. Meglio attenersi ai confini di una singola lingua, mentre allo stesso tempo lasciare che obj-c muoia più velocemente.
TruMan1,

2
Sì, ho pubblicato questa risposta letteralmente il giorno in cui Swift ha annunciato ....... Quindi c'era un motivo
Anton

1
import Foundation

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

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

Il risultato è un NSMutableDictionary non un dizionario tipizzato Swift, ma la sintassi per usarlo è la stessa ( out["a"] == 1in questo caso) quindi avresti un problema solo se stai usando un codice di terze parti che prevede un dizionario Swift, o davvero è necessario il controllo del tipo.

La risposta breve qui è che devi effettivamente fare il ciclo. Anche se non lo stai inserendo esplicitamente, è quello che farà il metodo che stai chiamando (addEntriesFromDictionary: qui). Suggerirei se non sei abbastanza chiaro sul perché sarebbe così dovresti considerare come unire i nodi foglia di due alberi B.

Se in realtà hai davvero bisogno di un tipo di dizionario nativo Swift in cambio, ti suggerirei:

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

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

Il rovescio della medaglia di questo approccio è che l'indice del dizionario - comunque sia fatto - può essere ricostruito più volte nel ciclo, quindi in pratica questo è circa 10 volte più lento dell'approccio NSMutableDictionary.


1

Tutte queste risposte sono complicate. Questa è la mia soluzione per 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) 

Sfortunatamente, questo funziona solo su NSMutableDictionary e non sui dizionari Swift nativi. Vorrei che questo fosse aggiunto nativamente a Swift.
Chris Paveglio,

0

Le mie esigenze erano diverse, avevo bisogno di unire insiemi di dati nidificati incompleti senza ostruzione.

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

Questo è stato più difficile di quanto volessi. La sfida consisteva nel mappare dalla tipizzazione dinamica alla tipizzazione statica e per risolvere questo ho usato protocolli.

È anche degno di nota il fatto che quando si utilizza la sintassi letterale del dizionario, si ottengono effettivamente i tipi di base, che non raccolgono le estensioni del protocollo. Ho interrotto i miei sforzi per supportare quelli poiché non riuscivo a trovare un modo facile per convalidare l'uniformità degli elementi della collezione.

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
}

se lo metti puoi rimuovere il primo loop: `var result = left`
NikeAlive

0

Vorrei solo usare la libreria Dollar .

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

Unisce tutti i dizionari e quest'ultimo dizionario sovrascrive il valore di una determinata chiave

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 è tornato yay!
Ben Sinclair l'

0

Ecco una bella estensione che ho scritto ...

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

usare:

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

dict1 = dict1.mergeOnto(target: dict2)

Quindi, dict1 verrà modificato in

["cat": 5, "dog": 6, "rodent": 10]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.