英文:
group data using a vector of keys in JavaScript, add empty array where key is not in data
问题
我想使用这个键的向量
const vec_of_vals = ["one", "two", "three", "four"]
来对对象数组进行分组和筛选
const data = [
{ grade: "one" },
{ grade: "one" },
{ grade: "one" },
{ grade: "two" },
{ grade: "four" },
{ grade: "four" },
{ grade: "four" },
{ grade: "four" },
{ grade: "five" },
{ grade: "five" },
{ grade: "five" },
{ grade: "six" }
]
我找到了可以帮助我做到这一点的帖子,但它们不包括初始 vec_of_vals
中的空 three
数组。
这是我尝试过的内容
function groupByKey(array, key) {
return array.reduce((hash, obj) => {
if (obj[key] === undefined) return hash;
return Object.assign(hash, {
[obj[key]]: (hash[obj[key]] || []).concat(obj),
});
}, {});
}
groupByKey(
data.filter((x) => vec_of_vals.includes(x.grade)),
"grade"
);
但这不会给我空的 three
数组,并且它不会保持原始数据的顺序!以下是我期望的输出:
期望的输出
let desired_output = {
"one": [
{ grade: "one" },
{ grade: "one" }
],
"two": [
{ grade: "two" }
],
"three": [],
"four": [
{ grade: "four" },
{ grade: "four" },
{ grade: "four" },
{ grade: "four" }
]
}
如何修改 groupByKey
函数,不仅按 grade
分组,还要插入任何缺失的成绩并删除原始键中不存在的任何成绩,同时保持原始 vec_of_vals
的顺序?
英文:
I'd like to use this vector of keys
const vec_of_vals = ["one", "two", "three", "four"]
to group and filter an array of objects
const data = [
{grade: "one"},
{grade: "one"},
{grade: "one"},
{grade: "two"},
{grade: "four"},
{grade: "four"},
{grade: "four"},
{grade: "four"},
{grade: "five"},
{grade: "five"},
{grade: "five"},
{grade: "six"}
]
And I've found posts that can help me to do this, but they don't include an empty "three"
array which is in my initial vec_of_vals
Here's what I've tried
function groupByKey(array, key) {
return array.reduce((hash, obj) => {
if (obj[key] === undefined) return hash;
return Object.assign(hash, {
[obj[key]]: (hash[obj[key]] || []).concat(obj),
});
}, {});
}
groupByKey(
data.filter((x) => vec_of_vals.includes(x.grade)),
"grade"
);
But this wont give me the empty three
array AND it doesn't keep the original order of my data!. As a visual here is my desired output:
Desired Output
let desired_output = {
"one" : [
{grade: "one"},
{grade: "one"},
],
"two" : [
{grade: "two"},
],
"three" : [],
"four" : [
{grade: "four"},
{grade: "four"},
{grade: "four"},
{grade: "four"}
]
}
How do I change the groupByKey
function to not only group by grade
but insert any missing grades and remove any grades that are not in the original key as well as maintain the order of the original vec_of_vals
?
答案1
得分: 1
你可以在你的成绩结果上使用 Array::reduce()
:
const grades = 'abcd'.split('');
const data = [
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
];
const grouped = grades.reduce((grouped, key) => (grouped[key] = data.filter(({grade}) => grade === key)) && grouped, {});
console.log(JSON.stringify(grouped));
或者你可以遍历你的数据数组(这样做速度会更快):
const grades = 'abcd'.split('');
const data = [
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
];
const grouped = grades.reduce((grouped, grade) => (grouped[grade] = []) && grouped, {});
data.forEach(item => grouped[item.grade]?.push(item));
console.log(JSON.stringify(grouped));
以及性能基准:
<script benchmark data-count="1">
const grades = 'abcd'.split('');
const data = JSON.parse(JSON.stringify(Array.from({ length: 300000 }).reduce(arr => arr.push(...[
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
]) && arr, [])));
// @benchmark reducing grades and filtering data
grades.reduce((grouped, key) => (grouped[key] = data.filter(({ grade }) => grade === key)) && grouped, {});
// @benchmark iterating data array
const grouped = grades.reduce((grouped, grade) => (grouped[grade] = []) && grouped, {});
data.forEach(item => grouped[item.grade]?.push(item));
grouped;
</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
英文:
You could use Array::reduce()
on your result grades:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const grades = 'abcd'.split('');
const data = [
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
];
const grouped = grades.reduce((grouped, key) => (grouped[key] = data.filter(({grade}) => grade === key)) && grouped, {});
console.log(JSON.stringify(grouped));
<!-- end snippet -->
Or you could iterate your data array (which is twice faster):
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const grades = 'abcd'.split('');
const data = [
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
];
const grouped = grades.reduce((grouped, grade) => (grouped[grade] = []) && grouped, {});
data.forEach(item => grouped[item.grade]?.push(item));
console.log(JSON.stringify(grouped));
<!-- end snippet -->
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
<script benchmark data-count="1">
const grades = 'abcd'.split('');
const data = JSON.parse(JSON.stringify(Array.from({ length: 300000 }).reduce(arr => arr.push(...[
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
]) && arr, [])));
// @benchmark reducing grades and filtering data
grades.reduce((grouped, key) => (grouped[key] = data.filter(({ grade }) => grade === key)) && grouped, {});
// @benchmark iterating data array
const grouped = grades.reduce((grouped, grade) => (grouped[grade] = []) && grouped, {});
data.forEach(item => grouped[item.grade]?.push(item));
grouped;
</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
<!-- end snippet -->
答案2
得分: 0
以下是翻译好的部分:
One possible and pretty straightforward approach would be based on two single reduce
tasks.
首先,将data
数组减少为一个对象,该对象根据其grade
值对数据项进行分组和收集...
const gradeCollection = data
.reduce((collection, dataItem) => {
const { grade } = dataItem;
(collection[grade] ??= []).push(dataItem);
return collection;
}, {});
最终结果可以从提供的成绩优先级列表(OP称之为“键的向量”或vec_of_vals
)和刚刚处理过的gradeCollection
中计算出来。
为了使result
对象的键值对(条目)与已提到的gradePrecedenceList
(以前称为vec_of_vals
)的成绩值对齐,可以对后者进行reduce
,在每次迭代中创建一个条目,其中键等于当前的grade
值,值从之前创建的gradeCollection
中以相同的grade
值查找。如果查找不成功,将分配一个空数组。
const result = gradePrecedenceList
.reduce((collection, grade) => {
collection[grade] = gradeCollection[grade] ?? [];
return collection;
}, {});
...可执行的示例代码证明了上述说法...
英文:
One possible and pretty straightforward approach would be based on two single reduce
tasks.
First one reduces the data
array into an object which groups and collects the data items each by its grade
-value ...
const gradeCollection = data
.reduce((collection, dataItem) => {
const { grade } = dataItem;
(collection[grade] ??= []).push(dataItem);
return collection;
}, {});
The final result already can be computed from both, the also provided grade-precedence list (what the OP calls "vector of keys" or vec_of_vals
) and the just processed gradeCollection
.
In order to align the result
object's key-value pairs (entries) with the grade-values of the already mentioned gradePrecedenceList
(formerly known as vec_of_vals
) one does reduce
the latter, creating with each iteration an entry where the key equals the current grade
-value and the value gets looked up from the before created gradeCollection
by the same grade
-value. In case of an unsuccessful lookup one would assign an empty array instead.
const result = gradePrecedenceList
.reduce((collection, grade) => {
collection[grade] = gradeCollection[grade] ?? [];
return collection;
}, {});
... executable example code which does prove the above said ...
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const data = [
{grade: "one"},
{grade: "one"},
{grade: "one"},
{grade: "two"},
{grade: "four"},
{grade: "four"},
{grade: "four"},
{grade: "four"},
{grade: "five"},
{grade: "five"},
{grade: "five"},
{grade: "six"}
];
const gradePrecedenceList = ["one", "two", "three", "four"];
const gradeCollection = data
.reduce((collection, dataItem) => {
const { grade } = dataItem;
(collection[grade] ??= []).push(dataItem);
return collection;
}, {});
const result = gradePrecedenceList
.reduce((collection, grade) => {
collection[grade] = gradeCollection[grade] ?? [];
return collection;
}, {});
console.log({ result });
console.log({ gradeCollection });
<!-- language: lang-css -->
.as-console-wrapper { min-height: 100%!important; top: 0; }
<!-- end snippet -->
And out of interest and for fun Alexander Nenashev's benchmark test running additionally the above implementation of "reduce twice" ...
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
<script benchmark data-count="10">
const grades = 'abcd'.split('');
const data = JSON.parse(JSON.stringify(Array.from({ length: 300000 }).reduce(arr => arr.push(...[
{ grade: "a" }, { grade: "a" }, { grade: "a" }, { grade: "b" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "d" }, { grade: "e" }, { grade: "e" }, { grade: "e" }, { grade: "f" }
]) && arr, [])));
// @benchmark reducing grades and filtering data
grades.reduce((grouped, key) => (grouped[key] = data.filter(({ grade }) => grade === key)) && grouped, {});
// @benchmark iterating data array
const grouped = grades.reduce((grouped, grade) => (grouped[grade] = []) && grouped, {});
data.forEach(item => grouped[item.grade]?.push(item));
grouped;
// @benchmark reduce twice
const gradeCollection = data
.reduce((collection, dataItem) => {
const { grade } = dataItem;
(collection[grade] ??= []).push(dataItem);
return collection;
}, {});
grades
.reduce((collection, grade) => {
collection[grade] = gradeCollection[grade] ?? [];
return collection;
}, {});
</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论