按列值对一个关联数组的关联数组进行分组,并保留原始第一级键

70

我有一个子数组的数组,格式如下:

[
    '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']
]

我想根据每个子数组中的id字段将其分组为一个新数组。

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 )
          )
)
7个回答

143
$arr = array();

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

ksort($arr, SORT_NUMERIC);

@Herbert,我猜写入不存在的ID会影响性能?还是会触发PHP警告? - Anson Kao
1
@SampleJACK:我的错误。第一眼看,我认为他正在验证 $old_arr 中是否存在一个 id。现在仔细检查后,使用 array_key_exists 对这段代码没有任何增益。结果与不使用它完全相同。就性能而言:它在循环内调用数组上的函数,这必须比写入不存在的键所造成的任何性能损失更加重要,因此建议放弃整个 if() 块。 - Herbert
@Herbert:我添加它是因为我认为如果错误报告阈值太低,会显示错误。我测试过了,似乎没有抱怨。 - user142162
@Tim:是的,我的错误报告已经调到“显示所有内容”,而且你说得没错——没有任何投诉。我并不是想暗示它是糟糕的代码。SampleJACK提到了性能问题,在考虑后,放弃它是有道理的。老实说,我以为它是在检查内部数组上的ID。那教训我要更仔细地阅读。:p 你依然因为好的代码得到我的+1。 - Herbert
我添加了一个答案作为后人参考,以澄清我一直在谈论的内容。 - Herbert
显示剩余2条评论

15
foreach($array as $key => $value){
   $newarray[$value['id']][$key] = $value;
}

var_dump($newarray);

小菜一碟 ;)


可能同样容易解释你的代码如何工作以及为什么你觉得它是最好的技术方法。 - mickmackusa
2
但实际上,保留此答案在页面上没有任何新价值。这个仅包含代码的答案(与Tim的技术完全相同)是在Tim发帖后10分钟发布的。 - mickmackusa

4
以下代码是对@Tim Cooper的代码进行改进,以解决当内部数组中不包含id时出现Undefined index: id错误的问题:
$arr = array();

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

ksort($arr, SORT_NUMERIC);

然而,它会删除没有id的内部数组。

例如:

$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' )
);

将完全删除'h'数组。

2
这是一个“虚构的问题”——并没有在OP的问题中表示出来。最好找到另一个提出这个问题的问题,并在那里发布它。 - mickmackusa

2

您还可以使用来自ouzo-goodiesArrays::groupBy()

$groupBy = Arrays::groupBy($array, Functions::extract()->id);

print_r($groupBy);

并且结果:

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
                )

        )

)

以下是有关数组函数的文档。


2

这里有一个函数,它将一个数组作为第一个参数,将条件(字符串或回调函数)作为第二个参数。该函数返回一个新的数组,按要求对数组进行分组。

/**
 * 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;
    }, []);
}

这是给定的数组:

$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' )
);

使用字符串和回调函数的示例:

$q = groupBy($arr, 'id');
print_r($q);

$r = groupBy($arr, function($item) {
    return $item['id'];
});
print_r($r);

这两个示例的结果是相同的:

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
                )

        )

)

在上面的例子中传递回调函数可能有些过度,但是当您传入对象数组、多维数组或者想要按某种任意方式分组时,使用回调函数会更加实用。

0
由于PHP的排序算法如何处理多维数组——它按大小排序,然后逐个比较元素,因此您实际上可以在重构之前对输入使用保键排序。在函数式编程中,这意味着您不需要将结果数组声明为变量。
代码:(演示)
asort($array);
var_export(
    array_reduce(
        array_keys($array),
        function($result, $k) use ($array) {
            $result[$array[$k]['id']][$k] = $array[$k];
            return $result;
        }
    )
);

我必须说,函数式编程对于这个任务并不是很有吸引力,因为必须保留第一级键。

虽然array_walk()更加简洁,但它仍需要将结果数组作为引用变量传递给闭包。(演示

asort($array);
$result = [];
array_walk(
    $array,
    function($row, $k) use (&$result) {
        $result[$row['id']][$k] = $row;
    }
);
var_export($result);

我建议使用经典的循环来完成这个任务。循环需要做的唯一事情就是重新排列第一和第二级键。(演示)
asort($array);
$result = [];
foreach ($array as $k => $row) {
    $result[$row['id']][$k] = $row;
}
var_export($result);

说实话,我预计ksort()比预循环排序更有效率,但我想要一个可行的替代方案。

0
也许值得一提的是,您也可以使用php的array_reduce函数。
$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


1
这不是期望的结果。您已经丢失了原始的第一级键,这些键应该成为第二级键。 - mickmackusa
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - hakre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接