[Tutor] A question on randomness

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Thu, 11 Jul 2002 14:19:50 -0700 (PDT)


On Thu, 11 Jul 2002, Andrea Valle wrote:

> It' a couple of year I work with random generators.
>
> - Something like this:
> rand_param=whrandom.randint((-(matr_param[param])), matr_param[param])

Hi Andrea,

(Side note: you should probably use the 'random' module directly if you're
using more recent versions of Python; I think that 'whrandom' was
deprecated a while back in favor of 'random'.)



> But it seems to me that the code works as it often produces looping
> patterns (at least, they seem so to me): if the range is 0, 5, something
> like 12312354542222 etc.

Hmmm... let's try it!

###
>>> [random.randrange(5) for i in range(10)]
[2, 3, 2, 1, 1, 2, 2, 4, 4, 4]
>>> [random.randrange(5) for i in range(10)]
[2, 1, 2, 2, 2, 3, 3, 1, 1, 2]
###

Yes, "streaks" are very possible, but that's because it's very possible to
randomly generate streaks.  Think "Rosencranz and Guildenstern are Dead".


Eliminating streaks actually reduces the randomness of our sequence, since
now we've guaranteed that long streaks can never happen.  Donald Knuth's
"The Art of Computer Programming, Volume 2", has an comprehensive
discussion of random numbers, so you might be interested in it if you want
more information on how random number generators work.




> I know pseudo-random generation is a complex matter. Is there a way to
> "improve" it? (I mean, to obtain a "good sequence" like 1432543122140133
> etc.)

Let's define a "non-streaky" random sequence as one without streaks longer
than length two.  Then we can make a generator of random numbers that just
remembers the last two entries.



Here's one way to do it, using Python 2.2's generators and iterators:

###
from __future__ import generators


def makeNonStreakySequence(seq):
    seq = iter(seq)
    x, y = seq.next(), seq.next()
    while 1:
        z = getNonStreakyValue(seq, x, y)
        yield x
        x, y = y, z


def getNonStreakyValue(seq, x, y):
    while 1:
        z = seq.next()
        if not (x == y == z): break
    return z
###


Once we have something like this, then we can say:

###
>>> def makeRandomNumberGenerator():
...     while 1:
...         yield random.randrange(5)
...
>>> random_numbers = makeRandomNumberGenerator()
>>> [random_numbers.next() for i in range(20)]
[1, 3, 2, 4, 2, 2, 4, 1, 2, 4, 2, 2, 1, 3, 3, 1, 2, 3, 3, 3]
>>> non_streaky = makeNonStreakySequence(random_numbers)
>>> [non_streaky.next() for i in range(100)]
[4, 4, 2, 1, 3, 2, 0, 2, 2, 4, 2, 1, 3, 4, 3, 2, 0, 4, 0, 3, 4, 4, 2, 3,
 3, 1, 1, 0, 3, 4, 3, 3, 4, 2, 3, 3, 0, 1, 1, 0, 1, 4, 4, 1, 2, 3, 4, 0,
 3, 0, 2, 0, 4, 2, 2, 4, 0, 1, 0, 3, 1, 1, 3, 4, 4, 0, 0, 4, 3, 1, 4, 1, 3, 2,
 0, 2, 3, 2, 3, 0, 3, 3, 4, 0, 4, 0, 2, 2, 0, 3, 0, 1, 2, 1, 1, 2, 4, 0,
 2, 3]
>>>
>>>
>>> def makeRandomBinarySequence():
...     while 1:
...         yield random.randrange(2)
...
>>> binary_seq = makeRandomBinarySequence()
>>> [binary_seq.next() for i in range(20)]
[0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0]
>>> nonstreaky_binary_seq = makeNonStreakySequence(binary_seq)
>>> [nonstreaky_binary_seq.next() for i in range(20)]
[0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1]
###


The generators aren't necessary here; we can write this without generators
if you'd like.  I'm just addicted to them because of their novelty...
*grin*

But I hope the idea makes sense: we just keep asking for random numbers,
but we keep track of the last two that the user has seen already, to
prevent streaks from occuring.



Hope this helps!