【问题标题】:Construct hierarchy tree from flat list with parent field? [duplicate]从具有父字段的平面列表构造层次结构树? [复制]
【发布时间】:2014-03-13 02:06:33
【问题描述】:

我有一个带有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
      }
    ]
]

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

【问题讨论】:

    标签: javascript arrays json tree hierarchy


    【解决方案1】:
    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;
    };
    

    Fiddle

    【讨论】:

    • 请记住,上面的答案使用了两个循环,因此可以改进。由于我找不到实现 O(n) 解决方案的 npm 模块,因此我创建了以下模块(单元测试,100% 代码覆盖率,大小只有 0.5 kb 并且包括类型。也许它可以帮助某人:npmjs.com/package /performant-array-to-tree
    • @PhilipStanislaus:O(2n) = O(n)。我的解决方案也是O(n)。我承认我没有达到速度记录。可以测量我的版本还是您的版本更快(两个简单循环与一个复杂循环),但我无法先验地判断哪个更快。但是,它们的时间复杂度完全相同
    • @Amadan 感谢您的想法。你是对的,他们的时间复杂度是相等的。我发布了链接,以防有人对 npm 包中的高效实现感兴趣。
    • 因为我的 npm 包performant-array-to-tree 收到了很多下载,所以我在 npm 上编写了一些可用包的基准测试。请在此处找到结果:github.com/philipstanislaus/array-to-tree-benchmarks。如果您有兴趣,很高兴添加其他人!
    【解决方案2】:

    接受的答案对我的研究非常有帮助,但是,我必须在心里解析 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;
    };
    

    它与一些 cmets 接受的答案相同,用于解释发生了什么。这是一个用例,它会根据级别生成一个 div 列表,其中包含内联 marginLeft 缩进的页面:

    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 对象的引用性质,根有它的孩子,孩子有他们的孩子,等等,但它本质上是从根连接起来的,因此是树状的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-05
      • 2010-09-24
      • 2016-11-07
      • 2018-02-06
      • 1970-01-01
      • 2018-10-07
      • 1970-01-01
      • 2018-04-13
      相关资源
      最近更新 更多