Here is a situation:
race 1 = 7
race 2 = 3
race 3 = 1
race 4 = 2
race 5 = 6
race 6 = 2
race 7 = 7
race 8 = 3
The smaller the number the better since these are race positions. race number 1 MUST be added, regardless of it's magnitude and must be added to any 5 others that are selected on merit. So basically I want to use PHP to add up 6 of the best races out of the 8 and the 6 must include 1, regardless of whether it is among the best
I thoughtn of sorting the numbers by having them sorted from lowest to highest and adding the first 6. The problem is that if race 1, is not among the best 6, then this cannot work.
Any help will be appreciated, I am still thinking so I cannot provide anything in terms of what i have tried as everything is still at thought level!
<?php
$race = array( 1 =>7, 2 => 3 );//etc
$sum = $race[1];
unset( $race[1] );
sort( $race, SORT_NUMERIC );
for( $i = 0; $i < 5; $i++ )$sum += array_pop( $race );
<?php
/* if manually creating the array */
$race1 = 7;
$races = array("race2" => 3, "race3" => 1, "race4" => 2); //...
/* if the array is created programmatically (preferred */
$race1 = $races[0];
$races = array_pop($races); //drops first element and resets the index
/* then.... */
asort($races);
$total = $race1;
for($i=0; $i<6; $i++)
{
$total += $races[$i];
}
?>
Related
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
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));
I've got a problem which takes up a lot of time. While it's supposed to be really easy (because it's just so simple!).
My problem:
I have these values inside two arraylists:
$row[0]->COUNTER1 20 10 15
$row[0]->GRADE_POINTS 0 3 5
I am supposed to change these arraylists into this example:
$row[0]->COUNTER1 20 0 0 10 0 15
$row[0]->GRADE_POINTS 0 1 2 3 4 5
So the missing values are supposed to have 0 as the counter.
While this isn't that hard to do it I'm probably over thinking it.
The code which I use to create the first set of numbers is:
$result = new SimpleXMLElement($xmlresult);
$xml = $result->children("soapenv", true)->Body->children();
$xmlBody = $xml[0];
$countPerResultaat = array();
foreach($xmlBody->query[0] as $row)
{
$countPerResultaat[] = (int) $row[0]->COUNTER1;
$xaxis[] = (string) $row[0]->GRADE_POINTS;
}
The code I though that would work is this:
for($i; $i<=10; $i++){
//foreach($xmlBody->query[0] as $row)
//{
$row = $xmlBody->query[0];
if($i==$row[0]->GRADE_POINTS){
$countPerResultaat[] = (int) $row[0]->COUNTER1;
$xaxis[] = (string) $row[0]->GRADE_POINTS;
}else{
$xaxis[] = (string) $i;
$countPerResultaat[] = (int) 0;
}
}
But the row can't be used, I really don't know how to fix this. My only solution would be to use another for-loop, which would create 100 values probably.
Thanks for helping in advance!
If I understand correctly and if $row[0]->COUNTER1 and $row[0]->GRADE_POINTS are arrays. You will just need to loop them and use in_array(). Consider this example:
$counter1 = array(20, 10, 15);
$grade_points = array(0, 3, 5);
$new_grade_points = range(min($grade_points), max($grade_points));
foreach($new_grade_points as $key => &$value) {
// check if its part of the missing index if not get the value,
// if its the missing index put 0
$value = (in_array($key, $grade_points)) ? array_shift($counter1) : 0;
}
$counter1 = array_values($new_grade_points); // now contains 20,0,0,10,0,15
$grade_points = array_keys($new_grade_points); // now contains 0,1,2,3,4,5
print_r($counter1);
Sample Output:
Array
(
[0] => 20
[1] => 0
[2] => 0
[3] => 10
[4] => 0
[5] => 15
)
I think you want to count the amount of times a grade has been given? You should just loop through as usual, and when there is no value you should/could define it as 0. After that just count how many duplicates you have in the array. That way the key of the $xaxis is the grade, and the value is the amount of times that grade has been given.
foreach($xmlBody->query[0] as $row)
{
$counter = (int) $row[0]->COUNTER1;
if(counter) $countPerResultaat[] = $counter;
else $countPerResultaat[] = 0;
}
$xaxis = array_count_values($counter);
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 figure out how to use a database of sales completions to influence split testing.
For example, say I have four different page layouts and for each I have the following stats:
Version 1: 6 sales,
Version 2: 1 sale,
Version 3: 3 sales,
Version 4: 4 sales,
Then it would make sense to have version 1 shown most often, and version 4 being next, while version 2 should hardly be shown at all.
Any ideas how I can achieve this?
Very simple solution, mainly depends on how your data looks currently as to what solution is easiest though.
$sales = array
(
1 => 6,
2 => 1,
3 => 3,
4 => 4
);
$weight = array();
foreach ($sales AS $layout => $num_sales)
{
for ($i = 0; $i < $num_sales; $i++)
{
$weight[] = $layout;
}
}
/*
$weight = array
(
1, 1, 1, 1, 1, 1,
2,
3, 3, 3,
4, 4, 4, 4
);
*/
// Pick a random one to use
$layout_to_use = $weight[rand(0, count($weight))];
Let's say you have display the layouts with following weights:
Version 1: 6
Version 4: 4
Version 3: 3
Version 2: 1
Sum of weight is 14 and weight 6 means that you want to show page approximately 6 times in 14 requests.
If you were using database (which I assume you do it would be)
SELECT SUM(weights) FROM version;
Easiest way to implement random selection with different probabilities of hitting item is to sum weights, sort items by their weights and than just iterate trough all items until you hit zero:
$i = rand( 1, $sum);
foreach( $items as $item){
$i -= $item->weight;
if( $i <= 0){
break;
}
}
// $item is now your desired $item
$items should be sorted list of class Item{ public $weight; ... }, because it's most probable that the first element will be used (second the second and so on) and least iterations will be required
What's happening inside:
$i = 12;
// first iteration, $weight = 6
$i = 6; // condition didn't match
// second iteration, $weight = 4
$i = 2; // condition didn't match
// third iteration, $weight = 3
$i = -1; // condition matched
// using version 3