1

I know this question has been asked many times, however, unfortunately, I didn't find any answer that helped my case.

This is what my data looks like:

let data=  [
    {
      "estimated_cost": 1.14,
      "inventory_type": "power",
      "cost_center_name": "Ex Hall",
    },
    {
      "estimated_cost": 1.19,
      "inventory_type": "power",
      "cost_center_name": "Ex Hall",
    },
    {
      "estimated_cost": 1.08,
      "inventory_type": "fuel",
      "cost_center_name": "Ex Hall",
    },
    {
      "estimated_cost": 1.17,
      "inventory_type": "power",
      "cost_center_name": "Ex Hall",
    },
    {
      "estimated_cost": 1.03,
      "inventory_type": "fuel",
      "cost_center_name": "Ex Hall",
    },
    {
      "estimated_cost": 1.20,
      "inventory_type": "power",
      "cost_center_name": "Mac",
    },
    {
        "estimated_cost": 1.19,
        "inventory_type": "water",
        "cost_center_name": "Mac",
     },
     {
        "estimated_cost": 1.14,
        "inventory_type": "power",
        "cost_center_name": "Mac",
     },
     {
        "estimated_cost": 1.18,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall",
     }
     ];

I want to group by two fields, inventory_type and cost_center_name, and add the values of estimated_cost.

My output should look like this:

 [
    {
        "estimated_cost": 4.68,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall"
    },
    {
        "estimated_cost": 2.11,
        "inventory_type": "fuel",
        "cost_center_name": "Ex Hall"
    },
    {
        "estimated_cost": 2.34,
        "inventory_type": "power",
        "cost_center_name": "Mac"
    },
    {
        "estimated_cost": 1.19,
        "inventory_type": "water",
        "cost_center_name": "Mac"
    },

]

I have this code to group by a single field and add the values, in case this helps:

export const groupBy = (data, groupByVar) => {
    var array = [];
    data.reduce(function (res, value) {
        if (!res[value[groupByVar]]) {
            let row = {};
            row[groupByVar] = value[groupByVar];
            row["estimated_cost"] = 0;
            row["cost_center_name"] = value["cost_center_name"];
            row["inventory_type"] = value["inventory_type"];
            res[value[groupByVar]] = row;
            array.push(res[value[groupByVar]]);
        }
        res[value[groupByVar]].estimated_cost += value.estimated_cost;
        return res;
    }, {});
    return array;
};
3
  • do you know in advance all the possible names for both fields? Commented Jan 5, 2021 at 3:36
  • I will only be using "cost_center_name" and "inventory_type" to groupBy. Commented Jan 5, 2021 at 3:45
  • Doesn't a reduce return a new array and not mutate the existing? Either way, you define an empty array named variable, and then explicitly return that as well. Commented Jan 5, 2021 at 3:45

6 Answers 6

1

Traverse array and build an object all having key as combination of inventory and cost center name and values to aggregate of estimated cost.

const group = (arr) => {
  const all = {};
  // Update key format as required. 
  const getKey = ({ inventory_type, cost_center_name }) =>
    `${inventory_type}||${cost_center_name}`;
  arr.forEach((item) => {
    const key = getKey(item);
    if (key in all) {
      all[key].estimated_cost += item.estimated_cost;
    } else {
      all[key] = { ...item };
    }
  });
  return Object.values(all);
};

let data = [
  {
    estimated_cost: 1.14,
    inventory_type: "power",
    cost_center_name: "Ex Hall",
  },
  {
    estimated_cost: 1.19,
    inventory_type: "power",
    cost_center_name: "Ex Hall",
  },
  {
    estimated_cost: 1.08,
    inventory_type: "fuel",
    cost_center_name: "Ex Hall",
  },
  {
    estimated_cost: 1.17,
    inventory_type: "power",
    cost_center_name: "Ex Hall",
  },
  {
    estimated_cost: 1.03,
    inventory_type: "fuel",
    cost_center_name: "Ex Hall",
  },
  {
    estimated_cost: 1.2,
    inventory_type: "power",
    cost_center_name: "Mac",
  },
  {
    estimated_cost: 1.19,
    inventory_type: "water",
    cost_center_name: "Mac",
  },
  {
    estimated_cost: 1.14,
    inventory_type: "power",
    cost_center_name: "Mac",
  },
  {
    estimated_cost: 1.18,
    inventory_type: "power",
    cost_center_name: "Ex Hall",
  },
];

console.log(group(data));

Sign up to request clarification or add additional context in comments.

Comments

1

Here's a general solution that should work with arbitrary keys which you'd like to group on.

The main function is groupByFields, which takes an array of objects and a list of fields and produces a flattened array of objects grouped on the fields. flattenObj is a helper function to smush down the grouped objects used for internal bookkeeping by groupByFields.

After that, it's a matter of applying summations to the elements in each group. If you treat the functions as a library, the client logic is fairly clean.

It seemed premature to generalize the reduction function but if you're doing this often, you can make estimated_cost a parameter and pack the whole thing into another function.

const data =  [ { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.19, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.08, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.17, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.03, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.20, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.19, "inventory_type": "water", "cost_center_name": "Mac", }, { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.18, "inventory_type": "power", "cost_center_name": "Ex Hall", } ];

const flattenObj = obj => {
  const arrays = [];
  
  for (const stack = [obj]; stack.length;) {
    const curr = stack.pop();
    
    if (Array.isArray(curr)) {
      arrays.push(curr);
    }
    else {
      stack.push(...Object.values(curr));
    }
  }
  
  return arrays.reverse();
};

const groupByFields = (a, fields) => 
  flattenObj(a.reduce((a, e) => {
    let curr = a;
    fields.forEach((field, i) => {
      if (!curr[e[field]]) {
        curr[e[field]] = i === fields.length - 1 ? [] : {};
      }

      curr = curr[e[field]];
    });  
    curr.push(e);
    return a;
  }, {}))
;

const reducer = group => group.reduce((a, e) => ({
  ...e, 
  estimated_cost: a.estimated_cost + e.estimated_cost
}), {estimated_cost: 0});

const fields = ["cost_center_name", "inventory_type"];
const grouped = groupByFields(data, fields);
console.log(grouped.map(reducer));

If this is too generalized for you and feels like too much code, you can drop the whole flattenObj function and just use a hardcoded

Object.values(grouped).map(o => Object.values(o).map(reducer))

instead. This effectively flattens the reduced object two levels (the number of fields you have) instead of an arbitrary number of levels as flattenObj does.

Comments

1

You can group based on multiple key and joining them and then add estimated_cost in an accumulator.

const data = [ { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.19, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.08, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.17, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.03, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.20, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.19, "inventory_type": "water", "cost_center_name": "Mac", }, { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.18, "inventory_type": "power", "cost_center_name": "Ex Hall", } ],
    result = Object.values(data.reduce((r, o) => {
      const key = o.inventory_type + '-' + o.cost_center_name;
      r[key] = r[key] || {...o, estimated_cost: 0};
      r[key].estimated_cost += o.estimated_cost;
      return r;
    },{}));
console.log(result);

Comments

0

I will use your cost_center_name and inventory_type to group all data:

const groupAddition = (data) => {
  let ret = {};
  for (const each of data) {
    const key = each.cost_center_name+each.inventory_type;
    if (ret[key]) {
      ret[key].estimated_cost += each.estimated_cost;
    } else {
      ret[key] = each;
    }
  }
  
  return Object.values(ret).map((e) => {
    e.estimated_cost = Math.round(e.estimated_cost * 100) / 100;
    return e;
  });
}

I have added additional step to round the final estimated_cost, as JS sometimes returns '2.1100000000000003' for addition..

by printing the result, it shows

console.log(JSON.stringify(groupAddition(data), null, 2));
[
  {
    "estimated_cost": 4.68,
    "inventory_type": "power",
    "cost_center_name": "Ex Hall"
  },
  {
    "estimated_cost": 2.11,
    "inventory_type": "fuel",
    "cost_center_name": "Ex Hall"
  },
  {
    "estimated_cost": 2.34,
    "inventory_type": "power",
    "cost_center_name": "Mac"
  },
  {
    "estimated_cost": 1.19,
    "inventory_type": "water",
    "cost_center_name": "Mac"
  }
]

Comments

0

The following will work for any keys mentioned in the fields array. This code will group by all the field mentioned in the fields array.

You can use reduce to do that,

const data =  [ { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.19, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.08, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.17, "inventory_type": "power", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.03, "inventory_type": "fuel", "cost_center_name": "Ex Hall", }, { "estimated_cost": 1.20, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.19, "inventory_type": "water", "cost_center_name": "Mac", }, { "estimated_cost": 1.14, "inventory_type": "power", "cost_center_name": "Mac", }, { "estimated_cost": 1.18, "inventory_type": "power", "cost_center_name": "Ex Hall", } ];



const fields = ["cost_center_name", "inventory_type"];

const groupByFields = (obj, fields) => {
   return obj.reduce((prev, curr) => {
      index = prev.findIndex(item => {
         let found = true;
         fields.forEach(key => {
            found = found && item[key] === curr[key]; 
         });
         return found;
      });
      
      if(index > -1) {
         prev[index].estimated_cost += curr.estimated_cost;
      } else {
         prev.push(curr);
      }
      return prev;
   
   }, []);
}

const res = groupByFields(data, fields);
console.log(res);

Comments

0

To group the entries, you can iterate over the array and create a json string from just the required fields. This creates a unique key which can be used an object property entry and will allow us to detect when we have entries to be grouped together.

We will initialise the entry to one object contained in a list (intermediate[key] = [a]) or push to the array if it already exists. Then we can strip off the object keys to leave just the values, which are our groups.

The second stage is to sum the estimated_costs field. To do this I created a specific function, instead of trying to cram it all into a one liner. The function takes the array of grouped objects and populates the other fields (inventory_type, cost_center) from the first element in each array (it cant be empty or it wouldn't have been returned from groupBy) and then sums the estimated_costs fields.

function makeKey(obj, props) {
    const result = {};

    for (p of props) {
        result[p] = obj[p];
    }
    return JSON.stringify(result);
}

function groupBy(arr, fields) {
    const intermediate = {};

    for (let a of arr) {
        const key = makeKey(a, fields);
        const val = intermediate[key];

        if (val !== undefined) val.push(a);
        else                   intermediate[key] = [a];
    }

    return Object.values(intermediate);
}

let data = [
    {
        "estimated_cost": 1.14,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall",
    },
    {
        "estimated_cost": 1.19,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall",
    },
    {
        "estimated_cost": 1.08,
        "inventory_type": "fuel",
        "cost_center_name": "Ex Hall",
    },
    {
        "estimated_cost": 1.17,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall",
    },
    {
        "estimated_cost": 1.03,
        "inventory_type": "fuel",
        "cost_center_name": "Ex Hall",
    },
    {
        "estimated_cost": 1.20,
        "inventory_type": "power",
        "cost_center_name": "Mac",
    },
    {
        "estimated_cost": 1.19,
        "inventory_type": "water",
        "cost_center_name": "Mac",
    },
    {
        "estimated_cost": 1.14,
        "inventory_type": "power",
        "cost_center_name": "Mac",
    },
    {
        "estimated_cost": 1.18,
        "inventory_type": "power",
        "cost_center_name": "Ex Hall",
    }
];

function sumCosts(group) {
    return {
        inventory_type: group[0].inventory_type,
        cost_center_name: group[0].cost_center_name,
        estimated_cost: group.map(g=>g.estimated_cost).reduce((accum, n) => accum + n, 0)
    }
}

console.log(groupBy(data, ['inventory_type', 'cost_center_name']).map(sumCosts));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.