PHP - How to compare two headings on a compass rose? - php

Working on a tool to make runway recommendations for flight simulation enthusiasts based off of the real world winds at a given airport. The ultimate goal is to compare, and return a list of available runways in a list, with the smallest wind variance displaying at the top of the list.
I would say that I probably have 95% of what I need, but where it gets slippery is for wind headings that approach 0 degrees (360 on a compass rose).
If runway heading is 029 and wind heading is 360, it is only a difference of 29 degrees, but the formula that I have written displays a difference of 331 degrees.
I have tried experimenting with abs() as part of the comparison but have gotten nowhere. I will link my current results here: https://extendsclass.com/php-bin/7eba5c8
Attempted switching comparisons for wind heading and runway heading (subtracting one from the other, and then the other way around) with the same result.
I am sure that the key lies in some little three line nonsense that I just cannot get the knack of (disadvantage of being a self-taught cowboy coder, I guess).
I saw a post about how to do it in C# from about 11 years ago but I never messed around with that particular deep, dark corner of the programming world.
The code is included below:
<?php
echo "<pre>\n";
//Dummy Runways. Will be replaced by data from AVWX
$rwy_hdgs = array(
"04R" => "029",
"04L" => "029",
"22R" => "209",
"22L" => "209",
"03R" => "029",
"03L" => "029",
"21L" => "216",
"21R" => "216",
"09L" => "089",
"09R" => "089",
"27R" => "269",
"27L" => "269"
);
//Dummy Wind Heading. Will be replaced by data from AVWX
$wind_dir = "360";
$runways = array();
$i = 1;
foreach($rwy_hdgs as $key => $value)
{
$diff = $value - $wind_dir;
$runways[$i]["rwy"] = $key;
$runways[$i]["hdg"] = $value;
$runways[$i]["diff"] = abs($diff);
$i++;
}
//Select "diff" value
$diff = array_column($runways, "diff");
//Sort $runways by difference betweeen wind and runways, with smallest value first
array_multisort($diff, SORT_ASC, $runways);
foreach ($runways as $runway){
echo "Wind Heading: " . $wind_dir . "\n";
echo "Runway: " . $runway["rwy"] . "\n";
echo "Heading: " . $runway["hdg"] . "\n";
echo "Variance: " . $runway["diff"] . "°\n\n";
}
echo "</pre>\n";
?>

When you subtract two angles in a circle, you can either go the "short way" or the "long way" - it's a circle... So you have to calculate both ways and then find out, which one is shorter - and the direction too, because you have a fixed start angle and a fixed target angle:
function angleDiff($angleStart, $angleTarget) {
$delta = $angleTarget - $angleStart;
$direction = ($delta > 0) ? -1 : 1;
$absDelta1 = abs($delta);
$absDelta2 = 360 - $absDelta1;
return $direction * ($absDelta1 < $absDelta2 ? $absDelta1 : $absDelta2);
}
This should give you positive numbers for clockwise turning and negative numbers for counter-clockwise turning from start to target angle.
Disclaimer: didn't actually test the code, sorry, might have flaws ;)

When I got it right, you want to calculate the difference between two angles always going the short way. So you could do it like this:
$diff = min([abs($a - $b), 360 - abs($a - $b)]);
whith $a and $b being the two angles. The result will always be between 0 and 180 degrees.

Related

Finding factors in known ratio

I'm making a calculator for pipe fittings. The idea is that the user inputs the angle of the turn, then the calculator will tell you how many of what fittings to use. I have access to 8°, 11.25°, 22.5°, 45°, and 90° fittings. But, I can simplify it into 8° and 11.25° fittings, since 22.5°, 45°, and 90° are multiples of 11.25. I can take the # of 11.25 degree fittings, then use the following code to break it into larger fittings.
$num90 = floor($angle11 / 90);
$runningtotal = $angle11 - $num90 * 90;
$num45 = floor($runningtotal / 45);
$runningtotal = $runningtotal - $num45 * 45;
$num22 = floor($runningtotal / 22.5);
$runningtotal = $runningtotal - $num22 * 22.5;
$num11 = floor($runningtotal / 11.25);
$runningtotal = $runningtotal - $num11 * 11.25;
echo "You will need:";
echo "<br>";
echo "$num90 -- 90° fittings";
echo "<br>";
echo "$num45 -- 45° fittings";
echo "<br>";
echo "$num22 -- 22.5° fittings";
echo "<br>";
echo "$num11 -- 11.25° fittings";
Basically, I need to solve the equation:
"8x + 11.25y = angle"
Where "angle" is a known value, and X and Y are integers.
I've made a list of all the possible angles using these fittings, so the program will use the closest possible angle to their request (e.g. if they need a 150° turn, they'll be shown the fittings needed for a 150.5° connection, which is possible). That means that X and Y will be whole numbers. I already have the code to select the closest angle, I'm not worried about it.
I've looked into solutions for the Change Making Problem, which deals with something extremely similar. Most of the equations and algorithms they found go way over my head in terms of complexity. I'm a recent high school graduate, so my math level isn't as good as others.
How would I go about solving this equation? Is this maybe too complicated for me, a beginner? Am I overlooking some super simple solution?
Or, should I just use the wolframalpha API to offload the math onto their side?
Any help would be very appreciated!
Try this:
print_r(get_best_fit(150));
function get_best_fit($angle){
$a = 8;
$b = 11.25;
$best_diff = $angle;
$best_fit = [0,0];
for($x = 0; $x <= $angle/$a; $x++){
$y = round(($angle-$a*$x)/$b);
$diff = $angle-($x*$a+$y*$b);
if(abs($diff) < $best_diff){
$best_diff = abs($diff);
$best_fit = [$x,$y];
}
}
return $best_fit;
}
Output
Array ( [0] => 16 [1] => 2 )
So you would need 16 x 8 + 2 x 11.25.

PHP Looping through a GPX to calculate total ascent/descent of a track

I want loop though a gpx file and calculate the total ascent and descent. I have a function that can calc the difference in elevation between two sets of lat long points and I've set up simplexml to read & loop through the gpx file trkseg points.
The problem is, that this is not accurate (really not accurate as it is in real).
This two lines will result in different ascent and descent, as it is in the real life:
$total_ascent += ($val - $last_elevation);
$total_descent += ($val - $last_elevation);
Does somebody know, how to calculate more accurate the total ascent and descent of a track?
This is my current code snippet to calculate it ($track_elevations is an array with elevations of whole track):
if (!empty($track_elevations)) {
$total_ascent = $total_descent = 0
$lowest_elevation = $highest_elevation = $last_elevation = null;
foreach ($track_elevations as &$val) {
if (!is_null($last_elevation)) {
if ($last_elevation < $val) {
$total_ascent += ($val - $last_elevation);
}
elseif ($last_elevation > $val) {
$total_descent += ($last_elevation - $val);
}
}
if (is_null($lowest_elevation) or $lowest_elevation > $val) {
$lowest_elevation = $val;
}
if (is_null($highest_elevation) or $highest_elevation < $val) {
$highest_elevation = $val;
}
$last_elevation = $val;
}
}
Example of $track_elevations array:
$track_elevations = array(
327.46,
328.27,
329.32,
330.11,
329.46,
329.39,
329.68,
331.04,
333.23,
333.46,
332.97,
332.88,
332.99,
332.75,
332.74,
334.01,
333.62
)
In real, I was riding bike on the flat road. But my snippet of code will calculate, I have ascended and descended couple meters. Maybe I should add there some limitation of precision between two elevations in a row...
What I want to achieve:
I will try to explain it more better - when I ride f.e. 20 km on flat road (almost with no ascent and descent), the total ascent and descent should be close to 0. But when I sum $track_elevations (like in my snippet of code), I will get in $total_ascent and $total_descent f.e. 500 meters... Its because of between each array element in $track_elevations is difference couple centimeters, but I am still riding on the flat road... And in the total sum it will gather to a large number of meters... Hope now it is more clear.
The problem is that if you calculate $lastHeight - $track_elevations[$i] for ascentet you get values lower than 0 and thats why you get high differences between $total_asc and $total_desc.
<?php
$track_elevations = array(
327.46,
328.27,
329.32,
330.11,
329.46,
329.39,
329.68,
331.04,
333.23,
333.46,
332.97,
332.88,
332.99,
332.75,
332.74,
334.01,
333.62
);
$lastHeight = $track_elevations[0];
$total_ascent = 0;
$total_descent = 0;
for ($i=1;$i<count($track_elevations);$i++) {
if ($track_elevations[$i] > $lastHeight) {
$total_ascent += -1 * ($lastHeight - $track_elevations[$i]);
}
else {
$total_descent += ($lastHeight - $track_elevations[$i]);
}
$lastHeight = $track_elevations[$i];
}
echo $total_ascent ."\n";
echo $total_descent . "\n\n--\n";
echo $total_ascent - $total_descent . "\n";
--
$ php -f gpx.php
8.1
1.94
--
6.16
--
manual calculates values:
Ascent:
0.81
1.05
0.79
0.29
1.36
2.19
0.23
0.11
1.27
---
8.1
Descent:
0.65
0.07
0.49
0.09
0.24
0.01
0.39
---
1.94
Are you sure this is "flat" ? Can you provide full data set? Also I don't think that this is a precision issue from PHP. Your datapoints have only two digit precision. Maybe the sensor is rounding ?
Even if you ride a perfectly flat road, elevation data gathered from GPS will always go up and down a little. GPS is not very accurate with altitude, even in perfect conditions.
If you use raw GPS input to calculate the total amount of climbing/descending, these little errors will sum up quickly with every single point and give completely unrealistic results.
There are a number of options to get better results, but they are all more or less complex:
You could "smooth" the elevation curve with proper algorithms.
You could discard GPS altitude data and replace it with pre-recorded altitude data for all coordinates on earth (called SRTM data).
You could use the SRTM data and mix it intelligently with your own GPS data.
In any case, getting proper altitude values is a difficult topic. You could try the simple way of not counting differences below 10m or some such, but it will most likely not lead to good results.

Constructing an Array for Dataset - Different Start Points?

I have an array of values set up like:
$array[0]['Year', 'Value']
Where there are 100 or so array's inside of the main array, each with 30 value pairs of 'Year' and 'Value'. This works great for all of my current applications, until I came upon various graphing libraries that want their data formatted like:
[x1, y1, y2, y3 ...], [x2, y1, y2, y3 ...], ...
This would be easy if all of my data points started from the same year, but they do not. My arrays all start at different years, and overlap. The problem with this is, that the graphing libraries format requires a 'null' value as a placemarker for that particular series if there is no Y-axis value for that X-point.
Example: My $array[0] goes from 1900-1930, my $array[1] goes from 1901-1931. So, the new array need to be [1900, $array[0]['Value']], null] (to indicate that the $array[1] doesn't have a value there, but is still a series on the graph. Then [1901, $array[0]['Value'], $array[1]['Value']], etc etc.
I've been drawing out diagrams and trying to wrap my head around creating a series of loops to take my old array and convert it... but I'm at a loss. Any ideas?
First of all you need to define your y points, for this we need a range. As you are creating your array I would have two variables, $minYear and $maxYear, then for every entry into the array you check if it's smaller than minYear, or bigger than maxYear, and if so replace the value with this one.
That gives us our min and max, now we need a range. You need to determine how many points between each year you have. To keep things simple I'm going to assume 1 year is one increment on the y axis. However if you have a maximum number of points on the y axis you can use, then you can use the following formulae to work out how many years are between each point:
$increment = ($maxYear - $minYear) / $numPointsOnYAxis;
That will give you how many years are between each point on the Y Axis. For simplicity you may wish to round that number using the ceil() function. It means you won't use every point on the Y axis, but it keeps the maths nice and easy.
Now we have our range you can use foreach loops to go through your data and plot the graph.
$space = '';
foreach($array as $num => $a) {
echo $space . '[x' . $num;
foreach($a as $year => $value) {
$y = ceil(($value - $minValue) / $increment); //Work out how many increments up the scale the year needs to be.
echo ', y' . $y;
}
echo ']';
$space = ', ';
}
That should echo out what you are after. Please not that I haven't tested this code.

Calculate average without being thrown by strays

I am trying to calculate an average without being thrown off by a small set of far off numbers (ie, 1,2,1,2,3,4,50) the single 50 will throw off the entire average.
If I have a list of numbers like so:
19,20,21,21,22,30,60,60
The average is 31
The median is 30
The mode is 21 & 60 (averaged to 40.5)
But anyone can see that the majority is in the range 19-22 (5 in, 3 out) and if you get the average of just the major range it's 20.6 (a big difference than any of the numbers above)
I am thinking that you can get this like so:
c+d-r
Where c is the count of a numbers, d is the distinct values, and r is the range. Then you can apply this to all the possble ranges, and the highest score is the omptimal range to get an average from.
For example 19,20,21,21,22 would be 5 numbers, 4 distinct values, and the range is 3 (22 - 19). If you plug this into my equation you get 5+4-3=6
If you applied this to the entire number list it would be 8+6-41=-27
I think this works pretty good, but I have to create a huge loop to test against all possible ranges. In just my small example there are 21 possible ranges:
19-19, 19-20, 19-21, 19-22, 19-30, 19-60, 20-20, 20-21, 20-22, 20-30, 20-60, 21-21, 21-22, 21-30, 21-60, 22-22, 22-30, 22-60, 30-30, 30-60, 60-60
I am wondering if there is a more efficient way to get an average like this.
Or if someone has a better algorithm all together?
You might get some use out of standard deviation here, which basically measures how concentrated the data points are. You can define an outlier as anything more than 1 standard deviation (or whatever other number suits you) from the average, throw them out, and calculate a new average that doesn't include them.
Here's a pretty naive implementation that you could fix up for your own needs. I purposely kept it pretty verbose. It's based on the five-number-summary often used to figure these things out.
function get_median($arr) {
sort($arr);
$c = count($arr) - 1;
if ($c%2) {
$b = round($c/2);
$a = $b-1;
return ($arr[$b] + $arr[$a]) / 2 ;
} else {
return $arr[($c/2)];
}
}
function get_five_number_summary($arr) {
sort($arr);
$c = count($arr) - 1;
$fns = array();
if ($c%2) {
$b = round($c/2);
$a = $b-1;
$lower_quartile = array_slice($arr, 1, $a-1);
$upper_quartile = array_slice($arr, $b+1, count($lower_quartile));
$fns = array($arr[0], get_median($lower_quartile), get_median($arr), get_median($upper_quartile), $arr[$c-1]);
return $fns;
}
else {
$b = round($c/2);
$a = $b-1;
$lower_quartile = array_slice($arr, 1, $a);
$upper_quartile = array_slice($arr, $b+1, count($lower_quartile));
$fns = array($arr[0], get_median($lower_quartile), get_median($arr), get_median($upper_quartile), $arr[$c-1]);
return $fns;
}
}
function find_outliers($arr) {
$fns = get_five_number_summary($arr);
$interquartile_range = $fns[3] - $fns[1];
$low = $fns[1] - $interquartile_range;
$high = $fns[3] + $interquartile_range;
foreach ($arr as $v) {
if ($v > $high || $v < $low)
echo "$v is an outlier<br>";
}
}
//$numbers = array( 19,20,21,21,22,30,60 ); // 60 is an outlier
$numbers = array( 1,230,239,331,340,800); // 1 is an outlier, 800 is an outlier
find_outliers($numbers);
Note that this method, albeit much simpler to implement than standard deviation, will not find the two 60 outliers in your example, but it works pretty well. Use the code for whatever, hopefully it's useful!
To see how the algorithm works and how I implemented it, go to: http://www.mathwords.com/o/outlier.htm
This, of course, doesn't calculate the final average, but it's kind of trivial after you run find_outliers() :P
Why don't you use the median? It's not 30, it's 21.5.
You could put the values into an array, sort the array, and then find the median, which is usually a better number than the average anyway because it discounts outliers automatically, giving them no more weight than any other number.
You might sort your numbers, choose your preferred subrange (e.g., the middle 90%), and take the mean of that.
There is no one true answer to your question, because there are always going to be distributions that will give you a funny answer (e.g., consider a biased bi-modal distribution). This is why may statistics are often presented using box-and-whisker diagrams showing mean, median, quartiles, and outliers.

PHP: find two or more numbers from a list of numbers that add up towards a given amount

I am trying to create a little php script that can make my life a bit easier.
Basically, I am going to have 21 text fields on a page where I am going to input 20 different numbers. In the last field I will enter a number let's call it the TOTAL AMOUNT. All I want the script to do is to point out which numbers from the 20 fields added up will come up to TOTAL AMOUNT.
Example:
field1 = 25.23
field2 = 34.45
field3 = 56.67
field4 = 63.54
field5 = 87.54
....
field20 = 4.2
Total Amount = 81.90
Output: field1 + fields3 = 81.90
Some of the fields might have 0 as value because sometimes I only need to enter 5-15 fields and the maximum will be 20.
If someone can help me out with the php code for this, will be greatly appreciated.
If you look at oezis algorithm one drawback is immediately clear: It spends very much time summing up numbers which are already known not to work. (For example if 1 + 2 is already too big, it doesn't make any sense to try 1 + 2 + 3, 1 + 2 + 3 + 4, 1 + 2 + 3 + 4 + 5, ..., too.)
Thus I have written an improved version. It does not use bit magic, it makes everything manual. A drawback is, that it requires the input values to be sorted (use rsort). But that shouldn't be a big problem ;)
function array_sum_parts($vals, $sum){
$solutions = array();
$pos = array(0 => count($vals) - 1);
$lastPosIndex = 0;
$currentPos = $pos[0];
$currentSum = 0;
while (true) {
$currentSum += $vals[$currentPos];
if ($currentSum < $sum && $currentPos != 0) {
$pos[++$lastPosIndex] = --$currentPos;
} else {
if ($currentSum == $sum) {
$solutions[] = array_slice($pos, 0, $lastPosIndex + 1);
}
if ($lastPosIndex == 0) {
break;
}
$currentSum -= $vals[$currentPos] + $vals[1 + $currentPos = --$pos[--$lastPosIndex]];
}
}
return $solutions;
}
A modified version of oezis testing program (see end) outputs:
possibilities: 540
took: 3.0897309780121
So it took only 3.1 seconds to execute, whereas oezis code executed 65 seconds on my machine (yes, my machine is very slow). That's more than 20 times faster!
Furthermore you may notice, that my code found 540 instead of 338 possibilities. This is because I adjusted the testing program to use integers instead of floats. Direct floating point comparison is rarely the right thing to do, this is a great example why: You sometimes get 59.959999999999 instead of 59.96 and thus the match will not be counted. So, if I run oezis code with integers it finds 540 possibilities, too ;)
Testing program:
// Inputs
$n = array();
$n[0] = 6.56;
$n[1] = 8.99;
$n[2] = 1.45;
$n[3] = 4.83;
$n[4] = 8.16;
$n[5] = 2.53;
$n[6] = 0.28;
$n[7] = 9.37;
$n[8] = 0.34;
$n[9] = 5.82;
$n[10] = 8.24;
$n[11] = 4.35;
$n[12] = 9.67;
$n[13] = 1.69;
$n[14] = 5.64;
$n[15] = 0.27;
$n[16] = 2.73;
$n[17] = 1.63;
$n[18] = 4.07;
$n[19] = 9.04;
$n[20] = 6.32;
// Convert to Integers
foreach ($n as &$num) {
$num *= 100;
}
$sum = 57.96 * 100;
// Sort from High to Low
rsort($n);
// Measure time
$start = microtime(true);
echo 'possibilities: ', count($result = array_sum_parts($n, $sum)), '<br />';
echo 'took: ', microtime(true) - $start;
// Check that the result is correct
foreach ($result as $element) {
$s = 0;
foreach ($element as $i) {
$s += $n[$i];
}
if ($s != $sum) echo '<br />FAIL!';
}
var_dump($result);
sorry for adding a new answer, but this is a complete new solution to solve all problems of life, universe and everything...:
function array_sum_parts($n,$t,$all=false){
$count_n = count($n); // how much fields are in that array?
$count = pow(2,$count_n); // we need to do 2^fields calculations to test all possibilities
# now i want to look at every number from 1 to $count, where the number is representing
# the array and add up all array-elements which are at positions where my actual number
# has a 1-bit
# EXAMPLE:
# $i = 1 in binary mode 1 = 01 i'll use ony the first array-element
# $i = 10 in binary mode 10 = 1010 ill use the secont and the fourth array-element
# and so on... the number of 1-bits is the amount of numbers used in that try
for($i=1;$i<=$count;$i++){ // start calculating all possibilities
$total=0; // sum of this try
$anzahl=0; // counter for 1-bits in this try
$k = $i; // store $i to another variable which can be changed during the loop
for($j=0;$j<$count_n;$j++){ // loop trough array-elemnts
$total+=($k%2)*$n[$j]; // add up if the corresponding bit of $i is 1
$anzahl+=($k%2); // add up the number of 1-bits
$k=$k>>1; //bit-shift to the left for looking at the next bit in the next loop
}
if($total==$t){
$loesung[$i] = $anzahl; // if sum of this try is the sum we are looking for, save this to an array (whith the number of 1-bits for sorting)
if(!$all){
break; // if we're not looking for all solutions, make a break because the first one was found
}
}
}
asort($loesung); // sort all solutions by the amount of numbers used
// formating the solutions to getting back the original array-keys (which shoud be the return-value)
foreach($loesung as $val=>$anzahl){
$bit = strrev(decbin($val));
$total=0;
$ret_this = array();
for($j=0;$j<=strlen($bit);$j++){
if($bit[$j]=='1'){
$ret_this[] = $j;
}
}
$ret[]=$ret_this;
}
return $ret;
}
// Inputs
$n[0]=6.56;
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
var_dump(array_sum_parts($n,$t)); //returns one possible solution (fuc*** fast)
var_dump(array_sum_parts($n,$t,true)); // returns all possible solution (relatively fast when you think of all the needet calculations)
if you don't use the third parameter, it returns the best (whith the least amount numbers used) solution as array (whith keys of the input-array) - if you set the third parameter to true, ALL solutions are returned (for testing, i used the same numbers as zaf in his post - there are 338 solutions in this case, found in ~10sec on my machine).
EDIT:
if you get all, you get the results ordered by which is "best" - whithout this, you only get the first found solution (which isn't necessarily the best).
EDIT2:
to forfil the desire of some explanation, i commented the essential parts of the code . if anyone needs more explanation, please ask
1. Check and eliminate fields values more than 21st field
2. Check highest of the remaining, Add smallest,
3. if its greater than 21st eliminate highest (iterate this process)
4. If lower: Highest + second Lowest, if equal show result.
5. if higher go to step 7
6. if lower go to step 4
7. if its lower than add second lowest, go to step 3.
8. if its equal show result
This is efficient and will take less execution time.
Following method will give you an answer... almost all of the time. Increase the iterations variable to your taste.
<?php
// Inputs
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
// Let's try to do this a million times randomly
// Relax, thats less than a blink
$iterations=1000000;
while($iterations-->0){
$z=array_rand($n, mt_rand(2,20));
$total=0;
foreach($z as $x) $total+=$n[$x];
if($total==$t)break;
}
// If we did less than a million times we have an answer
if($iterations>0){
$total=0;
foreach($z as $x){
$total+=$n[$x];
print("[$x] + ". $n[$x] . " = $total<br/>");
}
}
?>
One solution:
[1] + 8.99 = 8.99
[4] + 8.16 = 17.15
[5] + 2.53 = 19.68
[6] + 0.28 = 19.96
[8] + 0.34 = 20.3
[10] + 8.24 = 28.54
[11] + 4.35 = 32.89
[13] + 1.69 = 34.58
[14] + 5.64 = 40.22
[15] + 0.27 = 40.49
[16] + 2.73 = 43.22
[17] + 1.63 = 44.85
[18] + 4.07 = 48.92
[19] + 9.04 = 57.96
A probably inefficient but simple solution with backtracking
function subset_sums($a, $val, $i = 0) {
$r = array();
while($i < count($a)) {
$v = $a[$i];
if($v == $val)
$r[] = $v;
if($v < $val)
foreach(subset_sums($a, $val - $v, $i + 1) as $s)
$r[] = "$v $s";
$i++;
}
return $r;
}
example
$ns = array(1, 2, 6, 7, 11, 5, 8, 9, 3);
print_r(subset_sums($ns, 11));
result
Array
(
[0] => 1 2 5 3
[1] => 1 2 8
[2] => 1 7 3
[3] => 2 6 3
[4] => 2 9
[5] => 6 5
[6] => 11
[7] => 8 3
)
i don't think the answer isn't as easy as nik mentioned. let's ay you have the following numbers:
1 2 3 6 8
looking for an amount of 10
niks solution would do this (if i understand it right):
1*8 = 9 = too low
adding next lowest (2) = 11 = too high
now he would delete the high number and start again taking the new highest
1*6 = 7 = too low
adding next lowest (2) = 9 = too low
adding next lowest (3) = 12 = too high
... and so on, where the perfect answer would simply
be 8+2 = 10... i think the only solution is trying every possible combination of
numbers and stop if the amaunt you are looking for is found (or realy calculate all, if there are different solutions and save which one has used least numbers).
EDIT: realy calculating all possible combiations of 21 numbers will end up in realy, realy, realy much calculations - so there must be any "intelligent" solution for adding numbers in a special order (lik that one in niks post - with some improvements, maybe that will bring us to a reliable solution)
Without knowing if this is a homework assignment or not, I can give you some pseudo code as a hint for a possible solution, note the solution is not very efficient, more of a demonstration.
Hint:
Compare each field value to all field value and at each iteration check if their sum is equal to TOTAL_AMOUNT.
Pseudo code:
for i through field 1-20
for j through field 1-20
if value of i + value of j == total_amount
return i and j
Update:
What you seem to be having is the Subset sum problem, given within the Wiki link is pseudo code for the algorithm which might help point you in the right direction.

Categories