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.
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 have a db in which I store 20 demographics.
The demographics come from a csv and are stored in array. Each array key is a placeholder: d1, d2, up to d20. The incoming data may be [race, gender, class] for Client A and [income, region] for Client B.
For both clients the db table will store their values as d1, d2, d3...d20. The data are associated with a clientid upon insertion into the database.
I need to write a single insert statement for the database table. I need it to include d1...d20. There will always be 20 placeholders. Some are filled, some are not like int he example above.
The challenge is that I don't know how many will be filled.
So, I create an array of keys. I combine this with an array of incoming csv data.
$demos['d01']='';
$demos['d02']='';
$demos['d03']='';
$demos['d04']='';
...
$demos['d20']='';
And I produce this result.
print_r($rowdata);
[1] => Array
(
[d01] => 1
[firstname] => Fred
[lastname] => Dryer
[email] => FredDryer1#email.com
[d02] => Backfield
[d03] => North
[partnerid] => 14
[d04] =>
...
[d20] =>
)
In fact, this is what I want. But, I can only seem to get there MANUALLY by adding $rowdata_tmp['d04'] = ''; to match the addition of d04 to $demos_keys. When the number of keys doesn't match the number of elements in the second array, the code fails. See below.
I need to accomplish this match programmatically.
The solution I need is the for-loop below. I set it to start at 4 here for demonstration purposes because in this case I know that's where it needs to start.
I have tried $x = $place_to_start where $place_to_start = 4 derived through maths relative to the incoming the csvdata.
But, no joy. The loop doesn't work. 500 error. No help!
$place_to_start = 4;
foreach ($csvdata as $row) {
$rowdata_tmp = explode(',', trim($row));
$rowdata_tmp['partnerid'] = $partnerid;
$rowdata_tmp['d04'] = '';
// for ($x = 4; $x = 20; $x++) {
// if ($x <10) {
// $rowdata_tmp['d0'.$x] = '';
// } else {
// $rowdata_tmp['d'.$x] = '';
// }
// }
$rowdata[] = array_combine($demos_keys, $rowdata_tmp);
}
Anyone see my problem as it relates to the code? I understand there may have been more wise design choices made in the past...here's where we are. All comments are mucho apprieciado.
This part for ($x = 4; $x = 20; $x++) { will cause an infinite loop. To include 20 in the result, you can use
for ($x = 4; $x <= 20; $x++) {
What you might do is subtract the number of items in $rowdata_tmp from 20 and set the start of the for loop to the amount of $rowdata_tmp assuming that the keys from the csv are not the same.
$rowdata_tmp = explode(',', trim($row));
$nrItemsFromCsv = count($rowdata_tmp);
$nrItemsToAdd = 20 - $nrItemsFromCsv;
for ($x = $nrItemsFromCsv; $x <= $nrItemsToAdd; $x++) {
if ($x < 10) {
$rowdata_tmp['d0' . $x] = '';
} else {
$rowdata_tmp['d' . $x] = '';
}
}
print_r($rowdata_tmp);
I am working on a scratch card game where by a user purchases a scratch card from me/my store and can then redeem said card by scratching off numbers and matching 3 pictures to win a prize
Like physical scratch cards I want the prizes to be pre configured so that I know when a prize will be won, but obviously the users dont, this is so I can limit the prizes, so it is not true random, but will be random in terms of who wins the prize..
I know how I will setup the prizes but I need to check each data value in an array to ensure that there is not 3 matching numbers, unless they have actually won..
So far I have this (I will neaten it up and shorten the code down soon)
$numbers = array(
0 => rand(0,9),
1 => rand(0,9),
2 => rand(0,9),
3 => rand(0,9),
4 => rand(0,9),
5 => rand(0,9),
6 => rand(0,9),
7 => rand(0,9),
8 => rand(0,9)
);
Which just randomly generates 9 numbers and
echo "<table>";
$i=0;
for($x=0;$x<3;$x++){
echo "<tr>";
for($y=0;$y<3;$y++){
echo "<td>" . $numbers[$i] . "</td>";
$i++;
}
echo "</tr>";
}
Sorts them into a 3x3 table
I know there is the command in_array() which will sort through the array looking for certain values but I dont want to run an endless loop checking each value to see if I get a match, because it's labor intensive and would be messy as well.
I'm looking for a suggestion anyone might have so I can sort through the array and see if any 3 numbers exist, if they do, and they're not suppost to (ie the user hasn't actually won a prize) then one should be changed to a new number.
So thank you in advance for your help guys I look forward to your suggestions
Luke
**** EDIT **
+More info
To pick the winners I will use a database query such as this
$stock = $conn -> query("SELECT DISTINCT * FROM codes WHERE Available = 1 AND 0 = FLOOR(RAND()*Chance) ORDER BY RAND()");
Each code will have a set chance to win and if it matches up it will be shown, depending on the code won a certain prize will show
$stock = $conn -> query("SELECT DISTINCT * FROM codes WHERE Available = 1 AND Win = 1);
'Win' will be a table in the database which each time someone plays the game it goes down by 1
So each code will have the table Win which when it hits 1 it will pop out the in the next game
These are two ways I can think of doing it which I think will work, both ways roughly allow me to control when and if someone wins but the second solution is obviously a more accurate way of doing it
how about not changing a number if a triple one occurs, but preventing a triple one occurs.
function createScratchRange()
{
$result = array();
$options = array_merge(range(0,9), range(0,9));
for ($i = 0; $i < 9; $i++)
{
$key = array_rand($options, 1);
$result[] = $options[$key];
unset($options[$key]);
}
return $result;
}
and you might want to be able to create winners:
function createWinner()
{
$winningNumber = rand(0,9);
$result = array();
$possibleNumbers = range(0,9);
unset($possibleNumbers[$winningNumber]);
$options = array_merge($possibleNumbers,$possibleNumbers);
for ($i = 0; $i < 9; $i++)
{
$key = array_rand($options, 1);
$result[] = $options[$key];
unset($options[$key]);
}
// looks random now.. but no winning numbers... Lets just stick them in there.
$keys = array_rand($result, 3);
foreach($keys as $key)
{
$result[$key] = $winningNumber;
}
return $result;
}
and for testing:
var_dump(createScratchRange()); // this will never give 3 identical numbers in the array.
var_dump(createWinner()); // this will ALWAYS give 3 identical numbers, but never 2 sets of 3 identical numbers
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.