Python equivalent of zip for dictionaries
Asked Answered
C

7

51

If I have these two lists:

la = [1, 2, 3]
lb = [4, 5, 6]

I can iterate over them as follows:

for i in range(min(len(la), len(lb))):
    print la[i], lb[i]

Or more pythonically

for a, b in zip(la, lb):
    print a, b

What if I have two dictionaries?

da = {'a': 1, 'b': 2, 'c': 3}
db = {'a': 4, 'b': 5, 'c': 6}

Again, I can iterate manually:

for key in set(da.keys()) & set(db.keys()):
    print key, da[key], db[key]

Is there some builtin method that allows me to iterate as follows?

for key, value_a, value_b in common_entries(da, db):
    print key, value_a, value_b 
Carangid answered 9/5, 2013 at 9:18 Comment(1)
@Carangid python builtins are made usually because of their popularity. This is not used often enough to make it a builtinFleurdelis
K
43

There is no built-in function or method that can do this. However, you could easily define your own.

def common_entries(*dcts):
    if not dcts:
        return
    for i in set(dcts[0]).intersection(*dcts[1:]):
        yield (i,) + tuple(d[i] for d in dcts)

This builds on the "manual method" you provide, but, like zip, can be used for any number of dictionaries.

>>> da = {'a': 1, 'b': 2, 'c': 3}
>>> db = {'a': 4, 'b': 5, 'c': 6}
>>> list(common_entries(da, db))
[('c', 3, 6), ('b', 2, 5), ('a', 1, 4)]

When only one dictionary is provided as an argument, it essentially returns dct.items().

>>> list(common_entries(da))
[('c', 3), ('b', 2), ('a', 1)]

With no dictionaries, it returns an empty generator (just like zip())

>>> list(common_entries())
[]
Kauppi answered 9/5, 2013 at 9:41 Comment(2)
[Change1:] Also, sticking to the zip(*seq)-->seq contract, i suggest to return key, (values, tuple, ...) so as to mimic a dictionary. [Change2:] Finally a better name would be zipdic(*map)-->map.Intercommunicate
Doesn't work on python 3. ----> 5 yield (i,) + tuple(d[i] for d in dcts) TypeError: 'tuple' object is not callableEphod
E
14

The object returned by dict.keys() (called a dictionary key view) acts like a set object, so you can just take the intersection of the keys:

da = {'a': 1, 'b': 2, 'c': 3, 'e': 7}
db = {'a': 4, 'b': 5, 'c': 6, 'd': 9}

common_keys = da.keys() & db.keys()

for k in common_keys:
    print(k, da[k], db[k])

On Python 2 you'll need to convert the keys to sets yourself:

common_keys = set(da) & set(db)

for k in common_keys:
    print k, da[k], db[k]
Ergograph answered 9/5, 2013 at 9:23 Comment(1)
docs.python.org/3/library/…Artificer
E
6

Dictionary key views are already set-like in Python 3. You can remove set():

for key in da.keys() & db.keys():
    print(key, da[key], db[key])

In Python 2:

for key in da.viewkeys() & db.viewkeys():
    print key, da[key], db[key]
Essentiality answered 20/9, 2017 at 18:12 Comment(0)
P
1

In case if someone is looking for generalized solution:

import operator
from functools import reduce


def zip_mappings(*mappings):
    keys_sets = map(set, mappings)
    common_keys = reduce(set.intersection, keys_sets)
    for key in common_keys:
        yield (key,) + tuple(map(operator.itemgetter(key), mappings))

or if you like to separate key from values and use syntax like

for key, (values, ...) in zip_mappings(...):
    ...

we can replace last line with

yield key, tuple(map(operator.itemgetter(key), mappings))

Tests

from collections import Counter


counter = Counter('abra')
other_counter = Counter('kadabra')
last_counter = Counter('abbreviation')
for (character,
     frequency, other_frequency, last_frequency) in zip_mappings(counter,
                                                                 other_counter,
                                                                 last_counter):
    print('character "{}" has next frequencies: {}, {}, {}'
          .format(character,
                  frequency,
                  other_frequency,
                  last_frequency))

gives us

character "a" has next frequencies: 2, 3, 2
character "r" has next frequencies: 1, 1, 1
character "b" has next frequencies: 1, 1, 2

(tested on Python 2.7.12 & Python 3.5.2)

Page answered 20/9, 2017 at 17:55 Comment(0)
H
0

Python3: How about the following?

da = {'A': 1, 'b': 2, 'c': 3}
db = {'B': 4, 'b': 5, 'c': 6}
for key, (value_a, value_b) in  {k:(da[k],db[k]) for k in set(da)&set(db)}.items():
  print(key, value_a, value_b) 

The above snippet prints values of common keys ('b' and 'c') and discards the keys which don't match ('A' and 'B').

In order to include all keys into the output we could use a slightly modified comprehension: {k:(da.get(k),db.get(k)) for k in set(da)|set(db)}.

Haugh answered 26/4, 2020 at 10:5 Comment(1)
This is closer to itertools.izip_longest than it is to zip. As written, this gives ValueError.Carangid
U
0

I was wondering if taking one of the dicts as a reference would be efficient, and a quick experiment1 says it depends:

import timeit

dshort = {c: ord(c) for c in "abcdefghijklm"}
dlong = {c: ord(c) for c in "cdefghijklmnopqrstuvwxyz"}

print(timeit.repeat(lambda: {k: (dshort[k], dlong[k]) for k in dshort.keys() & dlong.keys()}))
print(timeit.repeat(lambda: {k: (v1, dlong[k]) for k, v1 in dshort.items() if k in dlong}))
print(timeit.repeat(lambda: {k: (dshort[k], v2) for k, v2 in dlong.items() if k in dshort}))
print(timeit.repeat(lambda: {k: (v1, dlong[k]) for k, v1 in dshort.items() if k in dlong.keys()}))
print(timeit.repeat(lambda: {k: (dshort[k], v2) for k, v2 in dlong.items() if k in dshort.keys()}))
[1.4048387547954917, 1.37691087462008, 1.3789333319291472, 1.3769562682136893, 1.3768599517643452]
[1.3084445232525468, 1.2111821668222547, 1.213303911499679, 1.2122336281463504, 1.2120618764311075]
[1.544621947221458, 1.5463114874437451, 1.5447452096268535, 1.5450493320822716, 1.5463362047448754]
[1.5736573031172156, 1.556909584440291, 1.5570594323799014, 1.5568421771749854, 1.5563717214390635]
[2.2037177812308073, 2.204036961309612, 2.2097349502146244, 2.214019880630076, 2.213061122223735]

If you know which dict is the shortest one, iterate over its items() and check if its keys are also in d — However, don't check as in d.keys(), which worsens performance! I guess this is because time is mostly spent on __getattr__.

If you are not sure about which is the shortest dict, just use d1.keys() & d2.keys() & d3.keys() & ... ;)


1 Version: Python 3.12.3 | packaged by conda-forge | (main, Apr 15 2024, 18:38:13) [GCC 12.3.0] on linux

Uniformed answered 16/5, 2024 at 7:44 Comment(0)
F
0

If it's for iterate them, you can zip like this :

d1 = {"1":1,"2":2}
d2 = {"3":3,"4":4}

for (k1, v1), (k2, v2) in zip(d1.items(), d2.items()):
    print(k1,v1,k2,v2)

Output:

1 1 3 3
2 2 4 4
Fro answered 2/7, 2024 at 9:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.