¿Cómo se agrega un diccionario de elementos a otro diccionario?

172

Las matrices en Swift admiten el operador + = para agregar el contenido de una matriz a otra. ¿Hay una manera fácil de hacer eso para un diccionario?

p.ej:

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

var combinedDict = ... (some way of combining dict1 & dict2 without looping)
rustyshelf
fuente
fromDict.forEach {intoDict[$0] = $1}
Sazzad Hissain Khan

Respuestas:

171

Puede definir +=operador para Dictionary, por ejemplo,

func += <K, V> (left: inout [K:V], right: [K:V]) { 
    for (k, v) in right { 
        left[k] = v
    } 
}
shucao
fuente
1
Oh hombre, luché mucho para encontrar la declaración genérica adecuada para esto, intenté todo excepto esto. Pero puedes soltar el @assignmenty return, ya estás mutando a la izquierda. Editar: en realidad, aunque no obtengo errores, creo que @assignmentdebería quedarme.
Roland
14
Más azúcar de sintaxis: func +=<K, V> (inout left: [K : V], right: [K : V]) { for (k, v) in right { left[k] = v } }
Ivan Vavilov
48
@animal_chin ¿Porque tenemos que implementar la mitad del lenguaje nosotros mismos? Si. Impresionado. No me malinterpreten Me encanta la sobrecarga del operador. Simplemente no me gusta tener que usarlo para las características básicas que deben ser incorporadas.
devios1
2
@devios Haha luego lo solicite para el repositorio Swift: D Dado que evidentemente Apple no puede ser
analizado
66
Tirando directamente de la Biblioteca SwifterSwift :public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach({ lhs[$0] = $1}) }
Justin Oroz
99

En Swift 4, uno debe usar merging(_:uniquingKeysWith:):

Ejemplo:

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]
Vin Gazoil
fuente
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
El segundo ejemplo que se muestra en esta respuesta es el equivalente de [NSMutableDictionary addEntriesFromDictionary:].
orj
92

Qué tal si

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

Eso agrega todas las claves y valores de dict2 en dict1.

jasongregori
fuente
43
Buena solución Un poco más corto: dict2.forEach {dict1 [$ 0] = $ 1}
Brett
1
Esta es una gran solución, pero para Swift 4, lo más probable es que obtenga un error que indique Closure tuple parameter '(key: _, value: _)' does not support destructuring(al menos en el momento de escribir esto). Sería necesario reestructurar el cierre de acuerdo con [esta respuesta de stackoverflow] ( stackoverflow.com/questions/44945967/… ):
JonnyB
78

Actualmente, mirando la Referencia de la Biblioteca Estándar de Swift para Diccionario, no hay forma de actualizar fácilmente un diccionario con otro.

Puedes escribir una extensión para hacerlo

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]
varilla
fuente
3
¡Este es un gran uso de extensión para el Diccionario!
Marc Attinasi
76

Swift 4 proporciona merging(_:uniquingKeysWith:), así que para su caso:

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

El cierre abreviado regresa $1, por lo tanto, el valor de dict2 se usará cuando haya un conflicto con las claves.

samwize
fuente
1
Solo quería señalar que este es el más conciso y más cercano que he encontrado a lo que dice la documentación de Apple - (void)addEntriesFromDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;. Con respecto a qué hacer con los duplicados, establece que: "Si ambos diccionarios contienen la misma clave, el objeto de valor anterior del diccionario receptor para esa clave se envía un mensaje de liberación, y el nuevo objeto de valor ocupa su lugar". la versión Swift, o en combinación (_: uniquingKeysWith :), que devuelve el segundo valor $1, es lo mismo que lo que addEntriesFromDictionaryhace.
Tim Fuqua
31

No está integrado en la biblioteca Swift, pero puede agregar lo que desee con la sobrecarga del operador, por ejemplo:

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
}

Esto sobrecarga al +operador de Diccionarios que ahora puede usar para agregar diccionarios con el +operador, por ejemplo:

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

var dict3 = dict1 + dict2 // ["a": "foo", "b": "bar"]
mythz
fuente
1
También puede hacerlo para + = para actualizar en su lugar un dict (según la pregunta op).
Rod
3
Puede eliminar mapy soltar el primer for (k, v)...ciclo si declara el leftparámetro como vary luego simplemente copia los valores righten él.
Nate Cook
2
@NateCook que mutaría el Diccionario, lo cual no es un comportamiento esperado para el +operador infijo.
mythz
Gracias por eso. Su respuesta probablemente fue más precisa para el código de muestra que publiqué, mientras que la otra fue más de lo que quería según mi pregunta. Mi mal, de todos modos les dio a ambos un
voto a favor
2
@mythz Realmente no está mutando, ya que la +sobrecarga del operador tampoco es un método Dictionary, es una función simple. Los cambios que realice en un leftparámetro variable no serían visibles fuera de la función.
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"]
Pavel Sharanda
fuente
66
un poco mejorfunc 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
    }
}
Olsen
fuente
no se puede llamar a una función mutante desde una función no mutante como esa
njzk2
La unionfunción tiene el valor pasado a ser a var, lo que significa que el diccionario copiado puede ser mutado. Es un poco más limpio que func union(dictionary: Dictionary) -> Dictionary { var dict2 = dictionary; dict2.unionInPlace(self); return dict2 }, aunque solo sea por una línea.
MaddTheSane
2
params var están en desuso y se eliminará en Swift 3. La forma preferida de hacer esto es ahora para declarar una var en el cuerpo: var dictionary = dictionary. Desde aquí: github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Wood
Para hacer que las cosas sean más seguras, agregue <Key, Value>a esas Dictionarys.
Raphael
12

Inmutable

Prefiero combinar / unir diccionarios inmutables con el +operador, así que lo implementé como:

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

Mudable

// 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"]
ricardopereira
fuente
55
No entiendo por qué esto no está integrado en Swift por defecto.
ioquatix
1
¿Pretende que los valores de la izquierda anulen a la derecha en su solución "Inmutable"? Creo que quieres decir tener right.reduce(left), al menos ese es el comportamiento esperado de la OMI (y es el comportamiento de tu segundo ejemplo), es decir. ["A":1] + ["A":2]debería salir["A":2]
ccwasden
La salida corresponde al código. Quiero que el valor inicial sea el lado correcto, como lo es ahora.
ricardopereira
12

No es necesario tener extensiones de diccionario ahora. El diccionario Swift (Xcode 9.0+) tiene una funcionalidad para esto. Echa un vistazo aquí . A continuación se muestra un ejemplo de cómo 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]
Vinayak Parmar
fuente
2
Estoy agregando un estilo funcional para el ejemplo anterior:oldDictionary.merge(newDictionary) { $1 }
Andrej
11

Una variante más legible usando una extensión.

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
    }    
}
orkoden
fuente
3
Muy buena y limpia solución!
user3441734
11

Puedes probar esto

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

var temp = NSMutableDictionary(dictionary: dict1);
temp.addEntriesFromDictionary(dict2)
Uttam Kumar
fuente
10

También puede usar reducir para fusionarlos. Prueba esto en el patio de recreo

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
}
farhadf
fuente
Esto parece interesante, pero ¿qué son dy p?
robar el
1
d es el resultado persistente de cada iteración del bloque reduce y p es el elemento de la colección que se está reduciendo.
farhadf
1
Esto parece fallar en
Swift
los parámetros var están en desuso en swift 3
Dmitry Klochkov
Esta es mi solución favorita de las mencionadas aquí. Filtrar / asignar / reducir victorias nuevamente para obtener excelentes soluciones concisas.
gokeji
7

Recomiendo la Biblioteca SwifterSwift . Sin embargo, si no desea utilizar toda la biblioteca y todas sus excelentes adiciones, puede hacer uso de su extensión de Diccionario:

Swift 3+

public extension Dictionary {
    public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) {
        rhs.forEach({ lhs[$0] = $1})
    }
}
Justin Oroz
fuente
En realidad, SE-110 ha sido revertido, por lo que la versión Swift 4 debería ser la misma que la versión Swift 3.
BallpointBen
5

Puede iterar sobre las combinaciones de valores clave del valor que desea combinar y agregarlas a través del método updateValue (forKey :):

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

Ahora todos los valores de dictionaryTwo se agregaron a dictionaryOne.

LeonS
fuente
4

Lo mismo que la respuesta de @ farhadf pero adoptada para 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
}
Dmitry Klochkov
fuente
4

Swift 3, extensión del diccionario:

public extension Dictionary {

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

}
aaannjjaa
fuente
4

Algunas sobrecargas aún más optimizadas para 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}
    }
}
John Montgomery
fuente
3

Puede agregar una Dictionaryextensión como esta:

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

Entonces el uso es tan simple como el siguiente:

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

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

Si prefiere un marco que también incluya algunas características más útiles, entonces consulte HandySwift . Simplemente impórtelo a su proyecto y puede usar el código anterior sin agregar ninguna extensión al proyecto usted mismo.

Jeehut
fuente
Instalar una biblioteca para usar una sola función es una mala práctica
HackaZach
@HackaZach: Acabo de actualizar mi respuesta para incluir la parte apropiada del marco para evitar la inclusión de toda la biblioteca si solo se necesita esta pequeña parte. Mantengo la pista sobre el marco para las personas que desean usar varias de sus funciones. ¡Espero que esto ayude a mantener buenas prácticas!
Jeehut
3

Ya no hay necesidad de extensión ni ninguna función adicional. Puedes escribir así:

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

Puedes usar,

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

Puede usar la función bridgeToObjectiveC () para hacer que el diccionario sea un NSDictionary.

Será como el siguiente:

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)

Luego puede convertir el NSDictionary (combinar) o hacer lo que sea.

Anton
fuente
Perdón, ¿qué quieres decir exactamente?
Anton
Solo una preferencia. Parece complicado hacer un puente entre los idiomas. Es mejor quedarse dentro de los límites de un solo idioma, mientras que simultáneamente deja que obj-c muera más rápido.
TruMan1
2
Sí, publiqué esta respuesta literalmente el día que Swift anunció ....... Así que había una razón
Anton
1
import Foundation

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

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

El resultado es un NSMutableDictionary, no un diccionario de tipo Swift, pero la sintaxis para usarlo es la misma ( out["a"] == 1en este caso), por lo que solo tendría un problema si está utilizando un código de terceros que espera un diccionario Swift, o realmente Necesito el tipo de comprobación.

La respuesta corta aquí es que realmente tienes que hacer un bucle. Incluso si no lo está ingresando explícitamente, eso es lo que hará el método al que está llamando (addEntriesFromDictionary: aquí). Sugeriría que si no está claro por qué ese sería el caso, debería considerar cómo fusionaría los nodos de las hojas de dos árboles B.

Si realmente necesitas un tipo de diccionario nativo Swift a cambio, te sugiero:

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

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

La desventaja de este enfoque es que el índice del diccionario, como sea que esté hecho, puede reconstruirse varias veces en el ciclo, por lo que en la práctica es aproximadamente 10 veces más lento que el enfoque NSMutableDictionary.

Jim Driscoll
fuente
1

Todas estas respuestas son complicadas. Esta es mi solución para 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) 
Kevin ABRIOUX
fuente
Desafortunadamente, esto solo funciona en NSMutableDictionary y no en los diccionarios nativos de Swift. Deseo que esto se agregue a Swift de forma nativa.
Chris Paveglio
0

Mis necesidades eran diferentes, necesitaba fusionar conjuntos de datos anidados incompletos sin tropezar.

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

Esto fue más difícil de lo que quería que fuera. El desafío estaba en el mapeo del tipeo dinámico al tipeo estático, y usé protocolos para resolver esto.

También es digno de mención que cuando usa la sintaxis literal del diccionario, en realidad obtiene los tipos básicos, que no recogen las extensiones de protocolo. Aborté mis esfuerzos para apoyarlos, ya que no pude encontrar una forma fácil de validar la uniformidad de los elementos de la colección.

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)")
Chris Conover
fuente
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
}
apinho
fuente
si pone esto, puede eliminar el primer bucle: `var result = left`
NikeAlive
0

Solo usaría la biblioteca Dollar .

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

Fusiona todos los diccionarios y este último anula el valor en una clave determinada

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]
Andy
fuente
55
jQuery está de vuelta yay!
Ben Sinclair
0

Aquí hay una buena extensión que escribí ...

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

usar:

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

dict1 = dict1.mergeOnto(target: dict2)

Entonces, dict1 se modificará a

["cat": 5, "dog": 6, "rodent": 10]
Brian J. Roberts
fuente