合并与差集
概述
PHP 提供了多种数组合并和集合运算函数,包括 array_merge、array_combine、array_diff 系列和 array_intersect 系列。理解这些函数的区别和适用场景,对于正确处理数组数据至关重要。
基础概念
函数分类
| 分类 | 函数 | 说明 |
|---|---|---|
| 合并 | array_merge | 合并数组(字符串键覆盖,数字键重建) |
array_merge_recursive | 递归合并(字符串键递归合并) | |
array_combine | 用一个数组做键,一个做值 | |
| 差集 | array_diff | 值差集(忽略键) |
array_diff_key | 键差集 | |
array_diff_assoc | 键值对差集 | |
| 交集 | array_intersect | 值交集(忽略键) |
array_intersect_key | 键交集 | |
array_intersect_assoc | 键值对交集 |
语法与代码
array_merge — 合并数组
php
<?php
declare(strict_types=1);
// 合并索引数组 - 数字键重建
$arr1 = ['a', 'b'];
$arr2 = ['c', 'd'];
$merged = array_merge($arr1, $arr2);
// ['a', 'b', 'c', 'd']
// 合并关联数组 - 后面的覆盖前面的
$defaults = ['timeout' => 30, 'retries' => 3, 'debug' => false];
$userConfig = ['timeout' => 60, 'debug' => true];
$config = array_merge($defaults, $userConfig);
// ['timeout' => 60, 'retries' => 3, 'debug' => true]
// 数字键的特殊行为
$arr1 = [0 => 'a', 1 => 'b'];
$arr2 = [0 => 'c', 1 => 'd'];
$merged = array_merge($arr1, $arr2);
// ['a', 'b', 'c', 'd'](数字键重建,不是覆盖!)
// 使用 + 运算符合并(不同的行为)
$merged = $arr1 + $arr2;
// [0 => 'a', 1 => 'b'](相同键保留第一个)
// + 运算符对关联数组
$defaults = ['a' => 1, 'b' => 2];
$override = ['b' => 20, 'c' => 30];
$merged = $defaults + $override;
// ['a' => 1, 'b' => 2, 'c' => 30](相同键保留第一个)array_merge_recursive — 递归合并
php
<?php
declare(strict_types=1);
$arr1 = ['user' => ['name' => 'Alice', 'age' => 30]];
$arr2 = ['user' => ['name' => 'Bob', 'email' => 'b@b.com']];
// array_merge - 后者覆盖前者
$merged = array_merge($arr1, $arr2);
// ['user' => ['name' => 'Bob', 'email' => 'b@b.com']]
// array_merge_recursive - 递归合并
$merged = array_merge_recursive($arr1, $arr2);
// ['user' => ['name' => ['Alice', 'Bob'], 'age' => 30, 'email' => 'b@b.com']]
// 注意:相同字符串键的值会被合并为数组
$arr1 = ['color' => 'red', 'size' => 'M'];
$arr2 = ['color' => 'blue'];
$merged = array_merge_recursive($arr1, $arr2);
// ['color' => ['red', 'blue'], 'size' => 'M']array_combine — 创建关联数组
php
<?php
declare(strict_types=1);
$keys = ['name', 'age', 'email'];
$values = ['Alice', 30, 'a@b.com'];
$combined = array_combine($keys, $values);
// ['name' => 'Alice', 'age' => 30, 'email' => 'a@b.com']
// 键值数量不同时产生 ValueError(PHP 8.0+)
try {
array_combine(['a', 'b'], [1, 2, 3]); // ValueError
} catch (\ValueError $e) {
echo $e->getMessage();
}array_diff — 值差集
php
<?php
declare(strict_types=1);
// array_diff - 值差集(忽略键,只比较值)
$arr1 = [1, 2, 3, 4, 5];
$arr2 = [3, 4, 5, 6, 7];
$diff = array_diff($arr1, $arr2);
// [0 => 1, 1 => 2](在 $arr1 中但不在 $arr2 中的值)
// array_diff_key - 键差集
$config1 = ['debug' => true, 'cache' => true, 'log' => false];
$config2 = ['cache' => true, 'timeout' => 30];
$diff = array_diff_key($config1, $config2);
// ['debug' => true, 'log' => false]
// array_diff_assoc - 键值对差集
$arr1 = ['a' => 1, 'b' => 2, 'c' => 3];
$arr2 = ['a' => 1, 'c' => 30, 'd' => 4];
$diff = array_diff_assoc($arr1, $arr2);
// ['b' => 2, 'c' => 3](键值对不完全相同的)array_intersect — 值交集
php
<?php
declare(strict_types=1);
// array_intersect - 值交集(忽略键)
$arr1 = [1, 2, 3, 4, 5];
$arr2 = [3, 4, 5, 6, 7];
$inter = array_intersect($arr1, $arr2);
// [2 => 3, 3 => 4, 4 => 5](保留 $arr1 的键)
// array_intersect_key - 键交集
$conf1 = ['debug' => true, 'cache' => true, 'log' => false];
$conf2 = ['cache' => true, 'debug' => false];
$inter = array_intersect_key($conf1, $conf2);
// ['debug' => true, 'cache' => true](保留 $conf1 的值)
// array_intersect_assoc - 键值对交集
$arr1 = ['a' => 1, 'b' => 2, 'c' => 3];
$arr2 = ['a' => 1, 'b' => 20, 'c' => 3];
$inter = array_intersect_assoc($arr1, $arr2);
// ['a' => 1, 'c' => 3](键值完全相同的)详细说明
array_merge vs + 运算符
| 操作 | 数字键 | 字符串键 |
|---|---|---|
array_merge | 重建索引 | 后者覆盖前者 |
+ | 保留第一个 | 保留第一个 |
php
<?php
declare(strict_types=1);
// 配置合并推荐使用 array_merge(后者覆盖前者)
$defaults = ['a' => 1, 'b' => 2, 'c' => 3];
$override = ['b' => 20, 'd' => 40];
echo array_merge($defaults, $override)['b']; // 20
echo ($defaults + $override)['b']; // 2
// 确保默认值存在推荐使用 + 或 ??选择建议
需要后者覆盖前者(配置合并)用 array_merge。需要保留第一个值(确保键存在)用 + 或 ??。
实战示例
深度合并配置
php
<?php
declare(strict_types=1);
function deepMerge(array $base, array $override): array
{
foreach ($override as $key => $value) {
if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
$base[$key] = deepMerge($base[$key], $value);
} else {
$base[$key] = $value;
}
}
return $base;
}
$defaults = [
'db' => ['host' => 'localhost', 'port' => 3306, 'charset' => 'utf8'],
'cache' => ['enabled' => true, 'ttl' => 3600],
];
$custom = [
'db' => ['host' => '192.168.1.1', 'port' => 3307],
'cache' => ['ttl' => 7200],
];
$config = deepMerge($defaults, $custom);
// db: host=192.168.1.1, port=3307, charset=utf8
// cache: enabled=true, ttl=7200多维数组合并
php
<?php
declare(strict_types=1);
// 多维数组深度合并
function deepMerge(array $base, array $override): array
{
foreach ($override as $key => $value) {
if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
$base[$key] = deepMerge($base[$key], $value);
} else {
$base[$key] = $value;
}
}
return $base;
}
$defaults = [
'logging' => [
'driver' => 'daily',
'path' => '/var/log/app.log',
'level' => 'debug',
'days' => 14,
],
'cache' => [
'default' => 'redis',
'stores' => [
'redis' => ['host' => '127.0.0.1', 'port' => 6379],
'file' => ['path' => '/tmp/cache'],
],
],
];
$custom = [
'logging' => [
'level' => 'warning',
'path' => '/var/log/custom.log',
],
'cache' => [
'stores' => [
'redis' => ['host' => '192.168.1.100', 'password' => 'secret'],
],
],
];
$config = deepMerge($defaults, $custom);
print_r($config);差集与交集的实际应用
php
<?php
declare(strict_types=1);
// 数据库迁移:对比新增和删除的字段
$oldColumns = ['id', 'name', 'email', 'phone', 'fax'];
$newColumns = ['id', 'name', 'email', 'address', 'zip', 'phone'];
$added = array_diff($newColumns, $oldColumns);
$removed = array_diff($oldColumns, $newColumns);
$common = array_intersect($oldColumns, $newColumns);
echo "新增字段: " . implode(', ', $added) . "\n"; // address, zip
echo "删除字段: " . implode(', ', $removed) . "\n"; // fax
echo "保留字段: " . implode(', ', $common) . "\n"; // id, name, email, phone
// 权限对比
$oldPermissions = ['read', 'write', 'delete', 'admin'];
$newPermissions = ['read', 'write', 'execute', 'admin'];
$granted = array_diff($newPermissions, $oldPermissions); // execute
$revoked = array_diff($oldPermissions, $newPermissions); // delete
echo "新增权限: " . implode(', ', $granted) . "\n";
echo "移除权限: " . implode(', ', $revoked) . "\n";array_combine 的实际应用
php
<?php
declare(strict_types=1);
// CSV 头部映射
$header = ['id', 'name', 'email', 'department'];
$row = ['1', 'Alice', 'alice@example.com', 'Engineering'];
$record = array_combine($header, $row);
// ['id' => '1', 'name' => 'Alice', 'email' => 'alice@example.com', 'department' => 'Engineering']
// 类型转换
$typed = array_map(function (string $value, string $key): mixed {
return match ($key) {
'id' => (int)$value,
default => $value,
};
}, $row, $header);
// [0 => 1, 1 => 'Alice', 2 => 'alice@example.com', 3 => 'Engineering']
// 构建查找表
$ids = [101, 102, 103, 104];
$names = ['Alice', 'Bob', 'Charlie', 'Diana'];
$lookup = array_combine($ids, $names);
// [101 => 'Alice', 102 => 'Bob', 103 => 'Charlie', 104 => 'Diana']array_diff 比较规则
array_diff 使用松散比较(==)。字符串 '1' 和整数 1 被视为相等。如果需要严格比较,使用 array_uintersect 或 array_filter 配合 ===。
实际应用:数据同步
php
<?php
declare(strict_types=1);
// 数据同步:比较本地和远程数据
function syncData(array $local, array $remote): array
{
$localIds = array_column($local, 'id');
$remoteIds = array_column($remote, 'id');
return [
'to_create' => array_values(array_filter($remote,
fn(array $r): bool => !in_array($r['id'], $localIds, true))),
'to_update' => array_values(array_filter($remote,
fn(array $r): bool => in_array($r['id'], $localIds, true))),
'to_delete' => array_values(array_filter($local,
fn(array $l): bool => !in_array($l['id'], $remoteIds, true))),
];
}
$local = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B']];
$remote = [['id' => 2, 'name' => 'B-updated'], ['id' => 3, 'name' => 'C']];
print_r(syncData($local, $remote));
// to_create: [[id=>3, name=>C]]
// to_update: [[id=>2, name=>B-updated]]
// to_delete: [[id=>1, name=>A]]注意事项
array_merge 数字键行为
array_merge 对数字键总是重建索引(从 0 开始)。如果需要保留数字键,使用 + 运算符。
array_diff 比较规则
array_diff 使用松散比较(==),'1' 和 1 被视为相等。PHP 8.0+ 的比较行为可能略有不同。
最佳实践
- 配置合并用 array_merge:后者覆盖前者
- 确保键存在用 +:保留第一个值
- 深度合并用自定义函数:
array_merge_recursive的行为可能不符合预期 - 差集用 array_diff_key:比
array_diff更精确 - 交集用 array_intersect_assoc:键值完全匹配
array_replace 递归替换
php
<?php
declare(strict_types=1);
// array_replace - 替换数组元素(保持键名)
$base = ['a' => 1, 'b' => 2, 'c' => 3, 10 => 'ten'];
$replace = ['b' => 20, 10 => 'hundred'];
$result = array_replace($base, $replace);
// ['a' => 1, 'b' => 20, 'c' => 3, 10 => 'hundred']
// 注意:array_replace 保持数字键(不像 array_merge 重建索引)
// array_replace_recursive - 递归替换
$base = ['a' => ['x' => 1, 'y' => 2], 'b' => ['z' => 3]];
$replace = ['a' => ['y' => 20, 'w' => 99]];
$result = array_replace_recursive($base, $replace);
// ['a' => ['x' => 1, 'y' => 20, 'w' => 99], 'b' => ['z' => 3]]array_replace vs array_merge
array_replace 保持键名(包括数字键),array_merge 对数字键重建索引。关联数组两者行为相同。