
本教程详细阐述了如何使用javaScript和Lodash库,从复杂嵌套数据结构中识别并移除在所有对应数组中均出现的共同元素。通过两步法:首先构建一个包含所有待移除共同元素的映射对象,然后遍历原始数据,利用Lodash的intersection和difference等函数,实现数据的高效清洗和转换,最终获得期望的过滤结果。
理解问题:识别多层嵌套对象中数组的共同元素
在处理复杂的数据结构时,我们经常会遇到需要对嵌套对象中的数组进行批量操作的场景。一个典型的例子是,给定一个如下所示的javascript对象:
let newData = { '2020': { Thing1: ['ABC', '123'], Thing2: ['DEF'] }, '2020.5': { Thing1: ['ABC', '123', 'XYZ'], Thing2: ['DEF'] }, '2020.75': { Thing1: ['ABC', '123'], Thing2: ['XYZ'], Thing3: ['AAA'] } };
我们的目标是识别并移除在所有外层对象(例如 ‘2020’, ‘2020.5’, ‘2020.75’)中,针对相同内层键(例如 ‘Thing1’)的数组里都存在的共同元素。以上述数据为例,’Thing1’ 对应的所有数组中都包含 ‘ABC’ 和 ‘123’。因此,我们希望将这些共同元素从所有 ‘Thing1’ 数组中移除,得到如下结果:
{ '2020': { Thing1: [], Thing2: ['DEF'] }, '2020.5': { Thing1: ['XYZ'], Thing2: ['DEF'] }, '2020.75': { Thing1: [], Thing2: ['XYZ'], Thing3: ['AAA'] } }
直接遍历和手动比对会使代码变得冗长且易错。Lodash库提供了丰富的工具函数,能够帮助我们以更简洁、高效的方式解决此类问题。
立即学习“Java免费学习笔记(深入)”;
核心思路:两步法处理
解决这个问题的关键在于采用一个两步走的策略:
- 确定待移除的共同元素: 首先,我们需要遍历所有外层对象,针对每一个内层键(如 ‘Thing1’, ‘Thing2’),找出其所有对应数组的交集。这个交集就是我们需要从原始数据中移除的元素集合。
- 从原始数据中移除共同元素: 获得待移除的元素集合后,我们再遍历原始数据结构,对每个内层数组执行差集操作,即移除其中包含在待移除集合中的元素。
第一步:确定待移除的共同元素
为了找出所有共同元素,我们可以利用Lodash的 values、mergeWith 和 intersection 函数。
- _.values(obj):获取对象的所有属性值数组。在这里,它将返回 newData 中所有年份对应的对象。
- _.mergeWith(obj1, obj2, …, customizer):深度合并对象,并允许提供一个自定义合并函数。
- _.intersection(arr1, arr2, …):计算多个数组的交集,返回一个包含所有数组共同元素的数组。
我们将 mergeWith 的自定义函数设置为 intersection,这样当合并不同年份对象时,对于相同的 ‘Thing’ 键,其对应的数组会通过 intersection 函数计算出交集。
const { mergeWith, clone, values, intersection } = _; const allObjects = values(newData); // 获取所有年份对应的对象 const [firstObject, ...restObjects] = allObjects; // 使用mergeWith和intersection找出所有Thing数组的共同元素 // clone(firstObject)是为了避免直接修改原始数据 const elementsToRemove = mergeWith(clone(firstObject), ...restObjects, (objValue, srcValue) => { // 仅当两个值都是数组时进行交集操作 if (Array.isArray(objValue) && Array.isArray(srcValue)) { return intersection(objValue, srcValue); } // 否则,让mergeWith处理默认合并逻辑(例如,如果一个Thing键只在一个对象中存在,或者不是数组) return undefined; }); // elementsToRemove 结构示例: // { Thing1: ['ABC', '123'], Thing2: ['DEF'], Thing3: [] } // 注意:Thing3因为只在'2020.75'中存在,所以其交集为空。 console.log(elementsToRemove);
在 elementsToRemove 对象中,Thing1 将包含 [‘ABC’, ‘123’],Thing2 将包含 [‘DEF’],而 Thing3 将是空数组,因为并非所有对象都包含 Thing3,或者即使包含,其对应数组也可能没有共同元素。
第二步:从原始数据中移除共同元素
有了 elementsToRemove 对象后,我们需要遍历原始的 newData,并对每个内层数组执行差集操作。Lodash的 mapValues 和 difference 函数在此处非常有用。
- _.mapValues(obj, iteratee):创建一个新对象,其键与原对象相同,值则是通过 iteratee 函数处理后的结果。
- _.difference(arr, [values]):创建一个新数组,其中包含 arr 中不包含在 values 数组中的元素。
const { mapValues, difference } = _; const result = mapValues(newData, (innerObject) => { return mapValues(innerObject, (arrayValue, key) => { // 如果elementsToRemove中存在对应的key,则计算差集 // 否则,保持原数组不变(或处理不存在的Thing键) return difference(arrayValue, elementsToRemove[key] || []); }); }); console.log(result);
完整解决方案示例
将上述两步结合起来,我们可以创建一个独立的函数来处理这个过滤逻辑。
// 引入Lodash函数 const { mergeWith, clone, values, intersection, mapValues, difference } = _; /** * 从多层嵌套对象中,过滤掉在所有对应数组中均出现的共同元素。 * @param {Object} data 原始数据对象。 * @returns {Object} 过滤后的新数据对象。 */ const filterCommonElementsInNestedArrays = (data) => { // 1. 获取所有外层对象,用于计算共同元素 const allObjects = values(data); // 如果数据为空或只有一个外层对象,则没有“共同”元素可移除,直接返回克隆的数据 if (allObjects.length <= 1) { return clone(data); } const [firstObject, ...restObjects] = allObjects; // 2. 构建一个对象,存储每个Thing键对应的待移除共同元素 const elementsToRemove = mergeWith(clone(firstObject), ...restObjects, (objValue, srcValue) => { // 确保只对数组进行交集操作 if (Array.isArray(objValue) && Array.isArray(srcValue)) { return intersection(objValue, srcValue); } // 对于非数组类型或只有一个对象存在的情况,让mergeWith默认处理, // 或者返回undefined让其跳过此键的自定义合并。 // 如果某个键只存在于部分对象中,这里会将其合并结果设为undefined, // 后续差集操作时会将其视为空数组处理,确保安全。 return undefined; }); // 3. 遍历原始数据,移除共同元素 const filteredData = mapValues(data, (innerObject) => { return mapValues(innerObject, (arrayValue, key) => { // 从当前数组中移除在elementsToRemove中找到的共同元素 // 如果elementsToRemove[key]