← PHP range()函数:快速生成区间数组的实战教程 没有下一篇了 →

PHP array_reduce()函数:用回调把数组归约为单值的实战教程

著
原创 2026-10-08 PHP 已有人查阅

一、array_reduce()的定位

数组处理函数大致分两类:一类是"变换",比如array_map把每个元素改成另一个值;一类是"筛选",比如array_filter保留符合条件的元素。array_reduce()属于第三类——归约,把整个数组压缩成一个结果。

这个结果不一定是个数字。累加、累乘、字符串拼接、构造嵌套数组,都可以用它完成。理解"归约"这个概念,比记住函数签名更重要。

二、函数原型与参数

array_reduce(array $array, callable $callback, mixed $initial = null): mixed
参数 说明 是否必需
array 待归约的数组 必需
callback 用户自定义的回调函数,接收两个参数 必需
initial 初始值,作为第一次回调的第一个参数 可选

返回值:归约后的最终结果,类型由回调的返回类型决定,可以是int、float、string、array等。

回调函数的签名是callback(mixed$carry,mixed$item):mixed。这里有两个关键点:

第一,参数顺序。第一个参数是"累计值"(carry),第二个参数是"当前元素"。很多人第一次写会记反,把元素当成第一个参数,结果怎么算都不对。

第二,初始值的作用。如果不传initial,PHP会把数组的第一个元素当作初始carry,然后从第二个元素开始遍历。如果传了initial,则从第一个元素开始,initial作为第一次回调的carry。

三、初始值对结果的影响

这是array_reduce()最容易出问题的地方。看两个对比:

不传初始值:

<?php
function add($carry, $item) {
    $carry += $item;
    return $carry;
}

$nums = array(1, 2, 3, 4, 5, 6);
echo array_reduce($nums, "add");

输出:

21

传初始值10:

<?php
function add($carry, $item) {
    $carry += $item;
    return $carry;
}

$nums = array(1, 2, 3, 4, 5, 6);
echo array_reduce($nums, "add", 10);

输出:

31

初始值直接参与运算。做累加时初始值传0、做累乘时传1、做字符串拼接时传空串,是常见做法。但要注意,传了初始值后,数组的第一个元素也会进入回调,不会因为"已经有初始值"而被跳过。

四、回调里能做的事

回调的返回类型决定了整个归约的方向。几个典型场景:

字符串拼接:

<?php
function joinWithDash($carry, $item) {
    return $carry . "-" . $item;
}

$langs = array("PHP", "java", "python");
print_r(array_reduce($langs, "joinWithDash", "code"));

输出:

code-PHP-java-python

初始值"code"作为第一段,后面依次拼接。这种写法适合构造带前缀的字符串。

累乘:

<?php
function multiply($carry, $item) {
    $carry = $carry * $item;
    return $carry;
}

$nums = array(2, 4, 5, 10, 100);
print_r(array_reduce($nums, "multiply", 3));

输出:

120000

初始值3与所有元素相乘。如果初始值传1,结果就是元素本身的乘积。传0的话,整个结果会变成0,这是累乘场景下要避开的一个坑。

归约成数组:

回调不一定返回标量。把元素按条件分组,最终返回一个数组,同样可行:

<?php
$users = [
    ['name' => 'A', 'age' => 20],
    ['name' => 'B', 'age' => 30],
    ['name' => 'C', 'age' => 20],
];

$grouped = array_reduce($users, function ($carry, $user) {
    $carry[$user['age']][] = $user['name'];
    return $carry;
}, []);

print_r($grouped);

输出:

Array
(
    [20] => Array
        (
            [0] => A
            [1] => C
        )

    [30] => Array
        (
            [0] => B
        )
)

这里初始值传空数组[]是必需的。如果不传,第一次回调的carry会变成第一个用户数组,逻辑就乱了。

五、与foreach、array_sum的取舍

方式 适用场景 可读性 性能
array_reduce() 归约逻辑固定、想用函数式表达 高(逻辑集中) 略低(回调开销)
foreach 逻辑复杂、需要提前break 中 高
array_sum() 纯数字求和 高 高
array_product() 纯数字求积 高 高

选择原则:

  • 单纯求和用array_sum(),不要用array_reduce()绕一圈。

  • 单纯求积用array_product()。

  • 归约逻辑带条件判断、类型转换时,array_reduce()能把逻辑收在一个回调里,可读性好。

  • 需要在遍历中途break或做副作用操作时,foreach更合适。

个人经验:曾经在一个统计脚本里,用array_reduce()做"按状态累加订单金额",写法很紧凑。后来需求变成"累加过程中如果遇到异常状态就中断并记录日志",array_reduce()就不合适了,因为回调里没法直接中断整个归约。归约适合"必须把整个数组跑完"的场景,需要提前退出的还是foreach。

六、项目实例

实例一:代码号学习编程·计算购物车总价

<?php
$items = [
    ['price' => 99, 'qty' => 2],
    ['price' => 45, 'qty' => 1],
    ['price' => 120, 'qty' => 3],
];

$total = array_reduce($items, function ($carry, $item) {
    return $carry + $item['price'] * $item['qty'];
}, 0);

echo $total; // 603

初始值传0,保证累加的起点是数字,不会因数组为空而返回null。

实例二:代码号学习编程·拼接SQL条件片段

<?php
$conditions = ['status = 1', 'deleted = 0', 'uid > 0'];

$where = array_reduce($conditions, function ($carry, $item) {
    return $carry === '' ? $item : $carry . ' AND ' . $item;
}, '');

echo $where;

输出:

status = 1 AND deleted = 0 AND uid > 0

这里用初始值空串配合判断,避免了第一个元素前面多出一个AND。

实例三:代码号学习编程·数组按键分组

<?php
$logs = [
    ['level' => 'error', 'msg' => 'A'],
    ['level' => 'info', 'msg' => 'B'],
    ['level' => 'error', 'msg' => 'C'],
];

$byLevel = array_reduce($logs, function ($carry, $log) {
    $carry[$log['level']][] = $log['msg'];
    return $carry;
}, []);

print_r($byLevel);

输出:

Array
(
    [error] => Array
        (
            [0] => A
            [1] => C
        )

    [info] => Array
        (
            [0] => B
        )
)

分组类需求用array_reduce()比先array_filter再合并更直接。

七、踩坑记录

坑一:回调参数顺序写反。第一个是carry,第二个是item。写反后累加结果会错乱,且不容易一眼看出来。

坑二:累乘初始值传0。0乘任何数都是0,整个归约结果直接归零。累乘初始值应传1。

坑三:归约成数组时不传初始值。不传时第一次carry是第一个元素(可能是关联数组),后续往它里面塞数据,结构会和预期不同。归约成数组、字符串时应显式传初始值。

坑四:以为空数组会返回初始值。空数组配初始值时确实返回初始值;但空数组不传初始值时返回null。业务里如果依赖返回值类型,要显式传初始值。

坑五:在回调里做副作用。往数据库写、打日志这类操作放进回调,会让归约逻辑变得难以测试。回调保持纯粹,副作用放到外层。

八、本节课程知识要点

  1. array_reduce()把数组归约为单个值,返回类型由回调决定。

  2. 回调第一个参数是累计值carry,第二个是当前元素item。

  3. 不传初始值时,第一个元素充当初始carry;传了则从第一个元素开始回调。

  4. 累加初始值传0,累乘初始值传1,拼接字符串传空串,归约成数组传空数组。

  5. 纯求和用array_sum(),纯求积用array_product(),不要用array_reduce()绕路。

  6. 需要中途中断遍历时,array_reduce()不合适,改用foreach。

九、常见问题

问:array_reduce()能处理关联数组吗?

可以。它遍历的是值,键名不会传给回调。如果回调里需要键名,得先用array_keys或其他方式把键带进元素里。

问:回调能不能接收第三个参数?

不能。array_reduce()的回调只接收两个参数。需要额外上下文时,用use关键字把外部变量引入闭包。

问:初始值传null和不传有区别吗?

有。不传时初始carry是数组第一个元素;显式传null时,第一次回调的carry是null。两者行为不同,别混用。

问:归约结果类型和数组元素类型不一致可以吗?

可以。回调返回什么类型,最终结果就是什么类型。数组元素是数字,归约成字符串也没问题。

← PHP range()函数:快速生成区间数组的实战教程 没有下一篇了 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号