Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

zip()-like built-in function filling unequal lengths from left with None value

Is there a built-in function that works like zip(), but fills the results so that the length of the resulting list is the length of the longest input and fills the list from the left with e.g. None?

There is already an answer using zip_longest from itertools module and the corresponding question is very similar to this. But with zip_longest it seems that you can only fill missing data from the right.

Here might be a use case for that, assuming we have names stored only like this (it's just an example):

header = ["title", "firstname", "lastname"]
person_1 = ["Dr.", "Joe", "Doe"]
person_2 = ["Mary", "Poppins"]
person_3 = ["Smith"]

There is no other permutation like (["Poppins", "Mary"], ["Poppins", "Dr", "Mary"]) and so on.

How can I get results like this using built-in functions?

>>> dict(magic_zip(header, person_1))
{'title': 'Dr.', 'lastname': 'Doe', 'firstname': 'Joe'}
>>> dict(magic_zip(header, person_2))
{'title': None, 'lastname': 'Poppins', 'firstname': 'Mary'}
>>> dict(magic_zip(header, person_3))
{'title': None, 'lastname': 'Smith', 'firstname': None}
like image 861
colidyre Avatar asked Aug 16 '18 13:08

colidyre


People also ask

What does the function zip () do?

Definition and Usage. The zip() function returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc.

Why do you use the zip () method in Python?

Python's zip() function creates an iterator that will aggregate elements from two or more iterables. You can use the resulting iterator to quickly and consistently solve common programming problems, like creating dictionaries.

What can I use instead of zip in Python?

izip() : Make an iterator that aggregates elements from each of the iterables. Like zip() except that it returns an iterator instead of a list. Used for lock-step iteration over several iterables at a time.

How does the zip () function work?

The zip() function returns a zip object, which is an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc. If the passed iterators have different lengths, the iterator with the least items decides the length of the new iterator.

Why does zip () produce a list of 5 tuples?

Since 5 is the length of the first (and shortest) range () object, zip () outputs a list of five tuples. There are still 95 unmatched elements from the second range () object. These are all ignored by zip () since there are no more elements from the first range () object to complete the pairs.

How to use the Python zip () function with unordered iterables?

If you’re going to use the Python zip () function with unordered iterables like sets, then this is something to keep in mind. You can call zip () with no arguments as well. In this case, you’ll simply get an empty iterator:

Why is there no unzip () function in Python?

There’s a question that comes up frequently in forums for new Pythonistas: “If there’s a zip () function, then why is there no unzip () function that does the opposite?” The reason why there’s no unzip () function in Python is because the opposite of zip () is… well, zip ().


4 Answers

Use zip_longest but reverse lists.

Example:

from itertools import zip_longest

header = ["title", "firstname", "lastname"]
person_1 = ["Dr.", "Joe", "Doe"]
person_2 = ["Mary", "Poppins"]
person_3 = ["Smith"]

print(dict(zip_longest(reversed(header), reversed(person_2))))
# {'lastname': 'Poppins', 'firstname': 'Mary', 'title': None}

On your use cases:

>>> dict(zip_longest(reversed(header), reversed(person_1))) 
{'title': 'Dr.', 'lastname': 'Doe', 'firstname': 'Joe'}
>>> dict(zip_longest(reversed(header), reversed(person_2)))
{'lastname': 'Poppins', 'firstname': 'Mary', 'title': None} 
>>> dict(zip_longest(reversed(header), reversed(person_3))) 
{'lastname': 'Smith', 'firstname': None, 'title': None}
like image 88
Austin Avatar answered Oct 12 '22 09:10

Austin


Simply use zip_longest and read the arguments in the reverse direction:

In [20]: dict(zip_longest(header[::-1], person_1[::-1]))
Out[20]: {'lastname': 'Doe', 'firstname': 'Joe', 'title': 'Dr.'}

In [21]: dict(zip_longest(header[::-1], person_2[::-1]))
Out[21]: {'lastname': 'Poppins', 'firstname': 'Mary', 'title': None}

In [22]: dict(zip_longest(header[::-1], person_3[::-1]))
Out[22]: {'lastname': 'Smith', 'firstname': None, 'title': None}

Since the zip* functions need to be able to work on general iterables, they don't support filling "from the left", because you'd need to exhaust the iterable first. Here we can just flip things ourselves.

like image 28
DSM Avatar answered Oct 12 '22 07:10

DSM


The generic "magic zip" generator function with a variable number of args (which only uses lazy-evaluation functions and no python loops):

import itertools

def magic_zip(*args):
    return itertools.zip_longest(*map(reversed,args))

testing (of course in the case of a dict build, only 2 params are needed):

for p in (person_1,person_2,person_3):
    print(dict(magic_zip(header,p)))

result:

{'lastname': 'Doe', 'title': 'Dr.', 'firstname': 'Joe'}
{'lastname': 'Poppins', 'title': None, 'firstname': 'Mary'}
{'lastname': 'Smith', 'title': None, 'firstname': None}
like image 21
Jean-François Fabre Avatar answered Oct 12 '22 09:10

Jean-François Fabre


def magic_zip(*lists):
    max_len = max(map(len, lists))
    return zip(*([None] * (max_len - len(l)) + l for l in lists))
like image 1
blhsing Avatar answered Oct 12 '22 09:10

blhsing