¿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)


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

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]

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.


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]

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.


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

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

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

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

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]

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

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)

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
}

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

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.


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
}

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

}

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

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.


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


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.


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.


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) 

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)")

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

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]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.