js树结构所有叶子节点的值相加等于父节点的值?

const tableData = [
  {
    id: 1,
    title: '园区1',
    count1: 0,
    count2: 0,
    count3: 0,
    num1: 0,
    num2: 0,
    num3: 0,
    num4: 0,
    children: [
      {
        id: 11,
        parentId: 1,
        title: '集团1',
        count1: 0,
        count2: 0,
        count3: 0,
        num1: 0,
        num2: 0,
        num3: 0,
        num4: 0,
      },
      {
        id: 12,
        parentId: 1,
        title: '集团2',
        count1: 0,
        count2: 0,
        count3: 0,
        num1: 0,
        num2: 0,
        num3: 0,
        num4: 0,
        children: [
          {
            id: 11,
            parentId: 12,
            title: '公司1',
            count1: 0,
            count2: 0,
            count3: 0,
            num1: 0,
            num2: 0,
            num3: 0,
            num4: 0,
          },
          {
            id: 11,
            parentId: 12,
            title: '公司2',
            count1: 0,
            count2: 0,
            count3: 0,
            num1: 0,
            num2: 0,
            num3: 0,
            num4: 0,
          },
        ],
      },
    ],
  },
  {
    id: 2,
    title: '园区2',
    count1: 0,
    count2: 0,
    count3: 0,
    num1: 0,
    num2: 0,
    num3: 0,
    num4: 0,
    children: [
      {
        id: 21,
        parentId: 2,
        title: '集团1',
        count1: 0,
        count2: 0,
        count3: 0,
        num1: 0,
        num2: 0,
        num3: 0,
        num4: 0,
      },
      {
        id: 22,
        parentId: 2,
        title: '集团2',
        count1: 0,
        count2: 0,
        count3: 0,
        num1: 0,
        num2: 0,
        num3: 0,
        num4: 0,
        children: [
          {
            id: 221,
            parentId: 22,
            title: '公司1',
            count1: 0,
            count2: 0,
            count3: 0,
            num1: 0,
            num2: 0,
            num3: 0,
            num4: 0,
          },
          {
            id: 222,
            parentId: 22,
            title: '公司2',
            count1: 0,
            count2: 0,
            count3: 0,
            num1: 0,
            num2: 0,
            num3: 0,
            num4: 0,
          },
        ],
      },
    ],
  },
]

计算规则
count1要等于所有子集的 count1 相加
count2要等于所有子集的 count2 相加
count3要等于所有子集的 count3 相加
num1要等于所有子集的 num1 相加
num2要等于所有子集的 num2 相加
num3要等于所有子集的 num3 相加
...依此类推

这是demo,麻烦务必请看一眼demo,感觉看页面清晰一点,也能表达明白我的意思

https://element-plus.run/#eyJ...

实现了,但是我是一层一层写的,帮忙看下能否用递归,不然层级是不固定的
https://element-plus.run/#eyJ...

感谢各位提供的思路,已经实现了,谢谢

阅读 2.2k
4 个回答
2023-02-22 更新

回答的时候魔怔了,两种办法都有点奇怪。实际上只从叶到根依次计算的话,只需要重算每个节点当时子节点的数据之和就行了,不需要全算,也不需要算差值。

function reCalculate(node) {
    if (!node) { return; }

    const resetNode = (node) => {
        node.count1 = 0;
        node.num1 = 0;
        return node;
    };

    if (node.children?.length) {
        node.children.reduce((parent, it) => {
            parent.count1 += it.count1;
            parent.num1 += it.num1;
            return parent;
        }, resetNode(node));
    }

    reCalculate(findNode(node.parentId));
}

其中 findNode 可以参考下面的原回答(mapfindInTree 都可以)。


另外,针对作者的原回答(总算是打开了 Demo),做了一些优化处理:我认为 findRow() 应该是找一个节点,所以改了下。另外,calc() 实际上是在 calcParent(),所以我改了个名字。然后 calc 可以优化成循环,不需要使用递归。

修改后的代码:

const findRow = (data: any[], parentId: number) => {
  // 先在当前节点集合中查找
  const node = data.find(it => it.id === parentId);
  if (node) { return node; }

  // 如果没有就在各自的子节点中去查找(递归)
  for (let it of data) {
    if (!it.children?.length) { continue; }
    const found = findRow(it.children, parentId);
    if (found) { return found; }
  }
  return undefined;
}

// 使用新 findRow 的 calcParent(原 calc)
const calcParent = (row: any, field: string) => {
  const { parentId } = row;
  const curRow = parentId && findRow(tableData, parentId);
  if (!curRow) { return; }
  curRow[field] = (curRow.children ?? [])
      .reduce((sum, it) => sum + it[field], 0);
  calc(curRow);
};

// 去掉了递归
const calc = (row: any, field: string) => {
  // calcParent(row, field); 
  // 下面是非递归算法
  let parentId = row.parentId;
  while (parentId) {
    row = findRow(tableData, parentId);
    if (!row) { return; }
    row[field] = (row.children ?? []).reduce((sum, it) => sum + it[field], 0);
    parentId = row.parentId;
  }
}

修改后的 Demo 在这里


下面是原回答

应该需要从上往下递归重算,因为虽然改的只是一叶结点,回溯上去只有一条路径,但是每个父节点上的数据都是其所有子结点数据之和,必须要拿所有子节点的数据来重算。

function reCalculate(node) {
    if (Array.isArray(node)) { node.forEach(reCalculate); }

    if (node.children?.length) {
        [node.count1, node.num1] = Array(2).fill(0);
        node.children.reduce((r, it) => {
            reCalculate(it);
            r.count1 += it.count1;
            r.num1 += it.num1;
            return r;
        }, node);
    }
}

但是,如果能得到改变量的话,这个过程就可以简化了,只需要按 parentId 一直回溯,使用改变量来修改每个父节点的数据即可。

function reCalculateDelta(root, delta) {
    // 建立映射表(方便按 parentId 查),可提前准备好
    const map = (() => {
        const buildMap = (nodes, map = {}) => {
            nodes.forEach(it => {
                map[it.id] = it;
                if (it.children?.length) {
                    buildMap(it.children, map);
                }
            });
            return map;
        };
        return buildMap(Array.isArray(root) ? root : [root]);
    })();

    // 从 delta 对象里拿到 parentId(叶节点已经改过了不需要处理了)
    // delta 里包含每一个数据的变化值(不是终值),如果变小就是负数
    let { parentId } = delta;

    // 循环向上查找处理完所有 parent node
    while (parentId) {
        const node = map[parentId];
        if (node) {
            node.count1 += delta.count1;
            node.num1 += delta.num1;
        }
        parentId = node?.parentId;
    }
}

试验代码:为了找一个叶节点出来修改,所以写了个简易的 find 函数

function findInTree(nodes, predicate) {
    return nodes.find(predicate)
        ?? (() => {
            for (let node of nodes) {
                if (!node.children?.length) { continue; }
                const r = findInTree(node.children, predicate);
                if (r) { return r; }
            }
        })();
}
const target = findInTree(tableData, it => it.id === 221);
[target.count1, target.num1] = [3, 5];

// case 2
reCalculateDelta(tableData, { parentId: 22, count1: 3, num1: 5 });

// case 1
// reCalculate(tableData);
console.dir(tableData, { depth: null });

最近写了一篇关于这种计算的博客:树,计算父节点的值


递归一下就好了,每次判断一下 children 是否有长度。
每次返回一个对象,里面包含 count1/2/3num1/2/3,依次累加返回就是需要的结果了。

但是也不知道你要的 Count 的是否只要非0层的。还是说每层都要按照子级的累计。

参考了一下下面大佬,原回答多了一倍循环。优化性能。
从叶子全算到父级。
image.png

const tableData = [{id: 1, title: '园区1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 11, parentId: 1, title: '集团1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 12, parentId: 1, title: '集团2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 11, parentId: 12, title: '公司1', count1: 9, count2: 10, count3: 5, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 11, parentId: 12, title: '公司2', count1: 1, count2: 20, count3: 55, num1: 0, num2: 0, num3: 0, num4: 0,},],},],}, {id: 2, title: '园区2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 21, parentId: 2, title: '集团1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 22, parentId: 2, title: '集团2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 221, parentId: 22, title: '公司1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 222, parentId: 22, title: '公司2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,},],},],},]
// 深度遍历,callback 每个对象的回调参数
let treeCalc = (obj = {children: []}, callback = (parent, node) => {
}) => {
  obj && obj.children && obj.children.forEach(item => {
    treeCalc(item, callback);
    callback && callback(obj, item);
  })
  return obj;
}
// 顶级对象
let rootData = {
  count1: 0,
  count2: 0,
  count3: 0,
  num1: 0,
  num2: 0,
  num3: 0,
  num4: 0,
  children: tableData
}
// 根据统计叶子节点数量赋值到父级节点数量
treeCalc(rootData, (parent, node) => {
    parent.count1 += node.count1;
    parent.count2 += node.count2;
    parent.count3 += node.count3;
    parent.num1 += node.num1;
    parent.num2 += node.num2;
    parent.num3 += node.num3;
    parent.num4 += node.num4;
})

原回答
image.png

const tableData = [{id: 1, title: '园区1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 11, parentId: 1, title: '集团1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 12, parentId: 1, title: '集团2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 11, parentId: 12, title: '公司1', count1: 9, count2: 10, count3: 5, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 11, parentId: 12, title: '公司2', count1: 1, count2: 20, count3: 55, num1: 0, num2: 0, num3: 0, num4: 0,},],},],}, {id: 2, title: '园区2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 21, parentId: 2, title: '集团1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 22, parentId: 2, title: '集团2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0, children: [{id: 221, parentId: 22, title: '公司1', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,}, {id: 222, parentId: 22, title: '公司2', count1: 0, count2: 0, count3: 0, num1: 0, num2: 0, num3: 0, num4: 0,},],},],},]
// 深度遍历,callback 每个对象的回调参数
let treeCalc = (obj = {children: []}, callback = (obj) => {
}) => {
  obj && obj.children && obj.children.forEach(item => {
    treeCalc(item, callback);
  })
  callback && callback(obj);
  return obj;
}
// 顶级对象
let rootData = {
  count1: 0,
  count2: 0,
  count3: 0,
  num1: 0,
  num2: 0,
  num3: 0,
  num4: 0,
  children: tableData
}
// 根据统计叶子节点数量赋值到父级节点数量
treeCalc(rootData, (obj) => {
  obj.children && obj.children.forEach(item => {
    obj.count1 += item.count1;
    obj.count2 += item.count2;
    obj.count3 += item.count3;
    obj.num1 += item.num1;
    obj.num2 += item.num2;
    obj.num3 += item.num3;
    obj.num4 += item.num4;
  })
})

相关:
深度递归拷贝:不影响原先数据。
https://www.lodashjs.com/docs...
函数防抖:比如输入事件,延迟一秒调用函数。
https://www.lodashjs.com/docs...

const calc = (row: any, field: string) => {
  const { parentId } = row
  const curRow = findRow(tableData, parentId)
  if (Array.isArray(curRow) && curRow.length) {
    const sum = curRow[0].children.reduce((prev: { [x: string]: any }, cur: { [x: string]: any }) => {
      return prev[field] + cur[field]
    })
    curRow[0][field] = sum

    calc(curRow[0], field)
  }
}
推荐问题
logo
Microsoft
子站问答
访问
宣传栏