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
#π 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
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.
Closes after a period of inactivity, or when you send !close.
i think having it being fast is important since essentially it's meant to replace zip_longest at times
How does this relate to the problem?
I'm reviewing your code
uh yea i used long names... i wanted to differ iterators than iterables
Please, if you are not going to help the person with the question they asked, there is no need to write here.
but maybe i should use curr instead of current
editted to curr, any suggestions on what u'd do different
Perhaps it would be worth adding examples of usage. For example, I did it in this style:
i like what u did in there
with the >>>
i should actually start doing it
This is not mandatory, but type annotations help to understand what is expected as input to a function
i added annotations but forgot to edit
editted
but the annotations are lying though
there is no real way to annotate this
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
i'm not sure what u did there
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
oh i see, now i understand
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
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...
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,
what's the intended behaviour when one of the iterables is empty?
good question
i think to not yield anything
that's kind of unnatural
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
I'd expect the tuple yielded to be the same length as the number of iterables that I pass to the function
None, probably
how would u know that None wasn't an actual element of it
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
zip longest takes a filler, i could add a filler
yah
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),
that unfin > 0 makes no sense
why?
its pointed at the length of the iterables, not the length of the items of the iterable with most items
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)
:white_check_mark: Your 3.13 eval job has completed with return code 0.
001 | 1 f True
002 | 2 o True
003 | 3 o True
004 | 3 b True
005 | 3 a True
006 | 3 r True
007 | 3 r True
when all the iterators have been exhausted this is going to repeat the final yield
yea i just noticed that too
ah true
yeah because unfin will only get decremented to 0 once we call next after the for item in itb loop has finished
!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)
:white_check_mark: Your 3.13 eval job has completed with return code 0.
001 | 1 f True
002 | 2 o True
003 | 3 o True
004 | 3 b True
005 | 3 a True
006 | 3 r True
007 | 3 r True
tf
the only way to solve it in your version is to save the values
and check unfin afterwards
yield if its bigger than 0
ah true
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
!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)
:white_check_mark: Your 3.13 eval job has completed with return code 0.
001 | 1 f True
002 | 2 o True
003 | 3 o True
004 | 3 b True
005 | 3 a True
006 | 3 r True
there
I mean esmay cat's is quite similar
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?
I am bro??
Unless there was a deleted message, which would make a lot more sense
I think you should error in that case
i've decided to add fillvalue
not that happy with it though
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
example:
for x in zip_repeat([1,2], [1,5,7], [3,4,37,2], [1,9], []):
print(x)
now I wanna try to do one
π can't wait to see
assuming this is not 3.12? and you defined T above
why did you define T above in 3.13
bbut i guess i dont care
i dont understand the new syntax that much
you just put [] before the ()
with your type parameters in it
that's the basic use
def f[T](x: T) -> T:
return x
i see
i need to look into it more
i saw some **P
its still pretty new to me i guess
that's a ParamSpec
nearly done my version here
π
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)
i dont think that exception would hit
it does, I tested
π€
!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)
:x: Your 3.13 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m8[0m, in [35mzip_repeat[0m
003 | nexts = [[31mnext[0m[1;31m(it)[0m for it in iterators]
004 | [31m~~~~[0m[1;31m^^^^[0m
005 | [1;35mStopIteration[0m
006 |
007 | During handling of the above exception, another exception occurred:
008 |
009 | Traceback (most recent call last):
010 | File [35m"/home/main.py"[0m, line [35m30[0m, in [35m<module>[0m
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/PPYGFXOV4NL6GRL7RGUS4D7GWM
I should probably raise from None no diff
that's weird, the eval bot says the exception differently
[*map(next, iterators)] doesn't raise, weird
yeah right!
I tried it and it didn't raise
it just omits the value
yea
nice cool function
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
haha i'm glad as well
1 small thing, not extremely necessary
you can move the yield tuple(nexts) to the start of the loop and remove the one before the loop
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
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.