4

I need to loop trough a list and come back to first element when last item is reached.

The cycle object from itertools is designed for this.

myList = [1,2,3,4,5,6,7,8,9]

i = 0
for item in cycle(myList):
    index = i%9
    print (index)
    i += 1

Is there any other way than using a i variable?

Delgan
  • 18,571
  • 11
  • 90
  • 141
  • Your index doesn't seem to stay in sync; you have a 9-element cycle but are resetting mod 10. – DSM Oct 23 '14 at 15:08

2 Answers2

5

You can use enumerate:

for i, item in enumerate(cycle(myList)):

Below is a demonstration:

>>> from itertools import cycle
>>> for i, item in enumerate(cycle([1, 2, 3])):
...     print i, item
...
0 1
1 2
2 3
3 1
4 2
5 3

You can even give a specific number to start at:

for i, item in enumerate(cycle([1, 2, 3]), 10): # Start at 10

Reading @DSM's comment, I realize that you may want to flip the calls to cycle and enumerate:

>>> for i, item in cycle(enumerate([1, 2, 3])):
...     print i, item
...
0 1
1 2
2 3
0 1
1 2
2 3

This will cause i to refer to the index of item in the list rather than act as a counter variable.

  • Because of the `% 10` I wonder if the OP is really looking for something more like `cycle(enumerate([1,2,3]))` (with or without a start of 1), so that the same value of the list always corresponded to the same index. Hard to be sure, though. – DSM Oct 23 '14 at 15:10
0

As an extension to this, I was looking for a way to index into a cycle object.

For this, you can write a custom class with a __getitem__ method as a replacement for cycle.

from itertools import cycle, takewhile, dropwhile


class CyclicalList:
    def __init__(self, initial_list):
        self._initial_list = initial_list

    def __getitem__(self, item):
        if isinstance(item, slice):
            if item.stop is None:
                raise ValueError("Cannot slice without stop")
            iterable = enumerate(cycle(self._initial_list))
            if item.start:
                iterable = dropwhile(lambda x: x[0] < item.start, iterable)
            return [
                element
                for _, element in takewhile(lambda x: x[0] < item.stop, iterable)
            ]

        for index, element in enumerate(cycle(self._initial_list)):
            if index == item:
                return element

    def __iter__(self):
        return cycle(self._initial_list)

Which allows you to use regular index/slice notation.

myList = CyclicalList([1,2,3,4,5,6,7,8,9])
myList[10]  # 2
myList[10:20]  # [2, 3, 4, 5, 6, 7, 8, 9, 1, 2]

You might also want to implement other list methods and optimize, depending on your needs. But this addresses the main idea of indexing into a cycled object.

sytech
  • 29,298
  • 3
  • 45
  • 86