skip to Main Content

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?

2

Answers


  1. You could use Array::reduce() on your result grades:

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

    Or you could iterate your data array (which is twice faster):

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

    And the benchmark:
    enter image description here

    <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>
    Login or Signup to reply.
  2. 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 …

    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 });
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search