Calculating average from a percent - php

I am using DW to create record set that calculates the percentage from a table grouped by hospital name. I need to get the average of the percentage (by summation of the percentage divided by the number of hospital). I have only one table.
mysql_select_db($database_localapconnect, $localapconnect);
$query_Recordset1 = "SELECT COUNT(IF(ap_intervalname = 'Less than or equal to 12 hours', ap_intervalname, NULL))/ COUNT(ap_intervalname) *100 AS 'percent' FROM maintble GROUP BY `hospitalname`";
$Recordset1 = mysql_query($query_Recordset1, $localapconnect) or die(mysql_error());
$totalRows_Recordset1 = mysql_num_rows($Recordset1);
while($row_Recordset1 = mysql_fetch_array($Recordset1)) {
print_r( $row_Recordset1['percent'].'<br>');
}
print_r ($totalRows_Recordset1).'<br>' ;
echo sum($row_Recordset1['percent'])/$totalRows_Recordset1;
result:
83.0189
98.0000
86.2745
68.0365
94.9686
78.4314
6
I get the following error:
Fatal error: Call to undefined function sum() in C:\wamp\www\ap_database\Untitled-1.php on line 49

The function sum doesn't exist in PHP. If you want the sum of the elements of an array, you should use array_sum
But, in this specific case, I think it's better sum inside the loop, because you are already accessing all the elements anyway...
For example, like this:
$totalRows_Recordset1 = mysql_num_rows($Recordset1);
$sum = 0.0;
while($row_Recordset1 = mysql_fetch_array($Recordset1)) {
print_r( $row_Recordset1['percent'].'<br>');
$sum += $row_Recordset1['percent'];
}
print_r ($totalRows_Recordset1).'<br>' ;
echo $sum/$totalRows_Recordset1;

The easiest way is to use a subquery:
SELECT avg(percent)
FROM (SELECT AVG(CASE WHEN ap_intervalname = 'Less than or equal to 12 hours' THEN 100.0
ELSE 0 END) AS percent
FROM maintble
GROUP BY `hospitalname`
) m;

You're calling sum() in this line of php code.
sum($row_Recordset1['percent'])/$totalRows_Recordset1; /* mistake */
But that doesn't make sense. For one thing, $row_Recordset1['percent'] is just a plain old number. For another thing, php doesn't have a built in sum() function.
If you don't want to use SQL to do that computation, you need to add up the values in your recordset processing loop.
$sum = 0.0;
$count = 0;
while($row_Recordset1 = mysql_fetch_array($Recordset1)) {
$count ++;
$sum += $row_Recordset1['percent'];
print_r( $row_Recordset1['percent'].'<br>');
}
echo $sum / $count;
Notice I counted the rows explicitly rather than relying on num_rows(). That's for a good reason; under some circumstances num_rows() is not reliable before you read the recordset.

Related

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.

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

PHP - How To Make Pages For Overflowing Foreach?

Sorry for the rather unhelpful title, I am trying to make multiple pages for my foreach statement, as it reaches to a database with 100's of rows. Would I limit the query 1,30 for the first page, then 31-60 the second? If so how would I code that?
$totalRows = 100;// Total rows
$perPage = 30;
$pages = ceil($totalRows/$perPage);
for ($i = 0; $i<=$pages; $i++)
{
$sql = sprintf("SELECT * FROM table LIMIT %s, %s", $i*$perPage, $perPage);
// Execute
}
Queries can carry a significant overhead, so don't do multiple queries. Rather, do a single query, then use PHP to break the results up as you wish. You can use a loop counter along with the % operator to check if it's time to do a break.
For instance, if you are looping through results and your loop variable is $i, then you can do:
if ( $i > 0 && $i % 30 == 0 ) {
// Breaking action here ...
}
To detect every 30th iteration of your loop and implement your logic.

Adding data from an array to a new array

I have created an array of data, from which I can loop through specific fields and echo these values out, but what I need to do is add these values to a new array, ultimately allowing me to find the average of the values in the new array. As i've said, I can echo out the data, and I think I've figured how to get the average, if only I can create the new array! Any help would be greatly appreciated as I just can't find the answer anywhere, and I'm running low on talent!
My table contains approx 25 fields, im pulling out a number of rows based on a session variable. In the instance im working on, I need to take just the values from 1 column in the table, and add these to an array. The code below will loop through the values, and echo them out, 1 at a time:-
while ($cdarray=mysql_fetch_array($calldata)) {
echo $cdarray['score_total'];
}
This gives me 25555 which are the 4 values I would expect 25, 5, 5, 5
I've tried
while ($cdarray=mysql_fetch_array($calldata)) {
$cdts = $cdarray['score_total'];
$cdtsar = array($cdts);
}
Which results in $cdts being assigned a value of 5,
Any help greatly appreciated!!
This will get your data from the array, place it into a new one and calculates the average.
$cdtsar = array();
while ($cdarray=mysql_fetch_array($calldata)) {
$cdtsar[] = $cdarray['score_total'];
}
$average = array_sum($cdtsar) / count($cdtsar);
It actually prints 25 and 5 and 5 and 5, but there are no spaces in between so it looks like "25555". To verify this yourself:
while ($cdarray=mysql_fetch_array($calldata)) {
echo $cdarray['score_total'];
echo " / ";
}
To get the average, you can either use
$sum = $count = 0;
$average = null;
while ($cdarray=mysql_fetch_array($calldata)) {
$sum += $cdarray['score_total'];
++$count;
}
// Make sure to guard against divide by zero
if ($count) {
$average = $sum / $count;
}
or you might have the database calculate the average for you, if changing the query is an option.
If you want to assign the elements to the new array use like this
$cdtsar = array();
while ($cdarray=mysql_fetch_array($calldata)) {
array_push($cdtsar,$cdarray['score_total']);
}
To find the average of the array
$sum = array_sum($cdtsar);
$num = sizeof($cdtsar);
$avg = $sum/$num;
echo $avg;

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