How can I generate a nonexistent identifier in an array? - php

I want to generate a unique value with a length of 12 characters. To generate unique values, I use this method:
function generate_unique_id()
{
$time = substr(time(), -7);
$month = mb_strtolower(date("M"));
$symbol = "OM";
$string = $month."".$time."".$symbol;
$result = str_shuffle($string);
return $result;
}
I tested this code to generate 30,000 unique values, but each time the loop is exited without generating the required unique identifiers.
$array = [];
for($i=0; $i<=3; $i++)
{
$unique_id = generate_unique_id();
if(in_array($unique_id, $array)){
echo $i;
break;
}
$array[] = $unique_id;
}
How can I know the number of generated unique values with a length of 12 character strings and quickly generate a unique value if the generation capability has not reached the maximum number limit.

The code below generated 30,000 unique IDs in 21.3783249855041 seconds.
$ids = [];
while (count($ids) < 30000) {
$id = bin2hex(random_bytes(6));
if (!in_array($id, $ids)) array_push($ids, $id);
}
var_dump(count($ids));
var_dump($ids);
The code above will continue generating IDs until it gets 30,000 unique IDs, there is no reason to break.
1Generation time may vary.
Live Example
Repl
Update #1
For those that don't have PHP 7 available, you can use this function.
Update #2
This snippet is massively more efficient, as per #cckep comment:
$time_start = microtime(true);
$ids = [];
while (count($ids) < 30000) {
$id = bin2hex(random_bytes(6));
if (!isset($ids[$id])) $ids[$id] = true;
}
$ids = array_keys($ids);
$time_end = microtime(true);
$execution_time = ($time_end - $time_start);
var_dump(count($ids));
var_dump($ids);
echo $execution_time;

Untested, should work though (random prefix + hex-counter suffix):
<?php
function unique_id($length = 12)
{
static $counter = 0;
$suffix = dechex($counter);
$prefixLen = $length - strlen($suffix);
$prefix = substr(uniqid(), -$prefixLen);
$counter++;
return $prefix.$suffix;
}
$arr = array();
for ($i = 0; $i < 30000; $i++)
{
$id = unique_id();
if (in_array($id, $arr))
{
echo $id."\n";
break;
}
$arr[]= $id;
}
echo "Generated ".count($arr)." unique IDs.\n";
Note that this only works if you need all those IDs in one request / script execution. A new request would cause the static $counter variable to start anew, which doesn't guarantee unique ids anymore.

Related

PHP: Getting random combinations to specified input

I am trying to display possibilities for additions of specific numbers but have not been getting the right results.
<?php
$num3 = 20;
$set = null;
$balance = $num3;
$dig = mt_rand(1,5);
for($i = $balance; $i > 0; $i -= $dig ){
echo $dig.'<br>';
if($i < 1){
$set .= $i;
$balance = 0;
}
else{
$set .= $dig.'+';
$dig = mt_rand(1,5);
}
}
echo $set.'='.$num3;
?>
Here are some of the outputs:
2+5+1+4+5+3+=20
1+4+3+5+3+=20
3+1+1+2+3+4+4+1+3+=20
Appreciate any pointers. Thank in advance...
Ok, even though the requirement isn't completely clear, here's an approach:
(edit: demonstrating prevention of endless loop)
$max_loops = 1000;
$balance = 20;
$found = [];
while($balance > 0 && $max_loops-- > 0) {
$r = mt_rand(1, 5);
if ($balance - $r >= 0) {
$found[] = $r;
$balance -= $r;
}
}
echo implode(' + ', $found) . ' = '.array_sum($found);
Note: This code has a small risk of getting caught in an endless loop... though it's doubtful that it'll ever happen :)
Edit: Now the loop contains a hard-limit of 1000 iterations, after which the loop will end for sure...
To provoke an endless loop, set $balance = 7 and modify mt_rand(4, 5).
You can use a recursive function for this:
function randomAddends($target, $maxAddend = 5, $sum = 0, $addends = [])
{
// Return the array of addends when the target is reached
if ($target <= $sum) {
return $addends;
}
// generate a new random addend and add it to the array
$randomAddend = mt_rand(1, min($maxAddend, $target - $sum));
$addends[] = $randomAddend;
// make the recursive call with the new sum
return randomAddends($target, $maxAddend, $sum + $randomAddend, $addends);
}

random_int instead of array_rand in a loop with no doubles

I'm trying to create a generator for a lotto that I play very often.
The lotto is a 5 number draw, ranging from number 1-50 and the same number cannot appear again.
My current approach to do this is using array_rand() but after some reading I noticed that I should not use array_rand() for this purpose, instead I should be using random_int().
My current approach below:
$numbers = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50);
for ($i = 0; $i <= 4; $i++) {
$number = array_rand($numbers);
unset($numbers[$number]);
if ($number == 0) {
$number = array_rand($numbers);
unset($numbers[$number]);
}
$out1[] = array("<div class=\"number\">$number</div>");
}
As you can see above, this works and it generates 5 numbers without duplicating because I unset the number after it's been drawn.
My question is:
How can I do the same as above but using random_int()instead?
To clarify: Use random_int() to generate a random number but ensure that it doesnt generate the same number again in that run.
$numbers = array(); // Create an empty array
while (count($numbers) < 5) { // While less than 5 items in the array repeat the following
$random = random_int(1,50); // Generate a random number
if (!in_array($random, $numbers)) { // Check if the random number is already in the array, and if it is not then:
$numbers[] = $random; // add the random number to the array
}
}
foreach ($numbers as $n) { // Loop over your array and output with your added HTML
echo "<div class=\"number\">$n</div>";
}
Here's the way:
$out = [];
$used = [];
for ($i = 0; $i <= 4; $i++) {
do {
$randInt = random_int(1, 50);
} while (in_array($randInt, $used));
$used[] = $randInt;
$out[] = "<div class=\"number\">$randInt</div>";
}

Generating set of random unique codes in php

I'm trying to generate a set of unique Alpha Numeric code in php. I tried using anonymous function and closures.
Here when I generate more than 1000 codes, there are changes of codes getting duplicate. So I tried to generate a new code if any duplicate found.
Below is my code which isn't working as expected, I'm getting "still DUPLICATE Exist" a few times and its not regenerating code even a single time.
$start = 0;
$count = 10;
$codes = [];
$another = $this;
for ($i=$start; $i < $count; $i++) {
$getUniqueCode = function (&$codes) use ($another, &$getUniqueCode) {
$newCode = $another->genRandomCode();
if (in_array($newCode, $codes)) {
echo "Regenerate on DUPLICATE FOUND - $newCode <br/>";
return $getUniqueCode($codes);
} else {
return $newCode;
}
};
$newCode = $getUniqueCode($codes);
if (\in_array($newCode, $codes)) {
echo "still DUPLICATE Exist - $newCode <br/>";
}
array_push($codes, $newCode);
}
private function genRandomCode()
{
$str = "ABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789";
$randInt = rand(1000000, 9999999);
$randString = "";
for ($i=0; $i < 6; $i++) {
$randRem = $randInt % 36;
$randInt = $randInt / 36;
$randString .= $str[$randRem];
}
return $randString;
}
Your original code recurses, but i don't think you need to do that.
$start = 0;
$count = 10;
$codes = [];
$another = $this;
for ($i=$start; $i < $count; $i++) {
//do not pass $codes as a reference here
$getUniqueCode = function ($codes) use ($another)
{
$newCode = $another->genRandomCode();
while(in_array($newCode, $codes))
{
echo "Regenerate on DUPLICATE FOUND - $newCode <br/>";
}
return $newCode;
};
$newCode = $getUniqueCode($codes);
if (\in_array($newCode, $codes)) {
echo "still DUPLICATE Exist - $newCode <br/>";
}
array_push($codes, $newCode);
}
Arguably however, a better way to handle a coupon system like this is to generate the possible coupon codes beforehand, store them in a database, and select one at random for activation. this guarantees a unique code, and lets you keep track of which codes you have used so far.

How do I generate more than one random code

I have typed this up to generate a random code. I am trying to add them into a database as they are generated. How do i modify this code to generate x amount instead of one?
<?php
$tokens = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$serial = '';
for ($i = 0; $i < 4; $i++) {
for ($j = 0; $j < 5; $j++) {
$serial .= $tokens[rand(0, 35)];
}
if ($i < 3) {
$serial .= '-';
}
}
echo '<p>' . $serial;
?>
for more precise random token. Try adding current timestamp with a text. current Timestamp itself changes every second. therefore it will mostly be unique. A random text adding at front or last can make it even more unique for users working at a same time.
UPDATE: also add a function whether that unique string exists or not.
A pretty cool, easy method, can be something like this too.
<?php
echo md5(microtime());
Ofcourse it is not 100% safe etc but if you need a quick and easy "RANDOM" string it could be usefull.
Concerning your question:
<?php
function genereteRandomKey($count=10)
{
$data = [];
for($i=0;$i<$count;$i++)
{
$data[] = md5(microtime());
}
return $data;
}
var_dump(generateRandomKey());
To do this, we edit your code slightly and wrap it within a function, like this;
function createHash($length = 4)
{
$tokens = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$serial = '';
for ($it = 0; $it < $length; $it++) {
$serial .= $tokens[ mt_rand( 0, (strlen($tokens) - 1) )];
}
return $serial;
}
The length within the parameter can be left null and will default to 4, just in case you wanted to create longer codes.
To then create more codes, you simply loop via a for loop (Or any loop that you are using!) like so;
for ($i = 0; $i < 5; $i++) {
echo createHash() . '<br />';
}
Obviously, you won't be wishing to echo them out, but instead adding them to a database.
With that in mind, may I also suggest that instead of multiple INSERT queries, to catch them all inside of an array and just do one run on the query, which can be achieved like so;
$inserts = array ();
for ($i = 0; $i < 5; $i++) {
$inserts[] = createHash();
}
$sql = "INSERT INTO `table` (`row`) VALUES ('" . implode('\'), (\'', $inserts) . ");";
Which, in that test run, would output the following as your $sql;
INSERT INTO `table` (`row`) VALUES ('ISS7'), ('SB72'), ('N97I'), ('1WLQ'), ('TF6I);

What are better ways to insert element in sorted array in PHP

I've recently send my CV to one company that was hiring PHP developers. They send me back a task to solve, to mesure if I'm experienced enough.
The task goes like that:
You have an array with 10k unique elements, sorted descendant. Write function that generates this array and next write three different functions which inserts new element into array, in the way that after insert array still will be sorted descendant. Write some code to measure speed of those functions. You can't use PHP sorting functions.
So I've wrote function to generate array and four functions to insert new element to array.
/********** Generating array (because use of range() was to simple :)): *************/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30){
$arr = array();
for($i = 1; $i <= $elementsNum; $i++){
$rand = mt_rand(1, $dev);
$start -= $rand;
$arr[] = $start;
}
return $arr;
}
/********************** Four insert functions: **************************/
// for loop, and array copying
function insert1(&$arr, $elem){
if(empty($arr)){
$arr[] = $elem;
return true;
}
$c = count($arr);
$lastIndex = $c - 1;
$tmp = array();
$inserted = false;
for($i = 0; $i < $c; $i++){
if(!$inserted && $arr[$i] <= $elem){
$tmp[] = $elem;
$inserted = true;
}
$tmp[] = $arr[$i];
if($lastIndex == $i && !$inserted) $tmp[] = $elem;
}
$arr = $tmp;
return true;
}
// new element inserted at the end of array
// and moved up until correct place
function insert2(&$arr, $elem){
$c = count($arr);
array_push($arr, $elem);
for($i = $c; $i > 0; $i--){
if($arr[$i - 1] >= $arr[$i]) break;
$tmp = $arr[$i - 1];
$arr[$i - 1] = $arr[$i];
$arr[$i] = $tmp;
}
return true;
}
// binary search for correct place + array_splice() to insert element
function insert3(&$arr, $elem){
$startIndex = 0;
$stopIndex = count($arr) - 1;
$middle = 0;
while($startIndex < $stopIndex){
$middle = ceil(($stopIndex + $startIndex) / 2);
if($elem > $arr[$middle]){
$stopIndex = $middle - 1;
}else if($elem <= $arr[$middle]){
$startIndex = $middle;
}
}
$offset = $elem >= $arr[$startIndex] ? $startIndex : $startIndex + 1;
array_splice($arr, $offset, 0, array($elem));
}
// for loop to find correct place + array_splice() to insert
function insert4(&$arr, $elem){
$c = count($arr);
$inserted = false;
for($i = 0; $i < $c; $i++){
if($elem >= $arr[$i]){
array_splice($arr, $i, 0, array($elem));
$inserted = true;
break;
}
}
if(!$inserted) $arr[] = $elem;
return true;
}
/*********************** Speed tests: *************************/
// check if array is sorted descending
function checkIfArrayCorrect($arr, $expectedCount = null){
$c = count($arr);
if(isset($expectedCount) && $c != $expectedCount) return false;
$correct = true;
for($i = 0; $i < $c - 1; $i++){
if(!isset($arr[$i + 1]) || $arr[$i] < $arr[$i + 1]){
$correct = false;
break;
}
}
return $correct;
}
// claculates microtimetime diff
function timeDiff($startTime){
$diff = microtime(true) - $startTime;
return $diff;
}
// prints formatted execution time info
function showTime($func, $time){
printf("Execution time of %s(): %01.7f s\n", $func, $time);
}
// generated elements num
$elementsNum = 10000;
// generate starting point
$start = 300000;
// generated elements random range 1 - $dev
$dev = 50;
echo "Generating array with descending order, $elementsNum elements, begining from $start\n";
$startTime = microtime(true);
$arr = generateSortedArray($start, $elementsNum, $dev);
showTime('generateSortedArray', timeDiff($startTime));
$step = 2;
echo "Generating second array using range range(), $elementsNum elements, begining from $start, step $step\n";
$startTime = microtime(true);
$arr2 = range($start, $start - $elementsNum * $step, $step);
showTime('range', timeDiff($startTime));
echo "Checking if array is correct\n";
$startTime = microtime(true);
$sorted = checkIfArrayCorrect($arr, $elementsNum);
showTime('checkIfArrayCorrect', timeDiff($startTime));
if(!$sorted) die("Array is not in descending order!\n");
echo "Array OK\n";
$toInsert = array();
// number of elements to insert from every range
$randElementNum = 20;
// some ranges of elements to insert near begining, middle and end of generated array
// start value => end value
$ranges = array(
300000 => 280000,
160000 => 140000,
30000 => 0,
);
foreach($ranges as $from => $to){
$values = array();
echo "Generating $randElementNum random elements from range [$from - $to] to insert\n";
while(count($values) < $randElementNum){
$values[mt_rand($from, $to)] = 1;
}
$toInsert = array_merge($toInsert, array_keys($values));
}
// some elements to insert on begining and end of array
array_push($toInsert, 310000);
array_push($toInsert, -1000);
echo "Generated elements: \n";
for($i = 0; $i < count($toInsert); $i++){
if($i > 0 && $i % 5 == 0) echo "\n";
printf("%8d, ", $toInsert[$i]);
if($i == count($toInsert) - 1) echo "\n";
}
// functions to test
$toTest = array('insert1' => null, 'insert2' => null, 'insert3' => null, 'insert4' => null);
foreach($toTest as $func => &$time){
echo "\n\n================== Testing speed of $func() ======================\n\n";
$tmpArr = $arr;
$startTime = microtime(true);
for($i = 0; $i < count($toInsert); $i++){
$func($tmpArr, $toInsert[$i]);
}
$time = timeDiff($startTime, 'checkIfArraySorted');
showTime($func, $time);
echo "Checking if after using $func() array is still correct: \n";
if(!checkIfArrayCorrect($tmpArr, count($arr) + count($toInsert))){
echo "Array INCORRECT!\n\n";
}else{
echo "Array OK!\n\n";
}
echo "Few elements from begining of array:\n";
print_r(array_slice($tmpArr, 0, 5));
echo "Few elements from end of array:\n";
print_r(array_slice($tmpArr, -5));
//echo "\n================== Finished testing $func() ======================\n\n";
}
echo "\n\n================== Functions time summary ======================\n\n";
print_r($toTest);
Results can be found here: http://ideone.com/1xQ3T
Unfortunately I was rated only 13 points out of 30 for this task (don't know how it was calculated or what exactly was taken in account). I can only assume that's because there are better ways to insert new element into sorted array in PHP. I'm searching this topic for some time now but couldn't find anything good. Maby you know of better approach or some articles about that topic?
Btw on my localhost (PHP 5.3.6-13ubuntu3.6 with Suhosin-Patch, AMD Athlon(tm) II X4 620) insert2() is fastest, but on ideone (PHP 5.2.11) insert3() is fastest.
Any ideas why? I suppose that array_splice() is tuned up somehow :).
//EDIT
Yesterday I thought about it again, and figured out the better way to do inserts. If you only need sorted structure and a way to iterate over it and your primary concern is the speed of insert operation, than the best choise would be using SplMaxHeap class. In SplMaxHeap class inserts are damn fast :) I've modified my script to show how fast inserts are. Code is here: http://ideone.com/vfX98 (ideone has php 5.2 so there won't be SplMaxHeap class)
On my localhost I get results like that:
================== Functions time summary ======================
insert1() => 0.5983521938
insert2() => 0.2605950832
insert3() => 0.3288729191
insert4() => 0.3288729191
SplMaxHeap::insert() => 0.0000801086
It may just be me, but maybe they were looking for readability and maintainability as well?
I mean, you're naming your variables $arr, and $c and $middle, without even bothering to place proper documentation.
Example:
/**
* generateSortedArray() Function to generate a descending sorted array
*
* #param int $start Beginning with this number
* #param int $elementsNum Number of elements in array
* #param int $dev Maximum difference between elements
* #return array Sorted descending array.
*/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30) {
$arr = array(); #Variable definition
for ($i = 1; $i <= $elementsNum; $i++) {
$rand = mt_rand(1, $dev); #Generate a random number
$start -= $rand; #Substract from initial value
$arr[] = $start; #Push to array
}
return $arr;
}

Categories