Simple Moving Average in PHP - Error - php
There seems to be a error in sma calculation in the code below... can someone point out where..
/**
* Simple Moving Average (sma)
*
*A Moving Average is an indicator that shows the average value of a security's price over a period of time. When calculating a moving average, a mathematical analysis of the security's average value over a predetermined time period is made. As the security's price changes,its average price moves up or down.
*
*A simple, or arithmetic, moving average is calculated by adding the closing price of the security for a number of time periods (e.g., 12 days) and then dividing this total by the number of time periods. The result is the average price of the security over the time period. Simple moving averages give equal weight to each daily price.
*
*Formula:
*
* - SUM(Closes of n periods)/n
*/
<?php
$data = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
class sma
{
/**
* #var double[]
*/
private $sma;
/**
* #param double[] $data
* #param int $range
* #return double[]
*/
function get( $data, $range )
{
$position = 0;
while ( empty( $data[ $position ] ) ) {
$position++;
}
$i = $position;
while ( true ) {
if ( empty( $data[ $i + $range - 1 ] ) ) {
break;
}
$temp_sum = 0;
for ( $j = $i; $j < $i + $range; $j++ ) {
$temp_sum += $data[ $j ];
}
$this->sma[ $i + $range - 1 ] = $temp_sum / $range;
$i++;
}
return $this->sma;
}
}
$mysma = new sma();
$mysma->get($data,5); $sma = $mysma->get();
echo mysma;
?>
Also sma calculation in other code seems to be easier.. a few example is here.. if someone has done it in php similarly..??
(defn moving-average
[coll n]
(cond
(< n 1) nil
(= n 1) coll
:else (let [sums (reductions + 0 coll)]
(map #(/ (- %1 %2) n) (drop n sums) sums))))
(time (doall (moving-average coll n)))
# "Elapsed time: 9.184 msecs"
Also this..
double[] MovingAverage(int period, double[] source)
{
var ma = new double[source.Length];
double sum = 0;
for (int bar = 0; bar < period; bar++)
sum += source[bar];
ma[period - 1] = sum/period;
for (int bar = period; bar < source.Length; bar++)
ma[bar] = ma[bar - 1] + source[bar]/period
- source[bar - period]/period;
return ma;
}
Here's a translation based on the last piece of code in your question:
function get(array $data, $range)
{
$sum = array_sum(array_slice($data, 0, $range));
$result = array($range - 1 => $sum / $range);
for ($i = $range, $n = count($data); $i != $n; ++$i) {
$result[$i] = $result[$i - 1] + ($data[$i] - $data[$i - $range]) / $range;
}
return $result;
}
Related
How to calculate total cost based on price ranges of items
I need to calculate total cost of items, based on ranges: if ($itemCount <11) { $cost_for_range = 1; } if ($itemCount <26 && $itemCount >=11) { $cost_for_range = 0.75; } if ($itemCount <50 && $itemCount >=26) { $cost_for_range = 0.55; } Thing is - I need to count final cost taking earlier levels into consideration - eg if the cart has 30 items, the price would be: first 10 at $1 = $10 another 15 at $0.75 = $11.25 another 5 at $0.55 = $2.75 so total would be $24. How to calculate this in php?
Try this : $itemCount = 25; $total = 0; for ( $i = 0 ; $i < $itemCount ; $i++ ) { if ( $i < 10 ) { $total += 1; } else if ( $i < 25 ) { $total += 0.75; } else if ( $i < 50 ) { $total += 0.55; } else { $total += 0; // price if more than 50 } }
Having some fun using min and max to do the various ranges... function calculateCost ( int $count, float $cost ) { $total = min(10, $count) * $cost; $total += (min(15, max(0, $count - 10)) * ($cost * 0.75)); $total += (max(0, $count - 25) * ($cost * 0.55)); return $total; } the second calculation uses max(0, $count - 10), so this takes the first 10 off, but ensures it doesn't go negative. Then it uses the min(15, max(0, $count - 10)) to take the lowest of 15 or the number left (so with 30, this will be 15). The last one is just the max of the count - 25 or 0, so for 30, this is 5. Use it with... echo calculateCOst( 30, 1 ); gives 24
For a universal solution, it is good to define the discounts in an array. This solution is then ready for extensions and changes. $rebate = [10 => 1.0, 15 => 0.75, PHP_INT_MAX => 0.55]; The number and the discount array are transferred to the function that calculates the cost factor. The calculation itself is realized with a simple forech loop with termination condition. function calcCostFactor(int $count, array $rebate){ $factor = 0.0; foreach($rebate as $number => $val){ $factor += min($count,$number) * $val; $count -= $number; if($count <= 0) break; } return $factor; } example: $factor = calcCostFactor(30, $rebate); Try it self.
Expanding Round-robin tournament 1v1 to a 1v1v1v1
I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links. function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array { $teamCount = count($teams); if($teamCount < 4) { return []; } //Account for odd number of teams by adding a bye if($teamCount % 2 === 1) { array_push($teams, null); $teamCount += 1; } if($shuffle) { //Seed shuffle with random_int for better randomness if seed is null srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX)); shuffle($teams); } elseif(!is_null($seed)) { //Generate friendly notice that seed is set but shuffle is set to false trigger_error('Seed parameter has no effect when shuffle parameter is set to false'); } $quadTeamCount = $teamCount / 4; if($rounds === null) { $rounds = $teamCount - 1; } $schedule = []; for($round = 1; $round <= $rounds; $round += 1) { $matchupPrev = null; foreach($teams as $key => $team) { if($key >= $quadTeamCount ) { break; } $keyCount = $key + $quadTeamCount; $keyCount2 = $key + $quadTeamCount + 1; $keyCount3 = $key + $quadTeamCount + 2; $team1 = $team; $team2 = $teams[$keyCount]; $team3 = $teams[$keyCount2]; $team4 = $teams[$keyCount3]; //echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>"; //Home-away swapping $matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3]; $schedule[$round][] = $matchup ; } rotate($teams); } return $schedule; } Rotate function: function rotate(array &$items) { $itemCount = count($items); if($itemCount < 3) { return; } $lastIndex = $itemCount - 1; /** * Though not technically part of the round-robin algorithm, odd-even * factor differentiation included to have intuitive behavior for arrays * with an odd number of elements */ $factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1); $topRightIndex = $factor - 1; $topRightItem = $items[$topRightIndex]; $bottomLeftIndex = $factor; $bottomLeftItem = $items[$bottomLeftIndex]; for($i = $topRightIndex; $i > 0; $i -= 1) { $items[$i] = $items[$i - 1]; } for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) { $items[$i] = $items[$i + 1]; } $items[1] = $bottomLeftItem; $items[$lastIndex] = $topRightItem; } For example: If I set rounds to 5, every team play 5 matches. Array example Screenshot Dealing with the 5th round: Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.
Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here): abcd bcde cdef defg And then your break; hits. Instead it should look something like this: for ($i=0; $i<4; $i++) { $matchup = array(); for ($j=0; $j<4; $j++) { $matchup[] = $teams[4*$i+$j]; } $schedule[$round][] = $matchup ; } This way you get the following pairing (again, using letters as teams): abcd efgh ijkl mnop This algorithm will split the team list in four groups: abcd|efgh|ijkl|mnop Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice. adei|klnf|gjmc|pobh Here the teams ad, kl and op will face again.
Find the highest product in 4 directions in a matrix
I got this challenge to find the highest product of 4 consecutive numbers on a 20x20 matrix of integers. The numbers are read line by line from a file separated by a space. The products can be in horizontal, vertical and diagonal in both directions My "solution" gives the wrong answer. EDIT: I've updated the code to work without file input and added sample data; also fixed one of my mistakes that were pointed out in the comments $data = [ [89,32,92,64,81,2,20,33,44,1,70,75,39,62,76,35,16,77,22,27], [53,11,6,95,41,51,31,59,8,23,19,13,61,91,48,69,84,52,66,24], [93,72,85,97,21,79,56,5,45,3,65,30,83,87,43,7,34,0,4,14], [29,17,49,9,82,90,55,67,15,63,54,94,12,28,96,37,58,98,86,78], [74,40,50,60,26,99,80,18,10,46,36,68,25,57,47,71,42,73,88,38], [50,22,6,26,18,53,52,5,46,2,89,77,83,48,4,58,45,28,84,81], [49,82,31,14,69,17,91,54,34,40,0,33,30,95,60,44,29,24,85,16], [27,11,76,39,15,86,92,74,99,59,94,12,55,57,38,96,47,32,78,75], [51,20,87,42,62,41,7,35,23,21,71,25,67,97,80,90,88,64,13,70], [19,9,56,43,68,93,65,98,36,3,61,63,10,72,8,73,1,66,79,37], [22,58,52,12,3,41,28,72,42,74,76,64,59,35,85,78,14,27,53,88], [46,80,5,96,7,68,61,69,67,34,36,40,82,26,75,50,29,91,10,2], [30,39,19,48,33,93,1,45,66,98,0,23,62,25,51,71,56,77,24,21], [79,87,94,60,8,32,13,65,4,92,73,9,31,37,17,84,15,90,86,20], [95,6,81,70,47,16,44,83,49,43,55,54,18,63,38,11,97,89,99,57], [95,78,64,58,7,17,53,28,74,86,6,12,54,85,21,94,16,69,25,68], [13,20,41,97,1,2,80,30,0,84,67,45,93,96,82,92,62,33,18,44], [60,77,31,70,76,36,59,38,15,3,91,46,65,73,49,11,8,35,5,52], [61,66,79,40,26,72,89,71,75,99,22,9,43,32,14,81,98,88,87,83], [10,4,23,19,56,57,51,47,50,27,90,63,42,29,24,55,48,37,39,34] ]; $matrix = []; //maximums in possible directions $maxes = [0, 0, 0, 0]; //while ($line = trim(fgets(STDIN))) { while ($line = current($data)) { //the horizontal maxes can be calculated while loading //$array = explode(" ", $line); $array = $line; $hMax = array_product(array_slice($array, 0, 4)); for ($i = 1; $i < (count($array)-4); $i++) { $max = array_product(array_slice($array, $i, 4)); if($max > $hMax) { $hMax = $max; } } if ( $hMax > $maxes[0] ) { $maxes[0] = $hMax; } $matrix[] = $array; next($data); } // the last 3 rows can be skipped for($i = 0; $i < (count($matrix)-4); $i++) { for ($j = 0; $j < (count($matrix[$i])-1); $j++) { $vMax = 1; // vertical $dlMax = 1; // diagonal left $drMax = 1; // diagonal rigth for ($k = 0; $k < 5; $k++) { $vMax *= $matrix[$i + $k][$j]; if ( $j < (count($matrix[$i]) - 4) ) { $drMax *= $matrix[$i + $k][$j + $k]; } if ( $j > 3 ) { $dlMax *= $matrix[$i + $k][$j - $k]; } } if ( $maxes[1] < $vMax ) $maxes[1] = $vMax; // the index used to be 1 - my first mistake if ( $maxes[2] < $dlMax ) $maxes[2] = $dlMax; // the index used to be 1 - my first mistake if ( $maxes[3] < $drMax ) $maxes[3] = $drMax; // the index used to be 1 - my first mistake } } sort($maxes); echo end($maxes).PHP_EOL; Where did my approach go wrong, and how can it be sped up? Are there any math tricks that can be applied here (besides checking for zeros)? EDIT: the solution that the code gives for the current data is 4912231320 is it correct?
I've found 2 major errors, and now the result is a plausible 67352832 I'm considering it solved for that reason, but if anyone comes up with some math trick that simplifies or makes it faster I'll give up the accepted answer. The first mistake was for ($k = 0; $k < 5; $k++) { It should've been for ($k = 0; $k < 4; $k++) { since we are only counting 4 numbers at once, thats why the result was so large compared to 10^8 The second was if ( $j > 3 ) { which should've been if ( $j > 2 ) { which will now include one more diagonal possibility
We can consider the four directions a bottom- or right-most cell can be the last of in a sequence. If m[i][j][k][d] is the highest total for a sequence of length k coming from direction d, then: m[i][j][1][d] = data[i][j] for all d m[i][j][k]['E'] = data[i][j] * m[i][j - 1][k - 1]['E'] m[i][j][k]['NE'] = data[i][j] * m[i - 1][j - 1][k - 1]['NE'] m[i][j][k]['N'] = data[i][j] * m[i - 1][j][k - 1]['N'] m[i][j][k]['NW'] = data[i][j] * m[i - 1][j + 1][k - 1]['NW'] If we traverse north to south, east to west, the needed cells should have already been calculated, and, clearly, we're looking for max(m[i][j][4][d]) for all i, j, d
PHP: Price range / average calculation based on historical data
I'm building a little app that analyze ebay historical prices of sold items and for some keywords/items the range is very wide because the search is too broad or simply wrong, infected by item not properly related eg. search prices for iphone the results include either the phone, but also the charger and accessories/unrelated items which adulterate the prices data... so i have a range that goes form $5 fro a charger and 500$ for an iphone so, given that I will try to improve the search on my side, i'm wondering if there is math calculation to exclude the outliers say I have $1200 $549 $399 $519 $9 $599 $549 $9 $499 $399 $519 $99 $5 $5 how to i get the price range to be $300-$600 instead of $10-$800 or so... her ebelow the current php im using...not sure if is the best function remove_outliers($dataset, $magnitude = 1) { $count = count($dataset); $mean = array_sum($dataset) / $count; // Calculate the mean $deviation = sqrt(array_sum(array_map("sd_square", $dataset, array_fill(0, $count, $mean))) / $count) * $magnitude; // Calculate standard deviation and times by magnitude return array_filter($dataset, function ($x) use ($mean, $deviation) {return ($x <= $mean + $deviation && $x >= $mean - $deviation);}); // Return filtered array of values that lie within $mean +- $deviation. } function sd_square($x, $mean) { return pow($x - $mean, 2); } function calculate_median($arr) { sort($arr); $count = count($arr); $middleval = floor(($count - 1) / 2); if ($count % 2) { $median = $arr[$middleval]; } else { $low = $arr[$middleval]; $high = $arr[$middleval + 1]; $median = (($low + $high) / 2); } return $median; } $prices = remove_outliers($prices); //$prices is the array with all the prices stored $trend = calculate_median($prices); $trend = round(($trend)); $min = round(min($prices)); $max = round(max($prices));
I find this function useful. The $cleaness variable will give granularity /** * Returns an average value from a dirt list of numbers. * * #require * * $numbers = an array of numbers * $cleaness = a percentage value * * #return integer * an average value from a cleaner list. */ public function CleanAverage ( $numbers, $cleaness ) { // A $extremes_to_remove = floor(count($numbers)/100*$cleaness); if ($extremes_to_remove < 2) {$extremes_to_remove = 2;} // B sort ($numbers) ; // C //remove $extremes from top for ($i = 0; $i < ($extremes_to_remove/2); $i++) { array_pop($numbers); } // D // revers order rsort($numbers); // E //remove $extremes from top for ( $i = 0; $i < ($extremes_to_remove/2); $i++ ) { array_pop($numbers); } // F // average $average = array_sum($numbers)/count($numbers); return $average; }
How to generate random numbers to produce a non-standard distributionin PHP
I've searched through a number of similar questions, but unfortunately I haven't been able to find an answer to this problem. I hope someone can point me in the right direction. I need to come up with a PHP function which will produce a random number within a set range and mean. The range, in my case, will always be 1 to 100. The mean could be anything within the range. For example... r = f(x) where... r = the resulting random number x = the mean ...running this function in a loop should produce random values where the average of the resulting values should be very close to x. (The more times we loop the closer we get to x) Running the function in a loop, assuming x = 10, should produce a curve similar to this: + + + + + + + + + Where the curve starts at 1, peeks at 10, and ends at 100. Unfortunately, I'm not well versed in statistics. Perhaps someone can help me word this problem correctly to find a solution?
interesting question. I'll sum it up: We need a funcion f(x) f returns an integer if we run f a million times the average of the integer is x(or very close at least) I am sure there are several approaches, but this uses the binomial distribution: http://en.wikipedia.org/wiki/Binomial_distribution Here is the code: function f($x){ $min = 0; $max = 100; $curve = 1.1; $mean = $x; $precision = 5; //higher is more precise but slower $dist = array(); $lastval = $precision; $belowsize = $mean-$min; $abovesize = $max-$mean; $belowfactor = pow(pow($curve,50),1/$belowsize); $left = 0; for($i = $min; $i< $mean; $i++){ $dist[$i] = round($lastval*$belowfactor); $lastval = $lastval*$belowfactor; $left += $dist[$i]; } $dist[$mean] = round($lastval*$belowfactor); $abovefactor = pow($left,1/$abovesize); for($i = $mean+1; $i <= $max; $i++){ $dist[$i] = round($left-$left/$abovefactor); $left = $left/$abovefactor; } $map = array(); foreach ($dist as $int => $quantity) { for ($x = 0; $x < $quantity; $x++) { $map[] = $int; } } shuffle($map); return current($map); } You can test it out like this(worked for me): $results = array(); for($i = 0;$i<100;$i++){ $results[] = f(20); } $average = array_sum($results) / count($results); echo $average; It gives a distribution curve that looks like this:
I'm not sure if I got what you mean, even if I didn't this is still a pretty neat snippet: <?php function array_avg($array) { // Returns the average (mean) of the numbers in an array return array_sum($array)/count($array); } function randomFromMean($x, $min = 1, $max = 100, $leniency = 3) { /* $x The number that you want to get close to $min The minimum number in the range $max Self-explanatory $leniency How far off of $x can the result be */ $res = [mt_rand($min,$max)]; while (true) { $res_avg = array_avg($res); if ($res_avg >= ($x - $leniency) && $res_avg <= ($x + $leniency)) { return $res; break; } else if ($res_avg > $x && $res_avg < $max) { array_push($res,mt_rand($min, $x)); } else if ($res_avg > $min && $res_avg < $x) { array_push($res, mt_rand($x,$max)); } } } $res = randomFromMean(22); // This function returns an array of random numbers that have a mean close to the first param. ?> If you then var_dump($res), You get something like this: array (size=4) 0 => int 18 1 => int 54 2 => int 22 3 => int 4 EDIT: Using a low value for $leniency (like 1 or 2) will result in huge arrays, since testing, I recommend a leniency of around 3.