Starter
Starter

Reputation: 417

python comparing list values to keys in list of dicts

I have a list of dicts a and a list b as shown below. len(b) will not always be 4. it may be 2 or 3. I compared values in b to keys in each dict in b. I obtained the result shown, which should rightly be of length b but the order is not as desired.

My 2 problems are:

  1. The result is not printed in order. i.e. not depending on which value comes first in my list
  2. Is there a better way of comparison? ( I am sure there is) because len(b) will not always be 4. It may be smaller.

Here is what I have

a = [{0: 0, 1: 1, 2: 7, 3: 0}, {0: 0, 1: 0, 2: 12, 3: 0}, {0: 5, 1: 0, 2: 8, 3: 0}]
b = [2, 0, 1, 3]
c = []
for i in a:
    x = []
    for k, v in i.items():
        if k == b[0]: 
           x.append(v)
        if k == b[1]:
            x.append(v)
        if k == b[2]:
            x.append(v)
        if k == b[3]:
            x.append(v)
    c.append(x)
print(c)

My result is

[[0, 1, 7, 0], [0, 0, 12, 0], [5, 0, 8, 0]]

If you notice, for b = 2 i.e. b[0], the result from a[1] is rightly 7 but it comes after 0 and 1 in my final result even though 2 is the first item in b.

Thank you

Upvotes: 1

Views: 71

Answers (3)

Paul Panzer
Paul Panzer

Reputation: 53029

You could also use operator.itemgetter which provides a neat shorthand to retrieve multiple items in one go:

>>> a = [{0: 0, 1: 1, 2: 7, 3: 0}, {0: 0, 1: 0, 2: 12, 3: 0}, {0: 5, 1: 0, 2: 8, 3: 0}]
>>> b = [2, 0, 1, 3]
>>> 
>>> from operator import itemgetter
>>> get_b = itemgetter(*b)

Now for example:

>>> get_b(a[0])
(7, 0, 1, 0)

The result is a tuple, not a list. If that's ok you can make a list comprehension or use map:

>>> [get_b(d) for d in a]
[(7, 0, 1, 0), (12, 0, 0, 0), (8, 5, 0, 0)]
>>> list(map(get_b, a))
[(7, 0, 1, 0), (12, 0, 0, 0), (8, 5, 0, 0)]

If it must be lists, it's a bit more cumbersome:

>>> [list(get_b(d)) for d in a]
[[7, 0, 1, 0], [12, 0, 0, 0], [8, 5, 0, 0]]
>>> list(map(list, map(get_b, a)))
[[7, 0, 1, 0], [12, 0, 0, 0], [8, 5, 0, 0]]

On my system itemgetter appears to be a bit faster than one-by-one lookup even if we have to convert tuples to lists:

>>> from timeit import timeit
>>>
>>> def multi_get(a, b):
...     get_b = itemgetter(*b)
...     return [[*get_b(d),] for d in a]
... 
>>> def multi_get_tuple(a, b):
...     get_b = itemgetter(*b)
...     return [get_b(d) for d in a]
... 
>>> timeit("multi_get_tuple(a, b)", globals=globals(), number=1000000)
0.9130640690000291
>>> timeit("multi_get(a, b)", globals=globals(), number=1000000)
1.0864301430010528
>>> timeit("[[d[i] for i in b] for d in a]", globals=globals(), number=1000000)
1.40757593699891

Upvotes: 1

Ali Yılmaz
Ali Yılmaz

Reputation: 1695

The problem is, you do not iterate with regards to b. You iterate with regards to key order in dictionaries inside a. Thats why output elements are out of order.

For the correct solution, you should iterate with regards to b's items, like this:

a = [{0: 0, 1: 1, 2: 7, 3: 0}, {0: 0, 1: 0, 2: 12, 3: 0}, {0: 5, 1: 0, 2: 8, 3: 0}]
b = [2, 0, 1, 3]
c = []
for a_dict in a:
    x = []
    for b_element in b:
        if b_element in a_dict:
            x.append(a_dict[b_element])
            print("Appending %ss value %s to X! new X: %s" % (b_element, a_dict[b_element], str(x)))
    c.append(x)
print(c)

Prints:

Appending Key:2s Value:7 to X! new X: [7]
Appending Key:0s Value:0 to X! new X: [7, 0]
Appending Key:1s Value:1 to X! new X: [7, 0, 1]
Appending Key:3s Value:0 to X! new X: [7, 0, 1, 0]
Appending Key:2s Value:12 to X! new X: [12]
Appending Key:0s Value:0 to X! new X: [12, 0]
Appending Key:1s Value:0 to X! new X: [12, 0, 0]
Appending Key:3s Value:0 to X! new X: [12, 0, 0, 0]
Appending Key:2s Value:8 to X! new X: [8]
Appending Key:0s Value:5 to X! new X: [8, 5]
Appending Key:1s Value:0 to X! new X: [8, 5, 0]
Appending Key:3s Value:0 to X! new X: [8, 5, 0, 0]
[[7, 0, 1, 0], [12, 0, 0, 0], [8, 5, 0, 0]]

Upvotes: 1

jpp
jpp

Reputation: 164623

You can use a list comprehension:

res = [[d[i] for i in b] for d in a]

print(res)

[[7, 0, 1, 0], [12, 0, 0, 0], [8, 5, 0, 0]]

To understand how this works, it sometimes helps to write out the full nested for loop:

res = []
for d in a:
    res_int = []
    for i in b:
        res_int.append(d[i])
    res.append(res_int)

A functional approach is also possible:

res = [list(map(d.get, b)) for d in a]

Upvotes: 1

Related Questions