71

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 )
          )
)
0

7 Answers 7

144
$arr = array();

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

ksort($arr, SORT_NUMERIC);
Sign up to request clarification or add additional context in comments.

7 Comments

@Herbert, I'm guessing writing to a non-existing id affects performance? or does it fire off PHP warnings?
@SampleJACK: My mistake. At first glance I thought he was verifying that an id exists in $old_arr. Now that I examine it more closely, using array_key_exists doesn't add anything to this code. The result is exactly the same without it. In terms of performance: it calls a function on an array inside a loop which has to outweigh any performance hit you'd take from writing to a non-existent key, so I'd suggest dropping the whole if() block.
@Herbert: I had added it as I thought an error would be displayed if the error reporting threshold was too low. I tested it and doesn't seem to complain.
@Tim: Yeah, I have my error reporting cranked up to show everything and, you're right -- no complaints. I didn't mean to insinuate that it was bad code in any way. SampleJACK brought up performance and, after thinking about it, it makes sense to drop it. In all honesty, I thought it was checking the ids on the inner arrays. That'll teach me to read more carefully. :p You still get my +1 for good code.
I added an answer for posterity to clarify what I've been talking about.
|
16
foreach($array as $key => $value){
   $newarray[$value['id']][$key] = $value;
}

var_dump($newarray);

piece of cake ;)

2 Comments

Probably equally easy to explain how your code works and why you feel it is the best technique to use.
But really, there is no new value to keeping this answer on the page. This code-only answer (an exact duplicate of Tim's technique) was posted 10 minutes after Tim posted.
4

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.

1 Comment

This is an "invented problem" -- not represented in the OP's question. Probably better to find another question which poses this problem and post it there.
2

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.

Comments

2

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.

Comments

0

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

2 Comments

This is not the desired result. You have lost the original first level keys which are expected to become second level keys.
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.