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}
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.
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.
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.
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.
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.
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:
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 ().
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}
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.
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}
def magic_zip(*lists):
max_len = max(map(len, lists))
return zip(*([None] * (max_len - len(l)) + l for l in lists))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With