Related
i have this function:
function pairwiseDifference($arry)
{
$n = count($arry) - 1; // you can add the -1 here
$diff = 0;
$result = array();
for ($i = 0; $i < $n; $i++)
{
// absolute difference between
// consecutive numbers
$diff = abs($arry[$i] - $arry[$i + 1]);
echo $diff." ";
array_push($result, $diff); // use array_push() to add new items to an array
}
return $result; // return the result array
}
$arry = array(20,10,10,50);
echo "<br> Percent of commisions are: ";
// this echos 10,0,40 and assigns whatever your function returns
// to the variable $diffArray
$diffArray = pairwiseDifference($arry);
The problem are that im not expecting this output
becouse first number of array (20) is my commission
and the other numbers are my parents commission (10,10,50).
So basically i need to output like this: (0,0,30)
becouse i take 20 of commission,
first parent not take nothing becouse are less of my commission (10)
second parent not take nothing becouse are less of my commission (10)
and only last parent take 30 becouse are greater than my commission (50 - 20 my commission).
Thanks in advance
Since your first element of the array is your commission and the others are the the commissions of parents, and since it seems that you don't want to include your commission in the result array, you can do something like this:
function pairwiseDifference($arry)
{
$n = count($arry);
$diff = 0;
$result = array();
$myComm = $arry[0]; // your commision
for ($i = 1; $i < $n; $i++)
{
$diff = 0; // set $diff to 0 as default
if($myComm < $arry[$i]) // if your commision < parent commision
$diff = $arry[$i] - $myComm;
echo $diff." ";
array_push($result, $diff);
}
return $result;
}
$arry = array(20,10,10,50);
echo "<br> Percent of commisions are: ";
$diffArray = pairwiseDifference($arry);
echo $diffArray[0]; // prints 0
echo $diffArray[1]; // prints 0
echo $diffArray[2]; // prinst 30
To tweak the logic according to your code, there would be only 3 modifications.
Create a $max variable and assign it the value of $arry[0].
Make difference as 0 if current max is greater than current one, else take the difference.
Calculate the new max again using max() function.
Snippet:
<?php
function pairwiseDifference($arry)
{
$n = count($arry) - 1; // you can add the -1 here
$diff = 0;
$result = array();
$max = $arry[0]; // new addition
for ($i = 1; $i <= $n; $i++) // new modification <= n instead of < n
{
// absolute difference between
// consecutive numbers
$diff = $max < $arry[$i] ? $arry[$i] - $max : 0; // new modification
$max = max($max, $arry[$i]); // new modification
echo $diff." ";
array_push($result, $diff); // use array_push() to add new items to an array
}
return $result; // return the result array
}
$arry = array(20,10,10,50);
echo "<br> Percent of commisions are: ";
// this echos 10,0,40 and assigns whatever your function returns
// to the variable $diffArray
$diffArray = pairwiseDifference($arry);
I'm trying to calculate the mean of a range of XML elements in PHP, but haven't found any solution yet.
Here are the XML elements.
<root>
<quoteDay>
<date>2018-02-26</date>
<close>1586,96</close>
</quoteDay>
<quoteDay>
<date>2018-02-23</date>
<close>1577,11</close>
</quoteDay>
<quoteDay>
<date>2018-02-22</date>
<close>1565,5</close>
</quoteDay>
</root>
Here is the PHP code:
<?php
$xml = simplexml_load_file("file.xml") or die("Error: Cannot create object");
$id = -1;
$total[] = 0;
foreach ($xml->root as $root) {
foreach ($root->quoteDay as $quoteDay) {
$id ++;
$total[] += $root->quoteDay[$id]->close;
$close = number_format(round($quoteDay->close,0));
echo $quoteDay->date; echo $close; echo $total[$id+1];
}
}
?>
So, for each quoteDay, I would like to return the date, close and a moving average.
Date 2018-02-26 would return the mean of "close" for 2018-02-26 and 2018-02-23 = (1586,96+1577,11)/2.
Mean for 2018-02-23 would return (1577,11+1565,5)/2.
I've, as you can see, tried to sum a cumulative total sum for each element, but for some reason I can't understand it won't work.
How can I accomplish calculating a moving average for the elements?
In order to achieve your result you need to do a couple of things:
simplexml_load_file() already gives you the root, so there's no need for your first loop
The $total array is not necessary
Your XML has , as decimal separators, but PHP uses ., so you need to replace them in order to do math and not lose the decimals (here I cast to float which can make you lose precision, look into bcmath to avoid that)
I assume that for the first day, when there's no previous, the moving average is the day's value
So, your code would look like this:
<?php
$xml = simplexml_load_file("a.xml") or die("Error: Cannot create object");
$id = 0;
foreach ($xml->quoteDay as $quoteDay) {
echo "Moving average for ".$quoteDay->date.":".PHP_EOL;
$current = (float) str_replace(",", ".", $quoteDay->close);
$previous = $xml->quoteDay[$id + 1]
? (float) str_replace(",", ".", $xml->quoteDay[$id + 1]->close)
: $current;
$movingMean = ($current + $previous) / 2;
echo $movingMean.PHP_EOL;
echo PHP_EOL;
$id++;
}
Demo
Result
Moving average for 2018-02-26:
1582.035
Moving average for 2018-02-23:
1571.305
Moving average for 2018-02-22:
1565.5
To generalize it to $daysInMovingMean days, use a for loop to get the days up to the days needed, stopping earlier if necessary (i.e. no more days left):
$xml = simplexml_load_file("a.xml") or die("Error: Cannot create object");
$id = 0;
$daysInMovingMean = 3;
foreach ($xml->quoteDay as $quoteDay) {
echo "Moving average for ".$quoteDay->date.":".PHP_EOL;
$sum = 0;
for ($days = 0; $days < $daysInMovingMean; $days++) {
if (!$xml->quoteDay[$id + $days]) break;
$sum += (float) str_replace(",", ".", $xml->quoteDay[$id + $days]->close);
}
$sumovingMean = $sum / $days;
echo $sumovingMean.PHP_EOL;
echo PHP_EOL;
$id++;
}
Notice that in this example you get the same results as before if you set $daysInMovingMean = 2;
Demo
I have a code that generates total posts from a database per hour for the latest 10 hours. Now, the problem is that only hours with posts are displayed, but that won't work for me because i want to display the whole thing as a chart.
Example of the current array:
array("12"=>"20403",
"15"=>"17017",
"17"=>"84013");
The keys represent the hour in a 24 format. So what i need is a function that fills in the empty hours with 0 value.
Example:
$currenthour=date('H'); // i think it may be based on the latest hour.
array("11"="0",
"12"=>"20403",
"13"=>"0",
"14"=>"0",
"15"=>"17017",
"16"=>"0",
"17"=>"84013",
"18"=>"0",
"19"=>"0",
"20"=>"0");
Thanks!
foreach(range(0, 23) as $hour)
if(!isset($ary[$hour]))
$ary[$hour] = 0;
ksort($ary);
to fill in only last hours you may need something like
function last_hours($hour, $cnt) {
return $hour < $cnt - 1 ?
array_merge(range($hour, 0), range(23, 25 - $cnt + $hour)) :
range($hour, $hour - $cnt + 1);
}
and then
$now = date("G");
$new_array = array();
foreach(last_hours($now, 10) as $hour)
$new_array[$hour] = isset($ary[$hour]) ? $ary[$hour] : 0;
Use array_fill OR do it with a loop (assuming $hours is your array:
$currenthour=date('H');
for($i = $currenthour; $i < 23; $i++)
if(!isset($hours[$i]))
$hours[$i] = 0;
You can simply iterate through the array, and see if the value at the index is set. Like this:
Edit with your the last 10 hours:
$currenthour=date('H')
$beginrange = $currenthour - 10
if ($beginrange =< 0)
$beginrange = 23 + $beginrange
$endrange = $currenthour
//set up the for loop
foreach(range($beginrange, $endrange) as $i)
//check if the element is set
if(!isset($array[$i]))
// set it
$array[$i] = 0;
Given a list of ranges ie: 1-3,5,6-4,31,9,19,10,25-20
how can i reduce it to 1-6,9-10,19-25,31 ?
Here is what i've done so far, it seems a little bit complicated, so
is there any simpler/clever method to do this.
$in = '1-3,5,6-4,31,9,19,10,25-20';
// Explode the list in ranges
$rs = explode(',', $in);
$tmp = array();
// for each range of the list
foreach($rs as $r) {
// find the start and end date of the range
if (preg_match('/(\d+)-(\d+)/', $r, $m)) {
$start = $m[1];
$end = $m[2];
} else {
// If only one date
$start = $end = $r;
}
// flag each date in an array
foreach(range($start,$end) as $i) {
$tmp[$i] = 1;
}
}
$str = '';
$prev = 999;
// for each date of a month (1-31)
for($i=1; $i<32; $i++) {
// is this date flaged ?
if (isset($tmp[$i])) {
// is output string empty ?
if ($str == '') {
$str = $i;
} else {
// if the previous date is less than the current minus 1
if ($i-1 > $prev) {
// build the new range
$str .= '-'.$prev.','.$i;
}
}
$prev = $i;
}
}
// build the last range
if ($i-1 > $prev) {
$str .= '-'.$prev;
}
echo "str=$str\n";
NB: it must run under php 5.1.6 (i can't upgrade).
FYI : the numbers represent days of month so they are limited to 1-31.
Edit:
From a given range of dates (1-3,6,7-8), i'd like obtain another list (1-3,6-8) where all the ranges are recalculated and ordered.
Perhaps not the most efficient, but shouldn't be too bad with the limited range of values you're working with:
$in = '1-3,5,6-4,31,9,19,10,25-20';
$inSets = explode(',',$in);
$outSets = array();
foreach($inSets as $inSet) {
list($start,$end) = explode('-',$inSet.'-'.$inSet);
$outSets = array_merge($outSets,range($start,$end));
}
$outSets = array_unique($outSets);
sort($outSets);
$newSets = array();
$start = $outSets[0];
$end = -1;
foreach($outSets as $outSet) {
if ($outSet == $end+1) {
$end = $outSet;
} else {
if ($start == $end) {
$newSets[] = $start;
} elseif($end > 0) {
$newSets[] = $start.'-'.$end;
}
$start = $end = $outSet;
}
}
if ($start == $end) {
$newSets[] = $start;
} else {
$newSets[] = $start.'-'.$end;
}
var_dump($newSets);
echo '<br />';
You just have to search your data to get what you want. Split the input on the delimiter, in your case ','. Then sort it somehow, this safes you searching left from the current position. Take you first element, check whether it's a range and use the highest number in this range (3 out of 1-3 range or 3 if 3 is a single element) for further comparisions. Then take the 2nd element in your list and check if it's a direct successor of the last element. If yes combine the 1st and 2nd elements/range to a new range. Repeat.
Edit: I'm not sure about PHP but a regular expression is a bit overkill for this problem. Just look for a '-' in your exploded array, then you know it's a range. Sorting the exp. array safes you the backtracking, the stuff you are doing with $prev. You could also explode every element in the exploded array on '-' and check if the resulting array has a size > 1 to learn whether an element is a range or not.
Looking at the problem from an algorithmic stand-point, let's consider the limitations that you've put on the problem. All numbers will be from 1-31. The list is a collection of "ranges", each of which is defined by two numbers (start and end). There is no rule for whether start will be more, less than, or equal to end.
Since we have an arbitrarily large list of ranges but a definite means of sorting/organizing these, a divide and conquer strategy may yield the best complexity.
At first I typed out a very long and careful explanation of how I created each step in this algorithm (the dividing portion, the conquering potion, optimizations, etc.) however the explanation got extremely long. To shorten it, here's the final answer:
<?php
$ranges = "1-3,5,6-4,31,9,19,10,25-20";
$range_array = explode(',', $ranges);
$include = array();
foreach($range_array as $range){
list($start, $end) = explode('-', $range.'-'.$range); //"1-3-1-3" or "5-5"
$include = array_merge($include, range($start, $end));
}
$include = array_unique($include);
sort($include);
$new_ranges = array();
$start = $include[0];
$count = $start;
// And begin the simple conquer algorithm
for( $i = 1; $i < count($include); $i++ ){
if( $include[$i] != ($count++) ){
if($start == $count-1){
$new_ranges[] = $start;
} else {
$new_ranges[] = $start."-".$count-1;
}
$start = $include[$i];
$count = $start;
}
}
$new_ranges = implode(',', $new_ranges);
?>
This should (theoretically) work on arrays of arbitrary length for any positive integers. Negative integers would get tripped up since - is our delimiter for the range.
I've been stumped on this PHP issue for about a day now. Basically, we have an array of hours formatted in 24-hour format, and an arbitrary value ($hour) (also a 24-hour). The problem is, we need to take $hour, and get the next available value in the array, starting with the value that immediately proceeds $hour.
The array might look something like:
$goodHours = array('8,9,10,11,12,19,20,21).
Then the hour value might be:
$hour = 14;
So, we need some way to know that 19 is the next best time. Additionally, we might also need to get the second, third, or fourth (etc) available value.
The issue seems to be that because 14 isn't a value in the array, there is not index to reference that would let us increment to the next value.
To make things simpler, I've taken $goodHours and repeated the values several times just so I don't have to deal with going back to the start (maybe not the best way to do it, but a quick fix).
I have a feeling this is something simple I'm missing, but I would be so appreciative if anyone could shed some light.
Erik
You could use a for loop to iterate over the array, until you find the first that is greater than the one you're searching :
$goodHours = array(8,9,10,11,12,19,20,21);
$hour = 14;
$length = count($goodHours);
for ($i = 0 ; $i < $length ; $i++) {
if ($goodHours[$i] >= $hour) {
echo "$i => {$goodHours[$i]}";
break;
}
}
Would give you :
5 => 19
And, to get the item you were searching for, and some after that one, you could use something like this :
$goodHours = array(8,9,10,11,12,19,20,21);
$hour = 14;
$numToFind = 2;
$firstIndex = -1;
$length = count($goodHours);
for ($i = 0 ; $i < $length ; $i++) {
if ($goodHours[$i] >= $hour) {
$firstIndex = $i;
break;
}
}
if ($firstIndex >= 0) {
$nbDisplayed = 0;
for ($i=$firstIndex ; $i<$length && $nbDisplayed<$numToFind ; $i++, $nbDisplayed++) {
echo "$i => {$goodHours[$i]}<br />";
}
}
Which would give you the following output :
5 => 19
6 => 20
Basically, here, the idea is to :
advance in the array, until you find the first item that is >= to what you are looking for
get out of that first loop, when found
If a matching item was found
loop over the array, until either its end,
or you've found as many items as you were looking for.
You can also use the SPL FilterIterator. Though it's not the fastest solution there is, it has the advantage that you can "prepare" the iterator somewhere/anywhere and then pass it to a function/method that doesn't have to know how the iterator works on the inside, i.e. you could pass a completely different iterator the next time.
class GreaterThanFilterIterator extends FilterIterator {
protected $threshold;
public function __construct($threshold, Iterator $it) {
$this->threshold = $threshold;
parent::__construct($it);
}
public function accept() {
return $this->threshold < parent::current();
}
}
function doSomething($it) {
// no knowledge of the FilterIterator here
foreach($it as $v) {
echo $v, "\n";
}
}
$goodHours = array(8,9,10,11,12,19,20,21);
$it = new GreaterThanFilterIterator(14, new ArrayIterator($goodHours));
doSomething($it);
prints
19
20
21
As $goodHours is already sorted, that's something easy:
$next = 0;
foreach($goodHours as $test)
if($test > $hour && $next = $test)
break;
After that four-liner (that can be written in a smaller number of lines naturally), $next is either 0 if $hour could not be matched in $goodHours or it contains the value that immediately proceeds $hour. That is what you asked for.
This only works when $goodHours is sorted, in case it's not, you can sort it by using the asort() function.
Try this function:
function nextValueGreaterThan($haystack, $needle, $n=1) {
sort($haystack);
foreach ($haystack as $val) {
if ($val >= $needle) {
$n--;
if ($n <= 0) {
return $val;
}
}
}
}
$goodHours = array(8,9,10,11,12,19,20,21);
echo nextValueGreaterThan($goodHours, 14); // 19
echo nextValueGreaterThan($goodHours, 14, 3); // 21
Here's an answer similar to the rest of these, including an optional "offset" parameter, that gets your n'th item past the de-facto first one.
class GoodHours {
private $hours = array(8,9,10,11,12,19,20,21);
public function getGoodHour($hour, $offset = 0) {
$length = count($this->hours);
for ($i = 0 ; $i < $length && $this->hours[$i] < $hour ; $i++)
; // do nothing
return $this->hours[($i + $offset) % $length];
}
}
// some test values
$good = new GoodHours();
$x = $good->getGoodHour(5); // 8
$x = $good->getGoodHour(5,1); // 9
$x = $good->getGoodHour(5,2); // 10
$x = $good->getGoodHour(10); // 10
$x = $good->getGoodHour(10,1); // 11
$x = $good->getGoodHour(10,2); // 12
$x = $good->getGoodHour(21); // 21
$x = $good->getGoodHour(21,1); // 8
$x = $good->getGoodHour(21,2); // 9
$x = $good->getGoodHour(21); // 8
$x = $good->getGoodHour(22,1); // 9
$x = $good->getGoodHour(22,2); // 10