3

I have a dict like this

data = {
    'a': [95, 93, 90],
    'b': [643, 611, 610]
}

I want to iterate over the dict and fetch key and value from list of values for each item, something like this

{'a': 95, 'b': 643}
{'a': 93, 'b': 611}
{'a': 90, 'b': 610}

I have implemented the logic for this and it works fine, but when i see the temp_dict created in process, i see lots of intermediate unnecessary looping. The end result works just fine but i think it can be improved a lot.

import timeit

data = {
    'a': [95, 93, 90],
    'b': [643, 611, 610]
}


def calculate(**kwargs):
    temp_dict = {}
    index = 0
    len_values = list(kwargs.values())[0]

    while index < len(len_values):
        for k, v in kwargs.items():
            temp_dict[k] = v[index]
        index += 1
        yield temp_dict


start_time = timeit.default_timer()
for k in (calculate(**data)):
    print(k)
print(timeit.default_timer() - start_time)

How to do it more efficiently?

ajknzhol
  • 6,322
  • 13
  • 45
  • 72

5 Answers5

13

Try something like this -

>>> data = {
...     'a': [95, 93, 90],
...     'b': [643, 611, 610]
... }
>>> lst = list(data.items())
>>> lst1 = list(zip(*[i[1] for i in lst]))
>>> lst1
[(95, 643), (93, 611), (90, 610)]
>>> newlist = []
>>> for aval, bval in lst1:
...     newlist.append({lst[0][0]:aval , lst[1][0]:bval})
...
>>> newlist
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]

When passing a list using * as a parameter to a function, it will break the list into individual elements and pass it onto the function. Example - if we pass [[1,2],[3,4]] it would be passed as two different arguments - [1,2] and [3,4] - checkt this here (Section - * in Function calls)

Example to explain this -

>>> lst = [[1,2,3],[4,5,6],[7,8,9]]
>>> def func(a, b, c):
...     print(a)
...     print(b)
...     print(c)
...
>>> func(*lst)
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

zip - This function returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables.


A bit more scale-able model -

>>> lst = list(data.items())
>>> lst
[('a', [95, 93, 90]), ('b', [643, 611, 610])]
>>> lst1 = list(zip(*[i[1] for i in lst]))
>>> lst1
[(95, 643), (93, 611), (90, 610)]
>>> newlist = []
>>> for x in lst1:
...     d = {}
...     for i,y in enumerate(lst):
...             d[y[0]] = x[i]
...     newlist.append(d)
...
>>> newlist
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]
Anand S Kumar
  • 88,551
  • 18
  • 188
  • 176
4

A fun way to do it with a list comprehension.

>>> data = {
'a': [95, 93, 90],
'b': [643, 611, 610]
}
>>> [dict(zip(data, x)) for x in zip(*data.values())] 
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]

Or a more traditional (less fun) way

>>> result = []
>>> for tuple_ in zip(*data.values()):
...     d = {}
...     for key, val in zip(data, tuple_):
...         d[key] = val
...     result.append(d)
>>> print result
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]

And per comments, here is a way to do it without relying on 'non-guaranteed' behavior like the same ordering of data.keys() and data.values().

List Comprehension

>>> keys, values = zip(*data.items())
>>> [dict(zip(keys, tuple_)) for tuple_ in zip(*values)]
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]

And traditional for-loop

>>> result = []
>>> keys, values = zip(*data.items())
>>> for tuple_ in zip(*values):
...     d = {}
...     for key, val in zip(keys, tuple_):
...         d[key] = val
...     result.append(d)
>>> print result
[{'a': 95, 'b': 643}, {'a': 93, 'b': 611}, {'a': 90, 'b': 610}]
krethika
  • 3,908
  • 1
  • 24
  • 27
  • Your answer is beautiful. It can be further extended to use generator expressions, something like `yield from (dict(zip(kwargs, x)) for x in zip(*kwargs.values()))` – ajknzhol Jun 24 '15 at 06:32
  • yeah, hard to find a balance of what is fun to write and what is acceptable to read. – krethika Jun 24 '15 at 06:38
  • 1
    Dictionary has no sense of order, are you sure your solution would work all the time? – Anand S Kumar Jun 24 '15 at 09:48
  • 1
    I mean even if you access `data` dictionary in the same line multiple times, they can give result in different order (theoratically speaking) – Anand S Kumar Jun 24 '15 at 09:50
  • @AnandSKumar we are accessing elements of a list in a dictionary .So basically we are obtaining lists element lists are order in nature so I think it is correct .Correct me if I am wrong – The6thSense Jun 24 '15 at 10:15
  • what I mean is? Lets say you access data.values() one time - gives you value of 'a' first and value for 'b' second. Next after some time, or even in the same line you try to access data again and try to zip it, this time you get 'b' row first and 'a' second. Do you get the issue? – Anand S Kumar Jun 24 '15 at 10:17
  • @AnandSKumar It *will* work because the ordering of `values` and `keys` is always the same. but you are right. It's not "guaranteed" by the API, but rather an artifact of the implementation of `dict`. – krethika Jun 24 '15 at 16:02
1

Something similar to this (but replace the print with a yield):

keys = []
values = []
for k, v in data.iteritems():
    keys.append(k)
    values.append(v)
for vals in zip(*values):
    print dict(zip(keys, vals))

The zip(*values) in the second for-loop more or less transposes the list of lists in values. Slightly more compact way of writing the same:

keys = list(data)
for vals in zip(*data.values()):
    print dict(zip(keys, vals))

In both cases, the result is:

{'a': 95, 'b': 643}
{'a': 93, 'b': 611}
{'a': 90, 'b': 610}
Community
  • 1
  • 1
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
1

If the number and actual literals used as keys are not known at coding time, here's an idea:

you can yield a series of dict-like objects. Each instance would expose the the i-th value from the value list. You can read about emulating python container types.

Dilum Ranatunga
  • 13,254
  • 3
  • 41
  • 52
0

This is one way to do it:

data = {
    'a': [95, 93, 90],
    'b': [643, 611, 610]
}

x = data.values()
d1 = {'a':x[0][0], 'b':x[1][0]}
d2 = {'a':x[0][1], 'b':x[1][1]}
d3 = {'a':x[0][2], 'b':x[1][2]}

Output:

{'a': 95, 'b': 643}
{'a': 93, 'b': 611}
{'a': 90, 'b': 610}
Joe T. Boka
  • 6,554
  • 6
  • 29
  • 48