Organize array in certain way (roulette wheel items) - php

I'm struggling with (probably simple) array shuffling/generating algorithm.
I'm creating simple backend (PHP/MySQL) for a roulette wheel game (JS/HTML). The roulette doesn't have numeric values as usual instead there are 4 prizes user can win, distributed within 12 segments of a roulette wheel.
I need an array like this:
// Note that PRIZE4 is listed only once as it is valuable prize.
// PRIZE3 is less valuable so it is listed twice, etc.
// Prizes are skipping each other so you should never see two identic prizes next each other.
var items = [PRIZE1, PRIZE2, PRIZE1, PRIZE2, PRIZE1, PRIZE2, PRIZE1, PRIZE2, PRIZE1, PRIZE3, PRIZE4, PRIZE3];
And I have prizes in a SQL table like this:
+----+------------+--------------+
| id | name | giveaway_cap |
+----+------------+--------------+
| 1 | PRIZE1 | 255 |
| 2 | PRIZE2 | 300 |
| 3 | PRIZE3 | 30 |
| 4 | PRIZE4 | 15 |
+----+------------+--------------+
4 rows in set (0.00 sec)
Column giveaway_cap determines how many of each prize can be won (I'm storing these counts in different table) but could be used as weight of each prize.
I need some algorithm (preferably PHP) which will generate an array as described above based on this table.
Thanks.

I found this really nice algorithm searching on SO. It generates random numbers based on weight. Using it, one approach to your problem could be as follows:
// This is the function that generates random numbers based on weigth.
function getRandomWeightedElement(array $weightedValues) {
$rand = mt_rand(1, (int) array_sum($weightedValues));
foreach ($weightedValues as $key => $value) {
$rand -= $value;
if ($rand <= 0) {
return $key;
}
}
}
$items = [ "PRIZE1" => 255, "PRIZE2" => 300, "PRIZE3" => 30, "PRIZE4" => 15];// Array of available prizes. It can be retrieved from the DB.
$total = (int) array_sum($items);// Total. I use it to work out the weight.
$items_w = [ "PRIZE1" => (255 / $total) * 1000, "PRIZE2" => (300 / $total) * 1000, "PRIZE3" => (30 / $total) * 1000, "PRIZE4" => (15 / $total) * 1000];// find out the weight somehow. I just divide number of available items by the total.
$res = [];
$previous = NULL;
while ( count(array_diff(array_keys($items), $res))) {// Loop until the result has all available prizes.
$res = [];
for ($i = 0; $i < 12; $i++) {
while ($previous == ($new = getRandomWeightedElement($items_w))) {}// Two consecutive prizes of the same type aren't allowed.
$res[] = $new;
$previous = $new;
}
}
echo implode(',', $res);
It's just a solution, I'm sure there are multiple ways of solving the problem.
Note: I'm using php 5.4 short array syntax, if your PHP version is lower than PHP 5.4, substitute [] by array(). Also have in mind that there can be problems in certain situations such as there's only one type of prize left or if it's impossible to create an array of prizes without two consecutive prizes being the same. You'd have to control those situations anyhow.
Hope it helps.

<?php
$connect=mysqli_connect("localhost","my_user","my_password","my_db");
$sql="SELECT id,name,giveaway_cap FROM table";
$result=mysqli_query($connect,$sql);
$prizes = array();
while($row=mysqli_fetch_array($result)) //iterate 4 times on caps != to 0
{
$count = $row['giveaway_cap'];
if($count != '0')
{
$prizename = $row['name'];
$prizes[$prizename]=$count;
}
}
$prizewheel = array();
foreach ($prizes as $prize=>$value) // iterate 600 times if caps !=0
{
$prizewheel = array_merge($prizewheel, array_fill(0, $value, $prize));
}
$finalprize=array();
$f = 0;
while($f < 12) //iterate 12 times is # of caps >= 12,final array
{
$prizename = $prizewheel[array_rand($prizewheel)];
$finalprize[] = $prizename;
$f++;
}
?>

You could generate a random array like this:
$rows = array(
array(
'id' => 1,
'name' => 'PRIZE1',
'giveaway_cap' => 255,
),
array(
'id' => 2,
'name' => 'PRIZE2',
'giveaway_cap' => 300,
),
array(
'id' => 3,
'name' => 'PRIZE3',
'giveaway_cap' => 30,
),
array(
'id' => 4,
'name' => 'PRIZE4',
'giveaway_cap' => 15,
),
);
$output = array();
foreach ($rows as $row) {
for ($i = 0; $i < $row['giveaway_cap']; $i++) {
$output[] = $row['name'];
}
}
shuffle($output);
//to get the next prize
$nextPrizeKey = array_rand($output);
$nextPrize = $output[$nextPrizeKey];
//remove the won prize
unset($output[$nextPrizeKey]);
What this does is creates a separate element in the array for each of the prizes giveaway_caps. Then as prizes are won you reduce the number of the giveaway_cap and this will reduce the chance that a user will hit that prize again.
If you wanted the array to be static you could save it after the initial generation, either in a database or caching (APC, Memcahche). Then you would just remove each entry that the users land on until there are none left. You could get the next prize with array_rand and then remove the key from the array.
I hope this helps.
Mic

Related

Pick random value by weight php

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];
}

How to create equally distributed groups of three?

For a website application I need to form random groups of three. The user him/herself cannot grade him/herself (so cannot be part of the group). There will always be a minimum of 4 users.
For example, if there were 5 users, I would have an array as such: Array(0,1,2,3,4) and I would want the output to be (where the key is the user and the data is the group of 3).
Array(
[0 : 1,2,3],
[1 : 0,2,4],
[2 : 1,4,3],
[3 : 0,1,4],
[4 : 0,2,3]
);
Notice the user is never in the group and every user is assigned exactly 3 times.
You cannot simply randomly select users to groups because it might be assigned more than 3 times and force some groups to have less than 3 users in the group.
I hope I explained this problem appropriately and someone will be able to help.
Here is some code that puts three random numbers in a dictionary and works fine for [0,1,2,3] but will (most likely) fail for anything larger because the number will not be equally distributed (and will continue in the while loop forever because there are no possible numbers left).
$rows = range(0,3);
$array_size = count($rows);
$peers_array = Array();
$number_count = array_fill(0, $array_size, 0);
foreach($rows as $index => $row){
$randomNumbers = Array();
for($x = 0; $x < 3; ++$x){
$randomNumber = rand(0, $array_size-1);
while($randomNumber==$index or in_array($randomNumber, $randomNumbers) or $number_count[$randomNumber]>2)
$randomNumber = rand(0, $array_size-1);
$number_count[$randomNumber]++;
$randomNumbers[] = $randomNumber;
}
$peers_array[$index] = $randomNumbers;
}
print_R( $peers_array);
Ok so I've come up with my own solution. It took a little thought but thanks to suggestions I was able to come up with this solution:
First it generates a range from 0 to the number of users - 1 (e.g. for 5 it would give [0,1,2,3,4]) then every time after that it shifts the list one (e.g. [0,1,2,3,4] becomes [4,0,1,2,3]) then it takes each element at a given index of the array and puts it into a group (so if I only wanted groups of 2 it would give 0:[0,4] 1:[0,1] 2:[2,1] and so on...). You then shift the order between the number of users - the size of the group, don't ask just trust me. This guarantees all numbers appear an equal number of times but still randomizes the order.
The lines below accomplishes this:
function getUsers($user_num, $groups_of){
$index_list = range(0,$user_num-1);
$random_indexes = range(0,$user_num-1);
shuffle($random_indexes);
$groups = array();
foreach($index_list as $user_num)
$groups[] = array($random_indexes[$user_num]);
for($i = 0; $i<($groups_of-1); ++$i){
array_unshift($index_list, array_pop($index_list)); //puts the last element first
foreach($index_list as $index => $user_num)
$groups[$index][] = $random_indexes[$user_num];
}
$shift_number = rand(1, ($len_users-$groups_of));
for($i = 0; $i<$shift_number; ++$i)
array_unshift($groups, array_pop($groups));
return $groups
}
I was thinking array_pop would be a good approach, it works well for the first part of the problem, easy to get the current item and make sure it isn't available for the next part, however then you end up having to keep track of too many moving parts.
In the end went for array_diff to remove the current row from the original array.
$rows = range(0,15);
$results = array();
foreach ($rows as $row) {
$others = array_diff($rows,array($row));
shuffle($others);
$results[$row] = array_slice(array_values($others),0,3);
}
var_dump($results);
Results:
array (size=16)
0 =>
array (size=3)
0 => int 9
1 => int 1
2 => int 10
1 =>
array (size=3)
0 => int 10
1 => int 11
2 => int 14
2 =>
array (size=3)
0 => int 3
1 => int 15
2 => int 14
3 =>
array (size=3)
0 => int 9
1 => int 4
2 => int 1
etc...
I think this:(to generalize)
function getUsers($users,$groups_of){
$result = array();
for($i = 0; $i < $users; $i++){
$usernums = array();
while(count($usernums) < $groups_of){
$randomNumber = rand(0, $users-1);
if($i != $randomNumber && !in_array($randomNumber, $usernums)){
$usernums[] = $randomNumber;
}
}
$result[$i] = $usernums;
}
return $result;
}
$users = 5;
$groups_of = 3;
print_r(getUsers($users,$groups_of));

Array from Arrays

I have the following array:
$learners=array('Eliza'=87, 'Joe'=81, 'Anne'=69, 'Marley'=39, 'Teddy'=39, 'Jemma'=90, 'Sylvia'=87);
So far I have been able to separate the two arrays as follows:
$tudents=array_keys($learners);
$scores=array_values($learners);
The ranking is as follows:
Student Score Position
Jemma 90 1
Sylvia 87 2
Eliza 87 2
Joe 81 4
Anne 69 5
Marley 39 7
Teddy 69 7
I would like to create a new array with names as keys and positions as values i.e
$positions=array('Jemma'=1, 'Sylvia'=2, 'Eliza'=2, 'Joe'=4, 'Anne'=5, 'Marley'=7, 'Teddy'=7);
This will allow me to echo any name and position at any point on the script. I am not sure how to proceed.
The ranking is not straightforward if the scores have duplicates. If there is a tie at number 2, the 3rd position is skipped. If the tie occurs at the end of the scores, then both scores will be placed at the last position and the preceding position will be skipped, in the example above, position 6 has been skipped and the two 39s occupy position 7.
Any help will be appreciated
// Sort decending
arsort($data);
$vals = array_values($data);
$last = end($vals); // The lowest score
$prev = null;
$rank = 0;
$positions = array();
foreach($data as $student => $score) {
if ($score == $last) {
// The score is the same as the lowest, the rank is set to last position
$rank = count($data);
} else if ($prev != $score) {
// We only update if the score is not the same as prev
$rank++;
} else if ($prev == $score) {
// We matched on the key, replace any items with the
// same score with the current rank
$matches = array_keys($positions, $score);
foreach($matches as $key) {
$positions[$key] = $rank;
}
$positions[$student] = $rank;
// Now skip ahead to the next rank +1
$rank = $rank + count($matches) + 1;
continue;
}
$positions[$student] = $rank;
$prev = $score; // Remember the previous score
}
var_dump($positions);
Here's another solution:
First sort by value (the print_r is just to check progress).
arsort($learners);
print_r($learners);
Then make an array of rankings, but don't advance the rank if the score is the same as the previous element's score.
$rank = $pos = 1;
$prev_score = current($learners);
foreach ($learners as $name => $score) {
if ($score != $prev_score) {
$rank = $pos;
}
$ranking[$name] = $rank;
$prev_score = $score;
$pos++;
}
print_r($ranking);
Now correct the last entries, any element with the same score as the last element should be in 7th place. There's a rarely-used argument to array_keys() that searches for a given value.
$low_score = end($learners);
$last_place = count($learners);
foreach (array_keys($learners, $low_score) as $name) {
$ranking[$name] = $last_place;
}
print_r($ranking);
Output:
Array
(
[Jemma] => 90
[Sylvia] => 87
[Eliza] => 87
[Joe] => 81
[Anne] => 69
[Marley] => 39
[Teddy] => 39
)
Array
(
[Jemma] => 1
[Sylvia] => 2
[Eliza] => 2
[Joe] => 4
[Anne] => 5
[Marley] => 6
[Teddy] => 6
)
Array
(
[Jemma] => 1
[Sylvia] => 2
[Eliza] => 2
[Joe] => 4
[Anne] => 5
[Marley] => 7
[Teddy] => 7
)
Looks like PHP, right?
Basically go through your initial list and stuff them into a new array that uses the names as keys (you're in trouble here if two people have the same name, but I'm assuming this is a homework assignment and that's not an issue?)
$sorted = array();
for ($i=0;$i<count($positions);$i++) {
if (!isset($sorted[$positions[$i]["Student"]])) {
$sorted[$positions[$i]["Student"]] = $positions[$i]["Score"];
} else if ($sorted[$positions[$i]["Student"]]<$positions[$i]["Score"] {
$sorted[$positions[$i]["Student"]] = $positions[$i]["Score"];
}
}
What you're doing here is making an array where the KEY is the name of the student, and putting the first score you find in as the VALUE for that. So $sorted["Jemma"] = 90. Then if you hit that name again, and the score is higher than the current value for $sorted["Jemma"], you're replacing it.
After that, you run an arsort($sorted) to put it in order.

Check through an array and identify matching numbers

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

Prioritized Random Selection

I need to design an algorithm that displays ads on a website based on imprints. i record each imprint in DB. Cannot make use of mysql rand() limit 1 in my query as it will be uncontrolled query. i have to make sure that if an ad is selected its not only just an ad with lowest imprints but there is some randomness to it. Also New Ads get a biased selection some way.
In order to achieve this i have managed to write a function but somewhere I am not satisfied with it. Please go through it and let me know if it could done any better.
function getRandomAd($result){
while($row = mysql_fetch_assoc($result)){
$rows[] = $row;
}
$filteredRow = array();
foreach($rows as $row){
if($row['imprints'] == 0 ){
$row['piority'] = 10;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 0 and $row['imprints'] <= 5){
$row['piority'] = 8;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 5 and $row['imprints'] <= 15){
$row['piority'] = 6;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 15 and $row['imprints'] <= 30){
$row['piority'] = 4;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 30 and $row['imprints'] <= 60) {
$row['piority'] = 2;
$filteredRow[] = $row;
}
else{
$row['piority'] = 0;
$filteredRow[] = $row;
}
foreach($filteredRow as $row){
if($row['piority'] == 10 or $row['piority'] == 8) $high[] = $row;
if($row['piority'] == 6 or $row['piority'] == 4) $medium[] = $row;
if($row['piority'] == 2 or $row['piority'] == 0) $low[] = $row;
}
$countHigh = count($high);
$countMed = count($medium);
$countLow = count($low);
if($countHigh > $countLow and $countHigh > $countMed)$rowReturned = $high;
if($countMed > $countLow and $countMed > $countHigh) $rowReturned = $medium;
if($countLow > $countMed and $countLow > $countHigh) $rowReturned = $low;
}
return $rowReturned[array_rand($rowReturned,1)];
}
Returning all the rows and using PHP to sort and determine which row to spit out is terribly inefficient. This would be better handled on the database end than in PHP.
Also, your current set of if statements overlap with the >= operators.
Finally, I know you didn't do this but just a general note: you should almost never use ORDER BY RAND() with MySQL because the performance is terrible. This is because when you use a function return value to order results MySQL can't use an index for the order operation.
Here's a better solution ...
Generate a weighted random number in PHP that we can use as a condition for our query
Write a query that will return only one result based on the weighted random number
So, lets generate a random integer weighted towards the lower end of the imprint spectrum:
$max = 60;
$random = floor($max - mt_rand(mt_rand(1, $max), $max));
And write a query that uses the weighted random number to pick a single result from the table:
$query = '
SELECT id, advert_txt
FROM table
WHERE imprints >= '.$random.'
ORDER BY imprints ASC
LIMIT 1
';
Note that you would normally want to escape a PHP variable in a query like this (or preferably use prepared statements with PDO), but since we know the variable is an integer in this case it's okay.
This solution will appropriately weight returned results based on their respective imprint counts while maintaining MySQL's ability to use indexes to select the record, which is of paramount importance if you have a large number of rows in the table.
UPDATE
I would add that you could tweak the random number generation to your own needs. With this particular solution you'll never get back a row with >= 60 imprints. The solution remains sound though, because it's trivial to tweak how you determine the condition for the query. For example, maybe you would first run a query to get the MAX(imprints) from the database and use that value for your $max when determining the random number.
And second, just to demonstrate the behavior of the random number generation specified above, if you run it 10,000 times using a $max = 10 you'll get results similar to this:
0: 2966
1: 1965
2: 1420
3: 1101
4: 820
5: 621
6: 457
7: 333
8: 210
9: 107
UPDATE 2
In response to your comment for further clarification ...
In my original update I'm trying to say that if you just hard-code in the max value as 60, the original code will never return a row where imprints >= 60. This means that if you just use that code on it's own, it will work fine for a while but eventually (assuming you increment the imprints column each time you display an advertisement) all your records will have been imprinted 60+ times and you won't get any results back.
So to avoid such an issue the entire process would be something like this:
Query the table once to get the highest imprints column value to use for your maximum random value range like so: SELECT MAX(imprints) AS max FROM my_table;
Use the max value you retrieved in step one in the $max variable when generating your random weighted number.
Update the table by incrementing the imprints column up for the row you retrieved in step 2.
function getRandomAd($result) {
$imprint_weights = array(
array(
'min' => 0,
'max' => 0,
'weight' => 10
),
array(
'min' => 1,
'max' => 5,
'weight' => 8
),
array(
'min' => 6,
'max' => 15,
'weight' => 6
),
array(
'min' => 16,
'max' => 30,
'weight' => 4
),
array(
'min' => 31,
'max' => 60,
'weight' => 2
)
);
$weighted = array();
foreach ($result as $row) {
$imprints = $row['imprints'];
$imprint_weight = 1;
foreach ($imprint_weights as $match) {
if ($imprints >= $match['min'] && $imprints <= $match['max']) {
$imprint_weight = $match['weight'];
}
}
$weighted = array_merge($weighted, array_fill(0, $imprint_weight, $row));
}
return $weighted[array_rand($weighted)];
}
$data = array();
for ($x = 1; $x <= 100; $x++) {
$data[] = array(
'id' => $x,
'imprints' => rand(0, 65)
);
}
print_r(getRandomAd($data));
This provides an alternative way of doing what you want. The benifit here being that you can more easily change how an amount of imprints affect the weight of an ad, and also easily add more parameters (like age) to influence the selection of an ad.

Categories