I am looking to generate a random string based on the amount of products I have. For instance I have 100 products named "test" I want to be able to generate 100 product codes which will all be unique.
I am currently using this code:
<?php
/**
* The letter l (lowercase L) and the number 1
* have been removed, as they can be mistaken
* for each other.
*/
function createRandomPassword() {
$chars = "abcdefghijkmnopqrstuvwxyz023456789";
srand((double)microtime()*1000000);
$i = 0;
$pass = '' ;
while ($i <= 7) {
$num = rand() % 33;
$tmp = substr($chars, $num, 1);
$pass = $pass . $tmp;
$i++;
}
return $pass;
}
// Usage
$password = createRandomPassword();
echo "Your random password is: $password";
?>
Cheers
Using your function you can generate 100 random strings, i.e.
$product_names = array ();
for ($i=0; $i < 10; $i++ )
$product_names[] = "code-" . createRandomPassword();
print_r ( $product_names );
Your question is not clear though. Do you have a naming convention you want to follow, do you want to generate codes in a pattern, such as 'product1', 'product2', ... , 'product100', etc ?
EDIT: The code above creates the following output:
Array
(
[0] => code-opt6ggji
[1] => code-4qfjt653
[2] => code-8ky4xxo0
[3] => code-dfip2o5x
[4] => code-3e3irymv
[5] => code-dgqk0rzt
[6] => code-3fbeq0gr
[7] => code-tev7fbwo
[8] => code-idg04mdm
[9] => code-8c2uuvsj
)
There is already a built in function that will handle this for you trivially. uniqid generates a prefixed unique identifier based on the current time in microseconds.
http://php.net/manual/en/function.uniqid.php
<?php
// loop 100 times
for ($i=0; $i<100; $i++)
{
// print the uniqid based on 'test'
echo uniqid('test', true);
}
?>
It is worth noting that to ensure true uniqueness you would need to store all the codes generated and check that no duplicated are issued.
Related
I am populating my DB table with unique download codes.
My intention is to make sure that at the end I will have a 1000 unique codes.
So far I have this code in my controller method:
// determining how many codes have to be generated
$totalcount_generated_so_far = DownloadCode->count();
$quantity = 1000 - $totalcount_generated_so_far;
if($quantity < 0) {
return "nothing generated! You already have more than 1000 codes.";
}
$object = new DownloadCode;
for($i = 0; $i < $quantity; $i++) {
$object = new DownloadCode;
$length = 6;
$keys = array_merge(range(1,9), range('A', 'Z'));
$key1 = "";
for($i=0; $i < $length; $i++) {
$key1 .= $keys[mt_rand(0, count($keys) - 1)];
}
$object->code_download = $key1; // a ready to use 6-digit
$object->save();
}
return $quantity . " unique codes have been generated.";
Problem: The above code does not check if a generated code is unique.
TODO:
Make the function to check if the code has been already generated (a very rare event, but still!)?
Partial solution:
I could put the $object->save(); inside an if condition:
// check for uniqueness
$uniq_test = DownloadCode::where('code_download',$key2)->first();
if($uniq_test) {
$i--
} else {
$object->save();
}
Is there a better solution?
Thank you.
The problem with random numbers is that there is no way to guarantee that you can generate a certain number of them. For any value of n there is a probability, however small, that you will generate the same random number repeatedly, and thus never reach the number of different codes you need.
One answer is to use a deterministic function, but that can be predictable.
To generate a known number of random codes combine the two methods.
So, in pseudo code:
for some number of iterations
generate a random code of some length
append a sequential number in some range
return the list of codes.
Identical random codes will be distinguished by differing sequential suffixes, so no collision.
In PHP this would look something like this:
function makeCodes($numCodes, $codeLength) {
// source digits
$digits = '01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$codes = [];
$nextCode = 0;
// Make a number of codes
for ($i = 0; $i<$numCodes; $i++) {
$code = '';
// Create a randomised string from the source digits
for ($j = 0; $j<$codeLength-3;$j++) {
$code .= $digits[random_int(0,strlen($digits)-1)];
}
// Append the sequential element
$codes[] = sprintf('%s%03X', $code, $nextCode);
$nextCode++;
}
return $codes;
}
print_r(makeCodes(10, 24));
Returns:
(
[0] => BL8TKD86VW086XS3PBKZ4000
[1] => MSBYAAPWGLROKL0NKP48L001
[2] => XCDI783PW1J1RD9X3KM71002
[3] => GAKZE96PVA1X6DR7X1Y4N003
[4] => M6DCEEOMLYGC42DPD8GVY004
[5] => 1DKFL67IZ2EA0UTEIWW61005
[6] => XMSU0UUD9GHDAQN3XMYW5006
[7] => 4QOKM1YOPCW2NK1E6CL9Q007
[8] => VHMURGPH7AKR8HOEXPBAN008
[9] => EU0L5QAGPB211WZ5VDE4R009
)
This will produce a list of ten 24-digit codes made up of a 21-digit random prefix followed by a a 3-digit hex number in the range 000 to 009
There are obviously many possible variations on this. Change the sequential range to some other starting point; change the sequential length; prepend the sequential portion; embed the sequential portion, and so on. I'm sure you can dream up something to suit your preferences.
Demo: https://3v4l.org/cboZ0
Laravel has a helper Which generates safety unique IDs.
Generate UUID with Laravel
$random = str_random(20);
dd($random);
I have some word lets say BKOO.
I need to remove all combinations of missing letters to generate sub words of this initial word. First remove only 1 letter, then n letters to build at least 2 letters words.
So from our example it means to make words like KOO, BOO, OO, BK, BO.
My current algorithm btw says it is possible to generate 7 combinations out of BKOO. (I also include the initial word).
Array
(
[0] => BKOO
[1] => Array
(
[0] => BKOO
[1] => KOO
[2] => OO
[3] => KO
[4] => BOO
[5] => BO
[6] => BKO
[7] => BK
)
)
Note there isnt words like BOK or OOK because that would mean do the reorder, but i dont want to do this. I want just leave letters out of current word, and don't do reorder.
Now problem is, this very slow for lenght like 15. It takes forever. How to speed it up?
function comb($s, $r = [], $init = false) {
if ($init) {
$s = mb_strtoupper($s);
$r[] = $s;
}
$l = strlen($s);
if (!$s || $l < 3) return [];
for ($i=0; $i<$l; $i++) {
$t = rem_index($s, $i);
$r[] = $t;
$r = array_merge($r, comb($t));
}
$ret = array_unique((array)$r);
return $init ? array_values($ret) : $ret;
}
// remove character at position
function rem_index($str, $ind)
{
return substr($str,0,$ind++). substr($str,$ind);
}
$s = 'BKOO';
print_r(comb($s, [], true));
https://www.tehplayground.com/62pjCAs70j7qpLJj
NERD SECTION: 🤓 😄
Interesting note - first i thought i will generate array of some dropping indexes eg, first drop only 1 letter so say drop 0 then 1 etc etc, then 2-combinations so drop 1 and 2, 1 and 3 etc, but then i thought it would be quite difficult to drop N letters out of string at once, so i came with idea that i always drop some letter from the string, and recursively call the function again if you get me, so the next level is one char dropped already and does the drop iteration again. Problem is it is very slow for some reason.
Btw if you have also the math background, what is equation to compute the resulting combinations? To me the rough computation is lets say for 15 letters word 14 * 13 * 12 or at least it does such iteration, but that would be milions of combinations and obviously its not like that even for shorter words like 8.
Thanks.
You can iterate the string to get it.
function foo(&$res,$str,$min_length){
if(strlen($str) <= $min_length){
return;
}
$remains=[];
for($i=0; $i<strlen($str); $i++){
$remain = substr($str,0,$i) . substr($str,$i+1);
if(!isset($res[$remain])) { // only process unprocessed sub string
$res[$remain] = $remain;
$remains[] = $remain;
}
}
foreach($remains as $remain){
if(strlen($remain) == $min_length){
$res[$remain] = $remain;
}else {
foo($res, $remain, $min_length);
}
}
return;
}
$str = "BKOO";
$res = [];
foo($res,$str,2);
var_dump(array_values($res));
I'm about to create "lottary system."
Take a look at my table:
userid-lottaryid-amount
1 -------- 1 ---- 1
2 -------- 1 ---- 10
3 -------- 1 ---- 15
4 -------- 1 ---- 20
I want to choose a winner. and another person for second place.
I just can't select a winner randomly because 4th user has 20 tickets and 1st user has only one.
So I need to generate random results by weight in order to be more fair.
I found php function below but I couldn't figure out how to use it.
function weighted_random_simple($values, $weights){
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(0, array_sum($weights));
while($i < $count){
$n += $weights[$i];
if($n >= $num){
break;
}
$i++;
}
return $values[$i];
}
$values = array('1', '10', '20', '100');
$weights = array(1, 10, 20, 100);
echo weighted_random_simple($values, $weights);
I must fetch userid colomn as array to $values and amount colomn to $weights. But I couln't.
Here is my code so far:
$query = $handler->prepare("SELECT
`cvu`.`lottaryid` as `lottaryid`,
`cvu`.`userid` as `userid`,
`cvu`.`amount` as `amount`,
`members`.`id` as `members_memberid`,
`members`.`username` as `username`
FROM `lottariesandmembers` as `cvu`
LEFT JOIN `members` as `members` ON `cvu`.`userid` = `members`.`id` WHERE `cvu`.`lottaryid` = 2");
$query->bindParam(':lottaryid', $lottaryid, PDO::PARAM_INT);
$query->execute();
while($r = $query->fetch()) {
for ( $count=1 ; $count <= $r["amount"] ; $count++ ) {
$abcprint = "$r[userid].$count - $r[username] - <br>";
echo "$abcprint";
}
}
This code I have, only lists users as many times as their amount. For example:
1.1 user1
2.1 user2
2.2 user2
2.3 user2
..
2.10 user2
3.1 user3
..
3.15 user3
4.1 user4
..
4.20 user4
and so on.. But I'm stuck how to pick a winner on that list.
I would like to merge those codes and create this little script, if you would like to help me.
I'm also open for brainstorm if you see the solution on the other way around.
Instead of printing out the values as you are doing, you can just build a large array, and then choose a value randomly from that array.
while($r = $query->fetch()) {
for ( $i=0; $i <= $r["amount"]; $i++ ) {
// Add the user into the array as many times as they have tickets
$tickets[] = $r['userid'];
}
}
// select the first place winner
$first = $tickets[mt_rand(0, count($tickets) - 1)];
// remove the first place winner from the array
$tickets = array_values(array_filter($tickets, function($x) use ($first) {
return $x != $first;
}));
// select the second place winner
$second = $tickets[mt_rand(0, count($tickets) - 1)];
I'm sure there is a more efficient way to do this using math, but I need to think about it a bit more...
This isn't very elegant but should work for smallish lotteries.
It just constructs a massive array and picks an element at random.
Think of having a massive hat full of slips. Each holder gets their stake in 'slips' and each are labelled with their id. i.e. Ten slips with the holder's name 'a', 20 slips with 'b' and so on...
<?php
$holder_totals = array(
'a' => '10',
'b' => '20',
'c' => '20',
'd' => '50'
);
$big_hat = array();
foreach($holder_totals as $holder_id => $total) {
$holder_hat = array_fill(0, intval($total), $holder_id);
$big_hat = array_merge($big_hat, $holder_hat);
}
// Drum roll
foreach (range(1,4) as $n) {
$random_key = array_rand($big_hat);
printf("Winner %d is %s.\n", $n, $big_hat[$random_key]);
unset($big_hat[$random_key]); // Remove winning slip
}
Sample output:
Winner 1 is d.
Winner 2 is c.
Winner 3 is d.
Winner 4 is b.
Big hat looks like this:
Array
(
[0] => a
[1] => a
[2] => a
[3] => a
[4] => a
[5] => a
[6] => a
[7] => a
[8] => a
[9] => a
[10] => b
[11] => b
[12] => b
[13] => b
[14] => b
... and so on...
)
/**
* getRandomWeightedElement()
* Utility function for getting random values with weighting.
* Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50)
* An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%.
* The return value is the array key, A, B, or C in this case. Note that the values assigned
* do not have to be percentages. The values are simply relative to each other. If one value
* weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66%
* chance of being selected. Also note that weights should be integers.
*
* #param array $weightedValues
*/
function getRandomWeightedElement(array $weightedValues) {
$rand = mt_rand(1, (int) array_sum($weightedValues));
foreach ($weightedValues as $key => $value) {
$rand -= $value;
if ($rand <= 0) {
return $key;
}
}
}
Here is an efficient and flexible function. But You have to modify it if you want to use non-integer weighting.
You can use weightedChoice function from my library nspl.
use function \nspl\rnd\weightedChoice;
// building your query here
$pairs = [];
while($r = $query->fetch()) {
$pairs[] = [$r['userid'], $r['amount']];
}
$winnerId = weightedChoice($pairs);
You can install the library with composer:
composer require ihor/nspl
Or you can simply reuse weightedChoice code from GitHub:
/**
* Returns a random element from a non-empty sequence of items with associated weights
*
* #param array $weightPairs List of pairs [[item, weight], ...]
* #return mixed
*/
function weightedChoice(array $weightPairs)
{
if (!$weightPairs) {
throw new \InvalidArgumentException('Weight pairs are empty');
}
$total = array_reduce($weightPairs, function($sum, $v) { return $sum + $v[1]; });
$r = mt_rand(1, $total);
reset($weightPairs);
$acc = current($weightPairs)[1];
while ($acc < $r && next($weightPairs)) {
$acc += current($weightPairs)[1];
}
return current($weightPairs)[0];
}
I have the following array with undefined number of elements
$marks=array('2','4','9','3');
target=50;
I want to randomly loop through the array, add up the values I fetch until the total is my target.
$total=0; /////initialize total
for($i=0;$i<=sizeof($marks);++$i)
{
/////////Pick up random values add them up until $total==$target
/////////return the new array with selected elements that sums up to
/////////target
}
I hope my question is clear, also note that the loop should not iterate too many times since the elements might never add up to the total. I have tried adding the items in line but to no avail. Thanks in advance
I think this'll work for you and always return you value of count to be 50 only
$marks = array(6,7,9,6,7,9,3,4,12,23,4,6,4,5,7,8,4);
$target = 50;
function sum($marks, $target) {
$count = 0;
$result = [];
for ($i = 0; $i <= $target; $i++) {
if ($count < $target) {
$add = $marks[array_rand($marks)];
$count = $count + $add;
$result['add'][] = $add;
} elseif ($count == $target) {
break;
} elseif ($count >= $target) {
$extra = $count - $target;
$count = $count-$extra;
$result['extra'] = $extra;
}
}
return $result;
}
print_r(sum($marks, $target));
The way you describe your logic, a while loop might make more sense:
<?php
$marks = array(2, 4, 9, 3);
$target = 50;
$sum = 0;
$i = 0; // to keep track of which iteration we're on
// PHP can natively randomize an array:
shuffle($marks);
while ($sum < $target && $i < count($marks)) {
$sum += $marks[$i];
$i++; // keep track of which iteration we're on
}
// after the loop, we've either added every number in $marks,
// or $sum >= $target
Don't forget that it might exceed $target without ever being equal to it, as Dagon pointed out in a comment.
Look into PHP's native array shuffle: https://secure.php.net/manual/en/function.shuffle.php
This may be a good alternative for the above answer.
Why I say so is that I have set it in such a way that it doesn't let the total go over the target, and when there is such a situation, the current number in the array is decremented by one and added as a new element so that if there is no possible number in the stack, there will be one eventually making this loop not go on infinitely. :)
<?php
$marks = ['2', '4', '9', '3'];
$target = 50;
$total = 0;
$numbersUsed = [];
while($total != $target) {
$index = rand(0, count($marks) - 1);
$number = $marks[$index];
if($number + $total > $target) {
$number = 0;
$marks[] = $marks[$index] - 1;
} else {
$numbersUsed[] = $number;
}
$total += $number;
echo $total . "\n";
}
// To see which numbers were used:
print_r($numbersUsed);
?>
Testing:
Starting with the array ['2', '4', '9', '3'],
We loop and get the result:
4 13 17 20 22 31 35 44 46 48 48 48 48 50
And we get this array which includes the numbers used to get the final result:
Array
(
[0] => 4
[1] => 9
[2] => 4
[3] => 3
[4] => 2
[5] => 9
[6] => 4
[7] => 9
[8] => 2
[9] => 2
[10] => 2
)
I want a way to take any input (url) and get back a number between 1-4, distributed as even as possible 25% for any input. It's important that it gets the same value of 1-4 every time.
The reason I want this is so that I can create seemingly random and evenly disturbed content for a set of CNAMEs (subdomains) for a CDN. It would take pictures that were originally www.website.com/picture.png and output them as
cdn1.website.com/picture.png or
cdn2.website.com/picture.png or
cdn3.website.com/picture.png or
cdn4.website.com/picture.png
Effectively allowing me to bypass the browser restrictions set to a subdomain, giving me more parallel connections (Read more: http://yuiblog.com/blog/2007/04/11/performance-research-part-4/). The reason why I want the URL to always pass back to a specific CDN is for caching purposes; If the www.website.com/picture.png is first displayed as cdn1.website.com/picture.png and a second time around as cdn2.website.com/picture.png then the browser would not know that it has the same picture cached already under cdn1 and would download the same picture twice, rather than relying on cache.
Here the suggested php at it, but I as you can see from results that I don't get that 25% ratio I would like for small sample set. I am looking for alternatives that would also be somewhat close to 25% distribution for small samples.
<?php
$num_array = array();
for ($i = 1; $i <= 10000; $i++) {
$num_array[]=(crc32(genRandomURL()) % 4)+1;
}
print "<pre>";
print_r(array_count_values($num_array));
print "</pre>";
$num_array = array();
for ($i = 1; $i <= 10; $i++) {
$num_array[]=(crc32(genRandomURL()) % 4)+1;
}
print "<pre>";
print_r(array_count_values($num_array));
print "</pre>";
function genRandomURL($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
$string = "";
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters))];
}
return "http://www.website.com/dir/dir2/dir3/".$string.".png";
}
?>
Results:
Array
(
[3] => 2489
[1] => 2503
[2] => 2552
[4] => 2456
)
Array
(
[1] => 5
[2] => 1
[3] => 3
[4] => 1
)
How about creating a hash of the name, getting the last two bits of that hash, then finally converting them back into a decimal number. Should return the same value so long as your name doesn't change.
function img_id($string){
$hash = md5($string); // create hash
$bin_hash = base_convert($hash, 16, 2); // convert to binary
$last_bits = substr($bin_hash, -2); // get last two bits
$img_int = bindec($last_bits)+1; // turn bits to integer, and + 1
return $img_int; // will be number from 1 to 4
}
$picture = 'picture.png';
$cdn_id = img_id($picture);
$url = "cdn{$cdn_id}.website.com/{$picture}";
If your name might change then you could also look at doing a hash of the actual file contents.