首页 > 解决方案 > Iterating over an array of objects, summing values with the same index, and returning a new array of objects

问题描述

I have an array of objects, something like this:

const data = [                 // array1
  [{x: 1}, {y:2}, {z:3}], 
  [{x: 1}, {y:2}, {z:3}],
  [{x: 1}, {y:2}, {z:3}]
],[                            // array2
  [{x: 1}, {y:2}, {z:3}], 
  [{x: 1}, {y:2}, {z:3}],
  [{x: 1}, {y:2}, {z:3}]
]

What needs to be accomplished is summing x from the array1 with x from the array2 that have the same index. Same goes for y and z. The final result should be a new array of objects containing the summed values.

Something like this:

[
  [{totalXOne: 2}, {totalYOne: 4}, {totalZOne: 6}],
  [{totalXTwo: 2}, {totalYTwo: 4}, {totalZTwo: 6}],
  [{totalXThree: 2}, {totalYthree: 4}, {totalZThree: 6}],
]

Note: All arrays are the same length, and if a value is missing it will be replaced with 0)

I found something nice on MDN, but it's summing all x, y, z values, and it's returning single summed values, like this:

let initialValue = 0;
let sum = [{x: 1}, {x:2}, {x:3}].reduce(function(accumulator,currentValue) {
    return accumulator + currentValue.x;
}, initialValue)

Output:

[
  [{totalX: 3}, {totalY: 6}, {totalZ: 9}],  // this is not what I need
]

Is there any way I can achieve this?

UPDATE

I'm receiving JSON from another source. It contains a property called allEmpsData mapping over it I get the necessary salaryDataand mapping over it I'm getting the NET|GROSS|TAX data.

let allReports = [];

    setTimeout(() => {

        allEmpsData.map(x => {
            let reports = {};

            let years = [];
            let months = [];

            let netArr = [];
            let grossArr = [];
            let mealArr = [];
            let taxArr = [];
            let handSalaryArr = [];

            x.salaryData.map(y => {
                years.push(y.year);
                months.push(y.month);
                    netArr.push(y.totalNetSalary);
                    grossArr.push(y.bankGrossSalary);
                    mealArr.push(y.bankHotMeal);
                    taxArr.push(y.bankContributes);
                    handSalaryArr.push(y.handSalary);
                })
                reports.year = years;
                reports.month = months;
                reports.net = netArr;
                reports.gross = grossArr;        
                reports.meal = mealArr;        
                reports.taxesData = taxArr;        
                reports.handSalaryData = handSalaryArr;
                allReports.push(Object.assign([], reports));
        });
    }, 1000);

As I can tell, everything is working as it should, but the truth is,. I don't know any better. Then here goes the magic:

setTimeout(() => {
    result = allReports.reduce((r, a) =>
         a.map((b, i) =>
           b.map((o, j) =>
             Object.assign(...Object
              .entries(o)
               .map(([k, v]) => ({ [k]: v + (getV(r, [i, j, k]) || 0) }))
                    )
                )
            ),
            undefined
        );
            console.log(result);
        }, 1500);

... and it returns an empty array in the node console, but if I console.log any other property from the updated code above, it's there. Any suggestions?

标签: javascriptarraysnode.jsobjectreduce

解决方案


这是一种使用中间 ES6 的函数式编程方式Map

const data = [[[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}]], [[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}],[{x: 1}, {y:2}, {z:3}]]];

const result = data[0].map( (arr, i) => Array.from(data.reduce( (acc, grp) => (
    grp[i].forEach( o =>
        Object.entries(o).forEach( ([k, v]) => acc.set(k, (acc.get(k) || 0) + v)) 
    ), acc
), new Map), ([k, v]) => ({ [k]: v })) ); 

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

解释

为了便于解释,让我们就一些条款达成一致:

我们有输入(一个数组),由groups组成。每个组都是由rows组成的数组。每对象组成,每个对象都有一个属性/值对。

输出没有组级别,但它有rows,同样由objects组成,每个都有一个属性/值对。

因此,使用这些术语让我们看一下代码:

由于输出数组中的行数等于任何组中的行数,因此映射第一组的行似乎是一个好的开始,即 like data[0].map

对于输出中的每一行,我们需要求和,并且reduce是该工作的一个很好的候选函数,所以我们调用data.reduce. 对于该reduce调用的初始值,我传递了一个空的Map. 目的是用 key-sum 对填充该 Map。稍后我们可以将该 Map 分解为单独的对象,每个对象仅具有这些键/总和对之一(但这是稍后使用的)。

因此,reduce从 a 开始Map并遍历组。我们需要从每个组中取出第i来找到必须“添加”的对象。所以我们采取行grp[i]

对于该行中的每个对象,我们使用 . 获取属性名称和值Object.entries(o)。事实上,该函数返回一个数组,因此我们在forEach知道实际上只会迭代一次的情况下对其进行迭代,因为实际上只有一个属性。现在我们有了 key ( k) 和 value v。我们处于输入结构的最深层次。这里我们调整累加器。

acc.get(k)我们可以知道我们已经为特定键(例如“x”)积累了什么。如果我们还没有任何东西,它会通过做 0 初始化|| 0。然后我们将当前值添加v到它,并将该总和存储回 Map with acc.set(k, ....)。使用逗号运算符,我们将其acc返回到reduce实现(我们可以在return这里使用,但逗号运算符更简洁)。

所以 Map 得到每个键的所有总和。通过Array.from我们可以迭代每个键/和对,并使用回调参数,将该对转换为适当的小对象(使用{ [k]: v })。该[k]表示法在 ES6 中也是一个新奇事物——它允许在对象字面量中使用动态键名。

所以...Array.from返回一个小对象数组,每个对象都有一个总和。该数组表示要输出的一行。该map方法创建输出中所需的所有行。


推荐阅读