I l@ve RuBoard Previous Section Next Section

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.

    I l@ve RuBoard Previous Section Next Section