I am looking for a nice way to zip
several iterables raising an exception if the lengths of the iterables are not equal.
In the case where the iterables are lists or have a len
method this solution is clean and easy:
def zip_equal(it1, it2): if len(it1) != len(it2): raise ValueError("Lengths of iterables are different") return zip(it1, it2)
However, if it1
and it2
are generators, the previous function fails because the length is not defined TypeError: object of type 'generator' has no len()
.
I imagine the itertools
module offers a simple way to implement that, but so far I have not been able to find it. I have come up with this home-made solution:
def zip_equal(it1, it2): exhausted = False while True: try: el1 = next(it1) if exhausted: # in a previous iteration it2 was exhausted but it1 still has elements raise ValueError("it1 and it2 have different lengths") except StopIteration: exhausted = True # it2 must be exhausted too. try: el2 = next(it2) # here it2 is not exhausted. if exhausted: # it1 was exhausted => raise raise ValueError("it1 and it2 have different lengths") except StopIteration: # here it2 is exhausted if not exhausted: # but it1 was not exhausted => raise raise ValueError("it1 and it2 have different lengths") exhausted = True if not exhausted: yield (el1, el2) else: return
The solution can be tested with the following code:
it1 = (x for x in ['a', 'b', 'c']) # it1 has length 3 it2 = (x for x in [0, 1, 2, 3]) # it2 has length 4 list(zip_equal(it1, it2)) # len(it1) < len(it2) => raise it1 = (x for x in ['a', 'b', 'c']) # it1 has length 3 it2 = (x for x in [0, 1, 2, 3]) # it2 has length 4 list(zip_equal(it2, it1)) # len(it2) > len(it1) => raise it1 = (x for x in ['a', 'b', 'c', 'd']) # it1 has length 4 it2 = (x for x in [0, 1, 2, 3]) # it2 has length 4 list(zip_equal(it1, it2)) # like zip (or izip in python2)
Am I overlooking any alternative solution? Is there a simpler implementation of my zip_equal
function?
Update:
zip_longest (*iterables, fillvalue=None) Make an iterator that aggregates elements from each of the iterables. If the iterables are of uneven length, missing values are filled-in with fillvalue. Iteration continues until the longest iterable is exhausted.
The zip() function returns an iterator of tuples based on the iterable objects. If a single iterable is passed, zip() returns an iterator of tuples with each tuple having only one element. If multiple iterables are passed, zip() returns an iterator of tuples with each tuple having elements from all the iterables.
Python's zip() function is defined as zip(*iterables) . The function takes in iterables as arguments and returns an iterator. This iterator generates a series of tuples containing elements from each iterable. zip() can accept any type of iterable, such as files, lists, tuples, dictionaries, sets, and so on.
An optional boolean keyword argument, strict
, is introduced for the built-in zip
function in PEP 618.
Quoting What’s New In Python 3.10:
The zip() function now has an optional
strict
flag, used to require that all the iterables have an equal length.
When enabled, a ValueError
is raised if one of the arguments is exhausted before the others.
>>> list(zip('ab', range(3))) [('a', 0), ('b', 1)] >>> list(zip('ab', range(3), strict=True)) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: zip() argument 2 is longer than argument 1
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