9
\$\begingroup\$

I have a dictionary of dictionary such as d below, and I would like to get the list of all keys but not on the first level (i.e. ABC, DEF) but the one at the second level (i.e 0 and 1 for ABC ; 1 and 2 for DEF...)

I got this small code that does the job by reading all the dictionaries and by saving the keys (of second level):

d = {
    'ABC' : {'0' : [(1,0.5), (2, 0.25)], '1' : [(1,0.4), (3,0.35)]},
    'DEF' : {'1' : [(1,0.5), (4, 0.15)], '2' : [(2,0.3), (3,0.25)]},
    'GHI' : {'0' : [(3,0.2), (4, 0.05)], '2' : [(2,0.2), (2,0.25)]},
}

keys2 = []
for small_d in d.itervalues():
    keys2.extend(small_d.keys())

print set(keys2)
# set(['1', '0', '2'])

But I'm sure there is a more pythonic way to do it (and more efficient), any ideas?

\$\endgroup\$
6
\$\begingroup\$

Yes, there's a shorter (pythonic) way to do this using set comprehensions:

d = {
    'ABC' : {'0' : [(1,0.5), (2, 0.25)], '1' : [(1,0.4), (3,0.35)]},
    'DEF' : {'1' : [(1,0.5), (4, 0.15)], '2' : [(2,0.3), (3,0.25)]},
    'GHI' : {'0' : [(3,0.2), (4, 0.05)], '2' : [(2,0.2), (2,0.25)]},
}
print {j for i in d.itervalues() for j in i}
# set(['1', '0', '2'])

Then if you just want a list object you can use list() to convert the set to list.


More, in your example you could just make keys2 a set to begin with and call keys2.add instead of extend.


And some benchmarks from your solution, mine and other answers:

import timeit
from itertools import chain


def m1(d):
    return {j for i in d.itervalues() for j in i}


def m2(d):
    keys2 = []
    for small_d in d.itervalues():
        keys2.extend(small_d.keys())

    return set(keys2)


def m3(d):
    return set(chain.from_iterable(d.itervalues()))


def m4(d):
    keys2 = set([])
    for small_d in d.itervalues():
        keys2.update(small_d.keys())
    return keys2


d = {
    'ABC': {'0': [(1, 0.5), (2, 0.25)], '1': [(1, 0.4), (3, 0.35)]},
    'DEF': {'1': [(1, 0.5), (4, 0.15)], '2': [(2, 0.3), (3, 0.25)]},
    'GHI': {'0': [(3, 0.2), (4, 0.05)], '2': [(2, 0.2), (2, 0.25)]},
}

print(timeit.timeit(setup='from __main__ import m1, d', number=100000000))
print(timeit.timeit(setup='from __main__ import m2, d', number=100000000))
print(timeit.timeit(setup='from __main__ import m3, d', number=100000000))
print(timeit.timeit(setup='from __main__ import m4, d', number=100000000))
0.00136395591689003
0.0012866411345431158
0.0011429587956683193
0.0011374851827588035
\$\endgroup\$
  • \$\begingroup\$ Since you’re using itervalues everywhere else, you might as well use it in m1. It might actually be a little bit faster too. \$\endgroup\$ – Mathias Ettinger Jul 3 '17 at 14:56
  • \$\begingroup\$ Good call Matthias, I'll amend it when I'll have the time \$\endgroup\$ – Grajdeanu Alex. Jul 3 '17 at 16:11
  • \$\begingroup\$ For a better comparison you might want to try larger dictionaries since the differences in the current benchmark are miniscule at best. \$\endgroup\$ – AlexR Jul 3 '17 at 17:10
4
\$\begingroup\$

You can decrease code size using itertools:

from itertools import chain
return set(chain.from_iterable(d.itervalues()))

but i'm not sure if this version readable enough.

Also you can get rid of list and define keys2 as set:

keys2 = set([])
for small_d in d.itervalues():
    keys2.update(small_d.keys())
\$\endgroup\$
3
\$\begingroup\$
{key for inner in d.values() for key in inner}

It is a set comprehension - to not include repeated values.
(You may use the list() function to convert it to the list.


The explanation: Read it from the end:
It is the set of each key in inner, where every inner is from the list d.values().

\$\endgroup\$
  • \$\begingroup\$ @Wildcard - You're welcome. \$\endgroup\$ – MarianD Jul 4 '17 at 2:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.