3
\$\begingroup\$

I'm trying to build a lottery-like game in Python 3.6 where a function selects a winner from a participant list where every participant has a winning chance weighted by the value of the tickets they bought. I'm using the SystemRandom class in order to avoid software-state-dependency. Is this a good practice regarding good enough randomness? How could this code be improved? Also, does the shuffling does add anything to the randomness?

import random

# Randomly selects an element from a choice list using weighted probabilities
def randomWeightedSelection(choices, weights):

    # Build index list
    indices = list(range(len(choices)))

    # Shuffle indices
    random.shuffle(indices)

    # Shuffle weights accordingly
    shuffled_weights = [weights[i] for i in indices]

    # Create random generator
    rng = random.SystemRandom()

    # Select element
    selected_index = rng.choices(indices, weights=shuffled_weights, k=1)[0]

    return [choices[selected_index], selected_index]
\$\endgroup\$
3
\$\begingroup\$

According to the Python documentation random.SystemRandom() uses os.urandom() which

[...] returns random bytes from an OS-specific randomness source. The returned data should be unpredictable enough for cryptographic applications, though its exact quality depends on the OS implementation.

Given that your system does a good enough job, the output of random.SystemRandom() should be indistinguishable from true randomness, and thus be good enough for your purpose.

Also, if the randomness provided by your system is indistinguishable from true randomness, shuffling does not add anything to the randomness of your output (even if you had also used a cryprographically secure RNG for shuffling).

In this case your code could be reduced to

import random

def randomWeightedSelection(choices, weights):
    rng = random.SystemRandom()

    indices = list(range(len(choices)))

    selected_index = rng.choices(indices, weights = weights, k = 1)[0]

    return [choices[selected_index], selected_index]
\$\endgroup\$
  • 1
    \$\begingroup\$ You can go as far as using list(enumerate(choices)) for the population to get the value and the index at once: (index, value), = rng.choices(list(enumerate(choices)), weights=weights, k=1); return [value, index]. \$\endgroup\$ – Mathias Ettinger Oct 3 '18 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.