4

Why is it this sometimes returns 2?

function pickServer(){
    $varr = rand(1,4);
    if($varr==2){
        pickServer();
    }
    return $varr;
}
Nosredna
  • 83,000
  • 15
  • 95
  • 122
Abs
  • 56,052
  • 101
  • 275
  • 409
  • Thanks everyone, all correct, but I had to pick one only. :) – Abs Aug 19 '09 at 15:42
  • 15
    Actually most of them are missing the real problem (including the accepted answer). Don't use recursion as a Goto. Put a loop around the $var=rand(1,4) call until it returns a value !=2, don't call pickServer again inside this method. Think of the children! Think of your Stack Frame! If you run this code and hit a streak of 2's you could theoretically create StackOverflow conditions and I ain't talking about a Q&A Wiki site. – JohnFx Aug 19 '09 at 15:51
  • 5
    Don't loop at all. Your random generation range is one too large--you don't see it because the number you want to exclude is inside the range. If it were at the beginning or end you wouldn't loop, so why loop just because it's in the middle? Remap. – Nosredna Aug 19 '09 at 15:55
  • +! JohnFX - Thank you. That's what I was just writing up. Many of these solutions may technically work but have the bad side effect of being both indeterminate *and* recursive (including the accepted answer). That is a dangerous combination that will turn into one of those illusive bugs that will crash your system randomly... once in awhile... for no apparent reason. – Robert Cartaino Aug 19 '09 at 15:58
  • @Robert C. Cartaino, even if it's not recursive, there's still no reason for it to be indeterminate. – Nosredna Aug 19 '09 at 16:00
  • 1
    @Nosredna - I agree. The most *technically* correct answer would be to remap your random space to one that does not include "2". Then your method would execute in O(1) time. Looping inside the method is a quick, pragmatic approach which would solve the basic problem but isn't the best solution from an accademic standpoint. But I *do* agree with you that the method *shouldn't* be indeterminate and ***definitely*** not recursive. – Robert Cartaino Aug 19 '09 at 16:12
  • @Nosredna - By the way, your answer: http://stackoverflow.com/questions/1300797/return-random-number-but-not-2/1300895#1300895. Is the only completely correct answer and should have been "chosen." – Robert Cartaino Aug 19 '09 at 16:15
  • @Robert. Thanks. I'm not sure why people pick the "correct" answer so quickly. :-) This one went in 4 minutes! Granted, the asker got some information quickly, and might have been all he wanted (clear up the confusion of how the "2" got through), but having the chosen answer keep the recursive theme is just...wild. – Nosredna Aug 19 '09 at 16:20
  • @Robert C. Cartaino: While Nosredna's answer explains the whole thing better than mine, I'd like to know what in my answer you find to be incorrect. – balpha Aug 19 '09 at 16:37
  • @Balpha Your answer is not bad. I upvoted you. – Nosredna Aug 19 '09 at 16:39
  • @Nosredna: That wasn't my intention, but thanks anyway :-) – balpha Aug 19 '09 at 16:44
  • yeah, I was going to add a note about how this isn't the best way to do what he wanted, but it's just picking a number between 1 and 4... the performance hit isn't going to be worth the time spent explaining and rewriting the code. – nickf Aug 19 '09 at 23:32
  • @balpha - Your first answer (with the recursion) is dangerous (even though you point out that there is no guarantee) so I didn't read carefully the rest of the post. Your second example works but I had to scratch my head for a second to figure out why. @Nosredna's answer was more direct (just my opinion). Like @Abs said; you can only pick one answer. But it's all good. You got my +1. – Robert Cartaino Aug 20 '09 at 15:37

10 Answers10

27

The answer to your question, as others have pointed out, is that your code falls through without returning. If 2 is returned by the call to rand() on both the first attempt and the second attempt (there's a 1/16 chance of this happening), you'll get 2 as a result.

But your approach to solving the problem could be better.

Both recursion and looping are barmy for this problem. This is a mapping problem, not a randomness problem. (It resembles some common randomness coding interview problems which can be handled most easily in a rejection loop, but it really isn't a problem of that class.)

You want one of three outcomes, not four. (1, 3, and 4.) That means you should be generating a range of three random numbers, not four. You could remap with an array or use an if. Both possibilities are shown below. Let me know if I have syntax wrong--my PHPfu is weak this morning.

/* array remapping */
function pickServer() {
    $remap = array(1, 3, 4);
    return $remap[rand(1,3)];
}

/* if remapping */
function pickServer() {
    $server = rand(1,3);
    if ($server==2) {
        $server=4;
    }
    return $server;
}

I didn't notice it before, but balpha anticipated my answer. He remapped with an if in his second example. Instead of remapping 2 to 4, he just added one to any answer above 1, which is an equivalent solution.

Nosredna
  • 83,000
  • 15
  • 95
  • 122
  • 1
    The syntax looks correct to me. Well done. *This* should have been the selected answer. – Robert Cartaino Aug 19 '09 at 16:26
  • 2
    A true random RNG could return 2 forever. At least this will not get stuck in a loop. – OIS Aug 19 '09 at 16:27
  • 2
    I think you'll find that a true RNG always returns 4: http://imgs.xkcd.com/comics/random_number.png – nickf Aug 30 '09 at 13:42
  • +1. The original function and accepted answer are both criminally inefficient. No matter how many "holes" are in your range, you can always generalize it as a mapping problem. – Aaronaught Dec 22 '09 at 16:23
19

Because you're not stopping the function there. the fourth line should read:

return pickServer();
nickf
  • 537,072
  • 198
  • 649
  • 721
13

Another way to do it is using do … while:

function pickServer() {
    do {
        $varr = rand(1,4);
    } while ($varr == 2);
    return $varr;
}
Gumbo
  • 643,351
  • 109
  • 780
  • 844
10

What you probably want is

function pickServer(){
  $varr = rand(1,4);
  if($varr==2){
    $varr = pickServer();
  }
  return $varr;
}

-- but note that there's no guarantee that this doesn't go into a too long recursion. Maybe you should rather do something like this:

function pickServer(){
  $varr = rand(1,3);
  if($varr > 1){
    $varr = $varr + 1;
  }
  return $varr;
}
balpha
  • 50,022
  • 18
  • 110
  • 131
  • 1
    Works as well as the one by Nosredna, but had to think a sec. – OIS Aug 19 '09 at 16:42
  • Your second piece of code deals with the issue well. My concern with it is only that it's a bit tricky to decipher. In fact, I missed it when I was reading the answers the first time through precisely because it's non-obvious. I apologize for missing it. Had I noticed it the fist time, I would have credited you in my answer. – Nosredna Aug 19 '09 at 16:43
  • Update: I gave you props in my answer. – Nosredna Aug 19 '09 at 17:24
  • Can't remember enough PHP, but out of curiosity, could you do $varr+=$var>1; in PHP? (I know it's terrible-looking.) – Nosredna Aug 21 '09 at 14:20
2

Because when the value is 2 you don't return pickserver. And the function continues to return $varr.

MrHus
  • 32,888
  • 6
  • 31
  • 31
2
function pickServer(){
    $varr = rand(1,4);
    if($varr==2){
        return pickServer(); //leave here
    }
    return $varr;
}
Kai
  • 5,850
  • 13
  • 43
  • 63
1

I would so something like this:

function pickServer()
{
$servers = array(1,3,4);
return $servers[rand(1,count($servers))]; 
}
JD Isaacks
  • 56,088
  • 93
  • 276
  • 422
0

It calls the function recursively to get another number that isn't 2, but does nothing with that result.

Try changing pickServer() to return pickServer().

Better still, write the function iteratively so that it just loops until the value returned isn't 2.

Kylotan
  • 18,290
  • 7
  • 46
  • 74
0

You forgot to return the value...

function pickServer(){
$varr = rand(1,4);
if($varr==2){
    return pickServer();
}
return $varr;
}
Rik Heywood
  • 13,816
  • 9
  • 61
  • 81
0

You can remove the recursion and remap a randomly selected 2. Simply decrease the range and map the beginning of the range (in this case 2) to 1.

function pickServer(){
    $varr = rand(2,4);
    if($varr==2){
        return 1;
    }
    return $varr;
}
pdavis
  • 3,212
  • 2
  • 29
  • 31