PHP按标签去重保留最旧记录方法
本文介绍一种高效、可扩展的方法:先按标签升序、再按日期升序排序,随后一次遍历提取每个标签的首条(即最早)记录,避免嵌套循环,时间复杂度优化至O(nlogn)。

本文介绍一种高效、可扩展的方法:先按标签升序、再按日期升序排序,随后一次遍历提取每个标签的首条(即最早)记录,避免嵌套循环,时间复杂度优化至 O(n log n)。
本文介绍一种高效、可扩展的方法:先按标签升序、再按日期升序排序,随后一次遍历提取每个标签的首条(即最早)记录,避免嵌套循环,时间复杂度优化至 O(n log n)。
在处理带时间戳的标签化数据时,常见需求是:对相同 label 的多条记录,仅保留创建时间最早(即“最旧”)的一条。注意:题目中示例目标结果误将 "11/01/2022"(更早)标为保留项,但实际逻辑应为「保留最旧」→ 即日期值最小者(如 11/01/2022 < 15/01/2022)。而原答案代码因排序逻辑为 a.created <=> b.created(升序),导致相同标签下较早日期排在前面;后续取每组最后一条($i === $last 或 $data[$i+1]["label"] !== $data[$i]["label"])实则取的是每组第一条(因已升序排列,第一条即最旧),因此逻辑正确。
以下是推荐的完整实现方案:
✅ 推荐解法:双字段排序 + 单次遍历(高效稳定)
function filterByOldestPerLabel(array $data): array
{
// 按 label 升序,同 label 下按 created 升序(确保最旧日期在前)
usort($data, function ($a, $b) {
if ($a['label'] !== $b['label']) {
return $a['label'] <=> $b['label'];
}
// 将 "d/m/Y" 格式转为 DateTime 比较,避免字符串误判(如 "2/1/2022" > "11/1/2022")
$dateA = DateTime::createFromFormat('d/m/Y', $a['created']);
$dateB = DateTime::createFromFormat('d/m/Y', $b['created']);
return $dateA <=> $dateB;
});
$result = [];
$prevLabel = null;
foreach ($data as $item) {
// 首次出现该 label,或 label 切换时,保留当前项(即该 label 下最旧的一条)
if ($prevLabel !== $item['label']) {
$result[] = $item;
$prevLabel = $item['label'];
}
}
return $result;
}
// 使用示例
$data = [
['label' => 'Same label', 'created' => '15/01/2022'],
['label' => 'Same label', 'created' => '11/01/2022'],
['label' => 'Label alone', 'created' => '18/01/2022'],
];
$filtered = filterByOldestPerLabel($data);
print_r($filtered);输出结果:
Array
(
[0] => Array
(
[label] => Label alone
[created] => 18/01/2022
)
[1] => Array
(
[label] => Same label
[created] => 11/01/2022 // ✅ 最旧日期被保留
)
)⚠️ 关键注意事项
- 日期格式必须严格匹配:DateTime::createFromFormat('d/m/Y', ...) 要求输入为 日/月/年 格式。若原始数据含 Y-m-d 或其他格式,请同步调整格式字符串(如 'Y-m-d')。
- 错误处理增强建议:生产环境应检查 DateTime::createFromFormat() 返回值是否为 false,避免解析失败导致比较异常:
$dateA = DateTime::createFromFormat('d/m/Y', $a['created']); $dateB = DateTime::createFromFormat('d/m/Y', $b['created']); if (!$dateA || !$dateB) { throw new InvalidArgumentException('Invalid date format in data'); } - 性能对比:相比原始 array_search() + array_column() 的 O(n²) 方案,本方法 usort 为 O(n log n),单次遍历为 O(n),整体显著优于大数据量场景(如数千条以上)。
- 内存友好:无需额外哈希表存储中间状态,适合内存受限环境。
✅ 总结
该方案以「排序归一化 + 线性扫描」为核心思想,兼顾可读性、健壮性与性能。它不依赖外部库,兼容 PHP 7.0+,且易于扩展(例如改为保留最新记录,仅需将日期比较改为降序 ($dateB <=> $dateA))。对于标签去重并按时间维度筛选的典型业务场景(如工单去重、版本快照归档等),是简洁可靠的工程实践。


































