This question already has answers here:
Put multiple arrays in one large associative array
(2 answers)
Closed 2 years ago.
I am having two arrays with same keys from two different queries.
First query result:
Array
(
[0] => Array
(
[Contribution] => 1000.00
[P_Name] => A
)
[1] => Array
(
[Contribution] => 1500.00
[P_Name] => B
)
)
Second query result:
Array
(
[0] => Array
(
[Contribution] => 100.00
[P_Name] => A
)
[1] => Array
(
[Contribution] => 200.00
[P_Name] => B
)
)
The first array may be empty and/or the second may be empty.
I want to get the create a new array that finds the sum of Contribution values where P_Name values match, like this:
Array
(
[0] => Array
(
[Contribution] => 1100.00
[P_Name] => A
)
[1] => Array
(
[Contribution] => 1700.00
[P_Name] => B
)
)
I have tried array_merge():
$result1= $this->model1->getOthersTotal($date);
$result2=$this->model1->getMiscTotal($date);
$merged_result = array_merge( $result1, $result2 );
$merged_result contains:
Array (
[0] => Array (
[Contribution] => 1000.00
[P_Name] => A
)
[1] => Array (
[Contribution] => 1001.00
[P_Name] => A
)
[2] => Array (
[Contribution] => 69.00
[P_Name] => B
)
)
Input:
$a=[['Contribution'=>1000,'P_Name'=>'A'],
['Contribution'=>1500,'P_Name'=>'B'],
['Contribution'=>2000,'P_Name'=>'C']];
$b=[['Contribution'=>100,'P_Name'=>'A'],
['Contribution'=>200,'P_Name'=>'B'],
['Contribution'=>300,'P_Name'=>'D']];
If you temporarily assign associative keys to the subarrays using array_column(), then you can leverage array_merge_recursive() to group on P_Name values, then call array_sum() to do the addition if there is more than one value to for a given P_Name.
Method #1: (Demo)
$keyed=array_merge_recursive(array_column($a,NULL,'P_Name'),array_column($b,NULL,'P_Name'));
foreach($keyed as $p_name=>$array){
$result[]=['Contribution'=>(is_array($array['Contribution'])?array_sum($array['Contribution']):$array['Contribution']),'P_Name'=>$p_name];
}
var_export($result);
Or just do a standard merge to create one array, then loop and add as you go. Finalize the output array with array_values() to reindex the elements.
Method #2: (Demo)
foreach(array_merge($a,$b) as $array){
if(isset($result[$array['P_Name']])){
$result[$array['P_Name']]['Contribution']+=$array['Contribution'];
}else{
$result[$array['P_Name']]=$array;
}
}
$result=array_values($result);
var_export($result);
Output: (from either method)
array (
0 =>
array (
'Contribution' => 1100,
'P_Name' => 'A',
),
1 =>
array (
'Contribution' => 1700,
'P_Name' => 'B',
),
2 =>
array (
'Contribution' => 2000,
'P_Name' => 'C',
),
3 =>
array (
'Contribution' => 300,
'P_Name' => 'D',
),
)
It is out of the scope of this question, but chances are the best approach would be to perform this grouping/addition via database query.
Here is a quick and dirty way to do it: Loop over both arrays, the outer loop fills the inner loop. If no match was fount, $x remails 0, and the value will be added to the inner loop. If a match is found, $x is 1 and the inner loop will break to continue the outer loop.
$a = [['a' => 10,'b' => 'g'], ['a' => 11,'b' => 'h']];
$b = [['a' => 1, 'b' => 'g'], ['a' => 2, 'b' => 'h'], ['a' => 3, 'b' => 'i']];
// now its fool proof.
function mergeData( $a, $b )
{
if( empty( $a ) && empty( $b ) )
return [];
if( empty( $a ) )
return $b;
if( empty( $b ) )
return $a;
foreach( $b AS $i => $c ) {
$x = 0;
foreach( $a AS $ii => $d ) {
if( $c['b'] == $d['b'] ) {
$a[ $ii ]['a'] += $c['a'];
$x = 1;
break;
}
}
if( !$x )
$a[] = $b[ $i ];
}
return $a;
}
Output
Array
(
[0] => Array
(
[a] => 11
[b] => g
)
[1] => Array
(
[a] => 13
[b] => h
)
[2] => Array
(
[a] => 3
[b] => i
)
)
Little bit different approach
$array1 = [
[
'Contribution' => 10,
'P_Name' => 'A'
],
[
'Contribution' => 1500,
'P_Name' => 'B'
]
];
$array2 = [
[
'Contribution' => 200,
'P_Name' => 'B'
],
[
'Contribution' => 100,
'P_Name' => 'C'
],
];
$array3 = array_map(function($elem) use (&$array2){
foreach($array2 as $i => &$a2){
if($a2['P_Name'] == $elem['P_Name']){
$a2['Contribution'] += $elem['Contribution'];
return;
}
}
return $elem;
},$array1);
$array3 = array_merge(array_filter($array3),$array2);
print_r($array3);
output:
Array
(
[0] => Array
(
[Contribution] => 10
[P_Name] => A
)
[1] => Array
(
[Contribution] => 1700
[P_Name] => B
)
[2] => Array
(
[Contribution] => 100
[P_Name] => C
)
)
You can use array_reduce(), array_map(), and array_sum():
<?php
function merge(array ...$sets)
{
/**
* group contributions by name
*/
$contributions = array_reduce(
$sets,
function (array $contributions, array $set) {
foreach ($set as $element) {
$name = $element['P_Name'];
$contribution = $element['Contribution'];
if (!array_key_exists($name, $contributions)) {
$contributions[$name] = [];
}
$contributions[$name][] = $contribution;
}
return $contributions;
},
[]
);
/**
* normalize the array so we remove the name as key, and return a tuple of name and contribution, with the desired
* structure
*/
return array_values(array_map(function (array $contribution, $name) {
return [
'Contribution' => array_sum($contribution),
'P_Name' => $name,
];
}, $contributions, array_keys($contributions)));
}
$a = [
[
'Contribution' => 1000,
'P_Name' => 'A',
],
[
'Contribution' => 1500,
'P_Name' => 'B',
],
];
$b = [
[
'Contribution' => 100,
'P_Name' => 'A',
],
[
'Contribution' => 200,
'P_Name' => 'B',
],
];
$merged = merge($a, $b);
var_dump($merged);
Note Because of using variadics, any number of arrays can be passed to merge(). Requires at least PHP 5.6, though.
For reference, see:
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
http://php.net/manual/en/function.array-reduce.php
http://php.net/manual/en/function.array-key-exists.php
http://php.net/manual/en/function.array-map.php
http://php.net/manual/en/function.array-sum.php
http://php.net/manual/en/function.array-values.php
http://php.net/manual/en/function.array-keys.php
For an example, see:
https://3v4l.org/EI4Tn
Related
I have an array of subarrays in the following format:
[
'a' => ['id' => 20, 'name' => 'chimpanzee'],
'b' => ['id' => 40, 'name' => 'meeting'],
'c' => ['id' => 20, 'name' => 'dynasty'],
'd' => ['id' => 50, 'name' => 'chocolate'],
'e' => ['id' => 10, 'name' => 'bananas'],
'f' => ['id' => 50, 'name' => 'fantasy'],
'g' => ['id' => 50, 'name' => 'football']
]
And I would like to group it into a new array based on the id field in each subarray.
array
(
10 => array
(
e => array ( id = 10, name = bananas )
)
20 => array
(
a => array ( id = 20, name = chimpanzee )
c => array ( id = 20, name = dynasty )
)
40 => array
(
b => array ( id = 40, name = meeting )
)
50 => array
(
d => array ( id = 50, name = chocolate )
f => array ( id = 50, name = fantasy )
g => array ( id = 50, name = football )
)
)
$arr = array();
foreach ($old_arr as $key => $item) {
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
foreach($array as $key => $value){
$newarray[$value['id']][$key] = $value;
}
var_dump($newarray);
piece of cake ;)
The following code adapts #Tim Cooper’s code to mitigate Undefined index: id errors in the event that one of the inner arrays doesn’t contain an id:
$arr = array();
foreach($old_arr as $key => $item)
{
if(array_key_exists('id', $item))
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
However, it will drop inner arrays without an id.
E.g.
$old_arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' ),
'h' => array ( 'name' => 'bob' )
);
will drop the 'h' array completely.
You can also use Arrays::groupBy() from ouzo-goodies:
$groupBy = Arrays::groupBy($array, Functions::extract()->id);
print_r($groupBy);
And result:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
And here are the docs for Arrays and Functions.
Here is a function that will take an array as the first argument and a criteria (a string or callback function) as the second argument. The function returns a new array that groups the array as asked for.
/**
* Group items from an array together by some criteria or value.
*
* #param $arr array The array to group items from
* #param $criteria string|callable The key to group by or a function the returns a key to group by.
* #return array
*
*/
function groupBy($arr, $criteria): array
{
return array_reduce($arr, function($accumulator, $item) use ($criteria) {
$key = (is_callable($criteria)) ? $criteria($item) : $item[$criteria];
if (!array_key_exists($key, $accumulator)) {
$accumulator[$key] = [];
}
array_push($accumulator[$key], $item);
return $accumulator;
}, []);
}
Here is the given array:
$arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' )
);
And examples using the function with a string and a callback function:
$q = groupBy($arr, 'id');
print_r($q);
$r = groupBy($arr, function($item) {
return $item['id'];
});
print_r($r);
The results are the same in both examples:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
Passing the callback is overkill in the example above, but using the callback finds its use when you pass in an array of objects, a multidimensional array, or have some arbitrary thing you want to group by.
Maybe it's worth to mention that you can also use php array_reduce function
$items = [
['id' => 20, 'name' => 'chimpanzee'],
['id' => 40, 'name' => 'meeting'],
['id' => 20, 'name' => 'dynasty'],
['id' => 50, 'name' => 'chocolate'],
['id' => 10, 'name' => 'bananas'],
['id' => 50, 'name' => 'fantasy'],
['id' => 50, 'name' => 'football'],
];
// Grouping
$groupedItems = array_reduce($items, function ($carry, $item) {
$carry[$item['id']][] = $item;
return $carry;
}, []);
// Sorting
ksort($groupedItems, SORT_NUMERIC);
print_r($groupedItems);
https://www.php.net/manual/en/function.array-reduce.php
Because of how PHP's sorting algorithm treats multidimensional arrays -- it sorts by size, then compares elements one at a time, you can actually use a key-preserving sort on the input BEFORE restructuring. In functional style programming, this means that you don't need to declare the result array as a variable.
Code: (Demo)
asort($array);
var_export(
array_reduce(
array_keys($array),
function($result, $k) use ($array) {
$result[$array[$k]['id']][$k] = $array[$k];
return $result;
}
)
);
I must say that functional programming is not very attractive for this task because the first level keys must be preserved.
Although array_walk() is more succinct, it still requires the result array to be passed into the closure as a reference variable. (Demo)
asort($array);
$result = [];
array_walk(
$array,
function($row, $k) use (&$result) {
$result[$row['id']][$k] = $row;
}
);
var_export($result);
I'd probably recommend a classic loop for this task. The only thing the loop needs to do is rearrange the first and second level keys. (Demo)
asort($array);
$result = [];
foreach ($array as $k => $row) {
$result[$row['id']][$k] = $row;
}
var_export($result);
To be completely honest, I expect that ksort() will be more efficient than pre-loop sorting, but I wanted to a viable alternative.
I'm having this array:
array(
[1] => Array
(
[1111] => Array
(
[one] => 70
[two] => 7.0
)
)
[2] => Array
(
[1111] => Array
(
[one] => 10
[two] => 2.0
)
)
)
And i want it to sum [one] and [two] of the two different arrays and output an array like this:
Array
(
[1111] => Array (
[one] => 80
[two] => 9.0
)
)
Based on the key "1111"
I have tried with the following
$array = array_values( $array );
$sum_array = [];
foreach ( $array as $k => $sub_array ) {
foreach ( $sub_array as $id => $value ) {
$sum_array[$id] += array_key_exists( $id, $sum_array ) ? $sum_array[$id] += $value : $sum_array[$id] = $value;
$sum_array[$id]['two'] += array_sum( $sum_array[$id]['two'] ) / ( 100 * count( $sum_array[$id]['count'] ) );
}
}
But that gives me only the first value like this:
Array
(
[1111] => Array (
[one] => 70
[two] => 7.0
)
)
Can someone please tell me what i'm doing wrong?
You could use the array_reduce to reduce your array to only one element.
The array_reduce function takes two ( sometime three ) parameters. The first one is the array, the second one is a callback with two parameters , the collector, and the current item. If there is a third parameters to the array_reduce function, it will be used to initialize the collector ( like in our case ).
The function will parse each item and do something with it, the collector will be available at each iteration, that's why we use it to keep information of the data, in our case, it's the sum of the values. Once all the item has been iterated over, the function returns the collector.
<?php
$array = [
[
'1111' => [
'one' => 70,
'two' => 7.0
]
],
[
'1111' => [
'one' => 10,
'two' => 2.0
]
]
];
$output = array_reduce($array, function($collector, $item) {
$collector['1111']['one'] += $item['1111']['one'];
$collector['1111']['two'] += $item['1111']['two'];
return $collector;
}, ['1111' => ['one' => 0, 'two' => 0]]);
var_dump($output);
Here is an approach that uses a recursive merge. In the following when merged John's hours become an array. We then need to sum. As James only has one entry his hours do not become an array so we leave them be.
Hopefully the transitional merge output, will give you an idea of the process:
<?php
$items =
[
[
'john' => [
'hours' => 2,
'tips' => 7
]
],
[
'john' => [
'hours' => 3,
'tips' => 10
]
],
[
'james' => [
'hours' => 8,
'tips' => 0
]
]
];
$output = array_merge_recursive(...$items);
var_export($output);
array_walk($output, function(&$v) {
array_walk($v, function(&$v) {
if(is_array($v))
$v = array_sum($v);
});
});
echo "\nResult: \n";
var_export($output);
Output:
array (
'john' =>
array (
'hours' =>
array (
0 => 2,
1 => 3,
),
'tips' =>
array (
0 => 7,
1 => 10,
),
),
'james' =>
array (
'hours' => 8,
'tips' => 0,
),
)
Result:
array (
'john' =>
array (
'hours' => 5,
'tips' => 17,
),
'james' =>
array (
'hours' => 8,
'tips' => 0,
),
)
$total=0;
array_map(function ($item) use ($total)
{
$total += $item['one'] + $item['two'];
}, $array);
echo $total;
Or difference key
$one = array_sum(array_column($array, 'one'));
$two = array_sum(array_column($array, 'two'));
$resultArray = compact('one','two');
```
I have three array (is about the data migration)
$a = Array
(
[0] => Array
(
[0] => province
[1] => 701
[2] => AA
[3] => A
)
..
)
$b = Array
(
[0] => Array
(
[0] => district
[1] => 70101
[2] => BB
[3] => B
)
[1] => Array
(
[0] => district
[1] => 70102
[2] => BB1
[3] => B1
)
..
)
$c = Array
(
[0] => Array
(
[0] => commune
[1] => 7010101
[2] => CC
[3] => C
),
[1] => Array
(
[0] => commune
[1] => 7010102
[2] => CC1
[3] => C1
)
..
)
What I want is to merge all $a , $b , $c' to become a new array
in this example array that have value701is the key of sub array70101and70101is the key of sub array7010101`
So the final array may look something like this:
$d = array (
701=>array(
70101=>array(7010101,7010102),
70102=>array(7010201,7010202),
),
)
The attempt is like the following:
# Your data structure here:
$a = array(
'701' => 'foo',
'702' => 'bar',
);
$b = array(
'70101' => 'foo-foo',
'70102' => 'foo-bar',
);
$c = array(
'7010101' => 'foo-foo-foo',
'7010102' => 'foo-foo-bar',
'7020101' => 'bar-foo-foo',
'7020201' => 'bar-bar-foo',
);
# The array you want
$buffer = array();
# Loop through the deepest elements (here: commune)
foreach ($c as $key => $value) {
# Find the keys for the parent groups
$province_key = substr($key, 0, 3);
$district_key = substr($key, 0, 5);
# Fill the buffer
$buffer[$province_key][$district_key][$key] = $value;
}
# Debug: The generated array
echo '<pre>';
print_r($buffer);
echo '</pre>';
You can copy&paste it here and hit run.
Test data:
$arrayA = [
[
0 => 'province',
1 => 701,
2 => 'AA',
3 => 'A'
],
[
0 => 'province',
1 => 702,
2 => 'AA1',
3 => 'A1'
],
];
$arrayB = [
[
0 => 'district',
1 => 70102,
2 => 'BB',
3 => 'B'
],
[
0 => 'district',
1 => 70101,
2 => 'BB1',
3 => 'B1'
],
];
$arrayC = [
[
0 => 'commune',
1 => 7010101,
2 => 'CC',
3 => 'C'
],
[
0 => 'commune',
1 => 7010102,
2 => 'CC1',
3 => 'C1'
]
];
Solution:
function mergeArraysToOneOnField(array $arrayA, array $arrayB, array $arrayC, $fieldName) {
$result = [];
/*
checks like
!is_string($fieldName) && !is_integer($fieldName)
*/
$arrayARelevantFields = array_column($arrayA, $fieldName);
$arrayBRelevantFields = array_column($arrayB, $fieldName);
$arrayCRelevantFields = array_column($arrayC, $fieldName);
foreach ($arrayARelevantFields as $arrayARelevantField) {
$arrayAFilteredRelevantField = filterArrayByStrpos($arrayBRelevantFields, $arrayARelevantField);
foreach ($arrayAFilteredRelevantField as $arrayBRelevantField) {
$result[$arrayARelevantField][$arrayBRelevantField] =
filterArrayByStrpos($arrayCRelevantFields, $arrayBRelevantField)
;
}
}
return $result;
}
Test run:
$mergedArray = mergeArraysToOneOnField($arrayA, $arrayB, $arrayC, 1);
print_r($mergedArray);
Test Result:
Array
(
[701] => Array
(
[70102] => Array
(
)
[70101] => Array
(
[0] => 7010101
[1] => 7010102
)
)
)
The solution can be extended with recursion, in order to hanlde a variable number of input arrays:
function mergeArraysToOneOnField(array &resultArray, array $inputAarray, $field) {
...
}
$array1 = array("color" => "red", 2, 4);
$array2 = array("a", "b", "color" => "green", "shape" => "trapezoid", 4);
$result = array_merge($array1, $array2);
Output
Array ( [color] => green [0] => 2 [1] => 4 [2] => a [3] => b [shape] => trapezoid [4] => 4 )
I have 2 arrays:
Array ( [0] => Array ( [intTrackId] => 41 [intAverageRating] => 10 [bolNewRelease] => 0 [dtDateAdded] => 2013-03-08 17:32:26 ) [1] => Array ( [intTrackId] => 1 [intAverageRating] => 7 [bolNewRelease] => 0 [dtDateAdded] => 2013-03-08 18:54:35 ))
Array ( [0] => Array ( [intTrackId] => 41 [intAverageRating] => 5.5000 [bolNewRelease] => 1 [dtDateAdded] => 2014-03-25T09:39:28Q ) [1] => Array ( [intTrackId] => 361 [intAverageRating] => 8.0000 [bolNewRelease] => 1 [dtDateAdded] => 2014-03-25T09:39:28Q ))
I want to remove the items in the second which have a matching track ID in the first. So in this example, I would get:
Array ( [0] => Array ( [intTrackId] => 361 [intAverageRating] => 8.0000 [bolNewRelease] => 1 [dtDateAdded] => 2014-03-25T09:39:28Q ))
Is this possible with array_filter or is this a little complex for that?
Just use array_udiff() - it's intended to do this:
$one = Array (
0 => Array ('intTrackId' => 41, 'intAverageRating' => 10, 'bolNewRelease' => 0, 'dtDateAdded' => '2013-03-08 17:32:26' ),
1 => Array ('intTrackId' => 1, 'intAverageRating' => 7, 'bolNewRelease' => 0, 'dtDateAdded' => '2013-03-08 18:54:35' )
);
$two = Array (
0 => Array ('intTrackId' => 41, 'intAverageRating' => 5.5000, 'bolNewRelease' => 1, 'dtDateAdded' => '2014-03-25T09:39:28Q' ),
1 => Array ('intTrackId' => 361, 'intAverageRating' => 8.0000, 'bolNewRelease' => 1, 'dtDateAdded' => '2014-03-25T09:39:28Q' )
);
$result = array_udiff($two, $one, function($x, $y)
{
return $x['intTrackId']-$y['intTrackId'];
});
Yes it can be done with array_filter:
$array1 = array(...);
$array2 = array(...);
$newArray = array_filter($array2, function($item) use ($array1){
foreach($array1 as $elem){
if($item['intTrackId'] == $elem['intTrackId']){
return false;
}
}
return true;
});
I would first create a loop and store all track IDs from the first array in a separate array.
Then I'd loop over the second array and delete those keys that exist in the track ID array.
$track_ids = array();
foreach($array1 as $index => $items) {
$track_ids[$items['intTrackId']] = $index;
}
foreach($array2 as $items) {
if (isset($track_ids[$items['intTrackId']])) {
unset($array2[$track_ids[$items['intTrackId']]]);
}
}
I have an array of subarrays in the following format:
[
'a' => ['id' => 20, 'name' => 'chimpanzee'],
'b' => ['id' => 40, 'name' => 'meeting'],
'c' => ['id' => 20, 'name' => 'dynasty'],
'd' => ['id' => 50, 'name' => 'chocolate'],
'e' => ['id' => 10, 'name' => 'bananas'],
'f' => ['id' => 50, 'name' => 'fantasy'],
'g' => ['id' => 50, 'name' => 'football']
]
And I would like to group it into a new array based on the id field in each subarray.
array
(
10 => array
(
e => array ( id = 10, name = bananas )
)
20 => array
(
a => array ( id = 20, name = chimpanzee )
c => array ( id = 20, name = dynasty )
)
40 => array
(
b => array ( id = 40, name = meeting )
)
50 => array
(
d => array ( id = 50, name = chocolate )
f => array ( id = 50, name = fantasy )
g => array ( id = 50, name = football )
)
)
$arr = array();
foreach ($old_arr as $key => $item) {
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
foreach($array as $key => $value){
$newarray[$value['id']][$key] = $value;
}
var_dump($newarray);
piece of cake ;)
The following code adapts #Tim Cooper’s code to mitigate Undefined index: id errors in the event that one of the inner arrays doesn’t contain an id:
$arr = array();
foreach($old_arr as $key => $item)
{
if(array_key_exists('id', $item))
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
However, it will drop inner arrays without an id.
E.g.
$old_arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' ),
'h' => array ( 'name' => 'bob' )
);
will drop the 'h' array completely.
You can also use Arrays::groupBy() from ouzo-goodies:
$groupBy = Arrays::groupBy($array, Functions::extract()->id);
print_r($groupBy);
And result:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
And here are the docs for Arrays and Functions.
Here is a function that will take an array as the first argument and a criteria (a string or callback function) as the second argument. The function returns a new array that groups the array as asked for.
/**
* Group items from an array together by some criteria or value.
*
* #param $arr array The array to group items from
* #param $criteria string|callable The key to group by or a function the returns a key to group by.
* #return array
*
*/
function groupBy($arr, $criteria): array
{
return array_reduce($arr, function($accumulator, $item) use ($criteria) {
$key = (is_callable($criteria)) ? $criteria($item) : $item[$criteria];
if (!array_key_exists($key, $accumulator)) {
$accumulator[$key] = [];
}
array_push($accumulator[$key], $item);
return $accumulator;
}, []);
}
Here is the given array:
$arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' )
);
And examples using the function with a string and a callback function:
$q = groupBy($arr, 'id');
print_r($q);
$r = groupBy($arr, function($item) {
return $item['id'];
});
print_r($r);
The results are the same in both examples:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
Passing the callback is overkill in the example above, but using the callback finds its use when you pass in an array of objects, a multidimensional array, or have some arbitrary thing you want to group by.
Maybe it's worth to mention that you can also use php array_reduce function
$items = [
['id' => 20, 'name' => 'chimpanzee'],
['id' => 40, 'name' => 'meeting'],
['id' => 20, 'name' => 'dynasty'],
['id' => 50, 'name' => 'chocolate'],
['id' => 10, 'name' => 'bananas'],
['id' => 50, 'name' => 'fantasy'],
['id' => 50, 'name' => 'football'],
];
// Grouping
$groupedItems = array_reduce($items, function ($carry, $item) {
$carry[$item['id']][] = $item;
return $carry;
}, []);
// Sorting
ksort($groupedItems, SORT_NUMERIC);
print_r($groupedItems);
https://www.php.net/manual/en/function.array-reduce.php
Because of how PHP's sorting algorithm treats multidimensional arrays -- it sorts by size, then compares elements one at a time, you can actually use a key-preserving sort on the input BEFORE restructuring. In functional style programming, this means that you don't need to declare the result array as a variable.
Code: (Demo)
asort($array);
var_export(
array_reduce(
array_keys($array),
function($result, $k) use ($array) {
$result[$array[$k]['id']][$k] = $array[$k];
return $result;
}
)
);
I must say that functional programming is not very attractive for this task because the first level keys must be preserved.
Although array_walk() is more succinct, it still requires the result array to be passed into the closure as a reference variable. (Demo)
asort($array);
$result = [];
array_walk(
$array,
function($row, $k) use (&$result) {
$result[$row['id']][$k] = $row;
}
);
var_export($result);
I'd probably recommend a classic loop for this task. The only thing the loop needs to do is rearrange the first and second level keys. (Demo)
asort($array);
$result = [];
foreach ($array as $k => $row) {
$result[$row['id']][$k] = $row;
}
var_export($result);
To be completely honest, I expect that ksort() will be more efficient than pre-loop sorting, but I wanted to a viable alternative.