skip to Main Content

Issue

I am trying to add a 12 digit long (numeric values only!), unique identifier to a user table that will be given to the users to find each other.
Since it will be handed to the users it needs to be unrelated to the auto increment id on the users table.

The two methods that come to my mind are:

loop until you get a unique number

do {
    // get a random 12 digit number
    $identifier = str_pad(rand(0, 999999999999), 12, '0', STR_PAD_LEFT);
    // check if it is unique
    $exists = User::where('identifier', $identifier)->exists();
} while ($exists)

return $identifier

Drawbacks
In theory it could end up in an infinite loop (although almost impossible).

adjust the random number accordingly

// get all the identifiers as array
$identifiers = User::orderBy('identifier')->pluck('identifier')->toArray();

// random number becomes lower depending on the total users
$my_identifier = rand(0, 999999999999 - count($identifiers));

// increment for all the smaller identifiers
foreach($identifiers as $identifier) {
    if(intval($identifier) > $my_identifier) break;
    $my_identifier ++;
}

return str_pad($my_identifier, 12, '0', STR_PAD_LEFT)

Drawbacks
While this makes sure I don’t run into an infinite loop, having to loop through an array the length of my user count sounds pretty heavy.

Question

I think the best solution out of these two is the first one since the possibility of an infinite loop is basically non existent, while the second solution looks really heavy and impractical.

But is there any way to avoid both drawbacks from these two solutions?

2

Answers


  1. Chosen as BEST ANSWER

    So far there has not been an answer to create a 12 digit identifier that would be random, unique, "light", and unrelated to the user id.

    However both the options on the question and the option shared by shingo are perfectly viable, and after running some tests my conclusion was that I was overthinking things.

    At the end of the day none of these method actually had a drawback that made them non viable, you should choose what method to use depending on your requirements.

    loop until I get a unique number

    finite loops few loops unrelated to id
    no yes yes

    Let's say for this example we have 100000 users.
    A new user registers and I have to create a new identifier for said user.

    Only 100000 identifiers have been used out of the 1000000000 available identifiers giving a 1/10000 chance of the while loop looping even once.
    Furthermore the code would have to loop thousands of times before becoming an issue.

    This can only become an issue is if a large percentage of the identifiers are already being used.
    You should not use this method to give a 6 digit identifier to a table with 500000 users.

    adjust the random number accordingly

    finite loops few loops unrelated to id
    yes no yes

    The issue I had with this code was that it "loops too much".
    If my table had 100000 users it could easily end up looping for 100000 times.

    However after testing some code on a sandbox I came to the conclusion that a for loop this small can easily loop a 100000 times in a matter of milliseconds, therefore removing any worries about the code becoming a performance killer.

    While the idea of looping so much still bugs me, I would judge this method to be the "safest".

    create a pseudorandom number

    finite loops few loops unrelated to id
    yes yes? no

    This is the solution offered by shingo. The issue with this method is the one-to-one correspondence with the user id, which I wanted to avoid.

    However if you don't have any issues with that, this is probably the best method.
    I think this method also removes the need to store the 12 digit number on your database and it allows you to directly get the user id from the 12 digit code.


  2. You can use an auto increment number and create a corresponding pseudorandom number with some algorithm, here’s an example:

    class IdGenerater
    {
        private static $RANDOMCHARS = # random
        [
            '2083417956', '4823019567', '8402135679', '4802316759',
            '2483051679', '8421350679', '1503248697', '1053872469',
            '0157824639', '1502784639', '5170248639', '0751248693',
        ];
        private $digits = [];
    
        public function generate(int $id) : int
        {
            $p = 0;
            while ($id >= 10)
            {
                $rem = $id % 10;
                $this->digits[$p++] = $rem;
                $id = (int)(($id - $rem) / 10);
            }
            $this->digits[$p++] = $id;
            for(; $p < 12; $p++)
                $this->digits[$p] = 0;
    
            $p = 0; $q = 0;
            for ($i = 0; $i < 12; $i++)
            {
                $p += $this->digits[$i];
                $q = $q * 10 + (int)self::$RANDOMCHARS[$i][$p % 10];
            }
            return $q;
        }
    }
    

    The auto increment number can be generated from another service, the result is one-to-one correspondence, but it’s not easy to revert.

    $gen = new IdGenerater();
    echo $gen->generate(0), PHP_EOL; # 248428110150
    echo $gen->generate(666666), PHP_EOL; # 727320824488
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search