Source code for sr.comp.knockout_scheduler.stable_random
"""A stable random number generator implementation."""
from __future__ import annotations
import hashlib
from typing import MutableSequence, TypeVar
T = TypeVar('T')
[docs]class Random:
"""
Our own random number generator that is guaranteed to be stable.
Python's random number generator's stability across Python versions is
complicated. Different versions will produce different results. It's easier
right now to just have our own random number generator that's not as good,
but is definitely stable between machines.
.. note::
This class is deliberately not a sub-class of :py:class:`random.Random`
since any of the functionality provided by the class (i.e. not just the
generation portion) could change between Python versions. Instead, any
additionally required functionality should be added below as needed
and _importantly_ tests for the functionality to ensure that the output
is the same on all supported platforms.
"""
def __init__(self) -> None:
self.state = 0
[docs] def seed(self, s: bytes | bytearray | memoryview) -> None:
h = hashlib.md5()
h.update(s)
self.state = int(h.hexdigest(), 16) & 0xffffffff
def _rand_bit(self) -> int:
bit = self.state & 1
nb = 0
for n in (20, 25, 30, 31):
nb ^= (self.state >> n) & 1
self.state <<= 1
self.state |= nb
return bit
[docs] def getrandbits(self, n: int) -> int:
v = 0
for _ in range(n):
v <<= 1
v |= self._rand_bit()
return v
[docs] def random(self) -> float:
return self.getrandbits(32) / float(1 << 32)
[docs] def shuffle(self, x: MutableSequence[T]) -> None:
# Based on python's shuffle function
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = int(self.random() * (i + 1))
x[i], x[j] = x[j], x[i]
def _demo() -> None:
R = Random()
R.seed(b'hello')
for _ in range(10):
print(R.random())
items = list(range(16))
R.shuffle(items)
print(items)
if __name__ == "__main__":
_demo()