Reputation: 2481
For a rent application, I have an array $dates like this:
Array
(
[2013-07-19] => 1
[2013-07-21] => 3
[2013-07-23] => 2
[2013-07-24] => 4
[2013-07-25] => 4
[2013-07-26] => 2
[2013-07-27] => 2
[2013-07-30] => 3
[2013-07-31] => 1
)
The date is the key, and the values are the number of items rent in that day for a specific product
How can I split this array in many sub arrays containing each a list of consecutive days? Like this:
Array
(
[0] => Array
(
[2013-07-19] => 1
)
[1] => Array
(
[2013-07-21] => 3
)
[2] => Array
(
[2013-07-23] => 2
[2013-07-24] => 4
[2013-07-25] => 4
[2013-07-26] => 2
[2013-07-27] => 2
)
[3] => Array
(
[2013-07-30] => 3
[2013-07-31] => 1
)
)
Upvotes: 1
Views: 1133
Reputation: 47894
You won't need to keep track of the indexes in your result array if you push a reference variable into the result array to represent each group. When there is a gap between neighboring dates, create and new reference and push it into the result. If processing a contiguous date, then add the key-value pair to the reference.
Code: (Demo)
$array = [
'2013-07-19' => 1,
'2013-07-21' => 3,
'2013-07-23' => 2,
'2013-07-24' => 4,
'2013-07-25' => 4,
'2013-07-26' => 2,
'2013-07-27' => 2,
'2013-07-30' => 3,
'2013-07-31' => 1,
];
$result = [];
$last = null;
foreach ($array as $k => $v) {
if ($last !== date('Y-m-d', strtotime("$k - 1 day"))) {
unset($ref); # destroy the group reference (if it exists)
$result[] =& $ref; # push a new group reference into the result
}
$ref[$k] = $v; # add the current associative data to the group reference
$last = $k; # update the "last" date for the next iteration
}
var_export($result);
Output:
array (
0 =>
array (
'2013-07-19' => 1,
),
1 =>
array (
'2013-07-21' => 3,
),
2 =>
array (
'2013-07-23' => 2,
'2013-07-24' => 4,
'2013-07-25' => 4,
'2013-07-26' => 2,
'2013-07-27' => 2,
),
3 =>
array (
'2013-07-30' => 3,
'2013-07-31' => 1,
),
)
Upvotes: 0
Reputation: 89557
you can do it like this:
$data = array(
'2013-07-19' => 1,
'2013-07-21' => 3,
'2013-07-23' => 2,
'2013-07-24' => 4,
'2013-07-25' => 4,
'2013-07-26' => 2,
'2013-07-27' => 2,
'2013-07-30' => 3,
'2013-07-31' => 1
);
$result = array();
// choose a fake old date or the first date from $data
$ref = new DateTime('1821-11-11');
foreach ($data as $datum => $nb) {
// compare $ref + 1 day with $datum (the current item date)
if ($ref->modify('+1 day')->format('Y-m-d') !== $datum) {
$result[] = array();
$ref = new DateTime($datum);
}
$result[count($result) - 1][$datum] = $nb;
}
print_r($result);
Notices:
starting with PHP 7.3, you can easily extract the first key of the $data
array to create the first reference date using array_key_first
(instead of using a fake old date):
$ref = new DateTime(array_key_first($data));
You can also use the combo reset
+ key
for older versions, it doesn't matter since the foreach
loop will reset the array pointer immediately after.
If you want to avoid the creation of a new DateTime instance each time there is a jump between dates ($ref = new DateTime($datum);
), you can choose to only set the already existing instance with:
$ref->setDate(...explode('-', $datum));
(Not sure it will give you a great advantage in terms of readability or performance, but it's a possibility.)
Upvotes: 0
Reputation: 1794
$newArray = array();
$i = 0;
foreach ($array as $date => $key) {
$thisDay = end(explode('-', $date));
$nextDay = end(explode('-', next($array[$date])));
if ($thisDay + 1 != $nextDay + 0)
$i++;
if (!isset($newArray[$i])) {
$newArray[$i] = array($date => $key);
} else {
if (!isset($newArray[$i][$date])) {
$newArray[$i][$date] = $key;
} else {
$newArray[$i][$date] = $key;
}//END IF
}//END IF
}//END FOREACH LOOP
This is the code i would use to do what you are asking.
UPDATE:
I was wrong above. i have altered the code to work this time:
$newArray = array();
$i = 0;
foreach ($array as $date => $key) {
$thisDay = end(explode('-', $date));
$nextDay = array_key_exists(date('Y-m-d', strtotime($date . ' +1 day')), $array);
if (!isset($newArray[$i])) {
$newArray[$i] = array($date => $key);
} else {
if (!isset($newArray[$i][$date])) {
$newArray[$i][$date] = $key;
} else {
$newArray[$i][$date] = $key;
}//END IF
}//END IF
if (!$nextDay)
$i++;
}//END FOREACH LOOP
Here is my test case:
<?php
$array = array(
'2013-07-19' => 1,
'2013-07-21' => 3,
'2013-07-23' => 2,
'2013-07-24' => 4,
'2013-07-25' => 4,
'2013-07-26' => 2,
'2013-07-27' => 2,
'2013-07-30' => 3,
'2013-07-31' => 1
);
echo '<pre>' . print_r($array, true) . '</pre>';
$newArray = array();
$i = 0;
foreach ($array as $date => $key) {
$thisDay = end(explode('-', $date));
$nextDay = array_key_exists(date('Y-m-d', strtotime($date . ' +1 day')), $array);
if (!isset($newArray[$i])) {
$newArray[$i] = array($date => $key);
} else {
if (!isset($newArray[$i][$date])) {
$newArray[$i][$date] = $key;
} else {
$newArray[$i][$date] = $key;
}//END IF
}//END IF
if (!$nextDay)
$i++;
}//END FOREACH LOOP
echo '<pre>' . print_r($newArray, true) . '</pre>';
?>
Upvotes: 1
Reputation: 10292
$newArray = array();
foreach ($array as $date => $value)
{
// Make sure the newArray starts off with at least one element
if (empty($newArray))
$newArray[] = array();
// Calculate the difference in dates.
// (I like using DateTime, but use whichever method you like)
$dateTime = new DateTime($date);
$lastDateTime = new DateTime($lastDate);
$dateDiff = $dateTime->diff($lastDateTime);
// Add a new array to the end if the difference between this element and the last was more than a day
if ($dateDiff->days > 1)
$newArray[] = array();
// We can now be guaranteed that the last element of $newArray is the one we want to append to
$newArray[count($newArray) - 1][$date] = $value;
// Keep track of the last date you saw
$lastDate = $date;
}
Here it is in action: https://eval.in/38039
Upvotes: 1