使用 javascript 和 lodash 从多个对象中的数组中过滤出值

问题描述 投票:0回答:5

理论上这听起来相当简单,但某个人工智能机器人不断给我错误的信息。 我有这样的数据:

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'] }
};

我想做的就是从“Thing”数组中删除所有对象和特定数组中相同的值。在上述情况下,从 Thing1 数组中删除“ABC”和“123”。 返回的数据如下所示:

{
  '2020': { Thing1: [], Thing2: ['DEF'] },
  '2020.5': { Thing1: ['XYZ'], Thing2: ['DEF'] },
  '2020.75': { Thing1: [], Thing2: ['XYZ'], Thing3: ['AAA'] }
}

这是我从上述人工智能机器人那里得到的答案:

const numObjects = Object.keys(newData).length;

_.forEach(newData, (value, key) => {
    _.forEach(value, (arr, thing) => {
        newData[key][thing] = _.filter(arr, (value) => {
            const count = _.countBy(newData, (obj) => obj[thing] && obj[thing].includes(value));
            return Object.keys(count).length !== numObjects;
        });
    });
});

console.log(newData);

但这只会返回所有内容。我不认为它是在迭代 Thing 数组中的实际值。

任何帮助将不胜感激。

javascript arrays object lodash
5个回答
2
投票
  1. 创建一个对象 (
    toRemove
    ),其中每个属性包含原始对象之间共有的所有值。
  2. 映射原始对象的值,然后映射每个对象内的所有属性。获取每个数组之间的差异,它是
    toRemove
    对应属性。

const { mergeWith, clone, values, intersection, mapValues,  difference } = _;

const fn = obj => {
  const [first, ...rest] = values(newData);
  
  const toRemove = mergeWith(clone(first), ...rest, (a, b) => intersection(a, b));

  return mapValues(
    newData, 
    o => mapValues(o, (v, k) => difference(v, toRemove[k]))
  );
};

const newData = {"2020":{"Thing1":["ABC","123"],"Thing2":["DEF"]},"2020.5":{"Thing1":["ABC","123","XYZ"],"Thing2":["DEF"]},"2020.75":{"Thing1":["ABC","123"],"Thing2":["XYZ"],"Thing3":["AAA"]}};

const result = fn(newData);

console.log(result);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>


1
投票

作者的代码改变了原始对象。
首先我们获取值的计数,然后清理数组。

Damzaky 的纯解决方案速度较慢(但如果我们需要纯的话可能很有用)。

你在这里不需要 lodash。 ES6+ 允许做同样的事情。我在基准测试中添加了 2 个 lodash 解决方案,但它们是最慢的:

` Chrome/121
---------------------------------------------------------------------------
Nenashev's in-place solution            1.00x | x100000 124 127 132 133 136
Nenashev's pure solution                1.25x | x100000 155 160 161 162 167
Damzaky's pure solution                 2.23x | x100000 277 281 288 289 290
in-place lodash solution by 3limin4t0r  3.02x | x100000 374 376 387 394 402
lodash pure solution by Ori Drori       4.61x | x100000 572 580 589 590 599
---------------------------------------------------------------------------
https://github.com/silentmantra/benchmark `

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script name="benchmark" data-count="100000">

const getData = () => ({
    '2020': { Thing1: ['ABC', '123'], Thing2: ['DEF'] },
    '2020.5': { Thing1: ['ABC', '123', 'XYZ'], Thing2: ['DEF'] },
    '2020.75': { Thing1: ['ABC', '123'], Thing2: ['XYZ'], Thing3: ['AAA'] }
});

// @benchmark Nenashev's in-place solution
{
const newData = getData();

const values = Object.values(newData);

const mapped = values
    .reduce((map, item) =>
        Object.entries(item).forEach(
            ([key, arr]) => arr.forEach(value => {
                const values = map[key] ??= {};
                if (!values[value]) {
                    values[value] = 0;
                }
                values[value]++;
            })) || map, {});

values.forEach(item => {

    for (const key in item) {

        const arr = item[key];
        for (let i = 0; i < arr.length; i++) {
            mapped[key][arr[i]] === values.length &&
                arr.splice(i--, 1);
        }

    }

});

newData;

}
// @benchmark Nenashev's pure solution

{
    const newData = getData();

    const values = Object.values(newData);

    const mapped = values
        .reduce((map, item) =>
            Object.entries(item).forEach(
                ([key, arr]) => arr.forEach(value => {
                    const values = map[key] ??= {};
                    if (!values[value]) {
                        values[value] = 0;
                    }
                    values[value]++;
                })) || map, {});


    const result = {};
    for (const dataKey in newData) {

        const newItem = result[dataKey] = {};
        item = newData[dataKey];

        for (const key in item) {
            newItem[key] = item[key].filter(val=>mapped[key][val] !== values.length);
        }
    }

    result;

}

// @benchmark Damzaky's pure solution

{
const originalData = getData();

const mustRemove = Object.values(originalData).reduce((acc, val) => {
  let newAcc = { ...acc
  }
  Object.entries(val).forEach(([key, value]) => {
    newAcc[key] = key in newAcc ? value.filter(v => newAcc[key].includes(v)) : []
  })
  return newAcc
})

Object.entries(originalData).reduce((acc, [key, val]) => ({
  ...acc,
  [key]: Object.entries(val).reduce((cacc, [ckey, cval]) => ({
    ...cacc,
    [ckey]: cval.filter(c => !mustRemove[ckey].includes(c))
  }), {})
}), {})
}

// @benchmark in-place lodash solution by 3limin4t0r
{
let newData = getData();

const rows = Object.values(newData);
const things = _.uniq(rows.flatMap((row) => (
  Object.keys(row).filter(key => key.match(/^Thing/))
)));

const intersections = Object.fromEntries(things.map((thing) => (
  [thing, _.intersection(...rows.map(row => row[thing] || []))]
)));

for (const row of rows) {
  for (const thing of things) {
    if (!(thing in row)) continue; // skip if key not present
    row[thing] = _.difference(row[thing], intersections[thing]);
  }
}

newData;

}

// @benchmark lodash pure solution by Ori Drori
{

const { mergeWith, cloneDeep, values, intersection, mapValues,  difference } = _;

const newData = getData();

const toRemove = mergeWith(
  ...cloneDeep(values(newData)), 
  (a, b) => intersection(a, b)
)

const result = mapValues(
  newData, 
  o => mapValues(o, (v, k) => difference(v, toRemove[k]))
)

result;


}

</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>


1
投票

不确定这是否是最有效的方法,但我会首先获得每个“事物”的交集,然后将它们从“事物”中删除:

const originalData = {
  '2020': {
    Thing1: ['ABC', '123'],
    Thing2: ['DEF']
  },
  '2020.5': {
    Thing1: ['ABC', '123', 'XYZ'],
    Thing2: ['DEF']
  },
  '2020.75': {
    Thing1: ['ABC', '123'],
    Thing2: ['XYZ'],
    Thing3: ['AAA']
  }
};

const mustRemove = Object.values(originalData).reduce((acc, val) => {
  let newAcc = { ...acc
  }
  Object.entries(val).forEach(([key, value]) => {
    newAcc[key] = key in newAcc ? value.filter(v => newAcc[key].includes(v)) : []
  })
  return newAcc
})

const result = Object.entries(originalData).reduce((acc, [key, val]) => ({
  ...acc,
  [key]: Object.entries(val).reduce((cacc, [ckey, cval]) => ({
    ...cacc,
    [ckey]: cval.filter(c => !mustRemove[ckey].includes(c))
  }), {})
}), {})

console.log(result)


1
投票

如果你想替换

newData
的数据,无需新对象,嵌套的
for..of Object.keys()
filter()
就足够了:

const toRemove = [ 'ABC', '123' ];

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'] }
};

for (var o of Object.keys(newData)) {
    for (var key of Object.keys(newData[o])) {
        newData[o][key] = newData[o][key].filter(e => !toRemove.includes(e));
    }
}

console.log(newData)


1
投票

这是一个主要使用普通 JavaScript 的解决方案。我确实使用

_.uniq
_.intersection
_.difference
,因为它们没有简单的 JavaScipt 等效项。

此解决方案只需几个步骤即可发挥作用。

  1. 收集所有
    _.uniq
    “东西”钥匙。
  2. 存储每个“Thing”键的
    _.intersection
  3. 将每一行的“Thing”集合替换为新集合。新值是通过当前值和存储的交集之间的
    _.difference
    生成的。

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'] }
};

// we don't care aboute the `newData` labels, so take only the values
const rows = Object.values(newData);
// collect the different `Thing` keys
const things = _.uniq(rows.flatMap((row) => (
  Object.keys(row).filter(key => key.match(/^Thing/))
)));

// build an intersection array for each "Thing", use an empty array if the key is missing
const intersections = Object.fromEntries(things.map((thing) => (
  [thing, _.intersection(...rows.map(row => row[thing] || []))]
)));
console.log("intersections =", intersections);

// subtract the intersection from the row value (difference)
for (const row of rows) {
  for (const thing of things) {
    if (!(thing in row)) continue; // skip if key not present
    row[thing] = _.difference(row[thing], intersections[thing]);
  }
}

console.log("mutated newData =", newData);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>

请注意,这个答案改变了原始结构。

© www.soinside.com 2019 - 2024. All rights reserved.