-1

Given a list of n elements where n>1, I want to be able to iterate over the list and be able to repeat infinitely. If I reach the end of the list, I want to restart it. The catch here is that speed is what matters the most, so I don't want to create a method or use try catch statements. I currently do keep a current_player like this:

class Player(): 
 pass #whatever the player is

#Initialisaiton
players = [Player(), Player(), Player()]
current_player_index = 0
current_player = players[current_p´layer_index]

#Game loop:
turn_count = 0
while(turn_count < 100): #any number of max turns
  turn_count += 1
  current_player_index += 1
  if current_player_index == len(players): 
    current_player_index = 0
  current_player = players[current_player_index]

I'm just asking about a fast way to iterate an iterable in an infinite cycle.

Another example: The iterable can be food=["h","a","m"]. I want to know if there is a way to call next(food), lets say 10 times, and get "hamhamhamh" if all the outputs were concatenated

Thank you! The accepted answer looks like this:

import itertools

class Player(): 
     pass #whatever the player is

    #Initialisaiton
    players = [Player(), Player(), Player()]
    players_iterable = itertool.cycle(players)
    current_player = next(players_iterable)

    #Game loop:
    turn_count = 0
    while(turn_count < 100): #any number of max turns
      turn_count += 1
      current_player = next(players_iterable)
33fred33
  • 57
  • 7
  • iterate using modulo index? `index = (index + 1) % length_of_list` – dankal444 Apr 02 '23 at 12:29
  • 1
    Does `itertools.cycle` help you: https://docs.python.org/3/library/itertools.html#itertools.cycle? – ndc85430 Apr 02 '23 at 12:30
  • Modulus are generally quite slow, but generators and conditionals are slow too in CPython. All of them should take about few dozens of nanoseconds. Unless you use PyPy (or any similar JIT), I think the simpler the better. [Is premature optimization really the root of all evil?](https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil) – Jérôme Richard Apr 02 '23 at 12:58
  • Nest the game loop in a `while (True):`? – ncke Apr 02 '23 at 12:59
  • 1
    I would just use `for current_player in itertools.cycle(players):` until that *proves* to be too slow. – chepner Apr 02 '23 at 13:15
  • So that game allows exactly 100 turns? Or in reality something else? You're not even incrementing `turn_count`... – Kelly Bundy Apr 02 '23 at 13:53
  • @KellyBundy my bad, added the turn count. The number of iterations doesnt matter, let's assume it is gonna be a very very large number and that's why I care about performance. – 33fred33 Apr 06 '23 at 12:35
  • @ncke I'm not asking about a game structure, that was an example. I want to iterate the iterable in an infinite cycle. – 33fred33 Apr 06 '23 at 12:36
  • @dankal444 that's interesting. But actually the point was to avoid overhead regarding the indexes. next() is ideal from that perspective, but I fail to restart the iterator when it is exhausted. – 33fred33 Apr 06 '23 at 12:38
  • @33fred33 If you want to avoid overhead then why is `next` ideal? It doesn't do what you want, and it is still managing an index (you just can't see it because it's in someone else's code). It will always be faster to manage the `turn_count` and a `player_index` in the body of the loop. If you want to encapsulate the idea then it could be written as a generator function. – ncke Apr 08 '23 at 08:13
  • Do you need `turn_count` for anything else? Or just for terminating the loop? – Kelly Bundy Apr 08 '23 at 08:25
  • Updated the question to reflect what I was doing, instead of guessing how the answer should look like. – 33fred33 Apr 12 '23 at 14:27

1 Answers1

1

Generator function providing a cycle. It would, however, be faster to manage the indexes in the body of the loop itself. It's faster to zip the itertools.cycle.

def ncycle(n, xs):
    len_xs = len(xs)
    if len_xs == 0:
        raise Exception("Cannot cycle through an empty list!")
    
    i = 0
    j = 0
    while i < n:
        yield i, xs[j]
        i += 1
        j += 1
        if j >= len_xs:
            j = 0
for turn_count, current_player in ncycle(20, ['h', 'a', 'm']):
    print(turn_count, current_player)

0 h
1 a
2 m
3 h
4 a
5 m
6 h
and so on

With credit to @Kelly Bundy, it's faster to iterate through a zip. About twice as fast for large numbers of turns. Performance for enumerating an isliced cycle was just very marginally slower than the zip.

import itertools as itools

for turn_count, current_player in zip(range(20), itools.cycle(['h', 'a', 'm'])):
    # do something
ncke
  • 1,084
  • 9
  • 14
  • Better zip a range and a cycle. – Kelly Bundy Apr 08 '23 at 08:26
  • Or enumerate an isliced cycle. – Kelly Bundy Apr 08 '23 at 08:29
  • I'm just trying to avoid 'overhead' :-) – ncke Apr 08 '23 at 08:30
  • Which those would do. – Kelly Bundy Apr 08 '23 at 08:33
  • Every day is a school day! `for tc, cp in zip(range(100000), itertools.cycle([1,2,3])):` is about twice as fast as the generator function. – ncke Apr 08 '23 at 08:59
  • Ok :-). In [my own test](https://dpaste.org/qVS2j), they were even 3-4 times faster than even the non-generator version of yours. About 5 μs vs 18 μs for 100 turns over three values. – Kelly Bundy Apr 08 '23 at 11:00
  • I get roughly the same result with your timing code (which I think I will keep around for another time, thanks). – ncke Apr 08 '23 at 19:09
  • Thank you, this lead to the right answer. In fact, what was needed was to put the players in a list, and then create a buffer: "players_buffer=itertools.cycle(list_of_players)". Then, at the end of each turn I just update the current player with "current_player = next(players_buffer)". From this post: https://stackoverflow.com/questions/5237611/itertools-cycle-next. I used to have indices for each player and just increase the "current_player_index". Then, if the index was larger than the list of players, restart as 0. But that was taking extra time. – 33fred33 Apr 12 '23 at 14:19
  • @33fred33 Why don't you use a `for`-loop? It's faster. Both because of the explicit `next` call and because you need to additionally increment your `turn_count` yourself and check it against the limit yourself. – Kelly Bundy Apr 12 '23 at 15:24
  • @33fred33 About 2.5 times faster than your new way imy [test](https://dpaste.org/XoOG2) (about 4 μs versus 10 μs). Depending on your actual number of players, there might btw even be something even faster... – Kelly Bundy Apr 12 '23 at 15:46
  • Thank you @KellyBundy, it seems to be in fact faster, but I don't know in advance the number of turns the game will take, so I cannot iterate though the cycle and the main loop at the same time. I have a function "make_the_move()" that contains what is inside of the loop in my solution. I need the cycle to substitute the line "current_player = next(players_iterable)" in my solution. – 33fred33 Apr 12 '23 at 20:51