2012-03-28 9 views
12

tengo a 0 o más dicts en una lista:pitón: encontrar los pares de valores clave única comunes de varios predice: intersección dict

>>> dicts = [dict(a=3, b=89, d=2), dict(a=3, b=89, c=99), dict(a=3, b=42, c=33)] 

quiero crear un nuevo dict que contiene sólo las teclas que se encuentran en todos los dicts anteriores, y sólo si los valores son todos iguales:

>>> dict_intersection(*dicts) 
{"a": 3} 

siento que debería haber una elegante manera de escribir dict_intersection, pero sólo estoy subiendo con poco elegante y/o ine soluciones deficientes yo mismo. Sugerencias?

+0

se le olvidó la cita literal de cadena en su ejemplo. ;) Pregunta interesante: meditaré sobre esto y regresaré. –

+1

Y por supuesto, ¿qué has venido hasta ahora? – hochl

+0

@ Li-aung: Las comillas textuales de cadena no son necesarias al instanciar 'dict' como una clase. Me parece un atajo muy útil para evitar esas citas molestas. –

Respuesta

18
>>> dict(set.intersection(*(set(d.iteritems()) for d in dicts))) 
{'a': 3} 

Nota: Esta solución requiere que los valores del diccionario sean controlables, además de las teclas.

+1

maldita sea ... me gana ... – jamylak

+0

Bueno, me tienes vencido. Una vez más demostrando que una operación de intersección establecida se realiza mejor usando operaciones 'set';) –

1

Un enfoque ligeramente más sucio: tome la lista de teclas para cada diccionario, ordene cada lista y luego proceda como si estuviese fusionándolas (mantenga un índice para cada lista, avance el que tenga el valor más bajo)) Siempre que todos los índices apunten a la misma clave, verifique los valores para la igualdad; de cualquier manera, avance todos los índices.

+0

Conceptualmente elegante, pero la clasificación parece que sería costoso. También parece que necesitarías una gran cantidad de código de "fontanería" (aunque el uso juicioso de 'itertools' podría ayudar.) –

3
>>> dicts 
[{'a': 3, 'b': 89, 'd': 2}, {'a': 3, 'c': 99, 'b': 89}, {'a': 3, 'c': 33, 'b': 42}] 
>>> sets = (set(d.iteritems()) for d in dicts) 
>>> dict_intersection = dict(set.intersection(*sets)) 
>>> dict_intersection 
{'a': 3} 
4

Dado que los pares clave/valor ya deben estar en la primera dict, puede iterar sobre los elementos de este dictado.

dict(pair for pair in dicts[0].items() 
    if all((pair in d.items() for d in dicts[1:]))) 

Parece menos elegante que la respuesta de Interjay, pero funciona sin la restricción de valores aptos para el hash.

Editar: Se ha cambiado la expresión all a una expresión generadora de mejora de la velocidad

+0

+1 Creo que es bastante elegante porque funciona para los que no se pueden usar. – hochl

+0

@jamylak cómo se puede usar en caso de una lista de dict donde los valores no son controlables. el anterior funciona solo para encontrar elementos comunes en el primer dict y otro dict en la lista. – learnningprogramming

3

¿Cómo es esto?

def intersect_two_dicts (d1, d2): 
    return { k:v for k,v in d1.iteritems() if ((k in d2)and(d1[k]==d2[k])) } 

def intersect_dicts (list_of_dicts): 
    return reduce(intersect_two_dicts, list_of_dicts) 

# Tests 
dicts = [dict(a=3, b=89, d=2), dict(a=3, b=89, c=99), dict(a=3, b=42, c=33)] 
print (intersect_two_dicts(dicts[0], dicts[1])) 
print (intersect_dicts(dicts)) 

Editar (1): No estoy seguro de cuál de estos es el más rápido. Las soluciones set.intersection son sin duda las más elegantes (corto one liners!) Pero me gustaría ver algunas evaluaciones comparativas.

Editar (2): Bonus - consiguió ninguna entradas del diccionario cuyas (clave: valor) pares son comunes a cualquiera de los dos diccionarios:

{k:count for k,count in 
collections.Counter(itertools.chain(*[d.iteritems() for d in dicts])).iteritems() 
if count > 1} 
+0

Dice que reducir nombre no está definido – learnningprogramming

+1

@learnningprogramming si está utilizando Python 3, 'reduce()' ahora es 'import functools',' functools.reduce() '. –

Cuestiones relacionadas