web-dev-qa-db-fra.com

Intersection de deux dictionnaires en Python

Je travaille sur un programme de recherche sur un index inversé. L'index lui-même est un dictionnaire dont les clés sont des termes et dont les valeurs sont elles-mêmes des dictionnaires de documents courts, avec les numéros d'identification comme clés et leur contenu textuel comme valeurs. 

Pour effectuer une recherche 'AND' de deux termes, il me faut donc croiser leurs listes d’affichage (dictionnaires). Qu'est-ce qu'un moyen clair (pas nécessairement trop intelligent) de faire cela en Python? J'ai commencé par essayer le long chemin avec iter:

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

Vous pouvez facilement calculer l'intersection d'ensembles, créez donc des ensembles à partir des clés et utilisez-les pour l'intersection:

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

Un fait peu connu est qu'il n'est pas nécessaire de construire sets pour faire ceci:

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, remplacez viewkeys par keys; il en va de même pour viewvalues et viewitems.

De la documentation de viewitems:

In [113]: d1.viewitems??
Type:       builtin_function_or_method
String Form:<built-in method viewitems of dict object at 0x64a61b0>
Docstring:  D.viewitems() -> a set-like object providing a view on D's items

Pour les variables dicts plus grandes, cette opération est également légèrement plus rapide que la construction de sets et leur intersection:

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

Nous comparons ici des nanosecondes, qui peuvent vous intéresser ou non. Dans tous les cas, vous récupérez une set, donc utiliser viewkeys/keys élimine un peu l'encombrement.

101
Phillip Cloud
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}
60
emnoor

En Python 3, vous pouvez utiliser

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

Ok, voici une version généralisée du code ci-dessus dans Python3 . Elle est optimisée pour utiliser des compréhensions et des vues dict-set assez rapides.

Function intersecte de nombreux dictionnaires de manière arbitraire et renvoie un dict avec des clés communes et un ensemble de valeurs communes pour chaque clé commune:

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

Exemple d'utilisation:

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

Ici, les valeurs dict doivent être haschibles. Sinon, vous pouvez simplement changer les parenthèses {} en listant []:

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

Entourez simplement les instances du dictionnaire avec une classe simple qui obtient les deux valeurs souhaitées

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']
2
Eric Urban

Votre question n'est pas assez précise pour donner une réponse unique.

1. Intersection clé

Si vous voulez intersecter IDs de posts ( crédits à James ), faites:

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

Toutefois, si vous souhaitez parcourir les documents, vous devez déterminer quelle publication est prioritaire. Je suppose que c'est p1. Pour itérer des documents pour common_ids, collections.ChainMap sera le plus utile:

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

Ou si vous ne souhaitez pas créer un dictionnaire intersection séparé:

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

2. Intersection des articles

Si vous souhaitez intersecter éléments des deux publications, ce qui signifie que vous devez faire correspondre les noms IDs et les documents, utilisez le code ci-dessous ( crédits de DCPY ). Toutefois, cela n’est utile que si vous recherchez des doublons en termes.

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

3. Parcourez p1 'AND' p2.

Dans le cas où par "'AND' search" et en utilisant iter, vous vouliez effectuer une recherche dans les deux posts puis à nouveau collections.ChainMap est le meilleur moyen de parcourir tous les éléments de plusieurs posts:

from collections import ChainMap
for id, document in ChainMap(p1, p2):
    ...
0
WloHu
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'}

Je recommanderais de changer votre index de

index = {term: {doc_id: doc}}

à deux index un pour les termes, puis un index séparé pour contenir les valeurs

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

de cette façon, vous ne stockez pas plusieurs copies des mêmes données.

0
Aaron Goldman