问题
Suppose I have a randint(A, B)
function which generates a uniformly distributed random integer in the (inclusive) range [A, B]
. For example, 1 <= randint(1, 10) <= 10
. How can I efficiently randomly generate 3 distinct integers within a certain range?
This should be done with exactly 3 calls to randint(A, B)
. I don't care which permutation of the three numbers I get; it can be random or have a defined order.
I'm not just trying to find any algorithm to do this, but (within reason) the fastest ones which use the least additional memory.
def rand3(low, high):
assert high - low + 1 >= 3
# Magic!
return n1, n2, n3 # each number has low <= n <= high, each number is distinct
The easiest way to do this, albeit very expensive, is to generate a list of numbers [low, low + 1, ..., high]
, then do the Fisher-Yates shuffle to select 3 elements. It's not necessary to fully evaluate Fischer-Yates, as we can just roll three indices. However, I definitely do not want to create this array in memory.
For 2 integers, it suffices to roll the second integer in the range less one, adjusting its value upward if it's greater than or equal to the first number:
def rand2(low, high):
i1 = randint(low, high)
i2 = randint(low, high - 1)
n1 = i1
n2 = i2 + (i2 >= n1)
return n1, n2
However, it's not straightforward to extend this to three integers. I believe it should be possible to do something like this:
# I _think_ this works?
def rand3(low, high):
i1 = randint(low, high)
i2 = randint(low, high - 1)
i3 = randint(low, high - 2)
n1 = i1
n2 = i2 + (i2 >= n1)
if n1 > n2: n1, n2 = n2, n1
n3 = i3 + (i3 >= n1)
n3 = n3 + (n3 >= n2)
return n1, n2, n3
However, I feel like there has to be something simpler than this. Is the 3 element problem really that much more complex that we need to sort the 2 integers and that the second conditional (n3 >= n2
) must depend on an intermediate value of n3
? I was expecting something much closer to the 2 element problem.
回答1:
I don't know if these variations make you happier, but this rand3
has one fewer comparison at least.
The idea is to simulate a couple steps of Fisher--Yates. In rand3
, we work backward from the index i3
to find the value. The first if
undoes the second swap, and the second if
undoes the first swap.
import collections
from random import randint
def rand2(low, high):
i1 = randint(low, high)
i2 = randint(low, high - 1)
if i2 == i1:
i2 = high
return i1, i2
def rand3(low, high):
i1 = randint(low, high)
i2 = randint(low, high - 1)
i3 = randint(low, high - 2)
if i3 == i2:
i3 = high - 1
if i3 == i1:
i3 = high
if i2 == i1:
i2 = high
return i1, i2, i3
print(collections.Counter(rand3(1, 4) for i in range(1000000)))
回答2:
The easiest, though probably not most performant way is to just keep rechoosing the random numbers when they don't meet the criteria. Checking for duplicates is trivial, just make a set
and the duplicates will be thrown out.
def rand3(low, high):
assert high - low + 1 >= 3
s = set()
while len(s) != 3:
s = set([randint(low, high), randint(low, high), randint(low, high)])
return tuple(set)
来源:https://stackoverflow.com/questions/64358470/generate-3-distinct-random-integers-via-a-uniformly-distributed-rng