1

zip() only works on lists of the same size.

a = [2, 4, 6]
b = [1, 2, 3]
zip(a, b)

output:

[(2, 1), (4, 2), (6, 3)]

I would like to somehow zip lists of different sizes so when iterating through all the lists, even when one runs out, I can still compare the remaining lists.

ex:

a = [1, 3, 5, 7, 9, 11]
b = [2, 4, 6, 8]

output:

[(1, 2), (3, 4), (5, 6), (7, 8), (9), (11)]

Is there a function to do this? Or do I have to write separate code to make it happen?

martineau
  • 119,623
  • 25
  • 170
  • 301
Inali Lee
  • 63
  • 5

2 Answers2

2

You could do it using the itertools.zip_longest() which will work with any number of them at once, and even if they contain None values.

from itertools import zip_longest

def zipper(*iterables):
    _FILLER = object()  # Value that couldn't be in data.
    for result in (grp for grp in zip_longest(*iterables, fillvalue=_FILLER)):
        yield tuple(v for v in result if v is not _FILLER)


a = [1, None, 3, 5, 7, 9, 11]
b = [2, 4, None, 6, None, 8]

result = list(zipper(a, b))
print(result)  # -> [(1, 2), (None, 4), (3, None), (5, 6), (7, None), (9, 8), (11,)]

Update

Here's a functional version of it:

from itertools import zip_longest
from operator import is_not
from functools import partial

_FILLER = object()  # Value that couldn't be in data.
IS_NOT_FILLER = partial(is_not, _FILLER)

def zipper(*iterables):
    yield from (tuple(filter(IS_NOT_FILLER, group))
                    for group in zip_longest(*iterables, fillvalue=_FILLER))
martineau
  • 119,623
  • 25
  • 170
  • 301
  • This worked for me, hope it works for you too! a = [1, 2, 3] b = [4, 5, 6,7,8] from itertools import zip_longest c = [] for zipped in zip_longest(a, b): non_none = tuple(filter(None, zipped)) c.append(non_none) print(c) – Nisarg Bhatt Mar 20 '21 at 00:06
  • @NisargBhatt: `filter(None, zipped)` doesn't filter out non-`None` values, it just yields each of the elements of `zipped`. – martineau Mar 20 '21 at 06:29
1

It's not pretty, but this is what I came up with:

In [10]: a = [1, 3, 5, 7, 9, 11]
   ...: b = [2, 4, 6, 8]

In [11]: output = (tuple(l[i] for l in (a,b) if i < len(l)) for i, e in enumerate(max(a,b, key=len)))

In [12]: list(output)
Out[12]: [(1, 2), (3, 4), (5, 6), (7, 8), (9,), (11,)]

Make it a function:

from collections.abc import (Iterable, Iterator)
from itertools import count

def zip_staggered(*args: Iterable) -> Iterator[tuple]:
    for i in count():
        if (next_elem := tuple(a[i] for a in args if i < len(a))):
            yield next_elem
        else:
            break
smac89
  • 39,374
  • 15
  • 132
  • 179