17.14 Rolling Dice
Credit: Tim Keating
17.14.1 Problem
You need to generate pseudo-random numbers
simulating the roll of several dice, in which the number of dice and
number of sides on each die are parameters.
17.14.2 Solution
An implicit loop performed by the reduce built-in
function turns out to be the fastest solution, although this is not
immediately obvious:
import random
def dice(num, sides):
return reduce(lambda x, y, s=sides: x + random.randrange(s),
range(num+1)) + num
If you prefer to avoid lambda in favor of a named
nested function, here is an equivalent but somewhat more readable
alternative:
def dice(num, sides):
def accumulate(x, y, s=sides): return x + random.randrange(s)
return reduce(accumulate, range(num+1)) + num
17.14.3 Discussion
This recipe presents a simple but subtle
function that permits you to generate random numbers by emulating a
dice roll. The number of dice and the number of sides on each die are
the parameters of the function. For example, to roll four six-sided
dice, you would call dice(4, 6). Simulating a dice
roll is a good way to generate a random number with an expected
binomial profile. For example, rolling three six-sided dice will
generate a bell-shaped (but discrete) probability curve with an
average of 10.5.
After trying a more manual approach (a for loop
with an accumulator), I found that using
reduce is generally faster.
It's possible that this implementation could be
faster still, as I haven't profiled it very
aggressively. But it's fast enough for my purposes.
This recipe's use of reduce is
peculiar, since the function used for the reduction actually ignores
its second argument, y, which comes from the
range(num+1) sequence that is being reduced. The
only purpose of reduce here is to call the
accumulate function (or its
lambda equivalent) num times
(the first time with an x of 0,
since that's the first item in the
range, then every other time with the previous
result as argument x). Each time, the
accumulate
function adds a new random integer in the range from
0 included to sides excluded,
which is returned from the
randrange
function of the random standard module. In the
end, we just need to add num because each of the
num random numbers was in the range
0 to sides-1 rather than from
1 to sides.
This peculiar way to use reduce does, according to
measurement, appear to be marginally faster than, or at the very
least equal to, some clearer and more obvious alternatives, such as:
def dice(num, sides):
return reduce(operator.add,
[random.randrange(sides) for i in range(num)]) + num
and:
def dice(num, sides):
return reduce(operator.add, map(random.randrange, num*[sides])) + num
17.14.4 See Also
Documentation for the random standard library
module the Library Reference.
|