php - How to group subarrays by a column value?

Solution:

There is no native one, just use a loop.

$result = array();
foreach ($data as $element) {
    $result[$element['id']][] = $element;
}

Answer

Solution:

In a more functional programming style, you could use

$groupedById = array_reduce($data, function (array $accumulator, array $element) {
  $accumulator[$element['id']][] = $element;

  return $accumulator;
}, []);

Answer

Solution:

You can try the following:

$group = array();

foreach ( $array as $value ) {
    $group[$value['id']][] = $value;
}

var_dump($group);

Output:

array
  96 => 
    array
      0 => 
        array
          'id' => int 96
          'shipping_no' => string '212755-1' (length=8)
          'part_no' => string 'reterty' (length=7)
          'description' => string 'tyrfyt' (length=6)
          'packaging_type' => string 'PC' (length=2)
      1 => 
        array
          'id' => int 96
          'shipping_no' => string '212755-1' (length=8)
          'part_no' => string 'dftgtryh' (length=8)
          'description' => string 'dfhgfyh' (length=7)
          'packaging_type' => string 'PC' (length=2)
  97 => 
    array
      0 => 
        array
          'id' => int 97
          'shipping_no' => string '212755-2' (length=8)
          'part_no' => string 'ZeoDark' (length=7)
          'description' => string 's%c%s%c%s' (length=9)
          'packaging_type' => string 'PC' (length=2)

Answer

Solution:

I just threw this together, inspired by .NET LINQ

<?php

// callable type hint may be "closure" type hint instead, depending on php version
function array_group_by(array $arr, callable $key_selector) {
  $result = array();
  foreach ($arr as $i) {
    $key = call_user_func($key_selector, $i);
    $result[$key][] = $i;
  }  
  return $result;
}

 $data = array(
        array(1, "Andy", "PHP"),
        array(1, "Andy", "C#"),
        array(2, "Josh", "C#"),
        array(2, "Josh", "ASP"),
        array(1, "Andy", "SQL"),
        array(3, "Steve", "SQL"),
    );

$grouped = array_group_by($data, function($i){  return $i[0]; });

var_dump($grouped);

?>

And voila you get

array(3) {
  [1]=>
  array(3) {
    [0]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(3) "PHP"
    }
    [1]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(2) "C#"
    }
    [2]=>
    array(3) {
      [0]=>
      int(1)
      [1]=>
      string(4) "Andy"
      [2]=>
      string(3) "SQL"
    }
  }
  [2]=>
  array(2) {
    [0]=>
    array(3) {
      [0]=>
      int(2)
      [1]=>
      string(4) "Josh"
      [2]=>
      string(2) "C#"
    }
    [1]=>
    array(3) {
      [0]=>
      int(2)
      [1]=>
      string(4) "Josh"
      [2]=>
      string(3) "ASP"
    }
  }
  [3]=>
  array(1) {
    [0]=>
    array(3) {
      [0]=>
      int(3)
      [1]=>
      string(5) "Steve"
      [2]=>
      string(3) "SQL"
    }
  }
}

Answer

Solution:

Consume and cache the column value that you want to group by, then push the remaining data as a new subarray of the group you have created in the the result.

function array_group(array $data, $by_column)
{
    $result = [];
    foreach ($data as $item) {
        $column = $item[$by_column];
        unset($item[$by_column]);
        $result[$column][] = $item;
    }
    return $result;
}

Answer

Solution:

If you desire a Composer alternative with a full suite of tests, the array_group_by function achieves what you are looking for. Full disclosure: I am the author of said library.

$grouped = array_group_by($arr, 'id');

It also supports multi-level groupings, or even complex grouping through use of custom callback functions:

// Multilevel grouping
$grouped = array_group_by($arr, 'id', 'part_no');

// Grouping by a callback/callable function
$grouped = array_group_by($records, function ($row) {
    return $row->city;
});

Answer

Solution:

$arr = Data Araay;

$fldName = Group By Colum Name;

function array_group_by( $arr, $fldName) {
    $groups = array();
    foreach ($arr as $rec) {
        $groups[$rec[$fldName]] = $rec;
    }
    return $groups;
}

function object_group_by( $obj, $fldName) {
    $groups = array();
    foreach ($obj as $rec) {
        $groups[$rec->$fldName] = $rec;
    }
    return $groups;
}

Answer

Solution:

$arr = array();

foreach($old_arr as $key => $item)
{
   $arr[$item['id']][$key] = $item;
}

ksort($arr, SORT_NUMERIC);

Answer

Solution:

for($i = 0 ; $i < count($arr)  ; $i++ )
{
    $tmpArr[$arr[$i]['id']] = $arr[$i]['id'];
}
$vmpArr = array_keys($tmpArr);
print_r($vmpArr);

Answer

Solution:

Expanding on @baba's answer, which I like, but creates a more complex three level deep multi-dimensional (array(array(array))):

$group = array();
 foreach ( $array as $value ) {
   $group[$value['id']][] = $value; 
 }

// output only data from id 96
foreach ($group as $key=>$value) { //outer loop
 foreach ($value as $k=>$v){ //inner loop
  if($key==96){ //if outer loop is equal to 96 (could be variable)
   for ($i=0;$i<count($k);$i++){ //iterate over the inner loop
        printf($key.' has a part no. of '.$v['part_no'].' and shipping no. of '.$v['shipping_no'].'<br>');
   }
 }
}
 }

Will output:

96 has a part no. of reterty and shipping number of 212755-1

96 has a part no. of dftgtryh and shipping number of 212755-1

Answer

Solution:

It's trivial to do with LINQ, which is implemented in PHP in several libraries, including YaLinqo*. It allows performing SQL-like queries on arrays and objects. The groubBy function is designed specifically for grouping, you just need to specify the field you want to group by:

$grouped_array = from($array)->groupBy('$v["id"]')->toArray();

Where '$v["id"]' is a shorthand for function ($v) { return $v["id"]; } which this library supports.

The result will be exactly like in the accepted answer, just with less code.

* developed by me

Answer

Solution:

1. GROUP BY one key

This function works as GROUP BY for array, but with one important limitation: Only one grouping "column" ($identifier) is possible.

function arrayUniqueByIdentifier(array $array, string $identifier)
{
    $ids = array_column($array, $identifier);
    $ids = array_unique($ids);
    $array = array_filter($array,
        function ($key, $value) use($ids) {
            return in_array($value, array_keys($ids));
        }, ARRAY_FILTER_USE_BOTH);
    return $array;
}

2. Detecting the unique rows for a table (twodimensional array)

This function is for filtering "rows". If we say, a twodimensional array is a table, then its each element is a row. So, we can remove the duplicated rows with this function. Two rows (elements of the first dimension) are equal, if all their columns (elements of the second dimension) are equal. To the comparsion of "column" values applies: If a value is of a simple type, the value itself will be use on comparing; otherwise its type (array, object, resource, unknown type) will be used.

The strategy is simple: Make from the original array a shallow array, where the elements are imploded "columns" of the original array; then apply array_unique(...) on it; and as last use the detected IDs for filtering of the original array.

function arrayUniqueByRow(array $table = [], string $implodeSeparator)
{
    $elementStrings = [];
    foreach ($table as $row) {
        // To avoid notices like "Array to string conversion".
        $elementPreparedForImplode = array_map(
            function ($field) {
                $valueType = gettype($field);
                $simpleTypes = ['boolean', 'integer', 'double', 'float', 'string', 'NULL'];
                $field = in_array($valueType, $simpleTypes) ? $field : $valueType;
                return $field;
            }, $row
        );
        $elementStrings[] = implode($implodeSeparator, $elementPreparedForImplode);
    }
    $elementStringsUnique = array_unique($elementStrings);
    $table = array_intersect_key($table, $elementStringsUnique);
    return $table;
}

It's also possible to improve the comparing, detecting the "column" value's class, if its type is object.

The $implodeSeparator should be more or less complex, z.B. spl_object_hash($this).


3. Detecting the rows with unique identifier columns for a table (twodimensional array)

This solution relies on the 2nd one. Now the complete "row" doesn't need to be unique. Two "rows" (elements of the first dimension) are equal now, if all relevant "fields" (elements of the second dimension) of the one "row" are equal to the according "fields" (elements with the same key).

The "relevant" "fields" are the "fields" (elements of the second dimension), which have key, that equals to one of the elements of the passed "identifiers".

function arrayUniqueByMultipleIdentifiers(array $table, array $identifiers, string $implodeSeparator = null)
{
    $arrayForMakingUniqueByRow = $removeArrayColumns($table, $identifiers, true);
    $arrayUniqueByRow = $arrayUniqueByRow($arrayForMakingUniqueByRow, $implodeSeparator);
    $arrayUniqueByMultipleIdentifiers = array_intersect_key($table, $arrayUniqueByRow);
    return $arrayUniqueByMultipleIdentifiers;
}

function removeArrayColumns(array $table, array $columnNames, bool $isWhitelist = false)
{
    foreach ($table as $rowKey => $row) {
        if (is_array($row)) {
            if ($isWhitelist) {
                foreach ($row as $fieldName => $fieldValue) {
                    if (!in_array($fieldName, $columnNames)) {
                        unset($table[$rowKey][$fieldName]);
                    }
                }
            } else {
                foreach ($row as $fieldName => $fieldValue) {
                    if (in_array($fieldName, $columnNames)) {
                        unset($table[$rowKey][$fieldName]);
                    }
                }
            }
        }
    }
    return $table;
}

Answer

Solution:

This should group an associative array Ejm Group By Country

function getGroupedArray($array, $keyFieldsToGroup) {   
    $newArray = array();

    foreach ($array as $record) 
        $newArray = getRecursiveArray($record, $keyFieldsToGroup, $newArray);

    return $newArray;
}
function getRecursiveArray($itemArray, $keys, $newArray) {
    if (count($keys) > 1) 
        $newArray[$itemArray[$keys[0]]] = getRecursiveArray($itemArray,    array_splice($keys, 1), $newArray[$itemArray[$keys[0]]]);
    else
        $newArray[$itemArray[$keys[0]]][] = $itemArray;

    return $newArray;
}

$countries = array(array('Country'=>'USA', 'State'=>'California'),
                   array('Country'=>'USA', 'State'=>'Alabama'),
                   array('Country'=>'BRA', 'State'=>'Sao Paulo'));

$grouped = getGroupedArray($countries, array('Country'));

Answer

Solution:

Check indexed function from Nspl:

use function \nspl\a\indexed;
$grouped = indexed($data, 'id');

Answer

Solution:

function array_group_by($arr, array $keys) {

if (!is_array($arr)) {
    trigger_error('array_group_by(): The first argument should be an array', E_USER_ERROR);
}
if (count($keys)==0) {
    trigger_error('array_group_by(): The Second argument Array can not be empty', E_USER_ERROR);
}

// Load the new array, splitting by the target key
$grouped = [];
foreach ($arr as $value) {
    $grouped[$value[$keys[0]]][] = $value;
}

// Recursively build a nested grouping if more parameters are supplied
// Each grouped array value is grouped according to the next sequential key
if (count($keys) > 1) {
        foreach ($grouped as $key => $value) {
       $parms = array_merge([$value], [array_slice($keys, 1,count($keys))]);
       $grouped[$key] = call_user_func_array('array_group_by', $parms);

    }
}
return $grouped;

}

Answer

Solution:

function groupeByPHP($array,$indexUnique,$assoGroup,$keepInOne){
$retour = array();
$id = $array[0][$indexUnique];
foreach ($keepInOne as $keep){
    $retour[$id][$keep] = $array[0][$keep];
}
foreach ($assoGroup as $cle=>$arrayKey){
    $arrayGrouped = array();
        foreach ($array as $data){
            if($data[$indexUnique] != $id){
                $id = $data[$indexUnique];
                foreach ($keepInOne as $keep){
                    $retour[$id][$keep] = $data[$keep];
                }
            }
            foreach ($arrayKey as $val){
                $arrayGrouped[$val] = $data[$val];
            }
            $retour[$id][$cle][] = $arrayGrouped;
            $retour[$id][$cle] = array_unique($retour[$id][$cle],SORT_REGULAR);
        }
}
return  $retour;
}

Try this function

groupeByPHP($yourArray,'id',array('desc'=>array('part_no','packaging_type')),array('id','shipping_no')) 

Answer

Solution:

Recursive function grouping 2-dimensional array by keys from first to last

Input:

$arr = array(
    '0' => array(
        'key0' => 'value0',
        'key1' => 'value1',
        'key2' => 'value02',
    ),
    '2' => array(
        'key0' => 'value0',
        'key1' => 'value1',
        'key2' => 'value12',
    ),
    '3' => array(
        'key0' => 'value0',
        'key1' => 'value3',
        'key2' => 'value22',
    ),
);
$keys = array('key0', 'key1', 'key2');

Output:

$arr = array(
    'value0' => array(
        'value1 => array(
            'value02' => null,
            'value12' => null,
        ),
        'value3' => 'value22',
    ),
);

Code:

function array_group_by_keys(&$arr, $keys) {

    if (count($arr) < 2){
        $arr = array_shift($arr[0]);
        return;
    }

    foreach ($arr as $k => $item) {
        $fvalue = array_shift($item);
        $arr[$fvalue][] = $item;
        unset($arr[$k]);
    }

    array_shift($keys);
    foreach ($arr as &$sub_arr) {
        array_group_by_keys($sub_arr, $keys);
    }
}

Answer

Solution:

It's easy, you can group by any "key" in the array by using my function groupBy();

$data = [
    [
        "id" => 96,
        "shipping_no" => "212755-1",
        "part_no" => "reterty",
        "description" => "tyrfyt",
        "packaging_type" => "PC"
    ],
    [
        "id" => 96,
        "shipping_no" => "212755-1",
        "part_no" => "dftgtryh",
        "description" => "dfhgfyh",
        "packaging_type" => "PC"
    ],
    [
        "id" => 97,
        "shipping_no" => "212755-2",
        "part_no" => "ZeoDark",
        "description" => "s%c%s%c%s",
        "packaging_type" => "PC"
    ]
];

function groupBy($array, $key) {
    $groupedData = [];
    $data = [];
    $_id = "";
    for ($i=0; $i < count($array); $i++) { 
        $row = $array[$i];
        if($row[$key] != $_id){
            if(count($data) > 0){
                $groupedData[] = $data;
            }
    
            $_id = $row[$key];
            $data = [
                $key => $_id
            ];
        }
    
        unset($row[$key]);
        $data["data"][] = $row;

        if($i == count($array) - 1){
            $groupedData[] = $data;
        }
    }
    
    return $groupedData; 
}

print_r(groupBy($data, "id"));

The results will be:

Array
(
    [0] => Array
        (
            [id] => 96
            [data] => Array
                (
                    [0] => Array
                        (
                            [shipping_no] => 212755-1
                            [part_no] => reterty
                            [description] => tyrfyt
                            [packaging_type] => PC
                        )

                    [1] => Array
                        (
                            [shipping_no] => 212755-1
                            [part_no] => dftgtryh
                            [description] => dfhgfyh
                            [packaging_type] => PC
                        )

                )

        )

    [1] => Array
        (
            [id] => 97
            [data] => Array
                (
                    [0] => Array
                        (
                            [shipping_no] => 212755-2
                            [part_no] => ZeoDark
                            [description] => s%c%s%c%s
                            [packaging_type] => PC
                        )

                )

        )

)

If you change the "key" parameter, it should works without changes:

print_r(groupBy($data, "shipping_no"));

Array
(
    [0] => Array
        (
            [shipping_no] => 212755-1
            [data] => Array
                (
                    [0] => Array
                        (
                            [id] => 96
                            [part_no] => reterty
                            [description] => tyrfyt
                            [packaging_type] => PC
                        )

                    [1] => Array
                        (
                            [id] => 96
                            [part_no] => dftgtryh
                            [description] => dfhgfyh
                            [packaging_type] => PC
                        )

                )

        )

    [1] => Array
        (
            [shipping_no] => 212755-2
            [data] => Array
                (
                    [0] => Array
                        (
                            [id] => 97
                            [part_no] => ZeoDark
                            [description] => s%c%s%c%s
                            [packaging_type] => PC
                        )

                )

        )

)

Answer

Solution:

How about multiple level grouping.

data:

$rows = [
    ['country'=>'Japan',  'city'=>'Tokyo', 'surname'=>'Miyazaki', 'name'=>'Hayao'],
    ['country'=>'France', 'city'=>'Paris', 'surname'=>'Godard',   'name'=>'Jean-Luc'],
    ['country'=>'France', 'city'=>'Lyon',  'surname'=>'Godard',   'name'=>'Marguerite'],
    ['country'=>'Japan',  'city'=>'Tokyo', 'surname'=>'Miyazaki', 'name'=>'Akira'],
    ['country'=>'Japan',  'city'=>'Nara',  'surname'=>'Kurosawa', 'name'=>'Akira'],
    ['country'=>'France', 'city'=>'Paris', 'surname'=>'Duras',    'name'=>'Marguerite'],
];
$groups = groupBy($rows, 'country', 'city', 'surname');

code:

    function groupBy($rows, ...$keys)
    {
        if ($key = array_shift($keys)) {
            $groups = array_reduce($rows, function ($groups, $row) use ($key) {
                $group = is_object($row) ? $row->{$key} : $row[$key]; // object is available too.
                $groups[$group][] = $row;
                return $groups;
            }, []);
            if ($keys) {
                foreach ($groups as $subKey=>$subRows) {
                    $groups[$subKey] = self::groupBy($subRows, ...$keys);
                }
            }
        }
        return $groups;
    }

Answer

Solution:

What about array_combine() ? Using array_combine() stores each row on the index of $groupByColumn, so we can use that $groupByColumn as keys. This returns the last row for every group (array_combine() overwrites the value when the key already exists - see https://www.php.net/manual/en/function.array-combine.php#111668). If you want to return the first or some specific row, you can play around with array_reverse() or usort() etc.

$result = array_combine(
    array_column($source, $groupByColumn),
    $source
);

Answer

Solution:

By pushing a reference variable into the result array upon encountering a unique identifying column value, you don't need to re-index your result array after grouping.

Code: (Demo)

$result = [];
foreach ($array as $row) {
    $key = $row['id'];  // assign the grouping column's value
    unset($row['id']);  // remove the grouping column from the row
    if (!isset($ref[$key])) {
        $ref[$key] = [$row];      // declare first entry as a subarray
        $result[] = &$ref[$key];  // push the reference variable into the result array
    } else {
        $ref[$key][] = $row; // add a child to the reference
    }
}
var_export($result);

Source