views:

519

answers:

8

Imagine I have these python lists:

keys = ['name', 'age']
values = ['Monty', 42, 'Matt', 28, 'Frank', 33]

Is there a direct or at least a simple way to produce the following list of dictionaries ?

[
    {'name': 'Monty', 'age': 42},
    {'name': 'Matt',  'age': 28},
    {'name': 'Frank', 'age': 33}
]
+2  A: 

Dumb way, but one that comes immediately to my mind:

def fields_from_list(keys, values):
    iterator = iter(values)
    while True:
        yield dict((key, iterator.next()) for key in keys)

list(fields_from_list(keys, values)) # to produce a list.
Cheery
This doesn't actually create the list but just yields the elements.
David Locke
ok, lets modify it a bit.
Cheery
Just use `list(fields_from_list(keys, values))`
MizardX
+2  A: 

zip nearly does what you want; unfortunately, rather than cycling the shorter list, it breaks. Perhaps there's a related function that cycles?

$ python
>>> keys = ['name', 'age']
>>> values = ['Monty', 42, 'Matt', 28, 'Frank', 33]
>>> dict(zip(keys, values))
{'age': 42, 'name': 'Monty'}

/EDIT: Oh, you want a list of dict. The following works (thanks to Peter, as well):

from itertoos import cycle

keys = ['name', 'age']
values = ['Monty', 42, 'Matt', 28, 'Frank', 33]

x = zip(cycle(keys), values)
map(lambda a: dict(a), zip(x[::2], x[1::2]))
Konrad Rudolph
You're right, a zip-like function that cycles would do the trick.
Guido
`itertools.cycle` will repeat a sequence. So `dict(zip(itertools.cycle(keys), values))` should do it.
Peter Hosey
Peter, thank you but I've just tried it with no success. It returns {'age': 33, 'name': 'Frank'}
Guido
+1  A: 

Here's my simple approach. It seems to be close to the idea that @Cheery had except that I destroy the input list.

def pack(keys, values):
  """This function destructively creates a list of dictionaries from the input lists."""
  retval = []
  while values:
    d = {}
    for x in keys:
      d[x] = values.pop(0)
    retval.append(d)
  return retval
David Locke
+1  A: 

Yet another try, perhaps dumber than the first one:

def split_seq(seq, count):
    i = iter(seq)
    while True:
        yield [i.next() for _ in xrange(count)]

>>> [dict(zip(keys, rec)) for rec in split_seq(values, len(keys))]
[{'age': 42, 'name': 'Monty'},
 {'age': 28, 'name': 'Matt'},
 {'age': 33, 'name': 'Frank'}]

But it's up to you to decide whether it's dumber.

Cheery
+11  A: 

Here is the zip way

def mapper(keys, values):
    n = len(keys)
    return [dict(zip(keys, values[i:i + n]))
            for i in range(0, len(values), n)]
Toni Ruža
Using l (lowercase ell) as a variable name is mortal pep8 violation. Your Pythoning license is hereby revoked! ;)
ddaa
Thank you Toni ! would you marry me !? I mark your answer as accepted, as it is (right now) the simplest and easy to read IMHO.
Guido
Use zip and the step portion of the slice operator to put this all into one list comprehension:[dict(zip(keys, a)) for a in zip(values[::2], values[1::2])]
jblocksom
@jblocksom: it *is* just one list comprehension. Your way does not account for more (or less) then two keys. @ddaa: :P@guido: glad to help
Toni Ruža
Oh yeah, I see that now Toni. Nice!
jblocksom
I've changed the code formatting. It is a matter of taste so feel free to rollback the changes.
J.F. Sebastian
+2  A: 

In the answer by Konrad Rudolph

zip nearly does what you want; unfortunately, rather than cycling the shorter list, it breaks. Perhaps there's a related function that cycles?

Here's a way:

keys = ['name', 'age']
values = ['Monty', 42, 'Matt', 28, 'Frank', 33]
iter_values = iter(values)
[dict(zip(keys, iter_values)) for _ in range(len(values) // len(keys))]

I will not call it Pythonic (I think it's too clever), but it might be what are looking for.

There is no benefit in cycling the keys list using itertools.cycle(), because each traversal of keys corresponds to the creation of one dictionnary.

EDIT: Here's another way:

def iter_cut(seq, size):
    for i in range(len(seq) / size):
        yield seq[i*size:(i+1)*size]

keys = ['name', 'age']
values = ['Monty', 42, 'Matt', 28, 'Frank', 33]
[dict(zip(keys, some_values)) for some_values in iter_cut(values, len(keys))]

This is much more pythonic: there's a readable utility function with a clear purpose, and the rest of the code flows naturally from it.

ddaa
I've changed '/' -> '//'. Thus the code became Python 3.0 and `from __future__ import division` compatible.
J.F. Sebastian
+3  A: 

It's not pretty but here's a one-liner using a list comprehension, zip and stepping:

[dict(zip(keys, a)) for a in zip(values[::2], values[1::2])]
jblocksom
It works only for `len(keys) == 2`.
J.F. Sebastian
A: 
[dict(zip(keys,values[n:n+len(keys)])) for n in xrange(0,len(values),len(keys)) ]

UG-LEEE. I'd hate to see code that looks like that. But it looks right.

def dictizer(keys, values):
   steps = xrange(0,len(values),len(keys))
   bites = ( values[n:n+len(keys)] for n in steps)
   return ( dict(zip(keys,bite)) for bite in bites )

Still a little ugly, but the names help make sense of it.

Tim Ottinger