
本教程详细介绍了如何在javascript中处理一个对象数组,从每个对象中移除那些在数组中先前对象中已经出现过的重复键值对。通过构建一个高效的“已见”映射表,我们将逐步指导您实现一个函数,该函数能够生成一个仅包含在各自对象中首次出现的唯一键值对的新对象数组,从而确保数据去重并保持原始结构。
理解问题与目标
在处理复杂的数据结构时,我们经常会遇到需要去重的情况。本教程关注的是一个特定的去重场景:给定一个包含多个对象的数组,我们希望创建一个新的数组,其中每个对象只保留那些在整个处理过程中首次出现的键值对。这意味着,如果一个 key: value 对已经在数组中的某个先前对象中出现过,那么它在当前对象中就应该被移除。
让我们通过一个示例来具体说明:
原始输入数组:
const arr1 = [
{
"Param1": "20",
"Param2": "8",
"Param3": "11",
"Param4": "4",
"Param5": "18",
"Param6": "20",
"Param7": "8"
},
{
"Param6": "21",
"Param7": "8",
"Param8": "11",
"Param9": "4",
"Param10": "18"
},
{
"Param1": "20",
"Param2": "8",
"Param3": "10"
}
];期望输出数组:
立即学习“Java免费学习笔记(深入)”;
[
{
"Param1": "20",
"Param2": "8",
"Param3": "11",
"Param4": "4",
"Param5": "18",
"Param6": "20",
"Param7": "8"
},
{
"Param6": "21", // Param6: "20" 已在第一个对象中出现,但 Param6: "21" 是新的
"Param8": "11",
"Param9": "4",
"Param10": "18"
},
{
"Param3": "10" // Param1: "20" 和 Param2: "8" 已在第一个对象中出现
}
]可以看到,在第二个对象中,"Param7": "8" 被移除了,因为它在第一个对象中已经出现过。同样,在第三个对象中,"Param1": "20" 和 "Param2": "8" 被移除,而 "Param3": "10" 被保留,因为它与第一个对象中的 "Param3": "11" 键相同但值不同,且 "Param3": "10" 之前未出现过。
核心算法思路
要实现上述去重逻辑,我们需要一个机制来“记住”所有已经处理过的键值对。最有效的方法是使用一个“已见”映射表(seen map)。这个映射表将存储每个键以及该键所对应的值是否已被发现过的信息。
算法步骤如下:
-
初始化 seen 映射表: 创建一个空的 seen 对象(或 Map),其结构为 Record
>,即 seen[key][value] = true 表示该键值对已被发现。 - 初始化 result 数组: 创建一个空的数组来存放处理后的新对象。
- 遍历输入数组: 逐个处理 arr1 中的每个对象。
-
处理当前对象: 对于每个对象:
- 创建一个新的空对象 currentUniqueObject。
- 遍历当前对象中的所有键值对。
- 对于每个键 key 和值 value:
- 首先,检查 seen[key] 是否已存在。如果不存在,则初始化 seen[key] 为一个空对象 {}。
- 接着,检查 seen[key][value] 是否为 true。
- 如果为 true,表示这个 key: value 对之前已经出现过,因此我们忽略它。
- 如果为 false 或 undefined,表示这个 key: value 对是首次出现。
- 将其添加到 currentUniqueObject 中。
- 将 seen[key][value] 设置为 true,标记为已见。
- 添加结果: 将 currentUniqueObject 添加到 result 数组中。
- 返回 result: 遍历完成后,返回 result 数组。
JavaScript 实现
我们可以利用 Array.prototype.reduce 方法来优雅地实现这个算法,它允许我们迭代数组并累积一个单一的结果(在这里是 seen 映射表和 result 数组的组合)。
/** * 从对象数组中移除在先前对象中已出现过的重复键值对。 * * @param arr 输入的对象数组,每个对象包含字符串键和字符串值。 * @returns 包含唯一键值对的新对象数组。 */ const removeDuplicates = (arr: Record[]): Record [] => { // 使用 reduce 方法来累积 'seen' 映射和 'result' 数组 return arr.reduce<{ seen: Record >; // 存储已见键值对的映射 result: Record []; // 存储处理后的结果数组 }>( (accumulator, currentItem) => { // 对于当前对象,筛选出唯一的键值对 const uniqueItem = Object.fromEntries( Object.entries(currentItem).filter(([key, value]) => { // 确保 seen[key] 存在,如果不存在则初始化为 {} accumulator.seen[key] = accumulator.seen[key] ?? {}; // 检查当前键值对是否已在 'seen' 映射中 if (accumulator.seen[key][value]) { // 如果已见,则过滤掉(返回 false) return false; } // 如果未见,则标记为已见(设置为 true) accumulator.seen[key][value] = true; // 并保留该键值对(返回 true) return true; }), ); // 将处理后的唯一对象添加到结果数组中 accumulator.result.push(uniqueItem); return accumulator; }, // reduce 的初始值:一个包含空 'seen' 映射和空 'result' 数组的对象 { seen: {}, result: [] }, ).result; // 最后返回累加器中的 'result' 数组 };
示例用法
现在,让我们将 removeDuplicates 函数应用于我们之前的示例数据:
const arr1 = [
{
"Param1": "20",
"Param2": "8",
"Param3": "11",
"Param4": "4",
"Param5": "18",
"Param6": "20",
"Param7": "8"
},
{
"Param6": "21",
"Param7": "8",
"Param8": "11",
"Param9": "4",
"Param10": "18"
},
{
"Param1": "20",
"Param2": "8",
"Param3": "10"
}
];
const uniqueArray = removeDuplicates(arr1);
console.log(JSON.stringify(uniqueArray, null, 2));输出结果:
[
{
"Param1": "20",
"Param2": "8",
"Param3": "11",
"Param4": "4",
"Param5": "18",
"Param6": "20",
"Param7": "8"
},
{
"Param6": "21",
"Param8": "11",
"Param9": "4",
"Param10": "18"
},
{
"Param3": "10"
}
]这个输出与我们预期的结果完全一致,成功地移除了所有重复的键值对。
注意事项与总结
- 数据类型限制: 本实现假设对象的值是字符串。如果值可以是其他类型(如数字、布尔值、对象等),seen 映射的键(value 部分)可能需要进行调整,例如使用 JSON.stringify 来确保唯一性,但这会带来性能开销。对于原始类型(字符串、数字等),当前方法是高效的。
- 时间复杂度: 算法的时间复杂度大致为 O(N * K),其中 N 是输入数组中对象的数量,K 是每个对象中键值对的平均数量。这是因为我们需要遍历每个对象,并对每个对象的每个键值对进行查找和插入操作。
- 空间复杂度: 空间复杂度主要取决于 seen 映射表的大小,它将存储所有不重复的键值对。在最坏情况下(所有键值对都不同),空间复杂度为 O(N * K)。
- 不可变性: removeDuplicates 函数返回一个全新的数组和全新的对象,不会修改原始输入 arr1,这符合函数式编程的良好实践。
通过本教程,您已经学会了如何使用 Array.prototype.reduce 和一个自定义的“已见”映射表来高效地从对象数组中提取并保留那些在处理过程中首次出现的键值对。这种模式在处理需要基于历史状态进行数据过滤的场景中非常有用。










