获取具有无限深度的对象的嵌套数组的属性之和



我有一个对象数组,表示类似于系统硬盘文件夹结构的东西。数据表示在对象的嵌套数组中。每个对象都是一个文件夹,其中包含一些文件和文件夹。我确切地知道直接放置在每个节点中的文件大小的总和。但我不知道一个节点包含其子节点占用了多少空间。

以下是数据示例:

[{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: ...,
},
.
.
.
}]
}]
}]

我尝试使用Array.reduce,但它对我没有帮助,因为它只迭代对象的直接子级,而不是嵌套数组的n级别。类似这样的东西:

const parentSize = typeof parent['total'] !== 'undefined' ? parent['total'] : parent.filesSize;
parent['total'] = children.reduce((sum, child) => {
return sum + (typeof child.total !== 'undefined' ? child.filesSize : child.total);
}, parentSize);

我错过了什么?

使用reduce是可以的,但您需要:

  • 递归,以便在需要时也对子级调用此reduce
  • 以总是分配给CCD_ 4。检查它是否已经存在是没有用的,因为它不会。如果它确实存在,那么依赖它是有风险的,因为你不知道在添加该属性后是否对树进行了修改

let tree = [{id: 1,name: 'root',filesSize: 123456,children: [{id: 2,name: 'child 1',filesSize: 789654,}, {id: 3,name: 'child 2',filesSize: 321123,children: [{id: 4,name: 'child 3 - 1',filesSize: 88888,}, {id: 5,name: 'child 3 - 2',filesSize: 99999,children: [{id: 99999,name: 'child m - n',filesSize: 1234}]}]}]}];
let totalSize = tree.reduce(function recur(sum, child) {
return sum + (child.total = (child.children ?? []).reduce(recur, child.filesSize));
}, 0);
// The returned size can be interesting when the top level has
// multiple entries (which is not the case in your example): 
console.log(totalSize);
console.log(tree);

只是一个注释:

假设你的大小单位是字节:

1PB是1_000_000_000_000_000字节(或1e3 ** 5(:所以你的和(它是JSnumber(可以安全地容纳Number.MAX_SAFE_INTEGER / (1e3 ** 5),它大约是9。如果您预计您的大小将接近9 PB,则应该使用BigInt类型而不是number来存储总和。

其他答案演示了递归方法,这些方法简洁优雅,但如果层次结构过多(堆栈溢出!(,则会失败。

这里有一个迭代的替代方案:

TS游乐场

function getTotal (containerNode) {
let total = 0;
const stack = [containerNode];
while (stack.length > 0) {
const node = stack.pop();
// Already available, skip children
if (typeof node.total === 'number') {
total += node.total;
continue;
}
total += node.filesSize;
if (!node.children?.length) continue;
for (const child of node.children) stack.push(child);
}
return total;
}

// Example usage:
const nodes = [
{
id: 1,
name: 'root',
filesSize: 123456,
children: [
{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
total: 321123 + 88888 + 99999 + 34523,
children: [
{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [
{
id: 99999,
name: 'child m - n',
filesSize: 34523,
},
// ...
],
},
],
},
],
},
];
function test (containerNode, expectedTotal) {
const actual = getTotal(containerNode);
return `${containerNode.id}: ${actual === expectedTotal ? 'pass' : 'fail'}`;
}
const results = [
test(
nodes[0].children[1].children[1].children[0],
34523,
),
test(
nodes[0].children[1].children[1],
99999 + 34523,
),
test(
nodes[0].children[1].children[0],
88888,
),
test(
nodes[0].children[1],
321123 + 88888 + 99999 + 34523,
),
test(
nodes[0].children[0],
789654,
),
test(
nodes[0],
123456 + 789654 + 321123 + 88888 + 99999 + 34523,
),
];
for (const result of results) console.log(result);

我自由地将对象从上到下加倍,这样它就在根上分裂了四层。

/**@function
* @Name totalFileSize
* @Description - This function accepts array of objects and any nested array of 
* objects as well. It will extract a number value of a given key 
* and recursively search all sub-arrays as well. It will return the sum of all 
* extracted values.
* @param {array<object>} objArr - An array of objects
* @param {string} prop - A key/property with a number value  
* @returns {number} - A sum of all extracted number values
*/

传递对象数组和要获得其总和的属性。

//                data↘️         ↙️"filesSize"
const totalFileSize = (objArr, prop) =>

接下来,通过.map()运行每个对象,然后使用Object.entries()将每个对象转换为成对的数组。成对阵列是一个2D阵列,其中子阵列由两个元素组成:
[[key, value], [key, value],...]

objArr.map(obj => Object.entries(obj)
// [{A: 1, B: 'z'}, {...},...] => [["A", 1], ["B", "z"], [...],...]

现在我们有了一个2D阵列开关,使用array方法更容易操作。方法的逻辑选择是.reduce(),因为我们需要一个单独的结果。第二个参数表示当前迭代的元素,注意它被分解为一个数组(特别是一个键/值对(。在这种形式中,我们可以很容易地构造更细粒度的表达式。

//          ↙️On each iteration, this value accumulates when a match is made
.reduce((sum, [key, val]) => 
//   property↗️       ↖️value      

被破坏的值允许我们以更简洁和直接的方式编写。此函数的核心是作为三元条件的if/else if/else语句。

/* if the current key matches prop ("filesSize"), then add sum and the current value 
(val).*/
key == prop ? sum + parseInt(val) :
/* Note: the value needed to be converted to a number because the + operator coerced
it into a String.*/

现在转到三元运算符的第二个(或else if(。我注意到在其他答案中有一个运行的total属性,所以这里是获取它的位置:obj.total = sum + parseInt(totalFileSize(val, prop))。它是sum和每个递归调用的返回值。我不认为应该为每个对象计算总数(您得到的只是filesSize的重复值(。

/* else if val is an array recursively invoke totalFileSize() and pass val and 
prop in...*/
Array.isArray(val) ? obj.total = sum + parseInt(totalFileSize(val, prop)) : 
// ...then convert it's return into a number and add it to sum

否则将0添加到总和中。任何不匹配的值都不再令人担忧。

0, 0))
// The last zero is the initial value of `.reduce()`

最后要做的是清除返回值。数据增加了一倍,这样我就可以展示处理多个分支的功能。此时,"filesSize"中的所有数字现在都是数组中的两个总数。最后一步将所有分支相加。

.reduce((total, current) => total + current);

const data =[{"id":1,"name":"root","filesSize":123456,"children":[{"id":2,"name":"child 1","filesSize":789654},{"id":3,"name":"child 2","filesSize":321123,"children":[{"id":4,"name":"child 3 - 1","filesSize":88888},{"id":5,"name":"child 3 - 2","filesSize":99999,"children":[{"id":6,"name":"child 4 - 1","filesSize":325941}]}]}]},
{"id":7,"name":"root","filesSize":654321,"children":[{"id":8,"name":"child 1","filesSize":978855},{"id":9,"name":"child 2","filesSize":123321,"children":[{"id":10,"name":"child 3 - 1","filesSize":11111},{"id":11,"name":"child 3 - 2","filesSize":66666,"children":[{"id":12,"name":"child 4 - 1","filesSize":18756}]}]}]}];
const totalFileSize = (objArr, prop) =>
objArr.map(obj => Object.entries(obj)
.reduce((sum, [key, val]) => 
key == prop ? sum + parseInt(val) : 
Array.isArray(val) ?  obj.total = sum + parseInt(totalFileSize(val, prop)) : 
0, 0))
.reduce((total, current) => total + current);
console.log(`Input array is doubled at the root so this the total sum of 12 objects not 6 like OP example.`);
console.log(totalFileSize(data, "filesSize"));
console.log(data);

const data = [{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: 12345,
}]
}]
}]
}];
function calculateTotals(d) {
let total = d.filesSize;
if (d.children)
d.children.forEach(c => total += calculateTotals(c));
return d.total = total;
}
data.forEach(d => calculateTotals(d));
console.log(data);

最新更新