How can I generate an automatic weather? - php

I have to create an automatic weather including rain, snow, clouds, fog and sunny.
Depending on the season I need to set a percentage for all weather: the forecast will be updated 3 or 4 times during a day.
Example:
Winter | Rain: 30% Snow: 30% Sunny: 10% Cloudy: 10%, Fog: 20%
I do not know how to implement a random condition based on percentages. Some help?
Many thanks and sorry for my bad English.

Well, you can use:
$location = 'Rome';
$document = file_get_contents(str_replace(" ", "+", "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=".$location));
$xml = new SimpleXMLElement($document);
echo "$location: ".$xml->temp_c."° C";
Just take a look on the XML and see what data you have available.
EDIT
I didn't understand what the OP wanted the first time. Basically, it's even easier.
$weather = mt_rand(0,100);
$season = 'winter';
switch($season) {
case 'winter': {
if ($weather < 30) {
$output = 'Rainy';
} else if ($weather >=30 && $weather < 60) {
$output = 'Snowy';
}
// goes on on the same ideea of testing the value of $weather
break;
}
// other seasons
}
echo $output;
What I suggest tough, is to keep your values in arrays (for example the seasons) as well as the values for chances to have one type of weather or another.
array (
[winter] => array (
[30] => 'Rainy',
[60] => 'Snowy',
... // the other chances of weather
),
[spring] => array (
...
),
... // and so on
)
Use mt_rand(0,100) to get a random value and the array above to determine the weather.
Please let me know if this works for you.

Great answer by Claudiu but if you want to view with Fahrenheit (F) that possible example Below:
<?php
$location = 'Washington';
$document = file_get_contents(str_replace(" ", "+", "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" . $location));
$xml = new SimpleXMLElement($document);
echo $xml->temp_f . "° F";
?>

Related

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

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.

Create new elements in an array based on existing values

I have the following array which contains a set of periods:
Array
(
Array
(
[period_start] => 1
[period_end] => 12
)
Array
(
[period_start] => 4
[period_end] => 8
)
)
I want to split the periods that overlap other periods. For example, because the second period is overlapping the first, it should split the first period into two periods so that it looks something like this:
Array
(
Array
(
[period_start] => 1
[period_end] => 3
)
Array
(
[period_start] => 4
[period_end] => 8
)
Array
(
[period_start] => 9
[period_end] => 12
)
)
So that no two periods contain a start and end value within the range of another period. But I have no idea how best to achieve this in an efficient manner. Any help would be appreciated.
EDIT: To the comments, this post was more a plea for rubber ducking, not for getting someone else to do my work for me. I've got a solution to my problem (brace yourselves):
// Sort the collection by period_start in ascending order.
function sortByPeriod(&$collection) {
usort($collection, function ($value1, $value2) {
if (!array_key_exists('period_start', $value1) || !array_key_exists('period_start', $value2)) {
return 0;
}
if ($value1['period_start'] == $value2['period_start']) {
return 0;
}
return $value1['period_start'] < $value2['period_start'] ? -1 : 1;
});
}
$periods = array();
$products = array(
array(
'period_start' => 4,
'period_end' => 8
),
array(
'period_start' => 1,
'period_end' => 12
)
);
sortByPeriod($products);
foreach ($products as $product) {
// Store them in $periods using a key, so that if an identical period comes along on a future iteration, it doesn't get counted. The keys aren't required.
if (array_key_exists('period_start', $product) && !is_null($product['period_start'])) {
if (!array_key_exists($product['period_start'] . '-' . $product['period_end'], $periods)) {
$productStart = $product['period_start'];
$productEnd = $product['period_end'];
// Go through each period already inserted
foreach ($periods as &$period) {
$periodStart = $period['period_start'];
$periodEnd = $period['period_end'];
If the product's start overlaps the period's end
if ($productStart <= $periodEnd) {
// Set that period's end to the product's start - 1
$period['period_end'] = $productStart - 1;
// If the overlapping product is entirely within the period (e.g. period is 1-12, product is 4-8, like the example provided earlier)
if ($productEnd <= $periodEnd) {
// Add a new period, whose start is the product's end + 1 and the end is the initial period's end.
$periods[($productEnd + 1) . '-' . $periodEnd] = array(
'period_start' => $productEnd + 1,
'period_end' => $periodEnd
);
// The product's period isn't entirely within the period (e.g. period is 1-6, product is 4-8)
} else {
// Add a new period from product start to period end (e.g. following the example above, the period becomes 1-3, insert 4-6)
$periods[$productStart . '-' . $periodEnd] = array(
'period_start' => $productStart,
'period_end' => $periodEnd
);
// Set the product's start to the period's end + 1 (e.g. 7)
$productStart = $periodEnd + 1;
}
}
}
// Add the period (following the example iteration above, product start = 7, end = 8)
$periods[$productStart . '-' . $productEnd] = array(
'period_start' => $productStart,
'period_end' => $productEnd
);
}
}
// After one iteration, we have 1-3, 4-6 and 7-8
}
sortByPeriod($periods);
$periods = array_values($periods);
print_r($periods);
Which works and yields the expected output as shown above. However, as you can see, it isn't very well organised and I feel as if there would be a better way to approach this.
Thank you.
I realised that I was going about this in a bit of a strange way. My thinking was that if I had a period range (let's say 1-8) and another (4-12), it should split these ranges up and leave it with 1-3, 4-8, 9-12. While the output is indeed the output that I desire, getting to it by splitting it up and adding new periods to compensate for the missing ranges in between was too complicated. I was thinking of it like this (and I had it working this way):
I have three loaves of bread, each a different size. I need to split them all up so that I have several slices of the loaf, enough to equal the content of the largest loaf. Okay, let's take a piece out of this loaf, a piece out of that loaf, and put it all together.
It's all a mess. Really, the best way to go about it is to use the largest loaf and cut in the exact places that the other loaves would fit, so instead of using three loaves, i'm just using one.
I put the theory into practice (using JavaScript. I can port it to PHP). First I need a way for each period to be indistinguishable, so that if my initial periods are something along the lines of 1-3, 1-12 and 4-8, it will count the 1-3 and 4-8 as two separate periods. Luckily, all of my items are products that have a normal price and a discounted price. The discounted price takes effect during the discount period.
Next, I need to identify the largest period and note it's start and end:
var range = {};
this.products.forEach(product => {
if (!range.start) {
range.start = product.start;
}
if (!range.end) {
range.end = product.end;
}
if (product.start < range.start) {
range.start = product.start
}
if (product.end > range.end) {
range.end = product.end;
}
});
Now, for each product, I iterate from start to end by 1 each iteration, storing the product's discount price if it's period is within the current iteration, or otherwise it's normal price:
var periodCounter = [];
this.products.forEach(product => {
for (i = range.start; i <= range.end; i ++) {
if (!periodCounter[i]) {
periodCounter[i] = 0;
}
if (i >= product.start && i <= product.end) {
periodCounter[i] += product.def_amount;
} else {
periodCounter[i] += product.amount;
}
}
});
Great. Now I have an array filled with prices for all products during each single one month period. Now I need to identify the actual periods. This is really easy - iterate through the array, check to see if the current price is equal to the previous price. If it is, we're still in a period. If it isn't, we've reached the end of that period and started a new one:
var periods = [];
var periodStart = 0;
var periodEnd = 0;
for (i = range.start; i <= range.end; i ++) {
if (i == range.start) {
periodStart = i;
} else {
if (periodCounter[i] != periodCounter[i-1]) {
periodEnd = i-1;
periods.push({
start: periodStart,
end: periodEnd,
amount: periodCounter[i-1]
});
periodStart = i;
}
if (i == range.end) {
periods.push({
start: periodStart,
end: i,
amount: periodCounter[i]
});
}
}
}
If by any chance the totals of two "logical" periods equal the same price, it doesn't really matter. The end user just needs to know what the price is for these periods, they gain no information in knowing that two periods equal the same price, so in essence, you might as well just concatenate those periods into one large period, which is what would happen. If it's absolutely necessary to display the true "logical" periods, instead of storing the price in the counter, use a byte value (1, 2, 4, 8, etc).
I made a codepen for this project, using Vue.js and Bulma to display the products and the correct periods. Of course, I'm open to better ways that I might be able to go about this.

php RRD Graph floating values instead of integers

I am producing an rrd graph and I am facing 2 problems.
Problem 1: The numbers that I am printing they are integers without decimals, although when they are printed decimals appear. It is really confusing. So I looked online on the rrdgraph_graph and although that I am using the correct syntax and I am not applying any calculations I still do get floating values instead of integers.
According to the official website: %s place this after %le, %lf or %lg. This will be replaced by the appropriate SI magnitude unit and the value will be scaled accordingly (123456 -> 123.456 k).
I have attached a photo as sample of the output. I have also provide a working example code so if anyone understands the RRD's can view possible the error.
Problem 2: I was trying to add on my graph the VRULE:time#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]] function and based on the online instructions I can supply the time. Since my graph is shifting I was planning to do time (value) - 1800 sec. I wanted to place a vertical line in the middle of the graph so I could view approximately an average on 30 minutes values. When I am applying such a format I get this error:
<b>Graph error: </b>parameter '1400274668-1800' does not represent time in line VRULE:1400274668-1800#0000CD:Half way values
When I remove the subtraction everything work fine. Is there a way to produce this line in the middle of my graph?
<?php
$file = "snmp";
$rrdFile = dirname(__FILE__) . "/".$file.".rrd";
$in = "losses";
$png = "/home/linux/Desktop/";
$in_min = "vdef_in_min";
$in_max = "vdef_in_max";
$in_lst = "vdef_in_lst";
$in_av = "vdef_in_av";
$title = "Losses RRD::Graph";
$output = array("1h","1d");
$step = 5;
$heartbeat = 2 * $step;
while (1) {
sleep (1);
$options = array(
"--start","now -15s",
"--step", "".$step."",
"DS:".$in.":GAUGE:".$heartbeat.":0:U",
"RRA:LAST:0.5:1:3600",
"RRA:MIN:0.5:1:3600",
"RRA:MAX:0.5:1:3600",
"RRA:AVERAGE:0.5:6:600",
"RRA:LAST:0.5:300:288",
"RRA:MIN:0.5:300:288",
"RRA:MAX:0.5:300:288`",
"RRA:AVERAGE:0.5:600:144"
);
if ( !isset( $create ) ) {
$create = rrd_create(
"".$rrdFile."",
$options
);
if ( $create === FALSE ) {
echo "Creation error: ".rrd_error()."\n";
}
}
$t = time();
$losses = rand(0, 150);
$update = rrd_update(
"".$rrdFile."",
array(
"".$t.":".$losses.""
)
);
if ($update === FALSE) {
echo "Update error: ".rrd_error()."\n";
}
date_default_timezone_set('Europe/Stockholm');
$timezone = new DateTime(NULL, new DateTimeZone('Europe/Stockholm'));
$date = date('l jS \of F Y h\\:i\\:s A' , $timezone->format('U'));
$comment = "RRD last updated:".$date;
$comment = str_replace( ":", "\\:", $comment );
$graph = "Graph last updated:".$date;
$graph = str_replace( ":", "\\:", $graph );
foreach ($output as $test) {
$final = array(
"--start","end - ".$test."",
"--end", "".$t."",
"--title=".$file." RRD::Graph with - ".$test." Periods",
"--vertical-label=Bytes(s)/sec",
"--right-axis-label=latency(ms)",
"--alt-y-grid", "--rigid",
"--width", "800", "--height", "500",
"--lower-limit=0",
"--no-gridfit",
"--slope-mode",
"DEF:".$in."_def=".$file.".rrd:".$in.":LAST",
"CDEF:inbytes=".$in."_def",
"VDEF:".$in_lst."=inbytes,LAST",
"VDEF:".$in_min."=inbytes,MINIMUM",
"VDEF:".$in_max."=inbytes,MAXIMUM",
"VDEF:".$in_av."=inbytes,AVERAGE",
"COMMENT:\\n",
"LINE2:".$in."_def#FF0000:".$in."",
"GPRINT:".$in_min.": Minimum\:%6.2lf %S",
"GPRINT:".$in_max.":Maximum\:%6.2lf %S",
"GPRINT:".$in_lst.":Last\:%6.2lf %s",
"GPRINT:".$in_av.":Average\:%6.2lf %s",
"COMMENT:\\n",
"VRULE:".$t."#0000CD:Half way values",
"COMMENT:\\n",
"HRULE:50#FFFF00:Maximum value",
"COMMENT:\\n",
"COMMENT: ",
"COMMENT:\\n",
"COMMENT:".$comment."\\r",
"COMMENT:".$graph."\\r"
);
$outputPngFile = rrd_graph(
"".$png."".$test.".png",
$final
);
if ($outputPngFile === FALSE) {
echo "<b>Graph error: </b>".rrd_error()."\n";
}
}
$debug = rrd_lastupdate (
"".$rrdFile.""
);
if ($debug === FALSE) {
echo "<b>Graph result error: </b>".rrd_error()."\n";
}
var_dump ($debug);
}
?>
The answer to your first problem is almost certainly Data Normalisation. Since you are not updating the RRD precisely on the Step boundary every time, the submitted data values are normalised to a step boundary, resulting in the decimal values. To understand this, read Alex van den Bogeardt's excellent article on the subject.
Your second problem is that you simply cannot use the VRULE declaration in that way. The first parameter to VRULE may be either a number or a VDEF variable, but it cannot be a formula. Therefore, VRULE:12345678#0000CD:Foo is fine, as is VRULE:vdefname#FF00FF:Bar. However you may not use VRULE:123456-123#0000CD:No. Do the calculation before, like this:
"VRULE:".($t-1800)."#0000CD:Half way values",
... and this should result in a valid syntax.

Creating hour statistics using arrays and memcached

I am trying to count how many hits I have on my site each hour, but am not sure how to approach this.
Here is what i have now:
if($cacheAvailable == true){ // WE GOT A CACHE
date_default_timezone_set("UTC");
$thisHour = date("H", time());
$moveStats = $memcache->get('moveStats');
if(!$moveStats){
$todayStats = array(array(hour => $thisHour, hits => 1, executetime => $total_time));
$memcache->set('moveStats', $todayStats);
}
foreach ($moveStats as $k => $v) {
if($v['hour'] == $thisHour){
$moveStats[$k]['hits']=$moveStats[$k]['hits']+1;
}
}
$memcache->set('moveStats', $moveStats);
echo '<pre>';
print_r($moveStats);
echo '</pre>';
}
This makes an array like this:
Array
(
[0] => Array
(
[hour] => 18
[hits] => 6
[executetime] => 0
)
)
//##### EDIT ######//
I am able to add to the current hour but I don't know how to add a new hour when the clock turns into the new hour?
Hoping for help and thanks in advance.
You just have to check if that index already exists, if not create a new one, and always increase the old value:
$todayStats = $moveStats;
if (!isset($todayStats [$thisHour])) {
$todayStats[$thisHour] = 0;
}
$todayStats[$thisHour]['hits']++;
$todayStats[$thisHour]['executetime'] = $total_time;
But you have some other problems in your implementation:
- Don't use string without quotes. That will try to call a constant with that name and only as fallback return the string itself. It also raises a notice.
- $thisHour won't contain the current hour. If you really want to have the hour try: date('H') only.

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