从带有父字段的平面列表构建层次结构树?

IT技术 javascript arrays json tree hierarchy
2021-02-18 09:13:28

我有一个带有parent字段的“页面”对象列表此父字段引用列表中的另一个对象。我想根据这个字段从这个列表中创建一个树层次结构。

这是我的原始列表的样子:

[
  {
    id: 1,
    title: 'home',
    parent: null
  },
  {
    id: 2,
    title: 'about',
    parent: null
  },
  {
    id: 3,
    title: 'team',
    parent: 2
  },
  {
    id: 4,
    title: 'company',
    parent: 2
  }
]

我想把它转换成这样的树结构:

[
  {
    id: 1,
    title: 'home',
    parent: null
  },
  {
    id: 2,
    title: 'about',
    parent: null,
    children:  [
      {
        id: 3,
        title: 'team',
        parent: 2
      },
      {
        id: 4,
        title: 'company',
        parent: 2
      }
    ]
]

我希望有一个可重用的函数,我可以随时针对任意列表调用它。有人知道处理这个问题的好方法吗?任何帮助或建议将不胜感激!

2个回答
function treeify(list, idAttr, parentAttr, childrenAttr) {
    if (!idAttr) idAttr = 'id';
    if (!parentAttr) parentAttr = 'parent';
    if (!childrenAttr) childrenAttr = 'children';

    var treeList = [];
    var lookup = {};
    list.forEach(function(obj) {
        lookup[obj[idAttr]] = obj;
        obj[childrenAttr] = [];
    });
    list.forEach(function(obj) {
        if (obj[parentAttr] != null) {
            if (lookup[obj[parentAttr]] !== undefined) {
                lookup[obj[parentAttr]][childrenAttr].push(obj);
            } else {
                 //console.log('Missing Parent Data: ' + obj[parentAttr]);
                 treeList.push(obj);
            }               
        } else {
            treeList.push(obj);
        }
    });
    return treeList;
};

小提琴

@Amadan 感谢您的想法。你是对的,它们的时间复杂度是相等的。我发布了这个链接,以防有人对 npm 包中的有效实现感兴趣。
2021-04-16 09:13:28
@菲利普斯坦尼斯劳斯:O(2n) = O(n). 我的解决方案也是O(n)我承认我没有打破速度记录。可以衡量我的版本还是你的版本更快(两个简单的循环与一个复杂的循环),但我无法先验地判断哪个更快。但是,它们的时间复杂度完全相同
2021-04-27 09:13:28
由于我的 npm 包收到了相当多的下载,我在 npm 上performant-array-to-tree编写了一些可用包的基准测试。请在此处找到结果:github.com/philipstanislaus/array-to-tree-benchmarks如果您有兴趣,很高兴添加其他人!
2021-04-28 09:13:28
请记住,上述答案使用了两个循环,因此可以改进。由于我找不到实现 O(n) 解决方案的 npm module,我创建了以下一个(单元测试,100% 代码覆盖率,大小仅 0.5 kb 并包括类型。也许对某人有帮助:npmjs.com/package /性能阵列到树
2021-05-13 09:13:28

接受的答案对我的研究非常有帮助,但是,我必须在精神上解析我理解的 id 参数,使函数更加灵活,但对于算法的新手来说,可能有点难以推理。

如果其他人有这个困难,这里有基本相同的代码,但可能更容易理解:

const treeify = (arr, pid) => {
  const tree = [];
  const lookup = {};
  // Initialize lookup table with each array item's id as key and 
  // its children initialized to an empty array 
  arr.forEach((o) => {
    lookup[o.id] = o;
    lookup[o.id].children = [];
  });
  arr.forEach((o) => {
    // If the item has a parent we do following:
    // 1. access it in constant time now that we have a lookup table
    // 2. since children is preconfigured, we simply push the item
    if (o.parent !== null) {
      lookup[o.parent].children.push(o);
    } else {
      // no o.parent so this is a "root at the top level of our tree
      tree.push(o);
    }
  });
  return tree;
};

它与接受的答案相同,并带有一些注释来解释正在发生的事情。这是一个用例,它将导致marginLeft根据级别使用内联缩进呈现到页面的 div 列表

const arr = [
  {id: 1, title: 'All', parent: null},
  {id: 2, title: 'Products', parent: 1},
  {id: 3, title: 'Photoshop', parent: 2},
  {id: 4, title: 'Illustrator', parent: 2},
  {id: 4, title: 'Plugins', parent: 3},
  {id: 5, title: 'Services', parent: 1},
  {id: 6, title: 'Branding', parent: 5},
  {id: 7, title: 'Websites', parent: 5},
  {id: 8, title: 'Pen Testing', parent: 7}];
const render = (item, parent, level) => {
  const div = document.createElement('div');
  div.textContent = item.title;
  div.style.marginLeft = level * 8 + 'px';
  parent.appendChild(div);
  if (item.children.length) {
    item.children.forEach(child => render(child, div, ++level));
  }
  return parent;
}
const fragment = document.createDocumentFragment();
treeify(arr)
  .map(item => render(item, fragment, 1))
  .map(frag => document.body.appendChild(frag))

Codepen 如果你想运行它:https ://codepen.io/roblevin/pen/gVRowd?editors=0010

在我看来,这个解决方案的有趣部分是查找表使用项目的 ID 作为键保持平坦,并且只有根对象被推送到结果树列表中。然而,由于 JavaScript 对象的引用性质,根有它的孩子,孩子有他们的孩子,等等,但它本质上是从根连接的,因此像树一样。