简体   繁体   中英

Finding the sum of deeply nested array data by object category for multiple arrays

I have a function that returns 2 separate arrays containing 2 arrays.

[Array(3), Array(3)]

[Array(3), Array(3)]

Those nested arrays each contain 6 objects

{value: "10.50", category: "a"}, 
{value: "55.50", category: "a"}
{value: "10.50", category: "b"}, 
{value: "35.50", category: "b"},
{value: "15.50", category: "c"},
{value: "45.50", category: "c"}

I am trying to iterate through each main 2 arrays and return the summed values of the nested arrays by category like below: Keeping in mind the value is currently a string.

[
 [
  {value: "66.00", category: "a"},
  {value: "46.00", category: "b"},
  {value: "61.00", category: "c"},
 ]
]

I have tried the below but I cant seem to get it right, I have looked at numerous SO threads and I know there are tons of similar ones but nothing is working for me so far. TIA.

my poor attempt.

  const x = array.forEach((data) => {
    data.reduce(function (
      acc: any[],
      val: { category: string; value: string }
    ) {
      const o = acc
        .filter(function (obj) {
          return obj.category === val.category;
        })
        .pop() || { category: val.category, value: 0 };

      o.value += parseFloat(String(val.value));
      acc.push(o);
      return acc;
    },
    []);
  });

Sounds like you just need to group into an object indexed by category , adding up the values, making sure to coerce the strings to numbers, then turn it back into an array of objects afterwards:

 const outerArr = [ [ {value: "10.50", category: "a"}, {value: "55.50", category: "a"}, {value: "10.50", category: "b"}, {value: "35.50", category: "b"}, {value: "15.50", category: "c"}, {value: "45.50", category: "c"} ], // etc ]; const final = outerArr.map((subarr) => { const grouped = {}; for (const { value, category } of subarr) { grouped[category] = (grouped[category] || 0) + Number(value); } return Object.entries(grouped).map(([category, value]) => ({ value: value.toFixed(2), category })); }); console.log(final);

The final value is in string format, though you can change as per requirement. Example for single array:

Object.entries(
  arr.reduce((acc,curr)=>{
    acc[curr.category] = (acc[curr.category] || 0.0) + parseFloat(curr.value)
    return acc;
  },{})
)
.map(([a,b])=>({category:a,value:b.toFixed(2).toString()}));

There are several other good answers to your question, though I'd argue that this one is shorter, faster, and simpler to understand. (Look at the bottom of my solution for the working example). We can achieve this using a few simple array and object methods.

1. Consolidating the data

First, we'll consolidate each of your nested arrays of objects into single objects, with the categories (our unique values) as the keys, and their values. We can do this using the array reduce() method. Here we'll be using {} as our accumulator's initial value and then push to that accumulator object by setting its properties. As we iterate through, we'll add each value to the associated category property. This can cause errors if the property is not yet set on the accumulator object.

One way of working around this would be to use a ternary to check whether the property exists yet, and then either set the property if one doesn't yet exist, or add to the value if it does, like this:

// a is the accumulator, c is our category name, and v is our value
a[c] ? a[c] += parseFloat(v) : a[c] = parseFloat(v) // -> {a: 66, b: 46, c: 61}

A simpler way to do this is with nullish coalescing. Nullish coalescing will check to see if the value is null or undefined and if so, we use 0 as the current value. In this method, we are always setting property, and just overwriting it with itself (or 0 ) added to the current value being evaluated, like this:

a[c] = (a[c] ?? 0) + parseFloat(v) // -> {a: 66, b: 46, c: 61}

I also opted to use object restructuring when setting the parameter names in my reduce method to have all three variables easily accessible to me. This is not required though, so either of the below methods would work just the same. Using object destructuring also allows us to rename our destructured parameters to easier use ( c, v instead of category, value ). To learn more about destructuring and renaming parameters, check out Wes Bos's post, Rename & Destructure Variables in ES6 .

// Without Object Destructuring
arr.reduce((a, o) => (a[o.category] = (a[o.category] ?? 0) + parseFloat(o.value), a), {});

// With Object Destructuring
arr.reduce((a, {value: v, category: c}) => (a[c] = (a[c] ?? 0) + parseFloat(v), a), {});

2. Expanding the consolidated object(s) into an array of objects

Once we have our simplified object (eg {a: 66, b: 46, c: 61} ) for each array of objects, our next task is to transform it back into the series of objects format we started with, but now consolidated. To do this, we first convert our new object of key-value pairs into an array of nested arrays using the Object.entries() method. This will allow us to re-map the array as an array of objects, during which we'll rewrite our desired key names and convert our numbered values back into strings with a fixed decimal count of 2.

In an earlier step, we destructured an object using its property names. This time, because Object.entries() returns our data as an array, we'll be using array destructuring. This is similar to object destructuring, except that we'll need to use brackets when we restructure our array. Remember to always wrap your destructured parameters in parentheses or you'll get an error:

// obj = {a: 66, b: 46, c: 61};

Object.entries(obj).map([c, v] => ({ value: v.toFixed(2), category: c }))
// !!! Uncaught SyntaxError: Malformed arrow function parameter list

Object.entries(obj).map(([c, v]) => ({ value: v.toFixed(2), category: c }))
/* -> [
        {value: "66.00", category: "a"},
        {value: "46.00", category: "b"},
        {value: "61.00", category: "c"}
      ]
*/

3. Putting it all together. The final result:

Now all that's left is to put it all together: Here's the final result:

 const data = [ [ {value: "10.50", category: "a"}, {value: "55.50", category: "a"}, {value: "10.50", category: "b"}, {value: "35.50", category: "b"}, {value: "15.50", category: "c"}, {value: "45.50", category: "c"} ], // [... ] ]; const consolidate = arr => arr.map(e => Object.entries(e.reduce((a, {value: v, category: c}) => (a[c] = (a[c]?? 0) + parseFloat(v), a), {})).map(([c, v]) => ({ value: v.toFixed(2), category: c }))); console.log(consolidate(data));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM