Group row data within a 2d array based on a single column and push unique data into respective subarrays

StackOverflow https://stackoverflow.com/questions/23109569

Domanda

I need to group data in a multidimensional array data which can be related on a single column entry_id.

In addition to the entry_id there are other columns that will also be identical in other related rows (ic, name, and residency). While grouping, these data points can simply be overwritten -- in other words, I don't need to collect multiple copies of the same value within the respective group.

Finally, there will be data points within respective groups that will differ -- these values need to stored as subarrays within the groups so that no data is lost.

If there is only one row's data in a group, the file_no and detail data does not need to be converted to an array. This means that the result array will have variable depth -- some rows will flat and other may be 2 dimensional.

Sample input:

$array = [
    [
        'entry_id' => 1,
        'ic' => 2147483647,
        'name' => 'Kořínková Blanka',
        'residency' => 'Štětí, Lukešova 354, 411 08',
        'file_no' => 'KSUL 77 INS 18898 / 2013',
        'detail' => '749371da-725c-4738-8def-2f7167142a6f'
    ],
    [
        'entry_id' => 1,
        'ic' => 2147483647,
        'name' => 'Kořínková Blanka',
        'residency' => 'Štětí, Lukešova 354, 411 08',
        'file_no' => 'KSUL 77 INS 21218 / 2013',
        'detail' => '43b6a718-4647-451d-9c53-50dfee8403ff'
    ],
    [
        'entry_id' => 2,
        'ic' => 46900217,
        'name' => 'ENTEC a.s. "v likvidaci"',
        'residency' => 'Staré Město, Brněnská 1916, 686 03',
        'file_no' => 'KSBR 28 INS 1232 / 2013',
        'detail' => 'e2155a52-c464-4357-b71b-4f4ff75585eb'
    ],
];

Desired output (based on same 'entry_id' grouping):

Array
(
    [0] => Array
        (
            [entry_id] => 1
            [ic] => 2147483647
            [name] => Kořínková Blanka
            [residency] => Štětí, Lukešova 354, 411 08
            [file_no] => Array
                 (
                       [0] => KSUL 77 INS 18898 / 2013
                       [1] => KSUL 77 INS 21218 / 2013
                 )
            [detail] => Array
                 (
                       [0] => A749371da-725c-4738-8def-2f7167142a6f
                       [1] => 43b6a718-4647-451d-9c53-50dfee8403ff
                 )
        )

    [1] => Array
        (
            [entry_id] => 2
            [ic] => 46900217
            [name] => ENTEC a.s. "v likvidaci"
            [residency] => Staré Město, Brněnská 1916, 686 03
            [file_no] => KSBR 28 INS 1232 / 2013
            [detail] => e2155a52-c464-4357-b71b-4f4ff75585eb
        )
)
È stato utile?

Soluzione

Your issue can be resolved with one functional block, using array_reduce() and array_merge() principles:

$mergeId = 'entry_id';

$data = array_reduce($data, function($c, $x) use ($mergeId)
{
   $c[$x[$mergeId]] = isset($c[$x[$mergeId]])
      ?array_combine(
          $z=array_keys($c[$x[$mergeId]]), 
          array_map(function($y) use ($x, $c, $mergeId)
          {
             return in_array($x[$y], (array)$c[$x[$mergeId]][$y])
                ?$c[$x[$mergeId]][$y]
                :array_merge((array)$c[$x[$mergeId]][$y], [$x[$y]]);
          }, $z)
       )
      :$x;
   return $c;
}, []);

you may want to apply array_values() if you need to re-index result set (so keys would be consecutive, starting from 0). Check the fiddle.

Altri suggerimenti

Using a nested loop to iterate a single array for potential matches is an indirect "brute force" technique -- there are some fringe cases where this can be tolerated, but in this case the more performant and professional approach will be to assign temporary first level keys as you iterate each row. The new associative arrays permit simple/swift searching because of the way that PHP handles arrays/keys.

When an entry_id value is encountered for the first time, simply save the whole row. When an entry_id value is encountered after the first time, the scalar-typed file_no and detail elements need to be converted to array-type . This can be done manually, but array_merge_recursive() offers this "magic" natively.

Call array_values() after finished iterating if you do not want to keep the first level grouping keys.

Code: (Demo)

$result = [];
foreach ($array as $row) {
    if (!isset($result[$row['entry_id']])) {
        $result[$row['entry_id']] = $row;
    } else {
        $result[$row['entry_id']] = array_merge_recursive(
            $result[$row['entry_id']],
            ['file_no' => $row['file_no'], 'detail' => $row['detail']]
        );
    }
}
var_export(array_values($result));

Manually re-casting elements within groups (Demo)

$result = [];
foreach ($array as $row) {
    if (!isset($result[$row['entry_id']])) {
        $result[$row['entry_id']] = $row;
    } else {
        $result[$row['entry_id']]['file_no'] = (array) $result[$row['entry_id']]['file_no'];  // re-cast as single-element array
        $result[$row['entry_id']]['detail'] = (array) $result[$row['entry_id']]['detail'];  // re-cast as single-element array
        $result[$row['entry_id']]['file_no'][] = $row['file_no']; // push new element into subarray
        $result[$row['entry_id']]['detail'][] = $row['detail']; // push new element into subarray
    }
}
var_export(array_values($result));
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top