Home > Mobile >  How to sum values of equivalent keys in an array of objects where there can be multiple, newly gener
How to sum values of equivalent keys in an array of objects where there can be multiple, newly gener

Time:11-24

There exists an array of objects like so where there is a 'category' key and some 'series' keys.

arrOne = [
    {
        "series_1": 25,
        "category": "Category 1",
        "series_2": 50
    },
    {
        "series_1": 11,
        "category": "Category 2",
        "series_2": 22
    },
    {
        "series_1": 32,
        "category": "Category 1",
        "series_2": 74
    },
    {
        "series_1": 74,
        "category": "Category 3",
        "series_2": 98
    },
    {
        "series_1": 46,
        "category": "Category 3",
        "series_2": 29
    },

]

(Note that 'category' can be pretty much any value, though there will likely be multiple similar values as well as some unique values e.g. there are multiple objects with 'category' value 'Category 3' but only 1 with 'category' value 'Category 2')

The following lines of code will add up all of series_1 for objects with the same category

        var objForAllCategories = {};
        this.arrOne.forEach(item => {
            if (objForAllCategories.hasOwnProperty(item.category))
                objForAllCategories[item.category] = objForAllCategories[item.category]   item.series_1;
            else
                objForAllCategories[item.category] = item.series_1;
        });
        for (var prop in objForAllCategories) {
            this.allCategoriesAndValues.push({ 
                category: prop, 
                series_1: objForAllCategories[prop] 
            });
        }

So it would result in:

allCategoriesAndValues = [
    {
        "category": "Category 1",
        "series_1": 57       // 25   32 adding up series_1 from all 'Category 1' items in arrOne
    },
    {
        "category": "Category 2",
        "series_1": 11      // only 1 'Category 2' from arrOne
    },
    {
        "category": "Category 3",
        "series_1": 120     // 74   46 adding up series_1 from all 'Category 3' items in arrOne
    }
]

However, I want to be able to add not just series_1 but also all other items.

This example only has category and series_1 and series_2 as keys. However, there could be:

  1. series_3
  2. series_4
  3. series_5
  4. series_6
  5. series_7
  6. etc..

How can I account for all potential series_x?

Intended result:

allCategoriesAndValues = [
    {
        "category": "Category 1",
        "series_1": 57,
        "series_2": 124,
        ..... if 'series_3', 'series_4' etc. existed, it would be included in this as above
    },
    {
        "category": "Category 2",
        "series_1": 11,
        "series_2": 22,
        ..... if 'series_3', 'series_4' etc. existed, it would be included in this as above
    },
    {
        "category": "Category 3",
        "series_1": 120,
        "series_2": 127,
        ..... if 'series_3', 'series_4' etc. existed, it would be included in this as above
    }
]

CodePudding user response:

To handle the multiple properties logic, you can loop through each property and check whether it matches the regex series_\d . If it does, you know that it is a property that you need to increment, and handle it accordingly (a property existence check is also necessary, as pointed out by Jayce444).

The following solution uses Array.reduce. In the reducer function, it checks whether the accumulator array contains an item with the same category property as the one currently being looped through. If it does, it will increment the appropriate properties. Otherwise, it will push the current item to the accumulator array.

arrOne=[{series_1:25,category:"Category 1",series_2:50},{series_1:11,category:"Category 2",series_2:22},{series_1:32,category:"Category 1",series_2:74},{series_1:74,category:"Category 3",series_2:98},{series_1:46,category:"Category 3",series_2:29,series_3:50}];

const res = arrOne.reduce((a, b) => {
  let found = a.find(e => e.category == b.category)
  if (found) {
    Object.keys(b).forEach(e => {
      if (/series_\d /g.test(e)) found[e] = found[e] ? found[e]   b[e] : b[e];
    })
  } else {
    a.push(b)
  }
  return a;
}, [])

console.log(res)
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Something like this could work.

arrOne = [ { "series_1": 25, "category": "Category 1", "series_2": 50 }, { "series_1": 11, "category": "Category 2", "series_2": 22 }, { "series_1": 32, "category": "Category 1", "series_2": 74 }, { "series_1": 74, "category": "Category 3", "series_2": 98 }, { "series_1": 46, "category": "Category 3", "series_2": 29 },];

const result = [];
arrOne.reduce((acc, {category, ...series}) => {
  if (acc.has(category)) {
    Object.entries(series).forEach(([key, value]) => {
      if (key.startsWith('series_')) {
        acc.get(category)[key] = (acc.get(category)[key] || 0)   value;
      }
    });
  } else {
    const item = {category, ...series};
    result.push(item);
    acc.set(category, item);
  }
  return acc;
}, new Map());

console.log(result);
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Create a Map to collate series sums by category.

Then create an array from that map with the keys as category

const arr1 = [{"series_1":25,"category":"Category 1","series_2":50},{"series_1":11,"category":"Category 2","series_2":22},{"series_1":32,"category":"Category 1","series_2":74},{"series_1":74,"category":"Category 3","series_2":98},{"series_1":46,"category":"Category 3","series_2":29}]

const t1 = performance.now()

const cats = arr1.reduce((map, { category, ...series }) =>
  map.set(category, Object.entries(series)
    .reduce((s, [ key, count ]) => ({
      ...s,
      [ key ]: (s[key] ?? 0)   count
    }), map.get(category) ?? {})
  ), new Map())

const allCategoriesAndValues = Array.from(cats, ([ category, series ]) => ({
  category,
  ...series
}))

const t2 = performance.now()

console.info(allCategoriesAndValues)
console.log(`Took ${t2 - t1}ms`)
.as-console-wrapper { max-height: 100% !important; }
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

I will do that this way...

const arrOne = 
  [ { series_1: 25, category: 'Category 1', series_2: 50 } 
  , { series_1: 11, category: 'Category 2', series_2: 22 } 
  , { series_1: 32, category: 'Category 1', series_2: 74 } 
  , { series_1: 74, category: 'Category 3', series_2: 98 } 
  , { series_1: 46, category: 'Category 3', series_2: 29 } 
  ] 

console.time('chrono')

const allCategoriesAndValues =
  Object.entries(
  arrOne.reduce((r,{ category, ...series })=>
    {
    let cat = r[category] = r[category] ?? {} 
    Object.entries(series).forEach(([sName,val]) => cat[sName] = (cat[sName] ?? 0)   val);
    return r
    },{})
  ).map(([category,series])=>({category,...series}))

console.timeEnd('chrono')

console.log( allCategoriesAndValues )
.as-console-wrapper {max-height: 100%!important;top:0 }
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

You could just iterate over the array of objects and then the keys of each object, storing into a buffer object. You just have to either check for the existence of each key and add it if missing, or you can just coalesce falsey keys into a default value, like I did. I remove the category key from the object after I get its value, so that I don't have to try to skip it during the key iteration.

const arrOne = [
  {"series_1": 25, "category": "Category 1", "series_2": 50},
  {"series_1": 11, "category": "Category 2", "series_2": 22},
  {"series_1": 32, "category": "Category 1", "series_2": 74},
  {"series_1": 74, "category": "Category 3", "series_2": 98},
  {"series_1": 46, "category": "Category 3", "series_2": 29},
];

let buffer = {};
arrOne.forEach(i=>{
  let c = i.category;
  buffer[c] = buffer[c] || {};
  delete i.category;
  Object.keys(i).forEach(k=>{
    buffer[c][k] = buffer[c][k] || 0;
    buffer[c][k]  = i[k];
  });
});

console.log(buffer);

let final = Object.keys(buffer).map(k=>{return {[k]: buffer[k]}});
console.log(final);
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

If you don't need have this in an array, the last step is optional. It only exists to transform the object into an array.

CodePudding user response:

Here is how I would do it

const res = arrOne.reduce((acc, { category, ...vals }) => {
    if (acc[category]) {
        Object.entries(vals).forEach(([ key, val ]) => acc[category][key] = acc[category][key] ? acc[category][key]   val : val);

    } else {
        acc[category] = vals;

    }

    return acc;
}, {});
  • Related