1

I have have map of Object of the following type (example data below)

{[date:string]:Array<{[type:string]:{amount:number, price:number}}>}

And I need to reduce the array to a single object of amount sum and price averaged

I have read a couple of post on how to reduce data over an object like

How to map and reduce over an array of objects?

Javascript reduce on array of objects

In my case the "type" is a ENUM of ["PV","BHKW","FALLBACK"] but it could be extended. Apart from writing declarative for in loops, i can't figure out how to use array.reduce to accomplish this more elegantly.

Any suggestions?

{
    "2018-08-01T11:00:00+02:00": [
        {
            "BHKW": {
                "amount": 131,
                "price": 85
            },
            "FALLBACK": {
                "amount": 84,
                "price": 1
            }
        },
        {
            "BHKW": {
                "amount": 307,
                "price": 58
            },
            "PV": {
                "amount": 4,
                "price": 60
            }
        }
    ],
    "2018-08-01T12:00:00+02:00": [
        {
            "BHKW": {
                "amount": 288,
                "price": 59
            },
            "PV": {
                "amount": 742,
                "price": 73
            }
        },
        {
            "BHKW": {
                "amount": 250,
                "price": 49
            },
            "PV": {
                "amount": 507,
                "price": 98
            }
        },
        {
            "PV": {
                "amount": 368,
                "price": 22
            },
            "BHKW": {
                "amount": 357,
                "price": 73
            }
        },
        {
            "FALLBACK": {
                "amount": 135,
                "price": 62
            },
            "BHKW": {
                "amount": 129,
                "price": 93
            }
        }
    ],
Han Che
  • 8,239
  • 19
  • 70
  • 116

3 Answers3

2

You can use lodash#mapValues to transform each value of the object data object. To combine all objects with the same key values, you can use lodash#mergeWith with the callback testing if the keys being transformed will be evaluated.

const result = _.mapValues(data, collection => 
  _.mergeWith({}, ...collection, (a = 0, b = 0, key) =>
    ['amount', 'price'].includes(key)
      ? a + b
      : void 0 // is equivalent to undefined to retain current value
  ));

const data = {
    "2018-08-01T11:00:00+02:00": [
        {
            "BHKW": {
                "amount": 131,
                "price": 85
            },
            "FALLBACK": {
                "amount": 84,
                "price": 1
            }
        },
        {
            "BHKW": {
                "amount": 307,
                "price": 58
            },
            "PV": {
                "amount": 4,
                "price": 60
            }
        }
    ],
    "2018-08-01T12:00:00+02:00": [
        {
            "BHKW": {
                "amount": 288,
                "price": 59
            },
            "PV": {
                "amount": 742,
                "price": 73
            }
        },
        {
            "BHKW": {
                "amount": 250,
                "price": 49
            },
            "PV": {
                "amount": 507,
                "price": 98
            }
        },
        {
            "PV": {
                "amount": 368,
                "price": 22
            },
            "BHKW": {
                "amount": 357,
                "price": 73
            }
        },
        {
            "FALLBACK": {
                "amount": 135,
                "price": 62
            },
            "BHKW": {
                "amount": 129,
                "price": 93
            }
        }
    ]
};

const result = _.mapValues(data, collection => 
  _.mergeWith({}, ...collection, (a = 0, b = 0, key) =>
    ['amount', 'price'].includes(key)
      ? a + b
      : void 0 // is equivalent to undefined to retain current value
  ));
  
console.log(result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
1

solved it!

var x = _.reduce(
    list,
    (acc, item) => {
        for (const key in item) {
            if (!acc[key]) {
                acc[key] = { price: 0, amount: 0 };
            }

            if (item[key] && item[key].price) {
                acc[key].price += item[key].price;
            }

            if (item[key] && item[key].amount) {
                acc[key].amount += item[key].amount;
            }
        }
        return acc;
    },
    {}
);
Han Che
  • 8,239
  • 19
  • 70
  • 116
0

Another way (although not as elegant as the _.merge approach already posted) is with _.mapValues/_.reduce/_.assignWith or _.extendWith:

var data = { "2018-08-01T11:00:00+02:00": [{ "BHKW": { "amount": 131, "price": 85 }, "FALLBACK": { "amount": 84, "price": 1 } }, { "BHKW": { "amount": 307, "price": 58 }, "PV": { "amount": 4, "price": 60 } } ], "2018-08-01T12:00:00+02:00": [{ "BHKW": { "amount": 288, "price": 59 }, "PV": { "amount": 742, "price": 73 } }, { "BHKW": { "amount": 250, "price": 49 }, "PV": { "amount": 507, "price": 98 } }, { "PV": { "amount": 368, "price": 22 }, "BHKW": { "amount": 357, "price": 73 } }, { "FALLBACK": { "amount": 135, "price": 62 }, "BHKW": { "amount": 129, "price": 93 } } ] }

var result =
  _.mapValues(data, (x) =>
    _.reduce(x, (r, c) =>
      _.assignWith(r, c, (o, s) =>
        o && s ? ({ amount: o.amount + s.amount, price: o.price + s.price }) : {...o, ...s})
    )
  )

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Akrion
  • 18,117
  • 1
  • 34
  • 54