如何从类似配置的父子数组项中以编程方式聚合树结构?

How to aggregate a tree structure programmatically from config-like parent-child array items?

我想将我的 javascript 对象结构更改为一个类似于层次结构树模式对象的对象, 这是我的对象:

let input = [
  {'id':1 ,'pid' : 0},
  {'id':2 ,'pid' : 1},
  {'id':3 ,'pid' : 2},
  {'id':4 ,'pid' : 2},
  {'id':5 ,'pid' : 3},
  {'id':6 ,'pid' : 3},
  {'id':7 ,'pid' : 4},
  {'id':8 ,'pid' : 4},
  {'id':9 ,'pid' : 4}
];

并在互联网上找到了这个片段,它实际上运行良好(我忘了是谁制作的,无论如何非常感谢)

let input = [
{'id':1 ,'pid' : 0},
{'id':2 ,'pid' : 1},
{'id':3 ,'pid' : 2},
{'id':4 ,'pid' : 2},
{'id':5 ,'pid' : 3},
{'id':6 ,'pid' : 3},
{'id':7 ,'pid' : 4},
{'id':8 ,'pid' : 4},
{'id':9 ,'pid' : 4}
];

let register = {};

let output = {};

for (let el of input) {
  el = Object.assign({}, el);
  register[el.id] = el;
 
  if (!el.pid) { 
    output[el.id] = el;
  } else { 
    register[el.pid][el.id] = el
  }

  delete el.pid;
  delete el.id
}

document.body.innerHTML = "<pre>" + (JSON.stringify(output, undefined, 2))

当我用下面的 id 更改 PID 值时,出现了一些我没想到的事情, 我的意思是,从 {'id':4 ,'pid' : 2}{'id':4 ,'pid' : 9}, 结果是 register[el.pid] is undefined

请帮忙解决这个问题,提前致谢

通过编程方式从 parent-child config-like 项目聚合树是可能的,即使这些项目不是完美的顺序,只要这些关系是有效的,因此是可表示的。

循环父子引用,就像 OP creates/triggers 将 { 'id': 4, 'pid': 2 } 更改为 { 'id': 4, 'pid': 9 } 时那样,将链接松散到一个已经可以表示的整体树结构中。因此,此类引用无效,并且链接到前者的任何其他有效子结构也将丢失。

以上声明通过下一个提供的示例代码证明了自己...

function aggregateTree( 
  { index = {}, result = {} }, // accumulator.
  { id, pid },                 // parent child config.
) {
  const childItem = (index[id] ??= { [id]: {} });
  const parentItem = (pid !== 0)
    && (index[pid] ??= { [pid]: {} })
    || null;
  const node = (parentItem !== null)
    && parentItem[pid] // programmatically build partial trees.
    || result // assign partial tree references at root/resul level.

  Object.assign(node, childItem);

  return { index, result };
}

console.log('unordered but valid parent child relationships ...', [

  { 'id': 1 ,'pid': 0 },
  { 'id': 2 ,'pid': 1 },
  { 'id': 3 ,'pid': 2 },

  { 'id': 12, 'pid': 0 },

  { 'id': 11, 'pid': 6 },
  { 'id': 10, 'pid': 6 },

  { 'id': 4, 'pid': 5 },

  { 'id': 5 ,'pid': 3 },
  { 'id': 6 ,'pid': 3 },

  { 'id': 7 ,'pid': 4 },
  { 'id': 8 ,'pid': 4 },
  { 'id': 9 ,'pid': 4 }

].reduce(aggregateTree, { result: {} }).result);

console.log('ordered but partially invalid parent child relationships ...', [

  // circular references like 4 to 9 and 9 to 4,
  // thus not being linked to the overall tree,
  // can not be aggregated and displayed.

  { 'id': 1, 'pid': 0 },
  { 'id': 2, 'pid': 1 },
  { 'id': 3, 'pid': 2 },

  { 'id': 4, 'pid': 9 }, // circular thus disconnected.
  // { 'id': 9, 'pid': 4 },

  { 'id': 5, 'pid': 3 },
  { 'id': 6, 'pid': 3 },

  { 'id': 7, 'pid': 4 }, // lost due to being linked to a circular reference.
  { 'id': 8, 'pid': 4 }, // lost due to being linked to a circular reference.

  { 'id': 9, 'pid': 4 }, // circular thus disconnected.
  // { 'id': 4, 'pid': 9 },

].reduce(aggregateTree, { result: {} }).result);
.as-console-wrapper { min-height: 100%!important; top: 0; }

嗯,您不能将父级移动到它自己的子级。这仅适用于时间旅行 novels/movies.


为了在不删除属性的情况下创建树,您可以进行一个循环并同时获取 id 和父级,结果是具有根父级的对象 0

这种方法也适用于未排序的数据。

const
    input = [{ id: 1, pid: 0 }, { id: 2, pid: 1 }, { id: 3, pid: 2 }, { id: 4, pid: 2 }, { id: 5, pid: 3 }, { id: 6, pid: 3 }, { id: 7, pid: 4 }, { id: 8, pid: 4 }, { id: 9, pid: 4 }],
    tree = {};

for (const { id, pid } of input) {
    tree[pid] ??= {};
    tree[id] ??= {};
    tree[pid][id] ??= tree[id];
}

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