The following method satisfies your requirements. It uses the rejection sampling approach: Generate an integer uniformly at random, and accept it with probability proportional to its weight. If the number isn't accepted, we reject it and try again (see also this answer of mine).
import random
def weighted_random(mn, mx, mnweight, mxweight):
while True:
# Get the highest weight.
highestweight=max(mnweight,mxweight)
# Generate a uniform random integer in the interval [mn, mx].
r=random.randint(mn,mx)
# Calculate the weight for this integer. This ensures the min's
# weight is mnweight and the max's weight is mxweight
weight=mnweight+(mxweight-mnweight)*((i-mn)/(mx-mn))
# Generate a random value between 0 and the highest weight
v=random.random()*highestweight
# Is it less than this weight?
if v<weight:
# Yes, so return it
return r
# No, so try again
(Admittedly due to floating-point division as well as random.random()
, which outputs floating-point numbers, the implementation is not exactly "elegant", but the example below is, once we've written it. The implementation could be improved by the use of Fraction
s in the fractions
module.)
This method can also be implemented using the existing random.choices
method in Python as follows. First we calculate the required weights for random.choices
, then we pass those weights. However, this approach is not exactly efficient if the range between the minimum and maximum is very high.
import random
# Calculate weights for `random.choices`
def make_weights(mn, mx, mnweight, mxweight):
r=(mx-mn)
return [mnweight+(mxweight-mnweight)*(i/r) for i in range(mn, mx+1)]
def weighted_random(mn, mx, mnweight, mxweight):
weights=make_weights(mn, mx, mnweight, mxweight)
return random.choices(range(mn, mx+1), weights=weights)[0]
With the NumPy library this can even be implemented as follows:
import random
import numpy
def weighted_random(mn, mx, mnweight, mxweight):
return random.choices(range(mn, mx+1), \
weights=numpy.linspace(mnweight,mxweight,(mx-mn)+1))[0]
An example of the weighted_random
function follows:
# Generate 100 random integers in the interval [0, 10],
# where 0 is assigned the weight 30 and 10 is assigned the
# weight 10 and numbers in between are assigned
# decreasing weights.
print([weighted_random(0,10,30,10) for i in range(100)])