¿Cómo fusionar varios dictados con la misma clave?

83

Tengo varios dicts / pares clave-valor como este:

d1 = {key1: x1, key2: y1}  
d2 = {key1: x2, key2: y2}  

Quiero que el resultado sea un nuevo dictado (de la manera más eficiente, si es posible):

d = {key1: (x1, x2), key2: (y1, y2)}  

En realidad, quiero que el resultado d sea:

d = {key1: (x1.x1attrib, x2.x2attrib), key2: (y1.y1attrib, y2.y2attrib)}  

Si alguien me muestra cómo obtener el primer resultado, puedo averiguar el resto.

Salil
fuente
3
@Salil: ¿Podemos asumir que cada clave está presente en todos los diccionarios?
Björn Pollex
posible duplicado de la combinación de diccionarios de Python
Johnsyweb
Hola Space_C0wb0y, sí, las claves están presentes en todos los diccionarios.
Salil
Es absolutamente crucial especificar si todos los dictados tienen las mismas teclas.
yugr

Respuestas:

44

asumiendo que todas las claves están siempre presentes en todos los dictados:

ds = [d1, d2]
d = {}
for k in d1.iterkeys():
    d[k] = tuple(d[k] for d in ds)

Nota: En Python 3.x use el siguiente código:

ds = [d1, d2]
d = {}
for k in d1.keys():
  d[k] = tuple(d[k] for d in ds)

y si el dic contiene matrices numpy:

ds = [d1, d2]
d = {}
for k in d1.keys():
  d[k] = np.concatenate(list(d[k] for d in ds))
lloriquear
fuente
3
Creo que sólo "para k en d1".
Salil
y d.get (k, None) en lugar de d [k]
tahir
1
@tahir Esto significaría que los dictados tienen claves que no coinciden, por lo que la iteración d1no es correcta (puede perder claves en otros dictados).
yugr
1
Para usuarios de Python 3: d1.iterkeys () = d1.items ()
Riley
Todavía no me funciona en Python3.x. Intenté esto incluso si mis valores no son matrices, y funciona. Sin embargo, los valores de salida serán matrices. stackoverflow.com/questions/54040858/…
Ric S
73

Aquí hay una solución general que manejará una cantidad arbitraria de diccionarios, con casos en los que las claves están solo en algunos de los diccionarios:

from collections import defaultdict

d1 = {1: 2, 3: 4}
d2 = {1: 6, 3: 7}

dd = defaultdict(list)

for d in (d1, d2): # you can list as many input dicts as you want here
    for key, value in d.items():
        dd[key].append(value)

print(dd)

Muestra:

defaultdict(<type 'list'>, {1: [2, 6], 3: [4, 7]})

Además, para obtener su .attrib, simplemente cambie append(value)aappend(value.attrib)

Eli Bendersky
fuente
Creo que el OP quiere los valores como tupleno list.
user225312
1
@AA: ¿realmente importa? las tuplas serán más difíciles de construir en el caso más general de dictados de entrada múltiple donde algunas teclas no están presentes en todas partes, en mi humilde opinión
Eli Bendersky
1
A continuación, puede que desee hacer una normal, dictfuera de la defaultdictpor lo que tiene normal de dictcomportamiento para las llaves inexistentes, etc: dd = dict(dd)
Ned Deily
@Ned: buen punto, pero depende del uso eventual de los datos
Eli Bendersky
@Eli: No, no importa, pero solo estaba tratando de basarlo en lo que quería el OP y esperaba que hubiera una solución para las tuplas de usted :-)
user225312
4

Si solo tiene d1 y d2,

from collections import defaultdict

d = defaultdict(list)
for a, b in d1.items() + d2.items():
    d[a].append(b)
riza
fuente
4
dict1 = {'m': 2, 'n': 4}
dict2 = {'n': 3, 'm': 1}

Asegurándose de que las claves estén en el mismo orden:

dict2_sorted = {i:dict2[i] for i in dict1.keys()}

keys = dict1.keys()
values = zip(dict1.values(), dict2_sorted.values())
dictionary = dict(zip(keys, values))

da:

{'m': (2, 1), 'n': (4, 3)}
Mahdi Ghelichi
fuente
1
El orden de los elementos en values()no está definido, por lo que es posible que esté fusionando valores de claves no relacionadas.
yugr
Acabo de aplicar los cambios para que ahora pueda capturar sus comentarios
Mahdi Ghelichi
No creo que el cambio solucione el problema. Necesita usar sorted(d.items())o sorted(d.keys())lograr resultados predecibles.
yugr
¿Puede dar un ejemplo que demuestre lo contrario? dict2_sorted es un diccionario ordenado en Python.
Mahdi Ghelichi
El hecho de que funcione para diccionarios pequeños en su máquina no es una prueba. Mira la reproducción aquí .
yugr
3

Aquí hay un enfoque que puede usar y que funcionaría incluso si ambos dictonarios no tienen las mismas claves:

d1 = {'a':'test','b':'btest','d':'dreg'}
d2 = {'a':'cool','b':'main','c':'clear'}

d = {}

for key in set(d1.keys() + d2.keys()):
    try:
        d.setdefault(key,[]).append(d1[key])        
    except KeyError:
        pass

    try:
        d.setdefault(key,[]).append(d2[key])          
    except KeyError:
        pass

print d

Esto generaría la siguiente entrada:

{'a': ['test', 'cool'], 'c': ['clear'], 'b': ['btest', 'main'], 'd': ['dreg']}
sateesh
fuente
¿Se set(d1.keys() + d2.keys()) puede cambiar a set(list(d1.keys()) + list(d2.keys()))en la respuesta (para Python 3.x)? De lo contrario, arrojará un TypeError: unsupported operand type(s) for +: 'dict_keys' and 'dict_keys'error, en python3.x
R4444
1

Actualización de Python 3.x

De la respuesta de Eli Bendersky:

Python 3 eliminado dict.iteritems usa dict.items en su lugar. Consulte la wiki de Python: https://wiki.python.org/moin/Python3.0

from collections import defaultdict

dd = defaultdict(list)

for d in (d1, d2):
    for key, value in d.items():
        dd[key].append(value)
sincero
fuente
1

Este método fusiona dos dictados incluso si las claves de los dos diccionarios son diferentes:

def combine_dict(d1, d2):
    combined = {}
    for k in set(d1.keys()) | set(d2.keys()):
        combined[k] = tuple(d[k] for d in [d1, d2] if k in d)
    return combined

Ejemplo:

d1 = {
    'a': 1,
    'b': 2,
}
d2` = {
    'b': 'boat',
    'c': 'car',
}
combine_dict(d1, d2)
# Returns: {
#    'a': (1,),
#    'b': (2, 'boat'),
#    'c': ('car',)
# }
Flujo
fuente
1

Suponga que tiene la lista de TODAS las claves (puede obtener esta lista iterando a través de todos los diccionarios y obtener sus claves). Vamos a nombrarlo listKeys. También:

  • listValues es la lista de TODOS los valores de una única clave que desea fusionar.
  • allDicts: todos los diccionarios que desea fusionar.
result = {}
for k in listKeys:
    listValues = [] #we will convert it to tuple later, if you want.
    for d in allDicts:
       try:
            fileList.append(d[k]) #try to append more values to a single key
        except:
            pass
    if listValues: #if it is not empty
        result[k] = typle(listValues) #convert to tuple, add to new dictionary with key k
Largo
fuente
0
def merge(d1, d2, merge):
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge(result[k], v)
        else:
            result[k] = v
    return result

d1 = {'a': 1, 'b': 2}
d2 = {'a': 1, 'b': 3, 'c': 2}
print merge(d1, d2, lambda x, y:(x,y))

{'a': (1, 1), 'c': 2, 'b': (2, 3)}
ralphtheninja
fuente
0

Para complementar las soluciones de dos listas, aquí hay una solución para procesar una sola lista.

Una lista de muestra (relacionada con NetworkX; formateada manualmente aquí para facilitar la lectura):

ec_num_list = [((src, tgt), ec_num['ec_num']) for src, tgt, ec_num in G.edges(data=True)]

print('\nec_num_list:\n{}'.format(ec_num_list))
ec_num_list:
[((82, 433), '1.1.1.1'),
  ((82, 433), '1.1.1.2'),
  ((22, 182), '1.1.1.27'),
  ((22, 3785), '1.2.4.1'),
  ((22, 36), '6.4.1.1'),
  ((145, 36), '1.1.1.37'),
  ((36, 154), '2.3.3.1'),
  ((36, 154), '2.3.3.8'),
  ((36, 72), '4.1.1.32'),
  ...] 

Tenga en cuenta los valores duplicados para los mismos bordes (definidos por las tuplas). Para cotejar esos "valores" con sus "claves" correspondientes:

from collections import defaultdict
ec_num_collection = defaultdict(list)
for k, v in ec_num_list:
    ec_num_collection[k].append(v)

print('\nec_num_collection:\n{}'.format(ec_num_collection.items()))
ec_num_collection:
[((82, 433), ['1.1.1.1', '1.1.1.2']),   ## << grouped "values"
((22, 182), ['1.1.1.27']),
((22, 3785), ['1.2.4.1']),
((22, 36), ['6.4.1.1']),
((145, 36), ['1.1.1.37']),
((36, 154), ['2.3.3.1', '2.3.3.8']),    ## << grouped "values"
((36, 72), ['4.1.1.32']),
...] 

Si es necesario, convierta esa lista en dict:

ec_num_collection_dict = {k:v for k, v in zip(ec_num_collection, ec_num_collection)}

print('\nec_num_collection_dict:\n{}'.format(dict(ec_num_collection)))
  ec_num_collection_dict:
  {(82, 433): ['1.1.1.1', '1.1.1.2'],
  (22, 182): ['1.1.1.27'],
  (22, 3785): ['1.2.4.1'],
  (22, 36): ['6.4.1.1'],
  (145, 36): ['1.1.1.37'],
  (36, 154): ['2.3.3.1', '2.3.3.8'],
  (36, 72): ['4.1.1.32'],
  ...}

Referencias

Victoria Stuart
fuente
0

De blubb respuesta:

También puede formar directamente la tupla usando valores de cada lista

ds = [d1, d2]
d = {}
for k in d1.keys():
  d[k] = (d1[k], d2[k])

Esto podría ser útil si tuvieras un orden específico para tus tuplas.

ds = [d1, d2, d3, d4]
d = {}
for k in d1.keys():
  d[k] = (d3[k], d1[k], d4[k], d2[k]) #if you wanted tuple in order of d3, d1, d4, d2
congelado5032
fuente
0

Esta biblioteca me ayudó, tenía una lista de claves anidadas con el mismo nombre pero con diferentes valores, todas las demás soluciones seguían anulando esas claves anidadas.

https://pypi.org/project/deepmerge/

from deepmerge import always_merger

def process_parms(args):
    temp_list = []
    for x in args:
        with open(x, 'r') as stream:
            temp_list.append(yaml.safe_load(stream))

    return always_merger.merge(*temp_list)
jmcgrath207
fuente
0

Si las claves están anidadas:

d1 = { 'key1': { 'nkey1': 'x1' }, 'key2': { 'nkey2': 'y1' } } 
d2 = { 'key1': { 'nkey1': 'x2' }, 'key2': { 'nkey2': 'y2' } }
ds = [d1, d2]
d = {}
for k in d1.keys():
    for k2 in d1[k].keys():
        d.setdefault(k, {})
        d[k].setdefault(k2, [])
        d[k][k2] = tuple(d[k][k2] for d in ds)

rinde:

{'key1': {'nkey1': ('x1', 'x2')}, 'key2': {'nkey2': ('y1', 'y2')}}
lys
fuente
-4

Una posibilidad compacta

d1={'a':1,'b':2}
d2={'c':3,'d':4}
context={**d1, **d2}
context
{'b': 2, 'c': 3, 'd': 4, 'a': 1}
usuario2897775
fuente
la pregunta es sobre la combinación de dictados con la misma clave. tu no es la respuesta requerida.
Pbd