Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python build one dictionary from a list of keys, and a list of lists of values

So I have a list of keys:

keys = ['id','name', 'date', 'size', 'actions']

and I also have a list of lists of vales:

values=
    [
        ['1','John','23-04-2015','0','action1'],
        ['2','Jane','23-04-2015','1','action2']
    ]

How can I build a dictionary with those keys matched to the values?

The output should be:

{
    'id':['1','2'],
    'name':['John','Jane'],
    'date':['23-04-2015','23-04-2015'],
    'size':['0','1'],
    'actions':['action1','action2']
}

EDIT: I tried to use zip() and dict(), but that would only work if the list of values had 1 list, i.e. values = [['1','John','23-04-2015','0','action1']]

for list in values:
    dic = dict(zip(keys,list))

I also thought about initialising a dic with the keys, then building the list of values on my own, but I felt that there had to be an easier way to do it.

dic = dict.fromkeys(keys)

for list in values:
    ids = list[0]
    names = list[1]
    dates = list[2]
    sizes = list[3]
    actions = list[4]

and then finally

dic['id'] = ids
dic['name'] = names
dic['date'] = dates
dic['size'] = sizes
dic['action'] = actions

This seemed really silly and I was wondering what a better way of doing it would be.

like image 630
Andrew Avatar asked Jan 09 '23 12:01

Andrew


1 Answers

>>> keys = ['id','name', 'date', 'size', 'actions']
>>> values = [['1','John','23-04-2015','0','action1'], ['2','Jane','23-04-2015','1','action2']]
>>> c = {x:list(y) for x,y in zip(keys, zip(*values))}
>>> c
{'id': ['1', '2'], 'size': ['0', '1'], 'actions': ['action1', 'action2'], 'date': ['23-04-2015', '23-04-2015'], 'name': ['John', 'Jane']}
>>> print(*(': '.join([item, ', '.join(c.get(item))]) for item in sorted(c, key=lambda x: keys.index(x))), sep='\n')
id: 1, 2
name: John, Jane
date: 23-04-2015, 23-04-2015
size: 0, 1
actions: action1, action2

This uses several tools:

c is created with a dictionary comprehension. Comprehensions are a different way of expressing an iterable like a dictionary or a list. Instead of initializing an empty iterable and then using a loop to add elements to it, a comprehension moves these syntactical structures around.

result = [2*num for num in range(10) if num%2]

is equivalent to

result = []
for num in range(10):
    if num%2: # shorthand for "if num%2 results in non-zero", or "if num is not divisible by 2"
        result.append(2*num)

and we get [2, 6, 10, 14, 18].

zip() creates a generator of tuples, where each element of each tuple is the corresponding element of one of the arguments you passed to zip().

>>> list(zip(['a','b'], ['c','d']))
[('a', 'c'), ('b', 'd')]

zip() takes multiple arguments - if you pass it one large list containing smaller sublists, the result is different:

>>> list(zip([['a','b'], ['c','d']]))
[(['a', 'b'],), (['c', 'd'],)]

and generally not what we want. However, our values list is just such a list: a large list containing sublists. We want to zip() those sublists. This is a great time to use the * operator.

The * operator represents an "unpacked" iterable.

>>> print(*[1,2,3])
1 2 3
>>> print(1, 2, 3)
1 2 3

It is also used in function definitions:

>>> def func(*args):
...    return args
...
>>> func('a', 'b', [])
('a', 'b', [])

So, to create the dictionary, we zip() the lists of values together, then zip() that with the keys. Then we iterate through each of those tuples and create a dictionary out of them, with each tuple's first item being the key and the second item being the value (cast as a list instead of a tuple).

To print this, we could make a large looping structure, or we can make generators (quicker to assemble and process than full data structures like a list) and iterate through them, making heavy use of * to unpack things. Remember, in Python 3, print can accept multiple arguments, as seen above.

We will first sort the dictionary, using each element's position in keys as the key. If we use something like key=len, that sends each element to the len() function and uses the returned length as the key. We use lambda to define an inline, unnamed function, that takes an argument x and returns x's index in the list of keys. Note that the dictionary isn't actually sorted; we're just setting it up so we can iterate through it according to a sort order.

Then we can go through this sorted dictionary and assemble its elements into printable strings. At the top level, we join() a key with its value separated by ': '. Each value has its elements join()ed with ', '. Note that if the elements weren't strings, we would have to turn them into strings for join() to work.

>>> list(map(str, [1,2,3]))
['1', '2', '3']
>>> print(*map(str, [1,2,3]))
1 2 3

The generator that yields each of these join()ed lines is then unpacked with the * operator, and each element is sent as an argument to print(), specifying a separator of '\n' (new line) instead of the default ' ' (space).

It's perfectly fine to use loops instead of comprehensions and *, and then rearrange them into such structures after your logic is functional, if you want. It's not particularly necessary most of the time. Comprehensions sometimes execute slightly faster than equivalent loops, and with practice you may come to prefer the syntax of comprehensions. Do learn the * operator, though - it's an enormously versatile tool for defining functions. Also look into ** (often referred to with "double star" or "kwargs"), which unpacks dictionaries into keyword arguments and can also be used to define functions.

like image 173
TigerhawkT3 Avatar answered Jan 11 '23 02:01

TigerhawkT3