Home > Software design >  How to group & sum by multiple keys in an object array?
How to group & sum by multiple keys in an object array?

Time:11-15

I'm trying to find an efficient way to group and sum data from an object array in javascript. I am open to using the lodash as a library for this, and have researched other similar questions, but not exactly what I'm looking for. Here I would like to create a generic function that takes an array of data, an array of fields to group by and an array of fields to sum.

Take this example object array:

const data = [
{
  continent:'North America',
  country:'us',
  state:'ma',
  population: 1567986,
  fee: 24343
},
{
  continent:'North America',
  country:'us',
  state:'tx',
  population: 7567986,
  fee: 119343
},
{
  continent:'North America',
  country:'ca',
  state:'on',
  population: 467986,
  fee: 3343
},
{
  continent:'North America',
  country:'ca',
  state:'qb',
  population: 1268986,
  fee: 54248
},
{
  continent:'Europe',
  country:'ge',
  state:'mc',
  population: 4268986,
  fee: 97333
},
{
  continent:'Europe',
  country:'it',
  state:'fl',
  population: 978353,
  fee: 248
 },
]

I want to create a function that looks like:

const GroupByFunction = (array, groupbyFields, sumFields) {

}

if I called it with say these fields const result1 = GroupByFuntion(data,['continent'],['population','fee'])

the result would look like this where popSum and popFee are just made up attribute names to hold the summed values:

const result1 = [
  {
    contintent:'North America',
    popSum:10872944,
    popfee:201277,
    data: [
    {
      continent:'North America',
      country:'us',
      state:'ma',
      population: 1567986,
      fee: 24343
    },
    {
      continent:'North America',
      country:'us',
      state:'tx',
      population: 7567986,
      fee: 119343
    },
    {
      continent:'North America',
      country:'ca',
      state:'on',
      population: 467986,
      fee: 3343
    },
    {
      continent:'North America',
      country:'ca',
      state:'qb',
      population: 1268986,
      fee: 54248
    },
  ]
},
{
  continent:'Europe',
  popSum: 5247339,
  feeSum:97581,
  data: [
    {
      continent:'Europe',
      country:'ge',
      state:'mc',
      population: 4268986,
      fee: 97333
    },
    {
      continent:'Europe',
      country:'it',
      state:'fl',
      population: 978353,
      fee: 248
    },
  ]
}
]

But, I could also pass in multiple group by keys like this:

const result2 = GroupByFuntion(data,['continent','country'],['population','fee'])

and the result would look like this (the popSum and popFee are just junk data placeholders, they would be the summed values from the array):

const result2 = [
{
  continent:'North America',
  popSum:10872944,
  popfee:201277,
  country: [
    {
      country:'us',
      popSum: 2423423,
      popFee: 2323,
      data: [
        {
          continent:'North America',
          country:'us',
          state:'ma',
          population: 1567986,
          fee: 24343
        },
        {
          continent:'North America',
          country:'us',
          state:'tx',
          population: 7567986,
          fee: 119343
        },
      ]
    },
    {
      country:'ca',
      popSum: 54443,
      popFee: 34324,
      data: [
        {
          continent:'North America',
          country:'ca',
          state:'on',
          population: 467986,
          fee: 3343
        },
        {
          continent:'North America',
          country:'ca',
          state:'qb',
          population: 1268986,
          fee: 54248
        }, 
      ]
    }
  ]
},
{
  continent:'Europe',
  popSum: 3432423,
  popFee: 3432,
  country: [
    {
      country: 'gb',
      popSum: 32432,
      popFee: 564,
      data: [
        {
          continent:'Europe',
          country:'ge',
          state:'mc',
          population: 4268986,
          fee: 97333
        },
      ]
    },
    {
      country: 'it',
      popSum: 89332,
      popFee: 8932,
      data: [
        {
          continent:'Europe',
          country:'ge',
          state:'mc',
          population: 4268986,
          fee: 97333
        },
      ]
    },
  ]
},
]

Is this possible using the _groupBy and _sumBy functions in lodash, or some other way?

CodePudding user response:

This seems to achieve what you want :

const data = [
    {
        continent:'North America',
        country:'us',
        state:'ma',
        population: 1567986,
        fee: 24343
    },
    {
        continent:'North America',
        country:'us',
        state:'tx',
        population: 7567986,
        fee: 119343
    },
    {
        continent:'North America',
        country:'ca',
        state:'on',
        population: 467986,
        fee: 3343
    },
    {
        continent:'North America',
        country:'ca',
        state:'qb',
        population: 1268986,
        fee: 54248
    },
    {
        continent:'Europe',
        country:'ge',
        state:'mc',
        population: 4268986,
        fee: 97333
    },
    {
        continent:'Europe',
        country:'it',
        state:'fl',
        population: 978353,
        fee: 248
    },
]


const groupBy = (rawArray, groupbyFields, sumFields) => {
    const field = groupbyFields[0]
    const fieldValues = new Set()

    rawArray.forEach(e => fieldValues.add(e[field]))

    const array = []

    for (const fieldValue of fieldValues.values()) {
        let data = rawArray.filter(e => e[field] === fieldValue)

        if (groupbyFields.length > 1) {
            data = groupBy(data, groupbyFields.slice(1), sumFields)
        }

        const obj = { data }

        sumFields.forEach(sumField => {
            const sumFieldName = `${ sumField }Sum`
            obj[sumFieldName] = data.map(e => e[sumField] ?? e[sumFieldName] ?? 0).reduce((g, c) => g   c, 0)
        })

        obj[field] = fieldValue
        array.push(obj)
    }

    return array
}

console.log(JSON.stringify(groupBy(data, [ 'continent', 'country' ],[ 'population', 'fee' ]), null, '  '))

  • Related