
本文介绍如何将多层嵌套的数组(每个子数组包含对象)扁平化合并,并基于 Time 和 Element 组合键去重,最终按首次出现顺序分组为多个独立子数组。
本文介绍如何将多层嵌套的数组(每个子数组包含对象)扁平化合并,并基于 `time` 和 `element` 组合键去重,最终按首次出现顺序分组为多个独立子数组。
在实际开发中,我们常遇到类似这样的数据结构:一个外层数组包含多个内层数组,每个内层数组由若干具有相同字段(如 Time 和 Element)的对象组成。目标不是简单地展平所有对象,而是保留首次出现的 Element 分组逻辑——即所有 Element: 'Hi' 的对象归入第一个子数组,Element: 'Hello' 归入第二个,Element: 'Bye' 归入第三个,且同一 (Time, Element) 组合在整个结果中仅出现一次(以首次出现位置为准)。
关键难点在于:
- ❌ 不能直接
flat()后去重,否则会打乱原始分组语义; - ❌ 原始代码中误将外层数组项(即子数组)当作单个对象处理,导致
obj.Element报错; - ✅ 正确思路是:遍历每个子数组 → 对其内部对象逐个判断是否已存在 → 使用全局状态记录已见的
(Time, Element)组合 → 过滤出首次出现的对象 → 最终保留非空子数组。
✅ 推荐方案:map + filter + Set(清晰高效)
const arrays = [
[{ Time: 45, Element: 'Hi' }, { Time: 55, Element: 'Hi' }, { Time: 65, Element: 'Hi' }],
[{ Time: 45, Element: 'Hi' }, { Time: 55, Element: 'Hi' }, { Time: 65, Element: 'Hi' },
{ Time: 25, Element: 'Hello' }, { Time: 35, Element: 'Hello' }, { Time: 20, Element: 'Hello' }],
[{ Time: 45, Element: 'Hi' }, { Time: 55, Element: 'Hi' }, { Time: 65, Element: 'Hi' },
{ Time: 25, Element: 'Hello' }, { Time: 35, Element: 'Hello' }, { Time: 20, Element: 'Hello' },
{ Time: 100, Element: 'Bye' }]
];
// 全局唯一标识 Set:用 "Time$Element" 字符串避免对象引用比较问题
const seen = new Set();
const merged = arrays
.map(subArray =>
subArray.filter(obj => {
const key = `${obj.Time}$${obj.Element}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
})
)
.filter(subArray => subArray.length > 0); // 移除过滤后为空的子数组
console.log(merged);
// 输出:
// [
// [{Time:45,Element:'Hi'}, {Time:55,Element:'Hi'}, {Time:65,Element:'Hi'}],
// [{Time:25,Element:'Hello'}, {Time:35,Element:'Hello'}, {Time:20,Element:'Hello'}],
// [{Time:100,Element:'Bye'}]
// ]
? 原理说明
-
seen是一个Set,用于跨子数组追踪已处理的(Time, Element)组合; - 每次遇到新对象时,生成唯一字符串键(如
"45$Hi"),若未存在则加入Set并保留该对象; -
map()保证每个子数组独立过滤,filter()清理空结果,语义明确、易于调试。
⚠️ 注意事项
-
键构造需防冲突:使用
$分隔符可避免Time=12, Element='34'与Time=1, Element='234'误判为相同(若用纯拼接如1234);更健壮方案可用JSON.stringify({Time, Element}),但性能略低; -
顺序敏感:本方法严格遵循“首次出现优先”,符合题目预期;若需按
Element字典序分组,应先flat()再按Element分组去重; - 不可变性:原数组未被修改,返回全新结构,适合函数式编程场景;
-
扩展性:如需基于其他字段(如
id或name)去重,只需调整key生成逻辑即可。
? 进阶提示:单次 reduce 版(节省一次遍历)
若追求极致性能(尤其数据量极大时),可用 reduce 替代 map + filter:
const mergedReduce = arrays.reduce((acc, subArray) => {
const filtered = subArray.filter(obj => {
const key = `${obj.Time}$${obj.Element}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
if (filtered.length > 0) acc.push(filtered);
return acc;
}, []);
两种写法结果完全一致,推荐初学者使用 map + filter 版——逻辑分离、可读性强;熟悉后可根据场景选择优化版本。
掌握此模式后,你可轻松应对日志聚合、多源 API 数据去重、前端缓存合并等真实业务需求。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











