12345678910111213141516171819202122232425262728293031323334353637 |
- import random
- class RandomCycler:
- """
- Creates an internal copy of a sequence and allows access to its items in a constrained random
- order. For a source sequence of n items and one or several consecutive queries of a total
- of m items, the following guarantees hold (one implies the other):
- - Each item will be returned between m // n and ((m - 1) // n) + 1 times.
- - Between two appearances of the same item, there may be at most 2 * (n - 1) other items.
- """
-
- def __init__(self, source):
- if len(source) == 0:
- raise Exception("Can't create RandomCycler from an empty collection")
- self.all_items = list(source)
- self.next_items = []
-
- def sample(self, count: int):
- shuffle = lambda l: random.sample(l, len(l))
-
- out = []
- while count > 0:
- if count >= len(self.all_items):
- out.extend(shuffle(list(self.all_items)))
- count -= len(self.all_items)
- continue
- n = min(count, len(self.next_items))
- out.extend(self.next_items[:n])
- count -= n
- self.next_items = self.next_items[n:]
- if len(self.next_items) == 0:
- self.next_items = shuffle(list(self.all_items))
- return out
-
- def __next__(self):
- return self.sample(1)[0]
|