当前位置:首页 > 文章列表 > 文章 > php教程 > PHP array_reduce 怎样累积生成分组结果

PHP array_reduce 怎样累积生成分组结果

来源:17golang原创 2026-10-26 22:50:25 0浏览 收藏

我第一次用 array_reduce 整理订单接口时,想要的结果是“按商品类别分组”,写出来却只有一条记录。后来发现,array_reduce 不会自动理解“分组”:它只负责把每一轮回调返回的值交给下一轮。只要把 $carry 设计成分组数组,并在每轮返回它,就能稳定累积结果。

官方地址:https://www.php.net/manual/en/function.array-reduce.php

分组的核心写法是:用 [] 作为 initial,用记录中的分类字段作为累加器键;遇到新键就创建分组,遇到旧键就向该分组的 items 追加当前记录,最后返回完整的 $carry。

先把 carry 看成“正在生成的结果”

array_reduce($array, $callback, $initial) 每次把两个值交给回调:上一次回调返回的 $carry,以及当前遍历到的 $item。第一轮的 $carry 来自 $initial。因此,分组场景中的累加器不应该是一个数字,而应该是一个可以不断添加分组的数组。

PHP array_reduce 从输入记录经过 carry 和当前项累积为分组结果的结构图
图1:array_reduce 的 carry 累积链路说明图,展示重复分类如何回到同一个分组桶;这是静态说明图,不是运行截图。

我现在会先写出结果结构,再写回调。假设接口需要下面这样的结果:

 [
        'category' => 'book',
        'items' => [/* 订单记录 */],
        'total_amount' => 0,
    ],
];
?>

这个结构有两个好处:分类名称是稳定的键,前端或后续业务可以直接读取;同一分类的多条订单都留在 items 中,不会被后来的记录覆盖。

用 initial = [] 创建分组容器

把空数组作为第三个参数,是这类代码最重要的起点。它让第一轮回调拿到的 $carry 已经是可写的数组,也让没有数据时的返回值仍然是数组,而不是意外的 null。

 101, 'category' => 'book', 'amount' => 29.90],
    ['id' => 102, 'category' => 'tool', 'amount' => 49.00],
    ['id' => 103, 'category' => 'book', 'amount' => 15.50],
];

$grouped = array_reduce(
    $orders,
    function (array $carry, array $item): array {
        // 读取分组键;真实项目中应先约定缺失 category 的处理策略。
        $category = $item['category'];

        // 第一次遇到分类时创建桶,避免直接写入不存在的嵌套键。
        if (!isset($carry[$category])) {
            $carry[$category] = [
                'category' => $category,
                'items' => [],
                'total_amount' => 0.0,
            ];
        }

        // 重复分类进入同一个桶,不覆盖之前已经收集的订单。
        $carry[$category]['items'][] = $item;
        $carry[$category]['total_amount'] += $item['amount'];

        // 返回完整累加器,下一轮会把它作为新的 carry。
        return $carry;
    },
    [] // 显式 initial:空输入时也返回空数组。
);

print_r($grouped);
?>

这里的关键不是匿名函数本身,而是最后的 return $carry;。每次回调只返回当前订单,或者只返回当前分组,都会让上一轮已经积累的结果丢失。

新分组创建桶,已有分组追加记录

可以把回调想成一个很小的状态机:分类键不存在时创建状态,分类键已经存在时更新状态。上面的输入会得到两个分组,book 有两条订单,tool 有一条订单,总金额也只在对应桶内累加。

当前情况累加器动作结果变化
第一次遇到 book创建 book 桶items 增加第 1 条记录
第一次遇到 tool创建 tool 桶items 增加第 1 条记录
再次遇到 book复用已有桶items 追加第 2 条记录,金额继续累加

如果只想保留每组的计数,不需要把完整记录放入 items,也可以把桶缩小为 ['count' => 0]。先确定结果结构,再决定回调中保存哪些字段,通常比边写边改嵌套数组更不容易出错。

空数组、缺失字段和初始值要分开处理

PHP array_reduce 的 initial、空输入、新分组和已有分组追加边界说明图
图2:initial 与分组键边界说明图,强调空输入返回 initial、重复键追加以及回调必须返回累加器;这是静态说明图,不是运行截图。

官方手册说明,如果输入数组为空且没有传入 initial,array_reduce 会返回 null。所以接口约定希望始终返回数组时,应显式传入 []:

 $carry);

// 传入空数组:空输入仍得到可遍历的数组结果。
$withInitial = array_reduce([], fn (array $carry, array $item): array => $carry, []);

var_dump($withoutInitial); // NULL
var_dump($withInitial);    // array(0) { }
?>

另外,示例里直接读取 $item['category'],前提是输入契约保证该字段存在。若数据来自外部接口,可以明确选择跳过、归入 unknown,或抛出异常,不要让缺失字段在循环中悄悄生成错误分组。

最容易忽略的是“必须返回 carry”

回调可以改变数组变量,但 array_reduce 只把回调的返回值当成下一轮累加器。如果某个分支没有返回值,PHP 会得到 null,下一轮再按数组访问就可能出现警告或类型错误。

 0];
        $carry[$category]['count']++;

        // 所有路径都返回数组,避免下一轮 carry 变成 null。
        return $carry;
    },
    []
);
?>

什么时候适合用 array_reduce

当结果确实是“逐条输入累积成一个值”时,array_reduce 很清楚:计数、求和、按键聚合和构建摘要都适合。若回调里需要大量分支、多个外部副作用或复杂的异常恢复,普通 foreach 往往更直观;不要为了使用函数名而把状态逻辑隐藏在一个过长的闭包里。

我自己的判断是:分组逻辑不在于代码短,而在于累加器结构是否稳定。只要提前决定 initial、分组键和重复键的更新方式,array_reduce 就能把扁平记录整理成接口需要的结构;如果这些规则还没确定,先用普通循环把数据契约写清楚,维护成本通常更低。

相关问题

array_reduce 能同时拿到数组键吗?

标准回调只接收 $carry 和 $item,不会直接传入当前数组键。如果分组依据来自键名,先把键转换成记录字段,或使用更适合保留键的遍历方式。

为什么空输入得到 null?

因为没有任何一轮回调可以生成返回值;传入 initial 后,它既是首轮累加器,也是空输入时的最终结果。

回调里修改 carry 后还需要 return 吗?

需要。array_reduce 依赖回调返回值推进下一轮,所以每条路径都应返回完整累加器。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
PHP array_map 怎样保留多个数组的对应位置PHP array_map 怎样保留多个数组的对应位置
上一篇
PHP array_map 怎样保留多个数组的对应位置
PHP filter_var 怎样校验 URL 与邮箱地址
下一篇
PHP filter_var 怎样校验 URL 与邮箱地址
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    719次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    726次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    801次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    762次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    599次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码