Search code examples
phparraysgrouping

Group array rows by column value and push whole rows as subarrays within the group


My input is:

$item = [
    ['invoice_id' => '72,', 'item' => 'SN00001'],
    ['invoice_id' => '73,', 'item' => 'SN00002'],
    ['invoice_id' => '73,', 'item' => 'SN00003'],
    ['invoice_id' => '73,', 'item' => 'SN00004'],
    ['invoice_id' => '74,', 'item' => 'SN00005'],
    ['invoice_id' => '74,', 'item' => 'SN00006']
];

I want to re-group it with the invoice_id like this

[0] => Array
    (
        [invoice_id] => 72
        [group] => Array
            (
                [0] => Array
                    (
                        [invoice_id] => 72,
                        [item] => SN00001
                    )
            )
    )
[1] => Array
    (
        [invoice_id] => 73
        [group] => Array
            (
                [0] => Array
                    (
                        [invoice_id] => 73,
                        [item] => SN00002
                    )
                [1] => Array
                    (
                        [invoice_id] => 73,
                        [item] => SN00003
                    )
                [2] => Array
                    (
                        [invoice_id] => 73,
                        [item] => SN00004
                    )
            )
    )
[2] => Array
    (
        [invoice_id] => 74
        [group] => Array
            (
                [0] => Array
                    (
                        [invoice_id] => 74,
                        [item] => SN00005
                    )
                [1] => Array
                    (
                        [invoice_id] => 74,
                        [item] => SN00006
                    )
            )
    )

This is what I did so far

$items = [];
foreach($item as $k => $val){
    if(empty($items)){
        // if first row
        $items[$k]['invoice_id'] = $val['invoice_id'];
        $items[$k]['group'] = [$val];
    } else {
        if(!empty($items)){
            foreach($items as $key => $value){
                if($value['invoice_id'] == $val['invoice_id']){
                    // if same invoice_id merge the value into the group
                    $items[$key]['group'] = array_merge($items[$key]['group'], [$val]);
                } else {
                    // else create a array group
                    $items[$k]['invoice_id'] = $val['invoice_id'];
                    $items[$k]['group'] = [$val];
                }
            }
        }
    }
}

Sample: https://onecompiler.com/php/3y2hgzk79

The issue with my current codes is, it will create a duplicate item in some group. I was trying to use array_search and array_column but it didn't go as expected result so I switched to foreach instead and here I am. Any help will be much appreciated.


Solution

  • Here's a fairly concise solution based on some built-in PHP array handling functions (array_map, array_filter, array_unique and array_column). It uses array_column and array_unique to get a list of distinct invoice_id values, then array_map to generate the output, filtering the input array for each entry based on whether the invoice_id values match:

    $items = array_map(function ($inv_id) use ($item) {
        return array('invoice_id' => $inv_id,
                     'group' => array_filter($item, 
                                             function ($itm) use ($inv_id) {
                                                 return $itm['invoice_id'] == $inv_id;
                                             })
                     );
    }, array_unique(array_column($item, 'invoice_id'))
    );
    

    Output:

    Array
    (
        [0] => Array
            (
                [invoice_id] => 72,
                [group] => Array
                    (
                        [0] => Array
                            (
                                [invoice_id] => 72,
                                [item] => SN00001
                            )
                    )
            )
        [1] => Array
            (
                [invoice_id] => 73,
                [group] => Array
                    (
                        [1] => Array
                            (
                                [invoice_id] => 73,
                                [item] => SN00002
                            )
                        [2] => Array
                            (
                                [invoice_id] => 73,
                                [item] => SN00003
                            )
                        [3] => Array
                            (
                                [invoice_id] => 73,
                                [item] => SN00004
                            )
                    )
            )
        [4] => Array
            (
                [invoice_id] => 74,
                [group] => Array
                    (
                        [4] => Array
                            (
                                [invoice_id] => 74,
                                [item] => SN00005
                            )
                        [5] => Array
                            (
                                [invoice_id] => 74,
                                [item] => SN00006
                            )
                    )
            )
    )
    

    Note that internal array numbering does not start at 0; if you need that then add array_values to re-index in the appropriate places:

    $items = array_values(array_map(function ($inv_id) use ($item) {
        return array('invoice_id' => $inv_id,
                     'group' => array_values(array_filter($item, 
                                                          function ($itm) use ($inv_id) { 
                                                              return $itm['invoice_id'] == $inv_id; 
                                                          }))
                     );
    }, array_unique(array_column($item, 'invoice_id'))
    ));
    

    Demo on 3v4l.org