Algorithmatic
Algorithmatic

Reputation: 1892

Getting python's itertools cycle current element

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

Answers (3)

chicxulub
chicxulub

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

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

cs95
cs95

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

Related Questions