
在数据处理中,我们经常会遇到需要对结构化数据进行聚合的场景。例如,给定一组并行数组,其中一个数组作为分组依据(如状态),而其他数组包含需要聚合(如求和)的数值。本教程将探讨几种在php中实现这一目标的高效方法。
问题描述
假设我们有以下几组并行数组,它们在索引上是一一对应的:
$statuses = ['PROSPECT', 'BACKLOG', 'PROSPECT']; $of_tranxs = [2, 1, 2]; $revs = [3, 1, 3]; $mgps = [4, 1, 4];
我们的目标是根据 $statuses 数组中的值进行分组,并对 of_tranxs、revs 和 mgps 数组中对应的值进行求和。最终输出应是一个结构化的数组,例如:
array( 'status' => ['PROSPECT', 'BACKLOG'], 'of_tranx' => [4, 1], 'rev' => [6, 1], 'mgp' => [8, 1] )
下面将介绍三种不同的实现策略。
方法一:原地修改与重新索引
这种方法通过在原始数组上进行操作来聚合数据。它利用一个辅助数组来记录每个分组键首次出现时的索引,后续遇到重复键时,将对应的值累加到首次出现的索引位置,并删除重复项。最后,对所有数组进行重新索引以获得紧凑的结果。
立即学习“PHP免费学习笔记(深入)”;
实现步骤
- 遍历作为分组依据的 $statuses 数组。
- 使用一个 $found 数组记录每个状态首次出现的原始索引。
- 如果当前状态是首次出现,则将其原始索引存入 $found。
- 如果当前状态已存在于 $found 中,则将当前索引对应的 of_tranxs、revs 和 mgps 值累加到 $found 中记录的首次出现索引的位置。
- 累加完成后,删除当前索引在所有原始数组中的对应元素。
- 循环结束后,使用 array_values() 函数对所有数组进行重新索引,移除空缺的键,得到最终结果。
示例代码
$status) {
if (!isset($found[$status])) {
// 如果是首次遇到该状态,记录其索引
$found[$status] = $index;
continue;
}
// 如果该状态已存在,则将当前值累加到首次出现的位置
$of_tranxs[$found[$status]] += $of_tranxs[$index];
$revs[$found[$status]] += $revs[$index];
$mgps[$found[$status]] += $mgps[$index];
// 删除当前索引处的重复项
unset($statuses[$index], $of_tranxs[$index], $revs[$index], $mgps[$index]);
}
// 重新索引所有数组,确保键的连续性
$result = [
'status' => array_values($statuses),
'of_tranx' => array_values($of_tranxs),
'rev' => array_values($revs),
'mgp' => array_values($mgps)
];
var_export($result);
?>优点与缺点
- 优点: 内存效率相对较高,因为它直接修改原始数组。
- 缺点: 破坏性操作,会修改原始数组。代码可读性略低于构建新数组的方法,且最后需要多次调用 array_values()。
方法二:构建新数组并维护索引
此方法通过构建一个新的结果数组来避免对原始数据的修改。它维护一个映射关系,将每个状态映射到新结果数组中的一个递增索引,从而实现数据的聚合。
实现步骤
- 初始化一个空的 $result 数组和一个递增的 $i 变量作为新数组的索引。
- 初始化一个 $newIndex 数组,用于存储每个状态在新结果数组中的对应索引。
- 遍历 $statuses 数组。
- 如果当前状态在 $newIndex 中不存在:
- 将当前状态添加到 $result['status']。
- 将当前索引对应的 of_tranxs、revs、mgps 值添加到 $result 对应的子数组中。
- 将当前状态与 $i 进行映射,并将 $i 递增。
- 如果当前状态已存在于 $newIndex 中:
- 获取其在新数组中的索引 $newIndex[$status]。
- 将当前索引对应的 of_tranxs、revs、mgps 值累加到 $result 中对应索引的位置。
示例代码
新数组中的索引
$i = 0; // 新数组的递增索引
foreach ($statuses as $oldIndex => $status) {
if (!isset($newIndex[$status])) {
// 首次遇到该状态,在新数组中创建新条目
$newIndex[$status] = $i++;
$result['status'][] = $status;
$result['of_tranx'][] = $of_tranxs[$oldIndex];
$result['rev'][] = $revs[$oldIndex];
$result['mgp'][] = $mgps[$oldIndex];
} else {
// 状态已存在,累加到对应位置
$targetNewIndex = $newIndex[$status];
$result['of_tranx'][$targetNewIndex] += $of_tranxs[$oldIndex];
$result['rev'][$targetNewIndex] += $revs[$oldIndex];
$result['mgp'][$targetNewIndex] += $mgps[$oldIndex];
}
}
var_export($result);
?>优点与缺点
- 优点: 非破坏性操作,原始数组保持不变。代码逻辑清晰,易于理解和维护。
- 缺点: 需要额外的内存来存储 $result 和 $newIndex 数组。
方法三:利用引用高效构建结果集
这种方法结合了构建新数组的优点,并利用PHP的引用机制,避免了手动管理索引的复杂性,同时减少了 array_values() 的调用。它通过一个临时引用数组来直接操作最终结果数组中的元素。
实现步骤
- 初始化一个空的 $result 数组。
- 初始化一个 $ref 数组,用于存储每个状态的引用。
- 遍历 $statuses 数组。
- 如果当前状态在 $ref 中不存在:
- 创建一个包含当前状态及对应数值的关联数组。
- 将此关联数组的引用存储到 $ref[$status] 中。
- 将这个引用推入 $result 数组。这样,对 $ref[$status] 的任何修改都会直接反映到 $result 中。
- 如果当前状态已存在于 $ref 中:
- 通过 $ref[$status] 引用,直接累加对应数值。
示例代码
结果数组中对应元素的引用
foreach ($statuses as $i => $status) {
if (!isset($ref[$status])) {
// 首次遇到该状态,创建新的数据结构并将其引用存入 $ref
$ref[$status] = [
'status' => $status,
'of_tranx' => $of_tranxs[$i],
'rev' => $revs[$i],
'mgp' => $mgps[$i],
];
// 将引用推入 $result,后续对 $ref[$status] 的修改会直接影响 $result
$result[] = &$ref[$status];
} else {
// 状态已存在,通过引用直接累加值
$ref[$status]['of_tranx'] += $of_tranxs[$i];
$ref[$status]['rev'] += $revs[$i];
$ref[$status]['mgp'] += $mgps[$i];
}
}
var_export($result);
?>优点与缺点
- 优点: 非破坏性操作,原始数组保持不变。代码简洁,无需显式管理新数组的索引。避免了 array_values() 的多次调用。结果数组的每个元素都是一个包含所有相关字段的关联数组,结构更清晰。
- 缺点: 使用引用可能会增加初学者的理解难度。
注意事项
- 性能考量: 对于小型数据集,这三种方法的性能差异不大。但对于大型数据集,方法一(原地修改)可能在内存使用上略有优势,因为它避免了创建大量新数组元素。方法三(使用引用)在代码简洁性和避免重复 array_values() 调用方面表现良好,通常是推荐的选择。
- 代码可读性与维护性: 方法二和方法三通常被认为具有更好的可读性,因为它们不修改原始数据。方法三的输出格式(每个元素都是一个关联数组)在某些场景下可能更易于后续处理。
- 数据结构: 本教程中,所有数组都是并行且索引一一对应的。如果数据结构更复杂(例如,多维关联数组),则需要调整遍历和聚合逻辑。
- PHP版本: 示例代码在PHP 7.4及更高版本中均可正常运行。
总结
本文介绍了三种在PHP中根据一个数组分组并聚合其他并行数组数值的方法:原地修改与重新索引、构建新数组并维护索引,以及利用引用高效构建结果集。每种方法都有其独特的优点和适用场景。在实际开发中,开发者应根据项目需求、性能考量和代码可读性偏好来选择最合适的实现策略。通常情况下,方法三(利用引用)在提供清晰输出结构的同时,兼顾了效率和代码简洁性,是一个非常推荐的解决方案。











