2

How do I reduce this object array and and map it to a new array?

My data:

var objArray = 
  [{
    state: 'NY',
    type: 'A',
    population: 100
  },
  {
    state: 'NY',
    type: 'A',
    population: 300
  },
  {
    state: 'NY',
    type: 'B',
    population: 200
  },
  {
    state: 'CA',
    type: 'A',
    population: 400
  },
  {
    state: 'CA',
    type: 'A',
    population: 400
  }];

If an entry has the same state AND type I need to combine it into a single entry and sum their populations.

Finally I need to map it to an array in this format.

 var outputArray = [ ['A', 'NY', 400 ], ['B', 'NY', 200], ['A', 'CA', 800] ]
jm22
  • 131
  • 3
  • 9
  • use [Array#reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=b) and [Array#map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Map?v=b) – Jaromanda X Oct 11 '17 at 22:39
  • 3
    You need to show what you have tried. Stackoverflow isn't a free code writing or tutorial service. The objective here is to help you fix **your code** – charlietfl Oct 11 '17 at 22:41

5 Answers5

6

Well, first you'd want to reduce it. This can be done like so...

objArray.reduce((prev, obj) => {
    if(1 + (indx = prev.findIndex(oldObj => oldObj.type === obj.type && oldObj.state === obj.state))) {
        prev[indx].population += obj.population;
    } else {
        prev.push({...obj})
    }
    return prev;
}, [])

This takes the array being gathered, and modifies it in some way and returns it in the reduce callback. It will either modify an existing value's population, if it can find one with the correct state and type; or it will append a new object to the end of the array.

Now you need to map it.

.map(obj => [ obj.type, obj.state, obj.population ])
Max
  • 1,325
  • 9
  • 20
  • note, that this code mutates the original array too – Jaromanda X Oct 11 '17 at 22:57
  • 1
    It mutates `prev`, but that's different each iteration and is intentionally mutated. I don't think `objArray` is, though -- right? – Max Oct 11 '17 at 22:58
  • `console.log(objArray)` - you'll see that it has changed population in items 0 and 3 – Jaromanda X Oct 11 '17 at 22:59
  • May I ask, why is this the case? I'm not modifying `objArray` as far as I can tell – Max Oct 11 '17 at 23:06
  • 1
    you push `obj` - objects are pass by reference - so, when you update `prev[0].population`, it's the same object as `objArray[0]`, so `objArray[0].population` is the same property as `prev[0].population` – Jaromanda X Oct 11 '17 at 23:06
  • @JaromandaX is `{...obj}` a way to clone/deepClone an object? – Igor Soloydenko Oct 11 '17 at 23:09
  • @IgorSoloydenko Pretty much. `...` will spread the object out into key-value pairs. Then when you wrap this around an object literal, you're creating a new object with the same key value pairs. Same with arrays: `[...arr]`. This isn't deep, though, to my knowledge. – Max Oct 11 '17 at 23:10
  • Oh, it's a _shallow clone_. I was hoping I found an elegant _deep clone_. :( – Igor Soloydenko Oct 11 '17 at 23:11
  • @Max Why does `prev.push({obj})` cause prev to become `prev = { obj:{}, obj:{}, obj:{} }`. What's going on there? You use the spread operator to avoid that but I don't understand what causes the name `obj` to be pushed. If I push, say, `var testObj = { key : 'thing' }`, it works without the spread operator. Thanks – jm22 Oct 12 '17 at 03:13
  • 1
    @jm22 When you put a key without a value in a JS object literal, it's a shorthand for `obj: obj`, i.e. pushing itself. You have to use the spread op to push its contents, not itself – Max Oct 12 '17 at 07:10
1

I would recommend using the lodash package (it is an extremely common package in Javascript applications). It adds a lot of great functions for manipulating arrays. This post explains how to sum values on groups. You will need to modify this answer slightly to account for your two parameters, which you can do by changing the groupBy command to this:

_.groupBy(objArray, function(val){
    return val.state + "#" + val.type
})
Derek Brown
  • 4,232
  • 4
  • 27
  • 44
1

You could try something like this:

var arr = Object.keys(objArray).map(function (key) { return Object.keys(objArray[key]).map(function (key) { return [[objArray[0].state, objArray[0].type, objArray[0].population],[objArray[1].state, objArray[1].type, objArray[1].population]]}); })[0][0];
Nothing
  • 99
  • 12
1

If you know state and type will never have a certain character such as '_' you can make a key out of state and type such as 'NY_A' - a little like a composite key in a DB. Then you just create an object with these keys, add the populations and then pull them apart into an array:

Object.entries(
    objArray.reduce((acc,curr) => (
    acc[curr.type + '_' + curr.state] = curr.population + (acc[curr.type + '_' + curr.state] || 0)
    , acc), {}))
.map(item => [...item[0].split('_'), item[1]])
Mark
  • 90,562
  • 7
  • 108
  • 148
1
const _ = require('lodash')

const input = [
  { state: 'NY', type: 'A', population: 100 },
  { state: 'NY', type: 'A', population: 300 },
  { state: 'NY', type: 'B', population: 200 },
  { state: 'CA', type: 'A', population: 400 },
  { state: 'CA', type: 'A', population: 400 }
]

const expected = [
  ['A', 'NY', 400],
  ['B', 'NY', 200],
  ['A', 'CA', 800]
]

const output = _(input)
  .groupBy(({ state, type }) => [state, type].join(':'))
  .mapValues((states) => states.reduce((total, { population }) => total + population, 0))
  .map((population, stateType) => {
    const [state, type] = stateType.split(':')
    return [type, state, population]
  })
  .value()

console.log(expected)
console.log(output)
Leon Li
  • 337
  • 3
  • 4