使用JavaScript和Lodash从多个对象中的数组中筛选出数值。

huangapple go评论70阅读模式
英文:

Filter out value from arrays within multiple objects using javascript and lodash

问题

在理论上,这听起来相当容易,但某个 AI 机器人一直给我提供不正确的信息。
我有这样的数据:

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

这是我从前述 AI 机器人得到的答案:

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数组中的实际值。

任何帮助将不胜感激。

英文:

In theory this sounds reasonably easy, but a certain AI bot keeps giving me incorrect info.
I have data that looks like this:

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

All I want to do is remove values from the “Thing” arrays that are the same across all objects and specific array. In the case above remove ‘ABC’ and ‘123’ from the Thing1 arrays.
The returned data looking like this:

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

This was the answer I got from the aforementioned AI bot:

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);

But this just returns everything. I don’t think it’s iterating through the actual values in the Thing arrays.

Any help would be most appreciated.

答案1

得分: 2

  1. 创建一个对象 (toRemove),其中包含原始对象之间共同属性的所有值。
  2. 映射原始对象的值,然后映射每个对象内部的所有属性。获取每个数组之间的差异,以及它的 toRemove 对应属性。

请注意,代码部分不需要翻译。

英文:
  1. Create an object (toRemove) that contains, for each property, all the values that are common between the original objects.
  2. Map the original object's values, and then map all the propertoes inside each object. Get the difference between each array, and it's toRemove counterpart property.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

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

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

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

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

const result = fn(newData);

console.log(result);

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案2

得分: 1

以下是翻译好的部分:

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

Damzaky提供的纯解决方案速度较慢(但如果需要纯解决方案可能会有用)。

在这里不需要使用lodash。ES6+允许执行相同的操作。我已经添加了两种lodash解决方案到基准测试中,但它们是最慢的:

使用JavaScript和Lodash从多个对象中的数组中筛选出数值。

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-html -->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/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>

<!-- end snippet -->
英文:

The author's code mutates the original object.
First we get counts of values, then we clean the arrays.

A pure solution by Damzaky is slower (but could be useful if we need to go pure).

You don't need the lodash here. The ES6+ allows to do the same things. I've added 2 lodash solutions to the benchmark but they are the slowest:

使用JavaScript和Lodash从多个对象中的数组中筛选出数值。

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js&quot;&gt;&lt;/script&gt;
&lt;script name=&quot;benchmark&quot; data-count=&quot;100000&quot;&gt;
const getData = () =&gt; ({
&#39;2020&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.5&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;, &#39;XYZ&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.75&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;XYZ&#39;], Thing3: [&#39;AAA&#39;] }
});
// @benchmark Nenashev&#39;s in-place solution
{
const newData = getData();
const values = Object.values(newData);
const mapped = values
.reduce((map, item) =&gt;
Object.entries(item).forEach(
([key, arr]) =&gt; arr.forEach(value =&gt; {
const values = map[key] ??= {};
if (!values[value]) {
values[value] = 0;
}
values[value]++;
})) || map, {});
values.forEach(item =&gt; {
for (const key in item) {
const arr = item[key];
for (let i = 0; i &lt; arr.length; i++) {
mapped[key][arr[i]] === values.length &amp;&amp;
arr.splice(i--, 1);
}
}
});
newData;
}
// @benchmark Nenashev&#39;s pure solution
{
const newData = getData();
const values = Object.values(newData);
const mapped = values
.reduce((map, item) =&gt;
Object.entries(item).forEach(
([key, arr]) =&gt; arr.forEach(value =&gt; {
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=&gt;mapped[key][val] !== values.length);
}
}
result;
}
// @benchmark Damzaky&#39;s pure solution
{
const originalData = getData();
const mustRemove = Object.values(originalData).reduce((acc, val) =&gt; {
let newAcc = { ...acc
}
Object.entries(val).forEach(([key, value]) =&gt; {
newAcc[key] = key in newAcc ? value.filter(v =&gt; newAcc[key].includes(v)) : []
})
return newAcc
})
Object.entries(originalData).reduce((acc, [key, val]) =&gt; ({
...acc,
[key]: Object.entries(val).reduce((cacc, [ckey, cval]) =&gt; ({
...cacc,
[ckey]: cval.filter(c =&gt; !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) =&gt; (
Object.keys(row).filter(key =&gt; key.match(/^Thing/))
)));
const intersections = Object.fromEntries(things.map((thing) =&gt; (
[thing, _.intersection(...rows.map(row =&gt; 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) =&gt; intersection(a, b)
)
const result = mapValues(
newData, 
o =&gt; mapValues(o, (v, k) =&gt; difference(v, toRemove[k]))
)
result;
}
&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

答案3

得分: 1

这是代码示例,不需要翻译。

英文:

Not sure if this is the most efficient way, but I would get the intersections of every "Thing" first, and then remove them from the "Thing"s:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const originalData = {
&#39;2020&#39;: {
Thing1: [&#39;ABC&#39;, &#39;123&#39;],
Thing2: [&#39;DEF&#39;]
},
&#39;2020.5&#39;: {
Thing1: [&#39;ABC&#39;, &#39;123&#39;, &#39;XYZ&#39;],
Thing2: [&#39;DEF&#39;]
},
&#39;2020.75&#39;: {
Thing1: [&#39;ABC&#39;, &#39;123&#39;],
Thing2: [&#39;XYZ&#39;],
Thing3: [&#39;AAA&#39;]
}
};
const mustRemove = Object.values(originalData).reduce((acc, val) =&gt; {
let newAcc = { ...acc
}
Object.entries(val).forEach(([key, value]) =&gt; {
newAcc[key] = key in newAcc ? value.filter(v =&gt; newAcc[key].includes(v)) : []
})
return newAcc
})
const result = Object.entries(originalData).reduce((acc, [key, val]) =&gt; ({
...acc,
[key]: Object.entries(val).reduce((cacc, [ckey, cval]) =&gt; ({
...cacc,
[ckey]: cval.filter(c =&gt; !mustRemove[ckey].includes(c))
}), {})
}), {})
console.log(result)

<!-- end snippet -->

答案4

得分: 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)

请注意,上面的代码会从newData中删除包含在toRemove数组中的元素。

英文:

If you wish to replace the data of newData, without a new object, a nested for..of Object.keys() and a filter() would be enough:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const toRemove = [ &#39;ABC&#39;, &#39;123&#39; ];
let newData = {
&#39;2020&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.5&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;, &#39;XYZ&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.75&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;XYZ&#39;], Thing3: [&#39;AAA&#39;] }
};
for (var o of Object.keys(newData)) {
for (var key of Object.keys(newData[o])) {
newData[o][key] = newData[o][key].filter(e =&gt; !toRemove.includes(e));
}
}
console.log(newData)

<!-- end snippet -->

答案5

得分: 1

以下是使用主要的普通JavaScript的解决方案。我使用了 _.uniq_.intersection_.difference,因为它们没有一个简单的JavaScript等价物。

这个解决方案分为几个步骤:

  1. 收集所有 _.uniq 的 "Thing" 键。
  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'] }
};

// 我们不关心 `newData` 的标签,只取值
const rows = Object.values(newData);
// 收集不同的 "Thing" 键
const things = _.uniq(rows.flatMap((row) => (
  Object.keys(row).filter(key => key.match(/^Thing/))
)));

// 为每个 "Thing" 构建一个交集数组,如果键丢失,则使用空数组
const intersections = Object.fromEntries(things.map((thing) => (
  [thing, _.intersection(...rows.map(row => row[thing] || []))]
)));
console.log("intersections =", intersections);

// 从行值中减去交集(差异)
for (const row of rows) {
  for (const thing of things) {
    if (!(thing in row)) continue; // 如果键不存在则跳过
    row[thing] = _.difference(row[thing], intersections[thing]);
  }
}

console.log("mutated newData =", newData);

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

英文:

Here is a solution using mainly normal JavaScript. I do use _.uniq, _.intersection and _.difference since they don't have a simple JavaScipt equivalent.

This solution works in a few steps.

  1. Collect all the _.uniq "Thing" keys.
  2. Store an _.intersection of each "Thing" key.
  3. Replace the "Thing" collection of each row with a new one. The new value is produced by taking the _.difference between the current value and the stored intersection.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

let newData = {
&#39;2020&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.5&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;, &#39;XYZ&#39;], Thing2: [&#39;DEF&#39;] },
&#39;2020.75&#39;: { Thing1: [&#39;ABC&#39;, &#39;123&#39;], Thing2: [&#39;XYZ&#39;], Thing3: [&#39;AAA&#39;] }
};
// we don&#39;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) =&gt; (
Object.keys(row).filter(key =&gt; key.match(/^Thing/))
)));
// build an intersection array for each &quot;Thing&quot;, use an empty array if the key is missing
const intersections = Object.fromEntries(things.map((thing) =&gt; (
[thing, _.intersection(...rows.map(row =&gt; row[thing] || []))]
)));
console.log(&quot;intersections =&quot;, 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(&quot;mutated newData =&quot;, newData);

<!-- language: lang-html -->

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js&quot;&gt;&lt;/script&gt;

<!-- end snippet -->

Note that this answer mutates the original structure.

huangapple
  • 本文由 发表于 2023年6月12日 21:34:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定