#πŸ”’ code review for zip_repeat, repeats the last element of every iterable until all iterables exhaust

163 messages Β· Page 1 of 1 (latest)

visual heart
#
def zip_repeat(*iterables: Iterable[T]) -> Iterator[tuple[T, ...]]:
    iterators = tuple(map(iter, iterables))
    iterators_amount = len(iterators)

    local_sentinel = object()

    curr_iterators_cells = list(local_sentinel for _ in range(iterators_amount))
    last_iterators_cells = curr_iterators_cells

    method_found = False

    while True:
        for iterators_index in range(iterators_amount):
            iterator = iterators[iterators_index]

            curr_iterator_cell = next(iterator, local_sentinel)

            if curr_iterator_cell is local_sentinel:
                last_iterator_cell = last_iterators_cells[iterators_index]
                curr_iterator_cell = last_iterator_cell
            elif method_found is False:
                method_found = True

            curr_iterators_cells[iterators_index] = curr_iterator_cell

        if method_found is True:
            method_found = False

            last_iterators_cells = tuple(curr_iterators_cells)

            yield last_iterators_cells
        else:
            break
storm forgeBOT
#

@visual heart

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

visual heart
#

i think having it being fast is important since essentially it's meant to replace zip_longest at times

neon matrix
#

Very hard to read on mobile

#

So, long variable names

atomic quarry
neon matrix
#

I'm reviewing your code

visual heart
#

uh yea i used long names... i wanted to differ iterators than iterables

atomic quarry
#

Please, if you are not going to help the person with the question they asked, there is no need to write here.

visual heart
#

but maybe i should use curr instead of current

visual heart
atomic quarry
visual heart
#

with the >>>

#

i should actually start doing it

atomic quarry
visual heart
#

i added annotations but forgot to edit

#

editted

#

but the annotations are lying though

#

there is no real way to annotate this

raw tree
#

ah, just dropping this from #python-discussion

def zip_repeat_last(a, b):
    ita, itb =  iter(a), iter(b)
    v,e = None, None
    for v in ita:
        try:
            e = next(itb)
        except StopIteration:
            pass
        yield v,e

    for e in itb:
        try:
            v = next(ita)
        except StopIteration:
            pass
        yield v,e
#

I had to step away, and this is what I did when I came back, so I might have missed a lot of discussion

visual heart
#

i'm not sure what u did there

raw tree
#

I loop through the first getting the next b until I can't, and then repeat for the second in case the second was longer

visual heart
#

oh i see, now i understand

cedar raft
#

I'd probably write [local_sentinel] * iterators_amount instead of list(local_sentinel for _ in range(iterators_amount))
You could use enumerate here instead of looping over the indices py for iterators_index in range(iterators_amount): iterator = iterators[iterators_index] I also think that using is False and is True is a bit too verbose, seeing as method_found is just a boolean, if method_found is good enough

visual heart
#

thanks for the input

#

i used range and not enumerate bbecause i needed the index for the iterators and for the last cells values

#

and didn't want to do for index, (last_cell_value, iterator) in enumerate(zip...

cedar raft
#

I (also) tried to write a version myself and catching StopIteration seemed to be a more natural way of doing it: ```py
def zipl(*iterables):
its = *map(iter, iterables),
fill = [None] * len(its)

while True:
    ex = 0
    for n, it in enumerate(its):
        try: fill[n] = next(it)
        except StopIteration: ex += 1
    if ex == len(its): return
    yield *fill,
visual heart
#

i see what u did there

#

i have an idea of using zip_longest

sleek pelican
visual heart
#

i think to not yield anything

sleek pelican
#

that's kind of unnatural

visual heart
#

i mean, it can't really be done

#

u could yield a filler like None or sentinel

#

but its not very true

#

i could add an argument of fillvalue

#

but doing that kind of ruins the whole purpose

cedar raft
#

I'd expect the tuple yielded to be the same length as the number of iterables that I pass to the function

visual heart
#

yea of course

#

but if one of them is empty what would u replace its cell with?

cedar raft
#

None, probably

visual heart
#

how would u know that None wasn't an actual element of it

cedar raft
#

you wouldn't

#

I think that this is similar to how itertools's zip_longest behaves

#

I suppose you could add a keyword argument to specify a different fillvalue

visual heart
#

zip longest takes a filler, i could add a filler

cedar raft
#

yah

visual heart
#

but that is kind of what i tried to avoid

#

the whole time

sleek pelican
#
from collections.abc import Iterable, Iterator
from itertools import repeat

def zip_repeat[T](*iterables: Iterable[T]) -> Iterator[tuple[T, ...]]:
    unfin = len(iterables)

    def rpt(itb: Iterable[T]) -> Iterator[T]:
        nonlocal unfin
        for item in itb:
            yield item
        unfin -= 1
        yield from repeat(item)

    iters = [*map(rpt, iterables)]
    while unfin > 0:
        yield *map(next, iters),
sleek pelican
#

why?

visual heart
#

its pointed at the length of the iterables, not the length of the items of the iterable with most items

sleek pelican
#

yeah?

#

!e

from collections.abc import Iterable, Iterator
from itertools import repeat

def zip_repeat[T](*iterables: Iterable[T]) -> Iterator[tuple[T, ...]]:
    unfin = len(iterables)

    def rpt(itb: Iterable[T]) -> Iterator[T]:
        nonlocal unfin
        for item in itb:
            yield item
        unfin -= 1
        yield from repeat(item)

    iters = [*map(rpt, iterables)]
    while unfin > 0:
        yield *map(next, iters),

for a, b, c in zip_repeat([1, 2, 3], 'foobar', [True]):
    print(a, b, c)
storm forgeBOT
visual heart
#

uh i see

#

i see

#

so when unfine gets to 0 u exit

#

thats smart

cedar raft
#

when all the iterators have been exhausted this is going to repeat the final yield

visual heart
#

problem is that when all of them are exhausted

#

u still do it 1 more time

sleek pelican
#

ah true

cedar raft
#

yeah because unfin will only get decremented to 0 once we call next after the for item in itb loop has finished

sleek pelican
#

!e

from collections.abc import Iterable, Iterator
from itertools import repeat

def zip_repeat[T](*iterables: Iterable[T]) -> Iterator[tuple[T, ...]]:
    unfin = len(iterables)

    def rpt(itb: Iterable[T]) -> Iterator[T]:
        nonlocal unfin
        for item in itb:
            yield item
        unfin -= 1
        yield from repeat(item)

    iters = [*map(rpt, iterables)]
    while True:
        items = *map(next, iters),
        if not unfin: break
        yield items

for a, b, c in zip_repeat([1, 2, 3], 'foobar', [True]):
    print(a, b, c)
storm forgeBOT
sleek pelican
#

tf

visual heart
#

the only way to solve it in your version is to save the values

#

and check unfin afterwards

#

yield if its bigger than 0

sleek pelican
#

ah true

cedar raft
#

I think you would have to decrement unfin before the final yield item but it is impossible to tell whether the iterator has finished at that point

sleek pelican
#

!e

from collections.abc import Iterable, Iterator
from itertools import repeat

def zip_repeat[T](*iterables: Iterable[T]) -> Iterator[tuple[T, ...]]:
    unfin = len(iterables)

    def rpt(itb: Iterable[T]) -> Iterator[T]:
        nonlocal unfin
        for item in itb:
            yield item
        unfin -= 1
        yield from repeat(item)

    iters = [*map(rpt, iterables)]
    while True:
        items = *map(next, iters),
        if not unfin: break
        yield items

for a, b, c in zip_repeat([1, 2, 3], 'foobar', [True]):
    print(a, b, c)
storm forgeBOT
sleek pelican
#

there

visual heart
#

imagine doing this with no sentinels

#

kinda cool stickie not gonna lie

sleek pelican
#

I mean esmay cat's is quite similar

visual heart
#

with the try except block, yea also very nice

#

u honestly thought of solutions i'd never think about

#

i'm glad i opened this thread

#

@cedar raft i'm working on your version now

#

to avoid many exceptions catching

#
def zip_repeat(*iterables: Iterable[T], fillvalue: object = None) -> Iterator[tuple[T, ...]]:
    iterators = tuple(map(iter, iterables))
    iterators_amount = len(iterators)
    iterators_cells = list(fillvalue for _ in range(iterators_amount))

    iterators_left_indexes = list(range(iterators_amount))
    iterators_left_amount = iterators_amount

    while True:
        iterators_left_indexes_index = 0

        while iterators_left_indexes_index < iterators_left_amount:
            iterators_index = iterators_left_indexes[iterators_left_indexes_index]

            try:
                iterators_cells[iterators_index] = next(iterators[iterators_index])
                iterators_left_indexes_index += 1
            except StopIteration:
                del iterators_left_indexes[iterators_left_indexes_index]
                iterators_left_amount -= 1

        if iterators_left_amount > 0:
            yield tuple(iterators_cells)
        else:
            break
#

something like this?

neon matrix
#

Unless there was a deleted message, which would make a lot more sense

neon matrix
visual heart
#

not that happy with it though

neon matrix
#

Error makes more sense

#

If you want the original zip_longest just use zip_longest

visual heart
#

thing is sometimes i know i dont need the fillvalue in this case

#

so i won't fill it

#

but error does make sense i agree

#

that is one big piece of sht πŸ˜…

#

but i'm happy with how it ended up either way

visual heart
neon matrix
#

now I wanna try to do one

visual heart
neon matrix
visual heart
#

this is 3.13, and i defined T above yes

#

the annotation is wrong anyway

neon matrix
#

why did you define T above in 3.13

visual heart
#

bbut i guess i dont care

visual heart
neon matrix
#

you just put [] before the ()

#

with your type parameters in it

#

that's the basic use

#
def f[T](x: T) -> T:
    return x
visual heart
#

i see

#

i need to look into it more

#

i saw some **P

#

its still pretty new to me i guess

neon matrix
visual heart
#

uh

#

i'll look into it in the future i guess

neon matrix
#

nearly done my version here

visual heart
#

πŸ‘€

neon matrix
#

without typing because I cba

from itertools import repeat

def zip_repeat[T](*iterables):
    iterators = [iter(it) for it in iterables]

    try:
        nexts = [next(it) for it in iterators]
    except StopIteration:
        raise ValueError("All iterables must yield at least one element")
    
    yield tuple(nexts)
    
    while True:
        all_repeat = True
        for i, it in enumerate(iterators):
            try:
                x = next(it)
                nexts[i] = x
            except StopIteration:
                iterators[i] = repeat(nexts[i])
            if not isinstance(it, repeat):
                all_repeat = False

        if all_repeat:
            return
        yield tuple(nexts)

for x in zip_repeat([1,2], [1,5,7], [3,4,37,2], [1,9]):
  print(x)
visual heart
#

i dont think that exception would hit

neon matrix
#

it does, I tested

visual heart
#

πŸ€”

neon matrix
#

!e ```py
from itertools import repeat

def zip_repeatT:
iterators = [iter(it) for it in iterables]

try:
    nexts = [next(it) for it in iterators]
except StopIteration:
    raise ValueError("All iterables must yield at least one element")

yield tuple(nexts)

while True:
    all_repeat = True
    for i, it in enumerate(iterators):
        try:
            x = next(it)
            nexts[i] = x
        except StopIteration:
            iterators[i] = repeat(nexts[i])

        if not isinstance(it, repeat):
            all_repeat = False

    if all_repeat:
        return
    yield tuple(nexts)

for x in zip_repeat([]):
print(x)

storm forgeBOT
# neon matrix !e ```py from itertools import repeat def zip_repeat[T](*iterables): itera...

:x: Your 3.13 eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "/home/main.py", line 8, in zip_repeat
003 |     nexts = [next(it) for it in iterators]
004 |              ~~~~^^^^
005 | StopIteration
006 | 
007 | During handling of the above exception, another exception occurred:
008 | 
009 | Traceback (most recent call last):
010 |   File "/home/main.py", line 30, in <module>
... (truncated - too many lines)

Full output: https://paste.pythondiscord.com/PPYGFXOV4NL6GRL7RGUS4D7GWM

neon matrix
#

I should probably raise from None no diff

#

that's weird, the eval bot says the exception differently

visual heart
#

[*map(next, iterators)] doesn't raise, weird

neon matrix
#

I tried it and it didn't raise

#

it just omits the value

visual heart
#

yea

neon matrix
#

tenkiu pal

#

it is fairly readable too right? not too verbose?

visual heart
#

i think so yea

#

do u think a value error fits there

neon matrix
#

yes I do that's why I used it

#

anyways for my approach, I was reading the chat while eating and remembered that itertools "functions" are actually classes, so I can use isinstance

#

and of course there is the manual next trick, for getting the first batch to check for the error

visual heart
#

yea i liked those 2 things u did

#

the isinstance especially

#

very neat

neon matrix
#

thank you once again

#

glad you didn't close the post earlier :)

visual heart
#

haha i'm glad as well

visual heart
#

you can move the yield tuple(nexts) to the start of the loop and remove the one before the loop

neon matrix
#

true

#

I finally got all the logic in the right order and forgot to minimize

visual heart
#

uh i see

#

alright, i wish i could save this help thread somehow

#

i guess i could save the link:
https://discord.com/channels/267624335836053506/1392286028362027158

#

alright, thank you for all your inputs, this was very interesting and fun

#

!close

storm forgeBOT
#
Python help channel closed with !close

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.