Jace
Jace

Reputation: 43

Group and merge subarray data based on one column value

I have an array in PHP code below, and I want to convert this array to be grouped by data value. It's always hard to simplify arrays.

Original array:

$array = [
    ['date' => '2017-08-22', 'AAA' => 1231],
    ['date' => '2017-08-21', 'AAA' => 1172],
    ['date' => '2017-08-20', 'AAA' => 1125],
    ['date' => '2017-08-21', 'BBB' => 251],
    ['date' => '2017-08-20', 'BBB' => 21773],
    ['date' => '2017-08-22', 'CCC' => 3750],
    ['date' => '2017-08-20', 'CCC' => 321750],
];

Below is my desired array:

[
    '2017-08-22' => ['AAA' => 1231, 'CCC' => 3750],
    '2017-08-21' => ['AAA' => 1172, 'BBB' => 251],
    '2017-08-20' => ['AAA' => 1125, 'BBB' => 21773, 'CCC' => 321750],
]

It is also ok to have empty null value if the data doesn't exist. [BBB] => NULL for 2017-08-22. Can anybody help? Thanks in advance...

Upvotes: 2

Views: 7607

Answers (6)

mickmackusa
mickmackusa

Reputation: 47894

I definitely wouldn't recommend any techniques that involve more than one loop -- this process can certainly be performed in a single loop.

If you like language construct iteration, use a foreach() loop: (Demo)

$result = [];
foreach ($array as $row) {
    $date = $row['date'];
    unset($row['date']);
    $result[$date] = array_merge($result[$date] ?? [], $row);
}
var_export($result);

If you like to use functional programming and fewer global variables, use array_reduce(): (Demo)

var_export(
    array_reduce(
        $array, 
        function($accumulator, $row) {
            $date = $row['date'];
            unset($row['date']);
            $accumulator[$date] = array_merge($accumulator[$date] ?? [], $row);
            return $accumulator;
        },
        []
    )
);

These techniques unconditionally push data into the subarray with the key based on the date column value.

The above technique will work consistently even if the order of your subarray elements changes.

The ?? (null coalescing operator) is to ensure that array_merge() always has an array in the first parameter -- if processing the first occurrence of a given date, you simply merge the current iteration's data (what's left of it after unset() removes the date element) with an empty array.

Upvotes: 1

axiac
axiac

Reputation: 72226

Another perfect usage example for the PHP function array_reduce():

// The input array
$input = array(
    0 => array(
        'date' => '2017-08-22',
        'AAA'  => '1231',
    ),
    // The rest of your array here...
);


$output = array_reduce(
    $input,
    function (array $carry, array $item) {
        // Extract the date into a local variable for readability and speed
        // It is used several times below
        $date = $item['date'];
        // Initialize the group for this date if it doesn't exist
        if (! array_key_exists($date, $carry)) {
            $carry[$date] = array();
        }

        // Remove the date from the item...
        // ...and merge the rest into the group of this date
        unset($item['date']);
        $carry[$date] = array_merge($carry[$date], $item);

        // Return the partial result
        return $carry;
    },
    array()
);

The question is not clear. What is the expected result if one key (AAA f.e) is present on two or more dates? This answer keeps only the last value associated with it.

Upvotes: 0

simon
simon

Reputation: 2946

Another approach (quick & dirty) making use of an arrays internal pointer:

$newArray = [];
foreach ($array as $childArray) {
    $date = current($childArray);

    $value = next($childArray);  // this advances the internal pointer..
    $key = key($childArray);     // ..so that you get the correct key here

    $newArray[$date][$key] = $value;
}

This of course only works with the given array structure.

Upvotes: 0

Chetan Ameta
Chetan Ameta

Reputation: 7896

I believe this solution will work for you:

<?php
$array = Array
(
    0 => Array
    (
        'date' => '2017-08-22',
        'AAA' => '1231',
    ),
    1 => Array
    (
        'date' => '2017-08-21',
        'AAA' => '1172',
    ),
    2 => Array
    (
        'date' => '2017-08-20',
        'AAA' => '1125'
    ),
    3 => Array
    (
        'date' => '2017-08-21',
        'BBB' => '251'
    ),
    4 => Array
    (
        'date' => '2017-08-20',
        'BBB' => '21773',
    ),
    5 => Array
    (
        'date' => '2017-08-22',
        'CCC' => '3750'
    ),
    6 => Array
    (
        'date' => '2017-08-20',
        'CCC' => '321750'
    )
);
echo '<pre>';
$array1 = array('AAA' => null, 'BBB' => null, 'CCC' => null);
$array2 = array();

array_walk($array, function ($v) use (&$array2, $array1) {
    $a = $v['date'];
    if (!isset($array2[$a])) {
        $array2[$a] = $array1;
    }
    unset($v['date']);
    $array2[$a] = array_merge($array2[$a], $v);
});

print_r($array2);

Output

Array
(
    [2017-08-22] => Array
        (
            [AAA] => 1231
            [BBB] => 
            [CCC] => 3750
        )

    [2017-08-21] => Array
        (
            [AAA] => 1172
            [BBB] => 251
            [CCC] => 
        )

    [2017-08-20] => Array
        (
            [AAA] => 1125
            [BBB] => 21773
            [CCC] => 321750
        )

)

check output at: https://3v4l.org/NvLB8

Upvotes: 0

Sarkouille
Sarkouille

Reputation: 1232

Here : this should do the work.

$dst_array = array();
foreach ($array as $outerval) {
    foreach ($outerval as $key => $innerval) {
        if ($key != 'date') {
            $dst_array[$outerval['date']][$key] = $innerval;
        }
    }
}

It iterates through the array and then through the entries in each subarray. Any any that is not a date is assigned in the destination array in the subarray corresponding to its date and with its own current key.

Upvotes: 1

Philipp
Philipp

Reputation: 15629

A simple loop should do this..

$group = [];
foreach ($data as $item)  {
    if (!isset($group[$item['date']])) {
        $group[$item['date']] = [];
    }
    foreach ($item as $key => $value) {
        if ($key == 'date') continue;
        $group[$item['date']][$key] = $value;
    }
}

Upvotes: 2

Related Questions