I need to create a PHP function that will loop through a multi-dimensional array and return a value which represents the sum of a certain key, where the value of another key is <= the date value passed into the function. Here is a sample array of the data array:
Array
(
[Nov 18, 2011] => Array
(
[C] => 100
[I] => 100
[M] => 100
)
[Nov 22, 2011] => Array
(
[C] => 200
[I] => 200
)
[Nov 29, 2011] => Array
(
[C] => 300
[I] => -300
)
)
There will never be more than these 3 categories [C], [I] and [M] and the values for each date should be considered a running sum.
So, how do I calculate the value of the [C] category as of the Nov 22, 2011 date? The correct value is of course 300. (Being 100 from Nov 18 + 200 from Nov 22)
What if I needed to calculate the value of [I] as of Nov 29, 2011? The correct answer in this example would be 0. (Being 100 + 200 - 300)
I'm open to ideas on how best to achieve this. My instinct is to have some kind of function and then pass in the category and date values. Thanks.
Depends on how many times you're likely to want to do this and if you'll need to do it for each category, etc whether or not you use a function or just a simple loop.
Either way you could put the following into a function and just pass in $target_date and the category (replacing the hard-coded "C" I'm using for this example):
$c_total = 0;
foreach($main_array as $date => $sub_array)
{
if($date <= $target_date)
{
$c_total += $sub_array["C"];
}
}
EDIT: For clarity here's the function using strtotime() to convert the date values to timestamps for easier comparison:
function getCategoryTotal($target_date, $category)
{
$c_total = 0;
$target_date = strtotime($target_date);
foreach($main_array as $date => $sub_array)
{
if(strtotime($date) <= $target_date)
{
$c_total += $sub_array[$category];
}
}
return $c_total;
}
Related
I have two arrays of numbers, one containing a lot of numbers, one only a few. There are no duplicates within or between arrays:
$all = range(1, 50);
$few = array(7, 11, 19, 27, 29, 36, 40, 43);
$many = array_merge(array_diff($all, $few));
I now want to calculate the differences between each of the "few" numbers and all of the "many" that follow it but come before the next of the "few". For example, among $many, only 28 falls between 27 and 29 from $few, so I want to calculate the difference between 28 and 27. No other differences to 27 are calculated, because no other $many fall between 27 and 29. For 19 from $few I will calculate the differences to 20, 21, 22, 23, 24, 25 and 26, because they all lie between 19 and the next number from $few, which is 27.
To calculate the differences, I use loops. Here is a somewhat simplified code (which ignores the fact that there is no index [$i + 1] for the last number in $few):
$differences = array();
for($i = 0; $i < count($few); $i++) {
foreach($many as $m) {
if($m > $few[$i] && $m < $few[$i + 1]) {
$differences[] = $m - $few[$i];
}
}
}
If I have huge arrays, the loops will take a long time to run. So:
Is there a better way to calculate the differences, without using loops?
The resulting $differences looks like this:
Array $many $few
( ↓ ↓
[0] => 1 // 8 - 7 = 1
[1] => 2 // 9 - 7
[2] => 3 // 10 - 7
[3] => 1 // 12 - 11
[4] => 2
[5] => 3
[6] => 4
[7] => 5
[8] => 6
[9] => 7
[10] => 1
[11] => 2
[12] => 3
[13] => 4
[14] => 5
[15] => 6
[16] => 7
[17] => 1
[18] => 1
[19] => 2
[20] => 3
[21] => 4
[22] => 5
[23] => 6
[24] => 1
[25] => 2
[26] => 3
[27] => 1
[28] => 2
)
My basic reasoning is that as a human, I don't see two arrays that I compare:
... 16 17 18 | 20 21 22 23 24 25 26 | 28 29 30 31 ...
exclude | include | exclude
19 (27)
But rather one number line that I go along from one number to the next, and when I meet one marked "few" I will calculate all the differences to each of the following numbers, until I meet another one marked "few":
... 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ...
... m m m f m m m m m m m f m m m m ...
↑ ↑ ... ↑
start calculate stop
Because it is sorted, I don't have to go over the whole $many-array number by number for every number from $few. So can we somehow take the fact into account that the arrays are ordered? Or maybe build one array that contains the markers ("f", "m") and the numbers as keys? E.g.:
$all = array("drop this", "m", "m", "m", "m", "m", "m", "f", ...);
unset($all[0]); // drops the first element with index 0
Apart from the two calls to sort(), all you need is a single loop through $many.
// Input data provided in the question
$all = range(1, 50);
$few = array(7, 11, 19, 27, 29, 36, 40, 43);
$many = array_values(array_diff($all, $few));
// Display the values to see what we are doing
echo('$few = ['.implode(' ', $few)."]\n");
echo('$many = ['.implode(' ', $many)."]\n");
//
// The actual algorithm starts here
// Sort both $few and $many
// it works fast enough and it is required for the rest of the algorithm
sort($few);
sort($many);
// Be sure the last value of $few is larger than the last value of $many
// This is needed to avoid extra checking for the last element of $few inside the loop
if (end($few) < end($many)) {
array_push($few, end($many) + 1);
}
// Extract the first two items from $few
$current = array_shift($few);
$next = array_shift($few);
// This is the result
$differences = array();
// Run only once through $many, check each item against $next
// subtract $current from it; advance when $next was reached
foreach ($many as $item) {
// Skip the items smaller than the first element from $few
if ($item < $current) {
continue;
}
// If the next element from $few was reached then advance to the next interval
while ($next < $item) {
$current = $next;
$next = array_shift($few);
}
// Here $current < $item < $next
// This echo() is for debug purposes
echo('$current = '.$current.'; $item = '.$item.'; $next = '.$next.'; difference='.($item - $current)."\n");
// Store the difference
$differences[] = $item - $current;
}
I need to export a set of json data for highchart. But I'm having a problem with looping.
I've got 2 set of arrays:
Years Array (2015,2014,2013,2012) - form MySQL
Month Array (01,02,03,04,05,06,07,08,09,10,11,12)
So I make it in array - multidimensional. With this code.
$sql_arrYr=mysqli_query($con,"select year(bk_date1) as arrYr from booking_db group by year(bk_date1) order by arrYr asc");
while($rec_arrYr=mysqli_fetch_array($sql_arrYr)){
$arrYr[$rec_arrYr['arrYr']]=array("01","02","03","04","05","06","07","08","09","10","11","12");
}
Then I've got:
Array ( [2015] => Array ( [0] => 01 [1] => 02 [2] => 03 [3] => 04 [4] => 05 [5] => 06 [6] => 07 [7] => 08 [8] => 09 [9] => 10 [10] => 11 [11] => 12 )...);
Which is looking good coz I have all data I need in a set of arrays - year and each month values.
So I continue with fetching data from mySQL with this code.
foreach($arrYr as $key=>$val){
$rows=array();
$rows[type]='spline';
$rows[name]=$key;
foreach($val as $mon){
$sql_nBk=mysqli_query($con,"select count(*) as nBkr from booking_db where year(bk_date1)='$key' and month(bk_date1)='$mon'");
$rec_nBk=mysqli_fetch_array($sql_nBk);
$rows[data][]=$rec_nBk['nBkr'];
}
}
echo json_encode($rows);
The problem happened here. It's not loop. The only line I've got is.
{"type":"spline","name":2015,"data":["9","8","0","0","0","0","0","0","0","0","0","0"]}
Which I expect this:
{"type":"spline","name":2015,"data":["9","8","0","0","0","0","0","0","0","0","0","0"]}
{"type":"spline","name":2014,"data":["9","8","0","0","0","0","0","0","0","0","0","0"]}
{"type":"spline","name":2013,"data":["9","8","0","0","0","0","0","0","0","0","0","0"]}
{"type":"spline","name":2012,"data":["9","8","0","0","0","0","0","0","0","0","0","0"]}
Please give me a reason why it's not loop. Even I put them in a right loop.
You are setting $rows to an empty array each time through the outer loop. Also, you can't have duplicate keys. The first type is overwritten by the next (also name and data) so you have to make it multidimensional. You can use the $key variable for this to make it easy, or implement a counter $i and use $i++ or something.
$i = 0;
foreach($arrYr as $key=>$val){
$rows[$i]['type']='spline';
$rows[$i]['name']=$key;
foreach($val as $mon){
$sql_nBk=mysqli_query($con,"select count(*) as nBkr from booking_db where year(bk_date1)='$key' and month(bk_date1)='$mon'");
$rec_nBk=mysqli_fetch_array($sql_nBk);
$rows[$i]['data'][]=$rec_nBk['nBkr'];
}
$i++;
}
echo json_encode($rows);
I have an array of dates like this:
[Room 01 - DBL] => Array
(
[0] => Mon 23-06-2014
[1] => Tue 24-06-2014
[2] => Wed 25-06-2014
[3] => Sat 28-06-2014
[4] => Sun 29-06-2014
)
[Room 02 - TWN] => Array
(
[0] => Tue 24-06-2014
[1] => Wed 25-06-2014
[2] => Sat 28-06-2014
[3] => Sun 29-06-2014
)
You can see that neither room has the date for the thursday or friday. I want to be able to create a date range (either a DateTime Interval object or not - don't mind) for each group of dates. So Room 02 - TWN should give me two date periods - one for Tue-Wed and another for Sat-Sun. How would I go about this? I know how to make a single date time period using the first and last items in the array but don't know how to detect if there is a gap...
I'm not very clear about what you're trying to accomplish. Anyway, in the general case, you can do something like this.
The idea is to run the whole array of items, and if the "next" item is contiguous to a candidate interval you already have, you extend the interval. Else, the candidate interval becomes a standalone interval, and the item that just failed the check gives birth to a new candidate interval.
You need two functions: one that, given two items, returns whether it's true or false that they are contiguous; the other, given two items, returns an "interval" with those two items as extremes.
An empty $items will return an empty interval.
function build_intervals($items, $is_contiguous, $make_interval) {
$intervals = array();
$end = false;
foreach ($items as $item) {
if (false === $end) {
$begin = $item;
$end = $item;
continue;
}
if ($is_contiguous($end, $item)) {
$end = $item;
continue;
}
$intervals[] = $make_interval($begin, $end);
$begin = $item;
$end = $item;
}
if (false !== $end) {
$intervals[] = $make_interval($begin, $end);
}
return $intervals;
}
For numbers, you can use
$interv = build_intervals(
array( 1, 2, 3, 5, 6, 9, 10, 11, 13, 17, 18 ),
function($a, $b) { return ($b - $a) <= 1; },
function($a, $b) { return "{$a}..{$b}"; }
);
print_r($interv);
returns
Array
(
[0] => 1..3
[1] => 5..6
[2] => 9..11
[3] => 13..13
[4] => 17..18
)
With dates, you can keep them as DateTime and DateTimeIntervals. If you use timestamps, then you must supply a contiguousness criterion that's valid for timestamps. This can be awkward if you have two timestamps that are just before and after midnight the following day. To be sure, you should take times at always around midday (i.e., given two dates, get the timestamps of those dates at midday. If they're less than 36 hours apart, they're two adjacent days.
function($a, $b) {
$a_ts = strtotime("{$a} 12:00:00");
$b_ts = strtotime("{$b} 12:00:00");
return ($b - $a) <= (36 * 60 * 60);
},
I'm sorting an array as:
function date_compare($a, $b)
{
$t1 = strtotime($a['date']);
$t2 = strtotime($b['date']);
return $t1 - $t2;
}
usort($array, 'date_compare');
However, as you can see the output below, dates are not sorted. Any idea why? Thanks
Array
(
[0] => Array
(
[id] => 16870
[date] => Tue, 22 Mar 2011 13:12:19 +1100
[bar] => Foo
)
[1] => Array
(
[id] => 16871
[bar] => foo
[date] => Mon, 21 Mar 2011 23:06:32 -0500 (CDT)
)
[2] => Array
(
[id] => 16872
[bar] => foo
[date] => Tue, 22 Mar 2011 00:37:01 -0500 (CDT)
)
}
They're sorted just fine if you take the timezone into account, which strtotime does.
As others have pointed out, the dates are being sorted properly. You may want to do something like:
foreach ($dates as $i => $date)
{
$dates[$i]['ts'] = strtotime($date['date']);
$dates[$i]['date'] = date('Y-m-d H:i:s', $dates[$i]['ts']);
}
usort($dates, function($a, $b) { return $a['ts'] - $b['ts']; });
Here I'm creating a ts property for each date so strtotime() doesn't need to be called twice per sort comparison. Second, I'm changing the date to be in the local timezone. You can adjust the format string as you want.
Alternatively, you could just unset() the date component of the array and call date() directly on the ts component when you want to print it.
$arr =array(
28 => 23,
26 => 23,
15 => 12,
29 => 12,
1 => 12,
16 => 15,
30 => 15,
11 => 12,
8 => 23,
33 => 23
);
how to sort like this :
8 => 23
26 => 23
28 => 23
33 => 23
16 => 15
30 => 15
1 => 12
11 => 12
15 => 12
29 => 12
Use uksort, but make the array available to the comparison function for the secondary comparison by value. Making it a global variable would be the quickest + dirtiest way.
You could use uksort() which enables the custom callback to take a look at both the keys and, indirectly, their associated values. Then it's a simple matter of deciding which comparisons to make and returning the appropriate greater-than-less-then-or-zero value to influence the sort order.
Here's an example using a closure around a temporary variable (see Jacob's comment) which should hopefully make sense.
$temp = $arr;
uksort($arr, function ($a,$b) use ($temp) {
// Same values, sort by key ascending
if ($temp[$a] === $temp[$b]) {
return $a - $b;
}
// Different values, sort by value descending
return $temp[$b] - $temp[$a];
});
unset($temp);
print_r($arr);
Its quite easy. First use ksort and then use asort for the new sorted array. You will find your result.