0

What I have:

array(
    [
        'id' => 12, 'group' => 'abc', 'name' => 'Lorem',
    ],
    [
        'id' => 12, 'group' => 'def', 'name' => 'Ipsum',
    ],
    [
        'id' => 34, 'group' => 'ghi', 'name' => 'Dolor',
    ],
    [
        'id' => 34, 'group' => 'jkl', 'name' => 'Sit',
    ],
    [
        'id' => 34, 'group' => 'mno', 'name' => 'Amet',
    ],
);

What I want:

array (size=2)
  12 => 
    array (size=2)
      'abc' => 
        array (size=3)
          'id' => int 12
          'group' => string 'abc' (length=3)
          'name' => string 'Lorem' (length=5)
      'def' => 
        array (size=3)
          'id' => int 12
          'group' => string 'def' (length=3)
          'name' => string 'Ipsum' (length=5)
  34 => 
    array (size=3)
      'ghi' => 
        array (size=3)
          'id' => int 34
          'group' => string 'ghi' (length=3)
          'name' => string 'Dolor' (length=5)
      'jkl' => 
        array (size=3)
          'id' => int 34
          'group' => string 'jkl' (length=3)
          'name' => string 'Sit' (length=3)
      'mno' => 
        array (size=3)
          'id' => int 34
          'group' => string 'mno' (length=3)
          'name' => string 'Amet' (length=4)

What I tried:

The obvious:

$sorted = array();
foreach ($products as $product) {
    $sorted[$product['id']][$product['group']] = $product;
}
$products = $sorted;
unset($sorted);

But then, in an effort to avoid foreach() loops, and with help of @atomrc's answer to How to group subarrays by a column value?, I came up with this:

$sorted = array_reduce($products, function ($accumulator, $element) {
    $accumulator[$element['id']][] = $element;
    return $accumulator;
}, []);
array_walk($sorted, function(&$item) {
    $item = array_reduce($item, function ($accumulator, $element) {
        $accumulator[$element['group']] = $element;
        return $accumulator;
    }, []);
});

So while the above looks cool and all, it's also much bigger and seemingly more complex than that foreach. This is probably because my experience with array_reduce() is limited. Is there a way to have the array grouped in one go? For example, in the first array_reduce() call.

ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
  • 2
    The obvious looks pretty good to me. What's the issue with using a `foreach` loop? `array_reduce` and `array_walk` just do one internally anyway... – Nick Sep 18 '20 at 08:56
  • Code that you instinctively understand when you have to change something about it later, is often worth more than one using “more fancy” functions. – CBroe Sep 18 '20 at 09:02
  • `foreach` can actually be faster in some situations than using PHP's built in array functions (based on peoples benchmarks in the past) so there's no reason to avoid `foreach`. It of course depends on many factors. Using `foreach` is absolutely fine though. – M. Eriksson Sep 18 '20 at 09:03
  • There is no any reason to awoid `foreach` in your case and use callback-functions. Becasue `foreach` faster, with less overhead, with shorter and much more understandable code. Do not blindly follow the recommendation from that article to awoid `foreach` everywhere. – AterLux Sep 18 '20 at 09:06

1 Answers1

2

Personally I don't see an issue with the foreach solution, but if you want to use array_reduce you can simply use the inner code from the foreach loop:

$grouped = array_reduce($products, function ($c, $v) {
    $c[$v['id']][$v['group']] = $v;
    return $c;
}, []);

print_r($grouped);

The output of this code is the same as your foreach loop.

Note that this (and your foreach loop) won't deal correctly with the situation where there is more than one array with the same id and group values. For example, if we extend $products:

$products[] =     [
        'id' => 34, 'group' => 'jkl', 'name' => 'Consectetur',
    ];

Then the first jkl group value gets overwritten by the second. To deal with this you need to make each group an array e.g.

$grouped = array_reduce($products, function ($c, $v) {
    $c[$v['id']][$v['group']][] = $v;
    return $c;
}, []);

print_r($grouped);

In this case the jkl group then looks like:

[jkl] => Array
    (
        [0] => Array
            (
                [id] => 34
                [group] => jkl
                [name] => Sit
            )
        [1] => Array
            (
                [id] => 34
                [group] => jkl
                [name] => Consectetur
            )
    )

Demo on 3v4l.org

Nick
  • 138,499
  • 22
  • 57
  • 95