简体   繁体   English

Javascript:构建层次树

[英]Javascript: Building a hierarchical tree

My data has these properties:我的数据具有以下属性:

  1. Each entry has a unique id (Id)每个条目都有一个唯一的 id (Id)
  2. Each has a Parent field, which points to the Id of the parent.每个都有一个 Parent 字段,它指向父级的 Id。
  3. A node can have multiple children, but only one parent.一个节点可以有多个子节点,但只有一个父节点。

My first attempt to build a tree is below.我第一次尝试构建一棵树如下。 It is buggy as the recursion causes an infinite loop.由于递归导致无限循环,因此存在问题。 Even if I solve it, I am not sure if there is a better approach to do this.即使我解决了它,我也不确定是否有更好的方法来做到这一点。 Currently, I am doing it in 2 passes.目前,我分两次完成。

I would like it to be as efficient as possible as I have a decent amount of data.我希望它尽可能高效,因为我有大量的数据。 It also needs to rebuild the tree dynamically (the root can be any node)它还需要动态重建树(根可以是任何节点)

There is sample data in the program below:下面的程序中有示例数据:

 arry = [{"Id":"1", "Name":"abc", "Parent":""}, {"Id":"2", "Name":"abc", "Parent":"1"},
    {"Id":"3", "Name":"abc", "Parent":"2"},{"Id":"4", "Name":"abc", "Parent":"2"}]//for testing

I was hoping the output to be (it might be wrong nested structure, as I manually wrote it. but, what I am hoping is a valid JSON structure with node as a field 'value' and children as an array.)我希望输出是(它可能是错误的嵌套结构,因为我手动编写了它。但是,我希望是一个有效的 JSON 结构,其中节点作为字段“值”,子项作为数组。)

{
 "value": {"Id":"1", "Name":"abc", "Parent":""},
 "children": [
  {
   "value": {"Id":"2", "Name":"abc", "Parent":"1"},
   "children": [
    {
     "value": {"Id":"3", "Name":"abc", "Parent":"2"},
     "children": []
     },
     {
     "value": {"Id":"4", "Name":"abc", "Parent":"2"},
     "children": []
     }
   ]
..
}

Sample program:示例程序:

function convertToHierarchy(arry, root) 
{
//root can be treated a special case, as the id is known
    arry = [{"Id":"1", "Name":"abc", "Parent":""}, {"Id":"2", "Name":"abc", "Parent":"1"},
    {"Id":"3", "Name":"abc", "Parent":"2"},{"Id":"4", "Name":"abc", "Parent":"2"}]//for testing


    var mapping = {}; // parent : [children]
    for (var i = 0; i < array.length; i++) 
    {
        var node = arry[i];

    if (!mapping[node.Id]) { 
          mapping[node.Id] = {value: node, children:[] } ;
        }else{
      mapping[node.Id] = {value: node} //children is already set    
    }

    if (!mapping[node.Parent]) { //TODO what if parent doesn't exist.
                mapping[node.Parent] =  {value: undefined, children:[ {value: node,children:[]} ]};
        }else {//parent is already in the list
        mapping[node.Parent].children.push({value: node,children:[]} )
    }

    }
    //by now we will have an index with all nodes and their children.

    //Now, recursively add children for root element.

    var root = mapping[1]  //hardcoded for testing, but a function argument
    recurse(root, root, mapping)
    console.log(root)

    //json dump
}

function recurse(root, node, mapping)
{
    var nodeChildren = mapping[node.value.Id].children;
    root.children.push({value:node.value, children:nodeChildren})
   for (var i = 0; i < nodeChildren.length; i++) {
        recurse(root, nodeChildren[i], mapping);
    }
    return root;
}

I have 3 good solutions so far, and hope the upvotes suggest more idiomatic, efficient implementation.到目前为止,我有 3 个很好的解决方案,希望投票建议更惯用、更有效的实施。 I am not sure, utilizing the property of my data that, there will be only one root element in the set of input array, and also the root is always given, any of these implementation could be better.我不确定,利用我的数据的属性,输入数组集中只有一个根元素,而且根总是给出,这些实现中的任何一个都可能更好。 I should also be learning how to benchmark, as my requirement is how efficiently (fast/without much memory) the tree can be rebuild.我还应该学习如何进行基准测试,因为我的要求是重建树的效率(快速/没有太多内存)。 For example, the input is already cached (array) and rebuild the tree like例如,输入已经被缓存(数组)并像这样重建树

convertToHierarchy(parentid)
....
convertToHierarchy(parentid2)
...

Here's one solution:这是一种解决方案:

var items = [
    {"Id": "1", "Name": "abc", "Parent": "2"},
    {"Id": "2", "Name": "abc", "Parent": ""},
    {"Id": "3", "Name": "abc", "Parent": "5"},
    {"Id": "4", "Name": "abc", "Parent": "2"},
    {"Id": "5", "Name": "abc", "Parent": ""},
    {"Id": "6", "Name": "abc", "Parent": "2"},
    {"Id": "7", "Name": "abc", "Parent": "6"},
    {"Id": "8", "Name": "abc", "Parent": "6"}
];

function buildHierarchy(arry) {

    var roots = [], children = {};

    // find the top level nodes and hash the children based on parent
    for (var i = 0, len = arry.length; i < len; ++i) {
        var item = arry[i],
            p = item.Parent,
            target = !p ? roots : (children[p] || (children[p] = []));

        target.push({ value: item });
    }

    // function to recursively build the tree
    var findChildren = function(parent) {
        if (children[parent.value.Id]) {
            parent.children = children[parent.value.Id];
            for (var i = 0, len = parent.children.length; i < len; ++i) {
                findChildren(parent.children[i]);
            }
        }
    };

    // enumerate through to handle the case where there are multiple roots
    for (var i = 0, len = roots.length; i < len; ++i) {
        findChildren(roots[i]);
    }

    return roots;
}

console.log(buildHierarchy(items));​

Here's another one.这是另一个。 This should work for multiple root nodes:这应该适用于多个根节点:

function convertToHierarchy() { 

    var arry = [{ "Id": "1", "Name": "abc", "Parent": "" }, 
    { "Id": "2", "Name": "abc", "Parent": "1" },
    { "Id": "3", "Name": "abc", "Parent": "2" },
    { "Id": "4", "Name": "abc", "Parent": "2"}];

    var nodeObjects = createStructure(arry);

    for (var i = nodeObjects.length - 1; i >= 0; i--) {
        var currentNode = nodeObjects[i];

        //Skip over root node.
        if (currentNode.value.Parent == "") {
            continue;
        }

        var parent = getParent(currentNode, nodeObjects);

        if (parent == null) {
            continue;
        }

        parent.children.push(currentNode);
        nodeObjects.splice(i, 1);
    }

    //What remains in nodeObjects will be the root nodes.
    return nodeObjects;
}

function createStructure(nodes) {
    var objects = [];

    for (var i = 0; i < nodes.length; i++) {
        objects.push({ value: nodes[i], children: [] });
    }

    return objects;
}

function getParent(child, nodes) {
    var parent = null;

    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i].value.Id == child.value.Parent) {
            return nodes[i];
        }
    }

    return parent;
}

While the above solutions do work - I think they are very slow and unoptimised with too many loops and outdate methods (we will use ES6 syntax).虽然上述解决方案确实有效- 我认为它们非常慢并且没有经过太多循环和过时方法的优化(我们将使用ES6语法)。 I recommend using the bellow optimised solution which will give you a performance boost.我建议使用波纹管优化解决方案,这将为您带来性能提升。 Read this blog post to understand how this works. 阅读此博客文章以了解其工作原理。

javascript

 const hierarchy = (data) => { const tree = []; const childOf = {}; data.forEach((item) => { const { Id, Parent } = item; childOf[Id] = childOf[Id] || []; item.children = childOf[Id]; Parent ? (childOf[Parent] = childOf[Parent] || []).push(item) : tree.push(item); }); return tree; }; // print console.log(hierarchy([{"Id":"1", "Name":"abc", "Parent":""}, {"Id":"2", "Name":"abc", "Parent":"1"}, {"Id":"3", "Name":"abc", "Parent":"2"},{"Id":"4", "Name":"abc", "Parent":"2"}], { idKey: 'Id', parentKey: 'Parent' }));

Here some results and comparisons between other posters这里有一些结果和其他海报之间的比较

在此处输入图片说明

http://jsben.ch/ekTls http://jsben.ch/ekTls


If you looking for version with parameters for a more dynamic but slightly slower version here it is bellow:如果您正在寻找带有参数的版本以获得更动态但速度稍慢的版本,则如下所示:

const hierarchy = (data = [], { idKey = 'id', parentKey = 'parentId', childrenKey = 'children' } = {}) => {
    const tree = [];
    const childrenOf = {};
    data.forEach((item) => {
        const { [idKey]: id, [parentKey]: parentId = 0 } = item;
        childrenOf[id] = childrenOf[id] || [];
        item[childrenKey] = childrenOf[id];
        parentId ? (childrenOf[parentId] = childrenOf[parentId] || []).push(item) : tree.push(item);
    });
    return tree;
}

Happy hacking快乐黑客

I'd have done something like this.我会做这样的事情。 It handles multiple root nodes and is fairly readable IMO.它处理多个根节点,并且在 IMO 中具有相当的可读性。

array = [{"Id":"1", "Name":"abc", "Parent":""}, 
    {"Id":"2", "Name":"abc", "Parent":"1"},
    {"Id":"3", "Name":"abc", "Parent":"2"},
    {"Id":"4", "Name":"abc", "Parent":"2"},
    {"Id":"5", "Name":"abc", "Parent":""},
    {"Id":"6", "Name":"abc", "Parent":"5"}];


function buildHierarchy(source)
{

    Array.prototype.insertChildAtId = function (strId, objChild)
    {
        // Beware, here there be recursion
        found = false;
        for (var i = 0; i < this.length ; i++)
        {
            if (this[i].value.Id == strId)
            {
                // Insert children
                this[i].children.push(objChild);
                return true;
            }
            else if (this[i].children)
            {
                // Has children, recurse!
                found = this[i].children.insertChildAtId(strId, objChild);
                if (found) return true;
            }
        }
        return false;
    };

    // Build the array according to requirements (object in value key, always has children array)
    var target = [];
    for (var i = 0 ; i < array.length ; i++)
        target.push ({ "value": source[i], "children": []});

    i = 0;
    while (target.length>i)
    {
        if (target[i].value.Parent)
        {
            // Call recursion to search for parent id
            target.insertChildAtId(target[i].value.Parent, target[i]); 
            // Remove node from array (it's already been inserted at the proper place)
            target.splice(i, 1); 
        }
        else
        {
            // Just skip over root nodes, they're no fun
            i++; 
        }
    }
    return target;
}

console.log(buildHierarchy(array));

Implemented in ES6, with a simple sample input.在 ES6 中实现,带有一个简单的示例输入。 Can test in the browser console可以在浏览器控制台测试

let array = [{ id: 'a', children: ['b', 'c'] }, { id: 'b', children: [] }, { id: 'c', children: ['b', 'd'] }, { id: 'd', children: ['b'] }],
  tree = (data) => {
      let nodes = Object.create(null),
          result = {};
      data.forEach((item) => {
        if (!nodes[item.id]) {
          nodes[item.id] = {id: item.id, children: []}
          result = nodes
        }
        item.children.forEach((child) => {
          nodes[child] = {id: child, children: []}
          nodes[item.id].children.push(nodes[child])
        })
      })
      return result
    }

console.log(tree(array))

guys what if I am using it inside nodejs and need to created nested ul/li instead of json?伙计们,如果我在 nodejs 中使用它并且需要创建嵌套的 ul/li 而不是 json 呢? can please write the code可以请写代码吗

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM