Created
July 18, 2018 20:33
-
-
Save d-schmidt/d33f454bef3db108f4f9e6ecb4f97a6b to your computer and use it in GitHub Desktop.
A random Python list iterator without shuffle using the linear congruential generator (LCG) algorithm. A pseudorandom number generator producing all numbers < len(list) with a period == len(list) is created.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import warnings | |
def __prime_factors(n): | |
""" | |
https://stackoverflow.com/a/412942/6078370 | |
Returns all the prime factors of a positive integer | |
""" | |
factors = [] | |
d = 2 | |
while n > 1: | |
while n % d == 0: | |
factors.append(d) | |
n //= d | |
d += 1 | |
if d * d > n: | |
if n > 1: factors.append(n) | |
break | |
return factors | |
def __multiply_numbers(numbers): | |
""" multiply all numbers in array """ | |
result = 1 | |
for n in numbers: | |
result *= n | |
return result | |
def __next_good_number(start): | |
""" | |
https://en.wikipedia.org/wiki/Linear_congruential_generator#c%E2%89%A00 | |
some conditions apply for good/easy rotation | |
""" | |
number = start | |
factors = __prime_factors(number) | |
while len(set(factors)) == len(factors) or number % 4 == 0: | |
number += 1 | |
factors = __prime_factors(number) | |
return number, set(factors) | |
# first 46 primes for coprime calculation. | |
# add or use more (than 3) if better randomness is required | |
PRIMES = set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, | |
47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, | |
109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, | |
179, 181, 191, 193, 197, 199]) | |
def create_new_seed(target): | |
""" | |
be aware, m might become > target | |
https://stackoverflow.com/a/12096699/6078370 | |
1. m and the offset c are relatively prime, | |
2. a-1 is divisible by all prime factors of m, | |
3. a-1 is divisible by 4 if m is divisible by 4. | |
""" | |
# does 3. and 2. a < m | |
m, factors = __next_good_number(target) | |
# 2. | |
a = __multiply_numbers(factors) + 1 | |
# 1. https://en.wikipedia.org/wiki/Coprime_integers | |
otherPrimes = [p for p in PRIMES if p not in factors] | |
if not otherPrimes: | |
raise ValueError('all target prime factors in prime set') | |
if len(otherPrimes) < 5: | |
warnings.warn("very few remaining primes, losing randomness") | |
# randomize c to get random results while following 1. | |
random.shuffle(otherPrimes) | |
# I just used arbitary 3 of the other primes (~60000 different periods) | |
c = __multiply_numbers(otherPrimes[:3]) | |
# get random number < target as seed and first state | |
state = random.randint(0, target-1) | |
return state, m, a, c | |
def next_number(state, m, a ,c, limit): | |
""" linear congruential generator (LCG) algorithm """ | |
newState = (a * state + c) % m | |
# skip out of range (__next_good_number increases original target) | |
while newState >= limit: | |
newState = (a * newState + c) % m | |
return newState | |
def riter(alist): | |
""" iterate list using LCG """ | |
target = len(alist) | |
state, m, a, c = create_new_seed(target) | |
for _ in range(target): | |
yield alist[state] | |
state = next_number(state, m, a ,c, target) | |
if __name__ == "__main__": | |
# random iterator (no shuffle) | |
for i in riter(list(range(10))): | |
print(i) | |
# or use as deterministic random number generator 0 <= state < target | |
target = 2000 | |
state, m, a, c = create_new_seed(target) | |
# use hard-coded state (< target) and c=1 to be predictable | |
print('first={} m={} a={} c={} (target={})'.format(state, m, a, c, target)) | |
checkSum = (target-1)*target//2 # sum(range(target)) | |
checkList = [] | |
randomSum = 0 | |
for i in range(target): | |
state = newState = next_number(state, m, a ,c, target) | |
randomSum += newState | |
checkList.append(newState) | |
print(checkSum == randomSum) # true | |
print(len(checkList) == target) # true | |
print(len(set(checkList)) == len(checkList)) # true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment