Only show the advertisment that is closest to a random number - php

I got this where I want to display only one advertisement but randomly and it has more changes of displaying if the budget is higher. This is my code right now:
I already have the part where it calculates the percentage from 100% with alot of decimals but I'm lost in trying to display the advertisment with the percentage calculated and only show ONE advertisment.
If you have any questions or something like that, feel free to ask!
#php
$advertisment = DB::table('advertisment')
->orderBy('id', 'desc')
->get();
$totalbudget = 0;
$random = rand(100, 100000000000000);
$random1 = $random / 1000000000000;
echo $random1;
echo '<br><br>';
foreach ($advertisment as $ad) {
$total = $totalbudget + $ad->budget;
$totalbudget = $total;
}
// check what advertisment is the closest to the random number given
#endphp

I think I understand. If all of your advertisements have a budget, and your random1 represents a percent (1 - 100), you can try this in your existing function. It will also still calculate the total you were looking for, just use the Laravel sum() function on the collection. Note, I renamed the advertisement collection to plural for clarity.
$totalbudget = $advertisements->sum('budget');
$closest = null;
$closestAd = false
foreach ($advertisements as $ad) {
// first get percentage of total for this budget:
$thisPercent = ($ad->budget * 100) / $totalbudget;
if ($closest === null || abs($random1 - $closest) > abs($thisPercent - $random1)) {
$closest = $thisPercent;
$colsestAd = $ad;
}
}
At the end of the loop, you have both the percentage ($closest) and the ad itself ($closestAd) and the budget if you wish ($closestAd->budget).
There are a few unknowns, so this code may not work exactly. Please use it as a guide and tweak to get it working for you.

Related

Puzzler: How To Group Rows By A Particular Sum of a Particular Column

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));

PHP: Optimizing array iteration

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.

PHP distribute percentage based on total numbers

I'm trying to distribute 100% to total numbers (not equally), it can be done manually but I'm looking for a automatically way in PHP. I had to open calculator and get it done for manual.
What I'm trying to achieve is the result similar to this:
$value = 10000;
$total_numbers = 9
$a1 = $value*0.2;
$a2 = $value*0.175;
$a3 = $value*0.15;
$a4 = $value*0.125;
$a5 = $value*0.1;
$a6 = $value*0.08;
$a7 = $value*0.07;
$a8 = $value*0.05;
$a9 = $value*0.04;
So as you can see, the first variables have more quantity than the later ones, but if you add these, it will be 1 which is 100%. So lets say I have total_numbers=20 then I'll have to re-write it and get a calculator and do it the hard way to accomplish my goal. Is there any way this can be done automatically with a function where I can just tell the total number and it can distribute it to proportions or something?
The first one will always be bigger than rest, then second one bigger than rest but smaller than first, third one being greater than rest but small than first and second, and so on.
function distributeValue($value, $num) {
$parts = $num * ($num + 1) / 2;
$values = [];
for ($i = $num; $i > 1; --$i) {
$values[] = round($value * $i / $parts);
}
$values[] = $value - array_sum($values);
return $values;
}
var_dump(distributeValue(10000, 9));
This works by calculating the $numth triangle number (the number you get by adding all the numbers from 1 to $num) and dividing the total value up into this number of parts.
It then starts by taking $num parts, then $num-1 parts and so on.
Since it's rounding the numbers, the last step is to take the total minus all the other values which is around one part. If you are fine with getting floats instead of ints out, then you can remove the $values[] = $value - array_sum($values); line and change the condition of the for loop to $i > 0.

Find the matched value of an array with a given value

I have an array of value series like:
$series = [100,300,500,800,1000,3000,5000,10000,15000,20000];
Another value getting from DB like:
$point = $data[‘earned_point’];
I need the highest match from the series. such as I got a value from db (1500) the highest match of the value is 1000 in the series, so I need to get the $series[4] and make it
$reward = $series[4] * 0.1;
I'll run it in a loop to do it for all the values got from DB.
I'm posting alternate code as the accepted answer while is correct can be very inefficient if you are working with a large array.
<?php
function computeReward($series, $point, $percent = 0.1){
arsort($series); // sort the series in reverse so we can pass any array and the highest number will be the first because we are looking for a number lower or equal to our point
foreach($series as $min_point){
if($min_point <= $point){
return $min_point * $percent; // return the min_point * the percentage, this stops the loop there for being more efficient
}
}
}
$series = [100,300,500,800,1000,3000,5000,10000,15000,20000];
$point = $data['earned_point'];
$reward = computeReward($series, $point);
?>
Do you mean you want to get which highest $series item is equal or less than $point ?
<?php
$series = [100,300,500,800,1000,3000,5000,10000,15000,20000];
$point = $data['earned_point'];
foreach ($series as $min_point) {
if ($point >= $min_point) {
$matched_min_point = $min_point;
}
}
$reward = $matched_min_point*0.1;
Let me know if this works for you

Math average with php

Time to test your math skills...
I'm using php to find the average of $num1, $num2, $num3 and so on; upto an unset amount of numbers. It then saves that average to a database.
Next time the php script is called a new number is added to the mix.
Is there a math (most likely algebra) equation that I can use to find the average of the original numbers with the new number included. Or do I need to save the original numbers in the database so I can query them and re-calculate the entire bunch of numbers together?
array_sum($values) / count($values)
If what you mean by average is the 'mean' and you don't want to store all numbers then store their count:
$last_average = 100;
$total_numbers = 10;
$new_number = 54;
$new_average = (($last_average * $total_numbers) + $new_number) / ($total_numbers + 1);
Average = Sum / Number of values
Just store all 3 values, there's no need for anything complicated.
If you store the Average and Sum then calculate Number of values you'll lose a little accuracy due to truncation of Average.
If you store the Average and Number of values then calculate Sum you'll lose even more accuracy. You have more margin for error in calculating a correct value for Number of values than Sum thanks to it being an integer.
<?php
function avrg()
{
$count = func_num_args();
$args = func_get_args();
return (array_sum($args) / $count);
}
?>
http://php.net/manual/en/function.array-sum.php#101727
Thought that I should share my function
function avg(array $values) {
$sum = array_sum($values);
$count = count($values);
return ($count !== 0)? $sum / $count: NAN;
}
echo avg([1, 2, 3, 4]); // 2.5
Will return the average and also take into account 0, for example dividing by zero always returns NaN (Not a number)
1/0 = NaN
0/0 = NaN
If you know the amount of numbers you can calculate the old sum, add the new one and divide by the old amount plus one.
$oldsum = $average * $amount;
$newaverage = ($oldsum + $newnum) / ($amount + 1);
Typically what you might do is save two pieces of information:
the sum of all the numbers
the count of numbers
Whenever you want to get the average, divide the sum by the count (taking care for the case of count == 0, of course). Whenever you want to include a new number, add the new number to the sum and increment the count by 1.
This is called a 'running average' or 'moving average'.
If the database stores the average and the number of values averaged, it will be possible to calculate a new running average for each new value.
function avgvals($avg_vals,$avg_delimiter=',') {
if ( (is_string($avg_vals) && strlen($avg_vals) > 2) && (is_string($avg_delimiter) && !empty($avg_delimiter)) ) {
$average_vals = explode($avg_delimiter, $avg_vals);
$return_vals = ( array_sum($average_vals) / count($average_vals) );
} elseif ( (is_string($avg_vals) && strlen($avg_vals) <= 2) && (is_string($avg_delimiter) && !empty($avg_delimiter)) ) {
$return_vals = $avg_vals;
} else {
$return_vals = FALSE;
}
return $return_vals;
}
Code:
function avg($list){
$sum = array_sum($list);
$count = count($list);
return ($count)? $sum / $count: NAN;
}
print ("Average: ".avg([1,2,3,4,5]));
Output:
Average: 3
You need to save all the original numbers in the database.

Categories