views:

71

answers:

4

I'm trying to figure out a way to create random numbers that "feel" random over short sequences. This is for a quiz game, where there are four possible choices, and the software needs to pick one of the four spots in which to put the correct answer before filling in the other three with distractors.

Obviously, arc4random % 4 will create more than sufficiently random results over a long sequence, but in a short sequence its entirely possible (and a frequent occurrence!) to have five or six of the same number come back in a row. This is what I'm aiming to avoid.

I also don't want to simply say "never pick the same square twice," because that results in only three possible answers for every question but the first. Currently I'm doing something like this:

bool acceptable = NO;
do {
  currentAnswer = arc4random() % 4;
  if (currentAnswer == lastAnswer) {
    if (arc4random() % 4 == 0) {
      acceptable = YES;
    }
  } else {
    acceptable = YES;
  }
} while (!acceptable);

Is there a better solution to this that I'm overlooking?

A: 

To reduce the probability for a repeated number by 25%, you can pick a random number between 0 and 3.75, and then rotate it so that the 0.75 ends up at the previous answer.

To avoid using floating point values, you can multiply the factors by four:

Pseudo code (where / is an integer division):

currentAnswer = ((random(0..14) + lastAnswer * 4) % 16) / 4
Guffa
A: 

Set up a weighted array. Lets say the last value was a 2. Make an array like this:

array = [0,0,0,0,1,1,1,1,2,3,3,3,3];

Then pick a number in the array.

newValue = array[arc4random() % 13];

Now switch to using math instead of an array.

newValue = ( ( ( arc4random() % 13 ) / 4 ) + 1 + oldValue ) % 4;

For P possibilities and a weight 0<W<=1 use:

newValue = ( ( ( arc4random() % (P/W-P(1-W)) ) * W ) + 1 + oldValue ) % P;

For P=4 and W=1/4, (P/W-P(1-W)) = 13. This says the last value will be 1/4 as likely as other values.

If you completely eliminate the most recent answer it will be just as noticeable as the most recent answer showing up too often. I do not know what weight will feel right to you, but 1/4 is a good starting point.

drawnonward
+3  A: 

You populate an array of outcomes, then shuffle it, then assign them in that order.

So for just 8 questions:

answer_slots = [0,0,1,1,2,2,3,3]

shuffle(answer_slots)

print answer_slots
 [1,3,2,1,0,2,3,0]
Will
That's just as likely to create random clusters as not.
msw
@msw: They will only be as long as you allow them to be, though. If you're shuffling `0..3*3` there only can be up to three occurrences of the same number, for example.
Joey
aye, if the set is only of length 8, 87% of the possible sets have at least one adjacency. In which case you couldn't do better than this.
msw
+2  A: 

If your question was how to compute currentAnswer using your example's probabilities non-iteratively, Guffa has your answer.

If the question is how to avoid random-clustering without violating equiprobability and you know the upper bound of the length of the list, then consider the following algorithm which is kind of like un-sorting:

from random import randrange
# randrange(a, b) yields a <= N < b

def decluster():
    for i in range(seq_len):
        j = (i + 1) % seq_len
        if seq[i] == seq[j]:
            i_swap = randrange(i, seq_len) # is best lower bound 0, i, j?
            if seq[j] != seq[i_swap]:
                print 'swap', j, i_swap, (seq[j], seq[i_swap])
                seq[j], seq[i_swap] = seq[i_swap], seq[j]

seq_len = 20
seq = [randrange(1, 5) for _ in range(seq_len)]; print seq
decluster(); print seq
decluster(); print seq

where any relation to actual working Python code is purely coincidental. I'm pretty sure the prior-probabilities are maintained, and it does seem break clusters (and occasionally adds some). But I'm pretty sleepy so this is for amusement purposes only.

msw
Awesome. I ended up implementing something like this. The game is open-ended, so I'm just generating 100 values in a row, and then generating 100 more when those run out. It certainly "feels" more random than my approach.
John Biesnecker
Thanks for the interesting problem. After a day's pondering, I'm pretty sure the Vegas gaming commission would approve this algorithm as "uniform" (assuming a decent PRNG, which arc4 is).
msw
msw: I doubt they'd allow the use of a PRNG, cryptographically secure or not :-). I might try creating a decorator for our RNGs and run this one through our tests. But I think if you're avoiding clustering you're also throwing away a whole bunch of possible outcomes. Equiprobability isn't only for the Monobit test but you can also look at larger segments. That being said, for this question it's certainly correct – in general usually a bad idea ;-)
Joey
@Johannes: I think I understand your comment, but it is not obvious that the `decluster` algorithm removes any sequences from the outcome space. I know I'm going to burn a few peta-cycles finding out. If the lower bound of `i_swap` is zero, I think I can prove it, if it is `i` I'm not so but will be buy tomorrow.
msw