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
Related
How to burst the following rows into 3 groups where the sum of "dollars" is 10. All rows must be used and none more than once.
row|dollars
1|1
2|1
3|1
4|1
5|1
6|1
7|3
8|4
9|7
10|10
One (of many possible) desired outcomes would be...
Row Group 1 = 10
Row Group 2 = 7,9
Row Group 3 = 1,2,3,4,5,6,8
Extra Credit:
When it's not mathematically possible to get a sum of exactly $10 in each group, is there a formula for getting us closest to that?
I though maybe " HAVING sum(dollar) = 10 " or, for a close solution, just sorting and distributing one row to one group, but that didn't get me close.
Group Row By Sum of Specific Column equal to Specific Value sort of touches on this, but, assuming their could be millions of rows, there would be performance issues.
I'm stumped.
If it helps, I'm using php and mysql. A sql-only solution would be ideal, but some combination would be great too. Thanks for any suggestions.
Edit: If I wasn't clear, I want to return the rows that make this possible, not just "group by" them.
I don't think you could do it with just sql, but it could certainly help out. I'd start by having a query like select * from data where dollars <= 10 order by dollars desc and then make an algorithm in php that went through the results and added up dollars until it found three sets that add up to 10.
It would start out adding from larger to smaller until it found a correct sum, then store it, remove those items from the list and start again, three times. I'm on my phone, but will update the answer with a working example when I get to my computer.
EDIT: got to my computer. This is very messy code, but it should lead you in the right direction.
$dataset = [10,7,4,3,1,1,1,1,1,1];
$results = [];
function add_to_ten(&$data) {
$sum = 0;
$results = [];
foreach ($data as $index => &$datum) {
if ($datum == 0) {
continue;
}
if ($sum + $datum <= 10) {
$sum += $datum;
$results[] = $index;
$datum = 0;
}
if ($sum == 10) {
return $results;
}
}
}
print_r(add_to_ten($dataset));
print_r(add_to_ten($dataset));
print_r(add_to_ten($dataset));
i am working on an algorithm for sorting teams based on highest number of score. Teams are to be generated from a list of players. The conditions for creating a team is
It should have 6 players.
The collective salary for 6 players must be less than or equal to 50K.
Teams are to be generated based on highest collective projection.
What i did to get this result is generate all possibilities of team then run checks on them to exclude those teams that have more than 50K salary and then sort the remainder based on projection. But generating all the possibilities takes a lot of time and sometimes it consume all the memory. For a list of 160 players it takes around 90 seconds. Here is the code
$base_array = array();
$query1 = mysqli_query($conn, "SELECT * FROM temp_players ORDER BY projection DESC");
while($row1 = mysqli_fetch_array($query1))
{
$player = array();
$mma_id = $row1['mma_player_id'];
$salary = $row1['salary'];
$projection = $row1['projection'];
$wclass = $row1['wclass'];
array_push($player, $mma_id);
array_push($player, $salary);
array_push($player, $projection);
array_push($player, $wclass);
array_push($base_array, $player);
}
$result_base_array = array();
$totalsalary = 0;
for($i=0; $i<count($base_array)-5; $i++)
{
for($j=$i+1; $j<count($base_array)-4; $j++)
{
for($k=$j+1; $k<count($base_array)-3; $k++)
{
for($l=$k+1; $l<count($base_array)-2; $l++)
{
for($m=$l+1; $m<count($base_array)-1; $m++)
{
for($n=$m+1; $n<count($base_array)-0; $n++)
{
$totalsalary = $base_array[$i][1]+$base_array[$j][1]+$base_array[$k][1]+$base_array[$l][1]+$base_array[$m][1]+$base_array[$n][1];
$totalprojection = $base_array[$i][2]+$base_array[$j][2]+$base_array[$k][2]+$base_array[$l][2]+$base_array[$m][2]+$base_array[$n][2];
if($totalsalary <= 50000)
{
array_push($result_base_array,
array($base_array[$i], $base_array[$j], $base_array[$k], $base_array[$l], $base_array[$m], $base_array[$n],
$totalprojection, $totalsalary)
);
}
}
}
}
}
}
}
usort($result_base_array, "cmp");
And the cmp function
function cmp($a, $b) {
if ($a[6] == $b[6]) {
return 0;
}
return ($a[6] < $b[6]) ? 1 : -1;
}
Is there anyway to reduce the time it takes to do this task, or any other workaround for getting the desired number of teams
Regards
Because number of elements in array can be very big (for example 100 players can generate 1.2*10^9 teams), you can't hold it in memory. Try to save resulting array to file by parts (truncate array after each save). Then use external file sorting.
It will be slow, but at least it will not fall because of memory.
If you need top n teams (like 10 teams with highest projection) then you should convert code that generates result_base_array to Generator, so it will yield next team instead of pushing it into array. Then iterate over this generator. On each iteration add new item to sorted resulted array and cut redundant elements.
Depending on whether the salaries are often the cause of exclusion, you could perform tests on this in the other loops as well. If after 4 player selections their summed salaries are already above 50K, there is no use to select the remaining 2 players. This could save you some iterations.
This can be further improved by remembering the lowest 6 salaries in the pack, and then check if after selecting 4 members you would still stay under 50K if you would add the 2 lowest existing salaries. If this is not possible, then again it is of no use to try to add the two remaining players. Of course, this can be done at each stage of the selection (after selecting 1 player, 2 players, ...)
Another related improvement comes into play when you sort your data by ascending salary. If after selecting the 4th player, the above logic brings you to conclude you cannot stay under 50K by adding 2 more players, then there is no use to replace the 4th player with the next one in the data series either: that player would have a greater salary, so it would also yield to a total above 50K. So that means you can backtrack immediately and work on the 3rd player selection.
As others pointed out, the number of potential solutions is enormous. For 160 teams and a team size of 6 members, the number of combinations is:
160 . 159 . 158 . 157 . 156 . 155
--------------------------------- = 21 193 254 160
6 . 5 . 4 . 3 . 2
21 billion entries is a stretch for memory, and probably not useful to you either: will you really be interested in the team at the 4 432 456 911th place?
You'll probably be interested in something like the top-10 of those teams (in terms of projection). This you can achieve by keeping a list of 10 best teams, and then, when you get a new team with an acceptable salary, you add it to that list, keeping it sorted (via a binary search), and ejecting the entry with the lowest projection from that top-10.
Here is the code you could use:
$base_array = array();
// Order by salary, ascending, and only select what you need
$query1 = mysqli_query($conn, "
SELECT mma_player_id, salary, projection, wclass
FROM temp_players
ORDER BY salary ASC");
// Specify with option argument that you only need the associative keys:
while($row1 = mysqli_fetch_array($query1, MYSQLI_ASSOC)) {
// Keep the named keys, it makes interpreting the data easier:
$base_array[] = $row1;
}
function combinations($base_array, $salary_limit, $team_size) {
// Get lowest salaries, so we know the least value that still needs to
// be added when composing a team. This will allow an early exit when
// the cumulative salary is already too great to stay under the limit.
$remaining_salary = [];
foreach ($base_array as $i => $row) {
if ($i == $team_size) break;
array_unshift($remaining_salary, $salary_limit);
$salary_limit -= $row['salary'];
}
$result = [];
$stack = [0];
$sum_salary = [0];
$sum_projection = [0];
$index = 0;
while (true) {
$player = $base_array[$stack[$index]];
if ($sum_salary[$index] + $player['salary'] <= $remaining_salary[$index]) {
$result[$index] = $player;
if ($index == $team_size - 1) {
// Use yield so we don't need to build an enormous result array:
yield [
"total_salary" => $sum_salary[$index] + $player['salary'],
"total_projection" => $sum_projection[$index] + $player['projection'],
"members" => $result
];
} else {
$index++;
$sum_salary[$index] = $sum_salary[$index-1] + $player['salary'];
$sum_projection[$index] = $sum_projection[$index-1] + $player['projection'];
$stack[$index] = $stack[$index-1];
}
} else {
$index--;
}
while (true) {
if ($index < 0) {
return; // all done
}
$stack[$index]++;
if ($stack[$index] <= count($base_array) - $team_size + $index) break;
$index--;
}
}
}
// Helper function to quickly find where to insert a value in an ordered list
function binary_search($needle, $haystack) {
$high = count($haystack)-1;
$low = 0;
while ($high >= $low) {
$mid = (int)floor(($high + $low) / 2);
$val = $haystack[$mid];
if ($needle < $val) {
$high = $mid - 1;
} elseif ($needle > $val) {
$low = $mid + 1;
} else {
return $mid;
}
}
return $low;
}
$top_team_count = 10; // set this to the desired size of the output
$top_teams = []; // this will be the output
$top_projections = [];
foreach(combinations($base_array, 50000, 6) as $team) {
$j = binary_search($team['total_projection'], $top_projections);
array_splice($top_teams, $j, 0, [$team]);
array_splice($top_projections, $j, 0, [$team['total_projection']]);
if (count($top_teams) > $top_team_count) {
// forget about lowest projection, to keep memory usage low
array_shift($top_teams);
array_shift($top_projections);
}
}
$top_teams = array_reverse($top_teams); // Put highest projection first
print_r($top_teams);
Have a look at the demo on eval.in, which just generates 12 players with random salary and projection data.
Final remarks
Even with the above mentioned optimisations, doing this for 160 teams might still require a lot of iterations. The more often the salaries amount to more than 50K, the better the performance will be. If this never happens, the algorithm cannot escape from having to look at each of the 21 billion combinations. If you would know beforehand that the 50K limit would not play any role, you would of course order the data by descending projection, like you originally did.
Another optimisation could be if you would feed back into the combination function the 10th highest team projection you have so far. The function could then eliminate combinations that would lead to a lower total projection. You could first take the 6 highest player projection values and use this to determine how high a partial team projection can still grow by adding the missing players. It might turn out that this becomes impossible after having selected a few players, and then you can skip some iterations, much like done on the basis of salaries.
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 am look to code a system by where a given number is given by the user and then a script will work out the total number of combinations using that number all the way down to one.
so if the number was 10 it would need to find all the combinatins of ten numbers, nine numbers, 8 numbers and so on down to one!
So say if the number is 3
then you have
3 number combinations (1,2,3) => 1
2 number combinations (1,2)(1,3)(2,3) => 3
1 number combinations (1)(2)(3) => 3
if the nuber is 4
then you would have
4 number combinations (1,2,3,4) => 1
3 number combinations (1,2,3)(1,2,4)(1,3,4)(2,3,4) => 4
2 number combinations (1,2)(1,3)(1,4)(2,3)(2,4)(3,4) => 6
1 number combinations (1)(2)(3)(4) => 4
and so on...
i wouldn't need different order combinations just the combinations itself and again the number could be anything though its unlikely to be over 10
does anyone know a way to code this so any given nubmer it will produce the combinations?
at the moment i have hard coded up to 5 numbers but its alot of coding, there has to be a simpler way of doing it :)
Hope this makes sense :)
Any help/directions to go woud be hugely appreciated
Thanks
If you just want to know how many combinations there are (regardless of the order of the elements within the combinations) use this formula:
n!/(k!(n-k)!) where n is how many numbers you have and k is how many numbers you have per combination. So if the number is 9 and you want to know how many combinations you can have grouping those numbers in pairs. it would give you: 9!/(2!(9-2)!) = 36
helpful:
http://en.wikipedia.org/wiki/Combination
And as for coding it in php
$n = $_GET['number'];
echo "Combinations for $n numbers:";
for ($k = 1; $k <= $n; $k++) {
$combinations = factorial($n)/(factorial($k)*factorial($n-$k));
echo "<br>Grouped by $k:" . $combinations;
}
function factorial($number) {
if ($number == 0) return 1;
return $number * factorial($number - 1);
}
Edit:
To print out all combinations:
combinations(range(1, $n), $k);
function combinations($numbers, $count, $prefix = ""){
if ($count == 0) {
echo "<br>". $prefix;
} else {
foreach ($numbers as $number) {
$offset = array_search ( $number , $numbers)+1;
combinations(array_slice($numbers, $offset), $count-1, $prefix . $number);
}
}
}
I have a one to many relationship between a Report and a Location. My goal is to narrow my list of Reports down to as few Reports as possible containing all of the Locations represented.
If I simplify it to lists of numbers it would look like the following with the key being the report and the array being the list of locations:
{
1:[1,2],
2:[1],
3:[2,3],
4:[1,3,4]
}
The ideal solution would be to select Reports 1 or 3 and 4. Either 1 or 3 could be selected because they both include Location 2 and duplicate Location 1 with Report 4. Report 4 needs selected because it is the only one with Location 4.
Efficiency isn't a major concern. How is the best way to narrow the list down using PHP?
NP-completeness strikes again.
The problem you're trying to solve is called Set Cover, and, sure enough, is NP-Complete.
This means that an "efficient" (read, polynomial-time) algorithm for it is unlikely to exist.
The good news is that there are simple approximation algorithms that give you a decent approximation.
See this for how the "obvious" greedy algorithm (at each point, pick the report with the largest number of uncovered locations) gives you a log (R) approximation, where R is the number of reports (actually, it's even better than that).
If efficiency is not a problem as you have stated, I can propose you O(2^n * k) algorithm, where n is the number of lists and k is the sum of their lengths. Just take all possible combinations using bitmasks and for each of them calculate whether it covers everything or not.
P.S.
Here is an implementation(http://ideone.com/bAGpbL):
$arr = array(
0 => array(1,2),
1 => array(1),
2 => array(2,3),
3 => array(1,3,4),
);
// It is assumed that all indexes are sequential starting from 0
$total_cover = array();
foreach($arr as $sub_arr) {
foreach($sub_arr as $value) {
$total_cover[$value] = true;
}
}
$n = count($arr);
$best_cover = array_keys($arr);
for($i = 0; $i < (1 << $n); $i++) {
$cover = array();
$selected_list = array();
for($j = 0; $j < $n; $j++) {
if(($i >> $j) & 1) {
$selected_list[] = $j;
foreach($arr[$j] as $value) {
$cover[$value] = true;
}
}
}
$good_cover = true;
foreach($total_cover as $key => $value) {
if(!isset($cover[$key])) {
$good_cover = false;
break;
}
}
if($good_cover && count($selected_list) < count($best_cover)) {
$best_cover = $selected_list;
}
}
var_dump($best_cover);