Related
I have the following array
Array
(
[0] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => reterty
[description] => tyrfyt
[packaging_type] => PC
)
[1] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => dftgtryh
[description] => dfhgfyh
[packaging_type] => PC
)
[2] => Array
(
[id] => 97
[shipping_no] => 212755-2
[part_no] => ZeoDark
[description] => s%c%s%c%s
[packaging_type] => PC
)
)
How can I group the array by id? Is there any native php functions are available to do this?
While this approach works, I want to do this using a foreach, since with the above I will get duplicate items, which I'm trying to avoid?
On the above example id have 2 items, so its need to be inside of the id
There is no native one, just use a loop.
$result = array();
foreach ($data as $element) {
$result[$element['id']][] = $element;
}
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)
In a more functional programming style, you could use array_reduce
$groupedById = array_reduce($data, function (array $accumulator, array $element) {
$accumulator[$element['id']][] = $element;
return $accumulator;
}, []);
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"
}
}
}
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;
}
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;
});
$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;
}
$arr = array();
foreach($old_arr as $key => $item)
{
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
for($i = 0 ; $i < count($arr) ; $i++ )
{
$tmpArr[$arr[$i]['id']] = $arr[$i]['id'];
}
$vmpArr = array_keys($tmpArr);
print_r($vmpArr);
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
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
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;
}
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'));
Check indexed function from Nspl:
use function \nspl\a\indexed;
$grouped = indexed($data, 'id');
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;
}
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'))
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);
}
}
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;
}
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
)
)
)
)
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
);
I have the following array
Array
(
[0] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => reterty
[description] => tyrfyt
[packaging_type] => PC
)
[1] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => dftgtryh
[description] => dfhgfyh
[packaging_type] => PC
)
[2] => Array
(
[id] => 97
[shipping_no] => 212755-2
[part_no] => ZeoDark
[description] => s%c%s%c%s
[packaging_type] => PC
)
)
How can I group the array by id? Is there any native php functions are available to do this?
While this approach works, I want to do this using a foreach, since with the above I will get duplicate items, which I'm trying to avoid?
On the above example id have 2 items, so its need to be inside of the id
There is no native one, just use a loop.
$result = array();
foreach ($data as $element) {
$result[$element['id']][] = $element;
}
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)
In a more functional programming style, you could use array_reduce
$groupedById = array_reduce($data, function (array $accumulator, array $element) {
$accumulator[$element['id']][] = $element;
return $accumulator;
}, []);
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"
}
}
}
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;
}
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;
});
$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;
}
$arr = array();
foreach($old_arr as $key => $item)
{
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
for($i = 0 ; $i < count($arr) ; $i++ )
{
$tmpArr[$arr[$i]['id']] = $arr[$i]['id'];
}
$vmpArr = array_keys($tmpArr);
print_r($vmpArr);
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
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
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;
}
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'));
Check indexed function from Nspl:
use function \nspl\a\indexed;
$grouped = indexed($data, 'id');
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;
}
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'))
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);
}
}
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;
}
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
)
)
)
)
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
);
I want to iterate over a multidimensional array, count the occurrences of a String inside and delete Array items where the count is higher than e.g. 3.
I've already tried a pretty messy combination of array_search, array_count_values and strpos inside a N^N loop, but this takes way to long to process and the results are wrong...
This is the Array, I'm trying to alter
array(2) {
[0]=>
array(13) {
["id"]=>
string(6) "1234"
["name"]=>
string(28) "aa"
["productcategory"]=>
string(30) "Branch1^^subbranch1"
["streamID"]=>
int(0)
["streamContext"]=>
string(16) "static"
["prio"]=>
string(3) "100"
}
[1]=>
array(11) {
["id"]=>
string(6) "9876"
["name"]=>
string(30) "bb"
["productcategory"]=>
string(66) "Branch1^^subbranch2"
["streamID"]=>
int(0)
["streamContext"]=>
string(16) "static"
["prio"]=>
string(3) "100"
}
}
The surrounding Array can have around 200 Items. I'm looking for a way to remove Items if theyr productcategory is found more than X times.
Can you guys help me with this?
Yeah I've had to deal with something kind of similar. If you're looking at an array of around 200, then it should be too slow to create a counter loop and then unset the values of the original array based on those counters. I've provided a template to think about, to see if this is the direction you're after.
It makes a copy of the array, then counts the productcategory, of course I'm assuming that category^^subcategory is the count you are looking for.
<?php
$your_array = array(
array(
array(
"id" => "1234",
"name" => "aa",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch3",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch2",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch3",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
),
);
$counters = array();
$limit = 1; // whatever the limit is that you want
foreach ($your_array as $index => $array) {
for ($i = 0; $i < count($array); $i++) {
if (!isSet($counters[$array[$i]['productcategory']])) {
$counters[$array[$i]['productcategory']] = 0;
}
$counters[$array[$i]['productcategory']]++;
if ($counters[$array[$i]['productcategory']] > $limit) {
unset($your_array[$index][$i]);
}
}
}
print '<pre>' . print_r($counters, true) . '</pre>';
print '<pre>' . print_r($your_array, true) . '</pre>';
I'm unsetting that item in the sub array, as I'm not sure if you want to just unset the whole item.
My first question for you would be "where is your data coming from?" If this is coming out of a database, then I would recommend you tweak your query there. You can definitely solve this in PHP, but as your data set grows it will take longer and longer to loop over the dataset in PHP.
To solve this in PHP, I would recommend you create a new "product index" array. This array would be associative with the product name as the keys and the values would contain an array of all the top-level indexes in your dataset array. Once you've built the index array, you can loop over that to find which product types occur more than 3 times in the main dataset and quickly delete those items.
$productIndex = [];
// Build an index of product categories
foreach($dataset as $i => $row) {
if (!is_array($productIndex[$row['productcategory']]) {
$productIndex[$row['productcategory']] = [];
}
$productIndex[$row['productcategory']][] = $i;
}
// Search for indexes with > 3 rows
foreach($productIndex as $items) {
if (count($items) > 3) {
// Delete said rows
foreach ($items as $index) {
unset($dataset[$index]);
}
}
}
I havent been able to use a one size fits it all approach, but for future reference i will share my "solution". It doesnt feel like super sophisiticated but it gets the job done...
function filter_categories($input, $count) {
$output = $input;
$exploded_input = [];
foreach ($output as $key => $value) {
$exploded_items = explode("^^", $value["productcategory"]);
array_push($exploded_input, $exploded_items);
}
$sortedbyCategory = [];
$last_items = [];
$counted_items = [];
foreach ($exploded_input as $key => $value) {
$end = end($value);
array_push($last_items, $end);
}
$counted = array_count_values($last_items);
foreach ($counted as $key => $value) {
if($value<=$count) {
unset($counted[$key]);
}
}
foreach ($counted as $k => $v) {
for ($i=0; $i < count($input); $i++) {
if(strpos($input[$i]["productcategory"], $k)){
if($counted[$k] > $count) {
$input[$i]["hide"] = true;
$counted[$k]--;
}
}
}
}
foreach ($input as $key => $value) {
if(isset($value["hide"])) {
unset($input[$key]);
}
}
return $input;
}
I have an array like this:
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
And I have a variable that has the client given value from 1 to 100.
Question is: what is the best and fastest way to find the next biggest key, if the key itself doesn't exist?
E.g. I have a variable with value of 12, and I need to get the price for that. According to the array here, the price would be 15.40 because the next biggest key is 15.
I could try and find key number '12', and if it doesn't exist, i would add one(12+1) and try again, until next key is found, but is there any function that could do this for me, or something even better/faster?
Edit: Clarification about the structure of the array.
Array is like it is in the example here. Keys are ordered as can be seen.
A simple foreach will do, but to guard against either empty arrays or a needle that's higher than the highest key, here's an implementation that will cover that as well:
function find(array $pricing, $needle)
{
$last = null; // return value if $pricing array is empty
foreach ($pricing as $key => $value) {
if ($key >= $needle) {
return $key; // found it, return quickly
}
$last = $key; // keep the last key thus far
}
return $last;
}
$result = find($pricing, 12); // 15
$result = find($pricing, 101); // 100
$result = find([], 12); // null
Assuming you are looking for the 'requiredKey' and that the array is sorted by key
This seem to do what you want.
Code:
<?php
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
// What key we want...
$requiredKey = 12;
// outout in here
$foundKey = -1;
$foundValue = -1;
// always run the loop once...
do {
$foundKey = key($pricing); // store the current details
$foundValue = current($pricing);
next($pricing); // will be equal or greater
}
while ( current($pricing) !== false
&& $foundKey < $requiredKey);
echo '<pre>';
echo '<br />', 'key: ', $foundKey, ' value: ', $foundValue;
echo '</pre>';
Output:
key: 15 value: 15.4
Your logic is ok, you can do it using next()
http://php.net/manual/en/function.next.php
$search = 12;
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
$result = null;
if (!isset($pricing[$search])) {
do {
} while (next($pricing) && $search > key($pricing));
$result = current($pricing);
} else {
$result = $pricing[$search];
}
echo $result;
Your lookup array will not change so it makes perfect sense to declare it as a constant (instead of a variable) and to make your code immediately understandable, give the constant a self-explanatory name.
const THRESHOLD_PRICES = [
2 => 8.23,
5 => 10.97,
10 => 13.28,
15 => 15.40,
20 => 18.15,
25 => 20.36,
30 => 22.84,
40 => 25.60,
50 => 28.35,
60 => 31.89,
70 => 36.23,
80 => 39.40,
90 => 42.52,
100 => 44.75
];
A valuable benefit of using a constant is that it is not necessary to explicitly pass the data into a function's scope.
In your function, I recommend type hinting the parameter and the return value -- again to make the code easier to understand.
Before iterating the array, do a quick scan of the keys for an exact match. Because of the way that php treats arrays as hashmaps, this is a very low-cost technique.
Next iterate the array and break the loop as soon as the array key is larger than the search value.
Regardless of if/when the loop breaks, the final iteration will declare the targeted price, so unconditionally return that float value.
function getThresholdPrice(int $search): float {
if (isset(THRESHOLD_PRICES[$search])) {
return THRESHOLD_PRICES[$search];
}
foreach (THRESHOLD_PRICES as $threshold => $price) {
if ($threshold > $search) {
break;
}
}
return $price;
}
Tests: (Demo)
foreach (range(1, 105, 13) as $test) {
printf("%d => %.02f\n", $test, getThresholdPrice($test));
}
Output:
1 => 8.23
14 => 15.40
27 => 22.84
40 => 25.60
53 => 31.89
66 => 36.23
79 => 39.40
92 => 44.75
105 => 44.75
I have the following array
Array
(
[0] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => reterty
[description] => tyrfyt
[packaging_type] => PC
)
[1] => Array
(
[id] => 96
[shipping_no] => 212755-1
[part_no] => dftgtryh
[description] => dfhgfyh
[packaging_type] => PC
)
[2] => Array
(
[id] => 97
[shipping_no] => 212755-2
[part_no] => ZeoDark
[description] => s%c%s%c%s
[packaging_type] => PC
)
)
How can I group the array by id? Is there any native php functions are available to do this?
While this approach works, I want to do this using a foreach, since with the above I will get duplicate items, which I'm trying to avoid?
On the above example id have 2 items, so its need to be inside of the id
There is no native one, just use a loop.
$result = array();
foreach ($data as $element) {
$result[$element['id']][] = $element;
}
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)
In a more functional programming style, you could use array_reduce
$groupedById = array_reduce($data, function (array $accumulator, array $element) {
$accumulator[$element['id']][] = $element;
return $accumulator;
}, []);
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"
}
}
}
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;
}
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;
});
$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;
}
$arr = array();
foreach($old_arr as $key => $item)
{
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
for($i = 0 ; $i < count($arr) ; $i++ )
{
$tmpArr[$arr[$i]['id']] = $arr[$i]['id'];
}
$vmpArr = array_keys($tmpArr);
print_r($vmpArr);
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
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
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;
}
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'));
Check indexed function from Nspl:
use function \nspl\a\indexed;
$grouped = indexed($data, 'id');
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;
}
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'))
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);
}
}
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;
}
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
)
)
)
)
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
);