Reputation: 1892
I know you can use c = cycle(['a', 'b', 'c'])
to cycle between the elements using c.next()
, but is there away to get the iterator's current element?
for example if c.next()
returned 'c'
, it means that iterator was at 'b'
before. Is there a way I can get 'b'
without using next()
?
Upvotes: 14
Views: 4275
Reputation: 444
Here's a simple wrapper:
from itertools import cycle
from typing import Generic, Iterator, TypeVar
_T = TypeVar("_T")
class Cycler(Generic[_T]):
def __init__(self, cycle: "cycle[_T]") -> None:
self._cycle = cycle
@property
def current(self):
if not hasattr(self, "_current"):
return next(self)
return self._current
def __iter__(self) -> Iterator[_T]:
return self
def __next__(self):
self._current = next(self._cycle)
return self._current
This demo—
letters = ("a", "b", "c")
letters_cycler = Cycler(cycle(letters))
# The cycle can start by attempting to get the current element, or by calling next on it
print("Start cycle")
# print(next(letters_cycler))
print("Check:", letters_cycler.current)
print()
print("Next: ", next(letters_cycler))
print("Check:", letters_cycler.current)
print()
print("Next: ", next(letters_cycler))
print("Check:", letters_cycler.current)
print()
print("Next: ", next(letters_cycler))
print("Check:", letters_cycler.current)
print()
print("Next: ", next(letters_cycler))
print("Check:", letters_cycler.current)
print()
—prints:
Start cycle
Check: a
Next: b
Check: b
Next: c
Check: c
Next: a
Check: a
Next: b
Check: b
This demo—
letters = ("a", "b", "c")
letters_cycler = Cycler(cycle(letters))
stop_iteration_idx = 6
print("In the loop")
for idx, letter in enumerate(letters_cycler):
print(letter)
assert letter == letters_cycler.current
if idx == stop_iteration_idx:
# Break at the end of the loop to stop iterating the cycler
break
print("Outside of the loop:", letters_cycler.current)
assert letters_cycler.current == letter
—prints:
In the loop
a
b
c
a
b
c
a
Out of the loop: a
Upvotes: 0
Reputation: 15561
Note: This only works if there are no repeated elements.
Whether this is a serious practical limitation depends on each one's use.
In my case, most of the itertools.cycle
I have worked with fall under this category.
One can actually get the current state of a cycle
with a helper function, and other info as well.
It actually uses next
, but this is transparent to the caller.
import itertools
def get_cycle_props(cycle) :
"""Get the properties (elements, length, current state) of a cycle, without advancing it"""
# Get the current state
partial = []
n = 0
g = next(cycle)
while ( g not in partial ) :
partial.append(g)
g = next(cycle)
n += 1
# Cycle until the "current" (now previous) state
for i in range(n-1) :
g = next(cycle)
return (partial, n, partial[0])
def get_cycle_list(cycle) :
"""Get the elements of a cycle, without advancing it"""
return get_cycle_props(cycle)[0]
def get_cycle_state(cycle) :
"""Get the current state of a cycle, without advancing it"""
return get_cycle_props(cycle)[2]
def get_cycle_len(cycle) :
"""Get the length of a cycle, without advancing it"""
return get_cycle_props(cycle)[1]
# initialize list
test_list = [3, 4, 5, 7, 1]
c = itertools.cycle(test_list)
print('cycle state =', get_cycle_state(c))
print('cycle length =', get_cycle_len(c))
print('cycle list =', get_cycle_list(c))
next(c)
print('cycle state =', get_cycle_state(c))
print('cycle length =', get_cycle_len(c))
print('cycle list =', get_cycle_list(c))
produces the following output
cycle state = 3
cycle length = 5
cycle list = [3, 4, 5, 7, 1]
cycle state = 4
cycle length = 5
cycle list = [4, 5, 7, 1, 3]
This can actually be taken advantage of to "rewind" a cycle, with the function
def shift_cycle(cycle, npos=0) :
"""Shift a cycle, a given number of positions (can be negative)."""
(cycle_list, nelem, curr_state) = get_cycle_props(cycle)
for i in range(nelem+npos) :
g = next(cycle)
return
Try
shift_cycle(c, -2)
print('cycle state =', get_cycle_state(c))
Upvotes: 2
Reputation: 402553
Iterators/generators don't have any way to get the current value. You should either keep a reference to it or create some wrapper that holds onto it for you.
Upvotes: 5