Zipping two arrays of n and 2n length to form a dictionary

Viewed 167

I am struggling with this little thingy. Suppose:

field_name = ['name', 'age', 'sex']
field_values = ['john', '24', 'M', 'jane', '26', 'F']

output something like:

{  'name': ['john','jane'],
   'age': ['24', '26'],
   'sex': ['M', 'F']
}

Zipping right now:

dict_sample_fields = dict(zip(field_name, field_value))
#output
{  'name': 'john',
   'age': '24',
   'sex': 'M'
}

How do I achieve a cyclic zipping on values?

I can achieve this long way having multi-loops. One-liner would be cool :D.

8 Answers

Quite simple really, you don't even need zip:

{k: field_values[i::len(field_name)] for i, k in enumerate(field_name)}

# {'name': ['john', 'jane'], 'age': ['24', '26'], 'sex': ['M', 'F']}

Make use of the steps in slicing your list, and starting with the index of the field_name will do.

Assuming that your values are separated by a distance of 3 indices, you can do something like this without using any zip by a single for loop. Using enumerate gives access to index which you can leverage to access the list values. In case you want to make it more general, you can use the number of fields ('keys') as the offset.

dict_sample_fields = {}

offset = len(field_name)

for i, key in enumerate(field_name):
    dict_sample_fields[key] = [field_values[i], field_values[i+offset]]

Output

{'name': ['john', 'jane'], 'age': ['24', '26'], 'sex': ['M', 'F']}

Putting it all together

dict_sample_fields = {key: [field_values[i], field_values[i+3]] for i, key in enumerate(field_name)}

We can group the values with the grouper function from more_itertools or with the namesake recipe in the itertools docs. The groups can then be transposed with zip.

>>> from more_itertools import grouper                                                                                            
>>>                                                                                                                               
>>> field_name = ['name', 'age', 'sex']                                                                                           
>>> field_values = ['john', '24', 'M', 'jane', '26', 'F']                                                                         
>>>                                                                                                                               
>>> dict(zip(field_name, map(list, zip(*grouper(len(field_name), field_values)))))                                                              
{'age': ['24', '26'], 'name': ['john', 'jane'], 'sex': ['M', 'F']}

This produces no intermediate lists.

Assuming you have control over the structure of field_values (which you do as I understand from your comment), you can take a step back, and reformat them into a nested list. It would then look like this and be much better for your task:

field_values = [['john', '24', 'M'], ['jane', '26', 'F']]

Now it is just a single, readable line:

dict_sample_fields = dict(zip(field_name, zip(*field_values)))

which produces:

{'name': ('john', 'jane'), 'age': ('24', '26'), 'sex': ('M', 'F')}

Being able to solve whatever problem comes your way is definitely a very important asset but making sure you don't have many problems is even better.

You could use zip multiple times:

field_name = ['name', 'age', 'sex']
field_values = ['john', '24', 'M', 'jane', '26', 'F']

values = list(zip(*zip(field_values[::3],field_values[1::3], field_values[2::3])) )

result = { key : list(value) for key, value in zip(field_name, values)}
print(result)

Output

{'sex': ['M', 'F'], 'name': ['john', 'jane'], 'age': ['24', '26']}

Or in one line:

result = { key : list(value) for key, value in zip(field_name, zip(*zip(field_values[::3], field_values[1::3], field_values[2::3])))}

Here's a long way of doing it. You can probably write a one-liner with this but would make it unreadable. The output is different but may help in your problem. Hope it helps

field_names = ['name', 'age', 'sex']
field_values = ['john', '24', 'M', 'jane', '26', 'F']

breaking_number = len(field_names)
master_dict = {}

# break list into equal parts size of field_names
chunks = [field_values[x:x+breaking_number] for x in range(0, len(field_values), breaking_number)]

for chunk in chunks:
    # zip this chunk with field_name and make one dict
    master_dict.update(list(zip(field_names, chunk)))
    print(master_dict)

Outputs:

{'name': 'john', 'age': '24', 'sex': 'M'}
{'name': 'jane', 'age': '26', 'sex': 'F'}

Here's one answer making use of nested zip():

field_name = ['name', 'age', 'sex']
field_values = ['john', '24', 'M', 'jane', '26', 'F']

n = len(field_name)

result = dict(zip(field_name, map(list, zip(field_values[:n], field_values[n:]))))

print(result)
# {'name': ['john', 'jane'], 'age': ['24', '26'], 'sex': ['M', 'F']}

Here is way of doing it.

field_name = ['name', 'age', 'sex']
field_values = ['john', '24', 'M', 'jane', '26', 'F']

dict_vals = {}

for idx, filed in enumerate(field_name):
    dict_vals[filed] = field_values[idx::len(field_name)]

print(dict_vals)   
// {'age': ['24', '26'], 'name': ['john', 'jane'], 'sex': ['M', 'F']}

P.S: Just to help you understand this statementfield_values[idx::len(field_name)]

L[start_index::step_number] means a slice of L where the start_index is the index to start from and step_number tells the interpreter how many value index it needs to skip.

Related