views:

200

answers:

4

I am building a list of "agent id's" in my database with the following requirements:

  1. The ID must be 9 digits long (numeric only)
  2. The ID may not contain more than 3 of the same number.
  3. The ID may not contain more than 2 of the same number consecutively (i.e. 887766551; cannot have 888..)

So far I have part 1 down solid but am struggling with 2 and 3 above. My code is below.

function createRandomAGTNO() {
    srand ((double) microtime( )*1000000);
    $random_agtno = rand(100000000,900000000);
    return $random_agtno;
}

// Usage
$NEWAGTNO = createRandomAGTNO();

Any ideas?

+1  A: 

For second condition, you can create an array like this

$a = array( 0,0,1,1,2,2,3,3.....,9,9 );

and get random elements: array_rand() (see manual) to get digit, append it to your ID and remove value from source array by unsetting at index.

Generally, this solving also third condition, but this solution excludes all ID's with possible and acceptable three digits

killer_PL
[`shuffle`](http://us2.php.net/manual/en/function.shuffle.php) and [`array_shift`](http://us2.php.net/manual/en/function.array-shift.php) / [`array_pop`](http://us2.php.net/manual/en/function.array-pop.php) are a viable alternative to `array_rand` and unsetting indexes.
Charles
+7  A: 
  1. Do not re-seed the RNG on every call like that, unless you want to completely blow the security of your random numbers.
  2. Unless your PHP is very old, you probably don't need to re-seed the RNG at all, as PHP seeds it for you on startup and there are very few cases where you need to replace the seed with one of your own choosing.
  3. If it's available to you, use mt_rand instead of rand. My example will use mt_rand.

As for the rest -- you could possibly come up with a very clever mapping of numbers from a linear range onto numbers of the form you want, but let's brute-force it instead. This is one of those things where yes, the theoretical upper bound on running time is infinite, but the expected running time is bounded and quite small, so don't worry too hard.

function createRandomAGTNO() {
  do {
    $agt_no = mt_rand(100000000,900000000);
    $valid = true;
    if (preg_match('/(\d)\1\1/', $agt_no))
      $valid = false; // Same digit three times consecutively
    elseif (preg_match('/(\d).*?\1.*?\1.*?\1/', $agt_no))
      $valid = false; // Same digit four times in string
  } while ($valid === false);
  return $agt_no;
}
hobbs
My gut instinct says eiww due to the regex in the loop. But then again, they are both light regexes, and you save multiple `rand` calls (even the `array_rand` and `shuffle` functions call it multiple times internally) for each "try"... So it doesn't appear to be nearly as bad as instinct implies (In fact, it looks to be quite good)... +1...
ircmaxell
I'd say doing it mathematically would be the fastest. Regex can't beat it.
Poni
Try it. The regex solution posted above averages to 0.00006 seconds per run, and yours averages to 0.00017 seconds (I'm getting about 0.00015 for mine)... Oh, the average is over a total of 100000 runs each. So in terms of shear speed, it looks like regex takes the cake...
ircmaxell
@ircmaxell it works because nine times out of ten, my whole loop only runs *once* :)
hobbs
@hobbs: quite true. But the difference between 100 light loops and one heavy one is still not insignificant. So I think it boils down to the regex's being lighter than the `rand` call. If it was the other way around, another method would be "faster". But at these speeds, who really cares (unless you're generating billions of them)...
ircmaxell
@hobbs - your code returns the following error: Warning: preg_match() [function.preg-match]: No ending delimiter '/' found in C:\xampp\htdocs\dd.php on line 6
JM4
@JM4 simply change the two regex's to: `'/(\d)\1\1/'` and `'/(\d).*?\1.*?\1.*?\1/'`...
ircmaxell
@JM4 I corrected a typo and made another small improvement.
hobbs
@hobbs - thanks, works great. Thanks for help as well ircmaxell
JM4
A: 

The first solution that comes to mind is a recursive function that simply tests your three requirements and restarts if any three of them fail. Not the most efficient solution but it would work. I wrote an untested version of this below. May not run without errors but you should get the basic idea from it.

function createRandomAGTNO(){
  srand ((double) microtime( )*1000000);
  $random_agtno = rand(100000000,900000000);

  $random_agtno_array = explode('', $random_agtno);

  foreach($random_agtno_array as $raa_index => $raa){
    if($raa == $random_agtno_array[$raa_index + 1] && raa == $random_agtno_array[$raa_index + 2]) createRandomAGTNO();

    $dup_match = array_search($raa, $random_agtno_array);
    if($dup_match){
      unset($random_agtno_array[$dup_match]);
      if(array_search($raa, $random_agtno_array)) createRandomAGTNO();
    };
  }

  return $random_agtno;
}
Justin Lucas
A: 

Try this code:

<?php
function createRandomAGTNO() {
    //srand ((double) microtime( )*1000000);
    $digits = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 0 );
    shuffle($digits);
    $random_agtno = 0;
    for($i = 0; $i < 9; $i++)
    {
        if($i == 0)
        {
            while($digits[0] == 0)
                shuffle($digits);
        }
        /*if($i >= 2)
        {
            while(($random_agtno % 100) == $digits[0])
                shuffle($digits);
        }*/
        $random_agtno *= 10;
        $random_agtno += $digits[0];
        array_splice($digits, 0, 1);
    }
    return $random_agtno;
}

for($i = 0; $i < 1000; $i++)
{
    $NEWAGTNO = createRandomAGTNO();
    echo "<p>";
    echo $NEWAGTNO;
    echo "</p>";
}
?>

Good luck!

Edit: Removed the call to srand() and commented-out the "if($i >= 2)" code, which is impossible anyway, here.

Poni
The advantage here is that; 1) You're not using string/chars but numbers only. 2) You use the shuffle() command which is built in PHP, thus fast. I don't think you really need to use srand(), now that I see.. :)
Poni
Hi ircmaxell, I can't really get what you mean. Check the updated code. Look for an output, out of the 1000 numbers, that violates the rules, because I can't find.
Poni
As for triple digits - if this code takes digits from the above array how comes we get 3 times the same digit?
Poni
Oversight on my point. But it also doesn't live up to the requirement that a digit can appear up to three times (and as such will only generate a small sub-set of the possible numbers)...
ircmaxell
Are you sure? Can anyone else confirm ircmaxell's argument?
Poni
Oh, I see. My code will make sure a digit doesn't appear more than two times. My bad, misunderstood - asking this in Hebrew would avoid it (: . Should be very easy to fix though, I leave that to the OP practice (:
Poni
@Poni - were you removing user comments? It is very difficult to follow the thread here
JM4
Poni