How to reset and shuffle a "next" iterator?

Question:

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.

Asked By: Penguin

||

Answers:

If you want to reshuffle after reaching the end, I don’t know of a standard tool that will do that. But you can write your own iterable instead.

import random

class Shuffler:

    def __init__(self, sequence):
        self.collection = list(sequence)
        self.reshuffle()

    def reshuffle(self):
        random.shuffle(self.collection)
        self.c_iter = iter(self.collection)

    def __iter__(self):
        return self

    def __next__(self):
        if not self.collection:
            raise StopIteration
        while True:
            try:
                return next(self.c_iter)
            except StopIteration:
                self.reshuffle()

all_angles = list(range(-180,180))
shuffler = Shuffler(all_angles)
next_angle = iter(shuffler)

n_list = []
for i in range(1000):
    n_list.append(next(next_angle))
print(len(n_list)
Answered By: tdelaney

Try this. It re-shuffles the list if it recieves an error while calling next:

import random
all_angles = list(range(-180,180))
random.shuffle(all_angles)
next_angle = iter(all_angles)
n_list = []
for i in range(1000):
    try:
        n_list.append(next(next_angle))
    except StopIteration:
        random.shuffle(all_angles)
        next_angle = iter(all_angles)
        n_list.append(next(next_angle))
print(len(n_list)) # 1000
Answered By: The Thonnu

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
Answered By: Kelly Bundy
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.