Python equivalent of zip for dictionaries
Question:
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
Answers:
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 set
s yourself:
common_keys = set(da) & set(db)
for k in common_keys:
print k, da[k], db[k]
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())
[]
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
)
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]
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)}
.
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
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 set
s yourself:
common_keys = set(da) & set(db)
for k in common_keys:
print k, da[k], db[k]
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())
[]
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
)
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]
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)}
.