CustardBun
CustardBun

Reputation: 3867

More functional or concise way of aggregating this data in Javascript?

I have a dataset that looks like this:

dataPoints = [ 
   { "x": 1, "y": 10, "someOtherUnrelatedThing": 15, "someOtherThing": 12}, 
   { "x": 2, "y": 20, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
   { "x": 2, "y": 20, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
   { "x": 3, "y": 10, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
   { "x": 3, "y": 20, "someOtherUnrelatedThing": 15, "someOtherThing": 12},

   ...
]

Desired output:

[
   { "x": 1, "y": 10, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
   { "x": 2, "y": 40, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
   { "x": 3, "y": 30, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
]

I wrote this to do basically what I want. What I'm trying to do is for all datapoints that have the same x, add up the values of y and make a single datapoint out of them. For the sake of this use case, we can either assume the "other thing" fields are going to be equal or that it doesn't matter if they differ (we can just take either datapoint's values for those):

        let aggregatedDataPoints = new Map();
        for (let dataPoint of dataPoints) {
            if (aggregatedDataPoints.has(dataPoint.key.toString())) {
                let prevPoint = aggregatedDataPoints.get(dataPoint.key.toString());
                let newPoint = {...prevPoint};
                newPoint.y = prevPoint.y + dataPoint.y;
                aggregatedDataPoints.set(dataPoint.key.toString(), newPoint);
            } else {
                aggregatedDataPoints.set(dataPoint.key.toString(), dataPoint);
            }
        }
        return aggregatedDataPoints;

However, I'm not really happy with this solution as it looks a bit messy. I'm trying to see if I can utilize a more functional or concise approach to do the same thing, but I'm having trouble getting anything more elegant to work.

Any help would be appreciated!

Upvotes: 1

Views: 34

Answers (3)

OFRBG
OFRBG

Reputation: 1778

A slightly more idiomatic approach might look like this:

const result = {};
dataPoints.forEach(({x, y: currentY, ...other}) => {
  const y = currentY + (result[x]?.y || 0)

  result[x] = { ...other, x, y }
})

or if you don't like mutating properties:

Object.values(
  dataPoints.reduce((result, {x, y: currentY, ...other}) => {
    const y = currentY + (result[x]?.y || 0)

    return {
      ...result,
      [x]: { ...other, x, y }
    }
  }, {})
)

Upvotes: 1

dave
dave

Reputation: 64687

You could use .reduce:

dataPoints = [ 
   { "x": 1234, "y": 14, "someOtherUnrelatedThing": 15, "someOtherThing": 12}, 
   { "x": 1235 },
   { "x": 1234, "y": 34, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
].reduce((carry, current) => {
    const existing = carry.find(el => el.x === current.x);
    if (!existing) carry.push(current);
    else if (existing.y && current.y) existing.y += current.y;
    return carry;
}, [])

console.log(dataPoints);

Or, using a Map:

const m = new Map();

[ 
   { "x": 1234, "y": 14, "someOtherUnrelatedThing": 15, "someOtherThing": 12}, 
   { "x": 1235 },
   { "x": 1234, "y": 34, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
].forEach(el => {
    const existing = m.get(el.x);
    if (existing) existing.y += el.y;
    else m.set(el.x, el);
});


console.log(Object.fromEntries(m.entries()));

Upvotes: 1

Alberto
Alberto

Reputation: 12949

you can just use a object to map those values (if only need the pair [x,y]):

let dataPoints = [ 
    { "x": 1234, "y": 14, "someOtherUnrelatedThing": 15, "someOtherThing": 12}, 
    { "x": 1235, },
    { "x": 1234, "y": 34, "someOtherUnrelatedThing": 15, "someOtherThing": 12},
];
let res = {};
dataPoints.forEach(el => res[el.x] = +el.y + (res[el.y] || 0)  )
console.log(res);

Upvotes: 1

Related Questions