Ivan
Ivan

Reputation: 2481

Group associative elements by keys with contiguous dates to create an array of associative arrays

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

Answers (4)

mickmackusa
mickmackusa

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

Casimir et Hippolyte
Casimir et Hippolyte

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);

demo

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

Mic1780
Mic1780

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

Thomas Kelley
Thomas Kelley

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

Related Questions