Илья Зелень
Илья Зелень

Reputation: 8078

Laravel array group by and merge each group

I have such an array, how can I group by date and merge each group?

$input =  [
    [
        'date' => '2018-09-25',
        'foo' => 'value1',
    ],
    [
        'date' => '2018-09-25',
        'bar' => 'value2'
    ],
    [
        'date' => '2018-09-26',
        'baz' => 'value3'
    ]
];

That in the end it turned out like this:

[
    [
        'date' => '2018-09-25'
        'foo' => 'value1'
        'bar' => 'value2'
    ],
    [
        'date' => '2018-09-26'
        'baz' => 'value3'
    ]
]

Upvotes: 0

Views: 8572

Answers (2)

mickmackusa
mickmackusa

Reputation: 47874

To group row data from a 2d array in a single loop, use foreach() or array_reduce() and a reference array to avoid applying temporary first level keys to the result array.

While less visually elegant than chaining several Laravel collection methods together, there is only one loop used to directly achieve the result. Compare this to call several under-the-hood loops: collect(), groupBy(), map(), array_merge(), values(), then toArray().

Code: (Demo)

$result = [];
foreach ($input as $row) {
    $date = $row['date'];
    if (!isset($ref[$date])) {
        $ref[$date] = $row;
        $result[] =& $ref[$date];
        continue;
    }
    $ref[$date] += $row;
}
var_export($result);

The functional-style equivalent: (Demo)

var_export(
    array_reduce(
        $input,
        function ($result, $row) {
            static $ref = [];
            $date = $row['date'];
            if (!isset($ref[$date])) {
                $ref[$date] = $row;
                $result[] =& $ref[$date];
            } else {
                $ref[$date] += $row;
            }
            return $result;
        }
    )
);

If you are unmoved by the performance argument, then the following script will concisely chain @ИльяЗелень's answer. This removes the unneeded toArray() call when flattening each group and uses arrow function syntax. (PHPize Demo)

var_export(
    collect($input)
    ->groupBy('date')
    ->map(fn($group) => array_merge(...$group))
    ->values()
    ->toArray()
);

Or make the whole chain "fully Laravel" with: (PHPize Demo)

var_export(
    collect($input)
    ->groupBy('date')
    ->map(fn($group) => $group->flatMap(fn($set) => $set))
    ->values()
    ->toArray()
);

Upvotes: 0

Илья Зелень
Илья Зелень

Reputation: 8078

You can use laravel collection.

First you need to group by groupBy method, then map each group and merge every child array.

Code:

$result = collect($input)
    ->groupBy('date')
    ->map(function ($item) {
        return array_merge(...$item->toArray());
    });

You will get this collection:

enter image description here

And in the end remove the keys(date), you can use values, and simply convert the collection to an array(toArray).

End Code:

$result = collect($input)
    ->groupBy('date')
    ->map(function ($item) {
        return array_merge(...$item->toArray());
    })
    ->values()
    ->toArray();

Upvotes: 8

Related Questions