Search code examples
pythoniterator

How to reset and shuffle a "next" iterator?


I have a function that generates numbers from -180 to 180:

all_angles = list(range(-180,180))
random.shuffle(all_angles)
next_angle = iter(all_angles)

The issue is that it stops generating after 360 (which makes sense since it's from -180 to 180):

n_list = []
for i in range(1000):
    n_list.append(next(next_angle))
print(len(n_list))
>>> 360 # currently it only prints 360

How can I reset it and shuffle it once it goes over all of the 360 values? So the above for loop will generate 360 shuffled numbers, reset after it goes over all the 360 values (each appearing only once), and generate another set of 360 values, and so on.


Solution

  • Generator endlessly shuffling and yielding:

    def endless_shuffling(iterable):
        values = list(iterable)
        while True:
            random.shuffle(values)
            yield from values
    

    Instead of your iter(all_angles), use endless_shuffling(all_angles) (and remove your own other shuffling).

    One way to then get your list:

    random_angles = endless_shuffling(range(-180, 180))
    n_list = list(islice(random_angles, 1000))
    

    If you give it an empty iterable and ask it for a value, it'll "hang", so either don't do that or guard against that case (e.g., with an extra if values: or with while values:).

    I also tried a faster way to iterate than sending every value through a generator, but the shuffling dominates so it doesn't make a big difference:

    with shuffling:
    448.3 ms  endless_shuffling1
    426.7 ms  endless_shuffling2
    
    without shuffling:
     26.4 ms  endless_shuffling1
      5.1 ms  endless_shuffling2
    

    Full code (Try it online!):

    from random import shuffle
    from itertools import chain, islice
    from timeit import default_timer as time
    
    def endless_shuffling1(iterable):
        values = list(iterable)
        while True:
            shuffle(values)
            yield from values
    
    def endless_shuffling2(iterable):
        values = list(iterable)
        return chain.from_iterable(iter(
            lambda: shuffle(values) or values,
            []
        ))
    
    funcs = endless_shuffling1, endless_shuffling2
    
    for f in funcs:
        print(*islice(f('abc'), 21))
    
    for i in range(6):
        for f in funcs:
            t0 = time()
            next(islice(f(range(-180,180)), 999999, 1000000))
            print('%5.1f ms ' % ((time() - t0) * 1e3), f.__name__)
        print()
        if i == 2:
            print('without shuffling:\n')
            def shuffle(x):
                pass