
本文介绍一种高效、可扩展的方法:先按标签升序、再按日期升序排序,随后一次遍历提取每组标签中创建时间最早的元素,避免嵌套循环,显著提升大数据量下的性能。
本文介绍一种高效、可扩展的方法:先按标签升序、再按日期升序排序,随后一次遍历提取每组标签中创建时间最早的元素,避免嵌套循环,显著提升大数据量下的性能。
在 PHP 开发中,常需对关联数组按某个字段(如 label)分组,并在每组内依据时间字段(如 created)筛选极值数据——例如保留每个重复标签下创建时间最旧(即最早)的一条记录。这不同于简单的 array_unique() 或 array_filter(),关键在于:既要识别重复标签,又要在重复项中精确比对非标准格式的日期(如 "15/01/2022"),且需兼顾性能。
✅ 推荐方案:双维度排序 + 单次遍历(O(n log n) 时间复杂度)
核心思路是:
- 统一排序规则:先按 label 字典升序排列;同标签下,按 created 升序(即“最旧 → 最新”),这样每组首个元素即为所求;
- 一次扫描提取:遍历已排序数组,当遇到标签变更或到达末尾时,保存当前组第一个元素(即该标签下最旧记录)。
⚠️ 注意:原始答案示例中误将“最旧”理解为“最新”(输出了 "15/01/2022" 而非 "11/01/2022"),实际需求是保留最早日期,因此排序时 created 应升序(a
以下是修正后的完整实现:
立即学习“PHP免费学习笔记(深入)”;
$data = [
['label' => 'Same label', 'created' => '15/01/2022'],
['label' => 'Same label', 'created' => '11/01/2022'],
['label' => 'Label alone', 'created' => '18/01/2022'],
];
// 步骤1:双键排序 —— 先 label 升序,同 label 下 created 升序(确保最旧在前)
usort($data, function($a, $b) {
// 标签不同:按 label 字典序
if ($a['label'] !== $b['label']) {
return $a['label'] <=> $b['label'];
}
// 标签相同:将 d/m/Y 转为 DateTime 比较,升序 → 最旧优先
$dateA = DateTime::createFromFormat('d/m/Y', $a['created']);
$dateB = DateTime::createFromFormat('d/m/Y', $b['created']);
return $dateA <=> $dateB; // 升序:旧 → 新
});
// 步骤2:单次遍历,每组取第一个(即最旧)
$result = [];
$prevLabel = null;
foreach ($data as $item) {
if ($item['label'] !== $prevLabel) {
$result[] = $item; // 首次出现该 label,即为最旧记录
$prevLabel = $item['label'];
}
}
print_r($result);输出结果:
Array
(
[0] => Array
(
[label] => Label alone
[created] => 18/01/2022
)
[1] => Array
(
[label] => Same label
[created] => 11/01/2022 // ✅ 正确保留最旧日期
)
)? 关键说明与优化建议
- 日期解析健壮性:DateTime::createFromFormat() 比 strtotime() 更可靠,尤其对非 ISO 格式(如 d/m/Y);若存在无效日期,建议添加 false !== $dateA 校验;
- 性能优势:相比原问题中 array_search() + array_column() 的 O(n²) 嵌套查找,本方案仅需一次排序(O(n log n))和一次遍历(O(n)),万级数据下性能提升显著;
- 内存友好:无需额外哈希表存储中间状态,适合内存受限场景;
- 扩展性:如需改为保留“最新”记录,仅需将 $dateA $dateB 改为 $dateB $dateA(降序);
- PHP 版本要求:需 PHP 7.0+(支持太空船操作符 和 DateTime::createFromFormat)。
此方法逻辑清晰、代码简洁、性能可控,是处理“分组取极值”类需求的生产级推荐实践。



















