我有一个对象数组,表示类似于系统硬盘文件夹结构的东西。数据表示在对象的嵌套数组中。每个对象都是一个文件夹,其中包含一些文件和文件夹。我确切地知道直接放置在每个节点中的文件大小的总和。但我不知道一个节点包含其子节点占用了多少空间。
以下是数据示例:
[{
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);
只是一个注释:
假设你的大小单位是字节:
1
PB是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);