Intersección de dos diccionarios en Python

Estoy trabajando en un progtwig de búsqueda sobre un índice invertido. El índice en sí es un diccionario cuyas claves son términos y cuyos valores son en sí mismos diccionarios de documentos cortos, con números de ID como claves y su contenido de texto como valores.

Para realizar una búsqueda ‘AND’ de dos términos, debo intersecar sus listas de publicaciones (diccionarios). ¿Cuál es una forma clara (no necesariamente demasiado inteligente) de hacer esto en Python? Comencé probándolo a lo largo con iter :

 p1 = index[term1] p2 = index[term2] i1 = iter(p1) i2 = iter(p2) while ... # not sure of the 'iter != end 'syntax in this case ... 

Puedes calcular fácilmente la intersección de conjuntos, así que crea conjuntos a partir de las teclas y úsalos para la intersección:

 keys_a = set(dict_a.keys()) keys_b = set(dict_b.keys()) intersection = keys_a & keys_b # '&' operator is used for set intersection 

Un hecho poco conocido es que no es necesario construir set para hacer esto:

En Python 2:

 In [78]: d1 = {'a': 1, 'b': 2} In [79]: d2 = {'b': 2, 'c': 3} In [80]: d1.viewkeys() & d2.viewkeys() Out[80]: {'b'} 

En Python 3 reemplaza las keys viewkeys con las keys ; lo mismo se aplica a los viewvalues de viewvalues y elementos de viewitems .

De la documentación de los viewitems de viewitems :

 In [113]: d1.viewitems?? Type: builtin_function_or_method String Form: Docstring: D.viewitems() -> a set-like object providing a view on D's items 

Para dict más grandes, esto también es un poco más rápido que construir set y luego interseccionarlos:

 In [122]: d1 = {i: rand() for i in range(10000)} In [123]: d2 = {i: rand() for i in range(10000)} In [124]: timeit d1.viewkeys() & d2.viewkeys() 1000 loops, best of 3: 714 µs per loop In [125]: %%timeit s1 = set(d1) s2 = set(d2) res = s1 & s2 1000 loops, best of 3: 805 µs per loop For smaller `dict`s `set` construction is faster: In [126]: d1 = {'a': 1, 'b': 2} In [127]: d2 = {'b': 2, 'c': 3} In [128]: timeit d1.viewkeys() & d2.viewkeys() 1000000 loops, best of 3: 591 ns per loop In [129]: %%timeit s1 = set(d1) s2 = set(d2) res = s1 & s2 1000000 loops, best of 3: 477 ns per loop 

Estamos comparando nanosegundos aquí, lo que puede o no ser importante para usted. En cualquier caso, recuperas un set , por lo que usar viewkeys / keys elimina un poco de desorden.

 In [1]: d1 = {'a':1, 'b':4, 'f':3} In [2]: d2 = {'a':1, 'b':4, 'd':2} In [3]: d = {x:d1[x] for x in d1 if x in d2} In [4]: d Out[4]: {'a': 1, 'b': 4} 

En Python 3, puedes usar

 intersection = dict(dict1.items() & dict2.items()) union = dict(dict1.items() | dict2.items()) difference = dict(dict1.items() ^ dict2.items()) 

Simplemente envuelva las instancias del diccionario con una clase simple que obtenga los dos valores que desea

 class DictionaryIntersection(object): def __init__(self,dictA,dictB): self.dictA = dictA self.dictB = dictB def __getitem__(self,attr): if attr not in self.dictA or attr not in self.dictB: raise KeyError('Not in both dictionaries,key: %s' % attr) return self.dictA[attr],self.dictB[attr] x = {'foo' : 5, 'bar' :6} y = {'bar' : 'meow' , 'qux' : 8} z = DictionaryIntersection(x,y) print z['bar'] 

Bueno, aquí hay una versión generalizada del código anterior en Python3. Está optimizado para utilizar las vistas de comprensión y las series de dictados que son lo suficientemente rápidas.

La función intersecta muchos dictados arbitrarios y devuelve un dictado con claves comunes y un conjunto de valores comunes para cada clave común:

 def dict_intersect(*dicts): comm_keys = dicts[0].keys() for d in dicts[1:]: # intersect keys first comm_keys &= d.keys() # then build a result dict with nested comprehension result = {key:{d[key] for d in dicts} for key in comm_keys} return result 

Ejemplo de uso:

 a = {1: 'ba', 2: 'boon', 3: 'spam', 4:'eggs'} b = {1: 'ham', 2:'baboon', 3: 'sausages'} c = {1: 'more eggs', 3: 'cabbage'} res = dict_intersect(a, b, c) # Here is res (the order of values may vary) : # {1: {'ham', 'more eggs', 'ba'}, 3: {'spam', 'sausages', 'cabbage'}} 

Aquí los valores de dict deben ser hashables, si no lo son, simplemente podría cambiar los paréntesis {} a la lista []:

 result = {key:[d[key] for d in dicts] for key in comm_keys} 

Su pregunta no es lo suficientemente precisa como para dar una respuesta única.

1. Intersección de llaves

Si quieres intersectar ID de publicaciones ( créditos a James ) haz:

 common_ids = p1.keys() & p2.keys() 

Sin embargo, si desea iterar documentos debe considerar qué publicación tiene prioridad, asumo que es p1 . Para iterar documentos para common_ids , collections.ChainMap será más útil:

 from collections import ChainMap intersection = {id: document for id, document in ChainMap(p1, p2) if id in common_ids} for id, document in intersection: ... 

O si no quieres crear un diccionario de intersection separado:

 from collections import ChainMap posts = ChainMap(p1, p2) for id in common_ids: document = posts[id] 

2. Intersección de artículos

Si desea intersectar elementos de ambas publicaciones, lo que significa que coincida con las ID y los documentos, use el código a continuación ( créditos para DCPY ). Sin embargo, esto solo es útil si está buscando duplicados en términos.

 duplicates = dict(p1.items() & p2.items()) for id, document in duplicates: ... 

3. Iterar sobre p1 ‘Y’ p2 .

En caso de que con ” Y” busque “y usando iter quiera buscar en ambas publicaciones y luego nuevamente en collections.ChainMap ChainMap es el mejor para iterar sobre (casi) todos los elementos en varias publicaciones:

 from collections import ChainMap for id, document in ChainMap(p1, p2): ... 
 def two_keys(term_a, term_b, index): doc_ids = set(index[term_a].keys()) & set(index[term_b].keys()) doc_store = index[term_a] # index[term_b] would work also return {doc_id: doc_store[doc_id] for doc_id in doc_ids} def n_keys(terms, index): doc_ids = set.intersection(*[set(index[term].keys()) for term in terms]) doc_store = index[term[0]] return {doc_id: doc_store[doc_id] for doc_id in doc_ids} In [0]: index = {'a': {1: 'a b'}, 'b': {1: 'a b'}} In [1]: two_keys('a','b', index) Out[1]: {1: 'a b'} In [2]: n_keys(['a','b'], index) Out[2]: {1: 'a b'} 

Recomendaría cambiar su índice de

 index = {term: {doc_id: doc}} 

a dos índices uno para los términos y luego un índice separado para mantener los valores

 term_index = {term: set([doc_id])} doc_store = {doc_id: doc} 

De esa manera usted no almacena múltiples copias de los mismos datos.