在javascript中从平面数组构建树数组

Fra*_*nck 110 javascript arrays tree list

我有一个复杂的json文件,我必须使用javascript来使其分层,以便以后构建一个树.json的每个条目都有:id:唯一id,parentId:父节点的id(如果节点是树的根,则为0)level:树中的深度级别

json数据已经"排序".我的意思是一个条目将在其上方拥有父节点或兄弟节点,并且在其自身下面是子节点或兄弟节点.

输入:

{
    "People": [
        {
            "id": "12",
            "parentId": "0",
            "text": "Man",
            "level": "1",
            "children": null
        },
        {
            "id": "6",
            "parentId": "12",
            "text": "Boy",
            "level": "2",
            "children": null
        },
                {
            "id": "7",
            "parentId": "12",
            "text": "Other",
            "level": "2",
            "children": null
        },
        {
            "id": "9",
            "parentId": "0",
            "text": "Woman",
            "level": "1",
            "children": null
        },
        {
            "id": "11",
            "parentId": "9",
            "text": "Girl",
            "level": "2",
            "children": null
        }
    ],
    "Animals": [
        {
            "id": "5",
            "parentId": "0",
            "text": "Dog",
            "level": "1",
            "children": null
        },
        {
            "id": "8",
            "parentId": "5",
            "text": "Puppy",
            "level": "2",
            "children": null
        },
        {
            "id": "10",
            "parentId": "13",
            "text": "Cat",
            "level": "1",
            "children": null
        },
        {
            "id": "14",
            "parentId": "13",
            "text": "Kitten",
            "level": "2",
            "children": null
        },
    ]
}
Run Code Online (Sandbox Code Playgroud)

预期产量:

{
    "People": [
        {
            "id": "12",
            "parentId": "0",
            "text": "Man",
            "level": "1",
            "children": [
                {
                    "id": "6",
                    "parentId": "12",
                    "text": "Boy",
                    "level": "2",
                    "children": null
                },
                {
                    "id": "7",
                    "parentId": "12",
                    "text": "Other",
                    "level": "2",
                    "children": null
                }   
            ]
        },
        {
            "id": "9",
            "parentId": "0",
            "text": "Woman",
            "level": "1",
            "children":
            {

                "id": "11",
                "parentId": "9",
                "text": "Girl",
                "level": "2",
                "children": null
            }
        }

    ],    

    "Animals": [
        {
            "id": "5",
            "parentId": "0",
            "text": "Dog",
            "level": "1",
            "children": 
                {
                    "id": "8",
                    "parentId": "5",
                    "text": "Puppy",
                    "level": "2",
                    "children": null
                }
        },
        {
            "id": "10",
            "parentId": "13",
            "text": "Cat",
            "level": "1",
            "children": 
            {
                "id": "14",
                "parentId": "13",
                "text": "Kitten",
                "level": "2",
                "children": null
            }
        }

    ]
}
Run Code Online (Sandbox Code Playgroud)

Hal*_*yon 130

如果使用地图查找,则有一种有效的解决方案.如果父母总是来到他们的孩子面前,你可以合并两个for-loops.它支持多个根.它在悬空分支上给出错误,但可以修改为忽略它们.它不需要第三方库.据我所知,这是最快的解决方案.

function list_to_tree(list) {
    var map = {}, node, roots = [], i;
    for (i = 0; i < list.length; i += 1) {
        map[list[i].id] = i; // initialize the map
        list[i].children = []; // initialize the children
    }
    for (i = 0; i < list.length; i += 1) {
        node = list[i];
        if (node.parentId !== "0") {
            // if you have dangling branches check that map[node.parentId] exists
            list[map[node.parentId]].children.push(node);
        } else {
            roots.push(node);
        }
    }
    return roots;
}

var entries = [
    {
        "id": "12",
        "parentId": "0",
        "text": "Man",
        "level": "1"
    }, { /*...*/ }
];

console.log(list_to_tree(entries));
Run Code Online (Sandbox Code Playgroud)

如果你进入复杂性理论,这个解决方案就是Θ(n log(n)).递归滤波器解是Θ(n ^ 2),这可能是大数据集的问题.

  • 请记住,使用此解决方案,您的节点必须专门订购,以确保父母首先被推入地图,否则查找过程将出错...所以您需要在级别属性上对em进行排序,或者您需要首先将它们推入地图.并使用单独的for循环进行查找.(我更喜欢排序,但是当你没有level属性时,单独的循环可能是一个选项) (27认同)
  • @ iman.Bahrampour它实际上并没有比for-loop和if语句更简单.您可能已将代码缩减为两行,但它们不易于阅读或验证,并且您的代码复杂度为Θ(n ^ 2).也许您的解决方案更适合https://codegolf.stackexchange.com (3认同)
  • @Halcyon 在地图中查找需要恒定的时间,即 O(1)。 (2认同)

Ste*_*ris 70

正如@Sander所提到的,@ Halcyon的回答假定是一个预先排序的数组,以下不是.(但它假设您已经加载了underscore.js - 尽管它可以用vanilla javascript编写):

// Example usage
var arr = [
    {'id':1 ,'parentid' : 0},
    {'id':2 ,'parentid' : 1},
    {'id':3 ,'parentid' : 1},
    {'id':4 ,'parentid' : 2},
    {'id':5 ,'parentid' : 0},
    {'id':6 ,'parentid' : 0},
    {'id':7 ,'parentid' : 4}
];

unflatten = function( array, parent, tree ){
    tree = typeof tree !== 'undefined' ? tree : [];
    parent = typeof parent !== 'undefined' ? parent : { id: 0 };
        
    var children = _.filter( array, function(child){ return child.parentid == parent.id; });
    
    if( !_.isEmpty( children )  ){
        if( parent.id == 0 ){
           tree = children;   
        }else{
           parent['children'] = children
        }
        _.each( children, function( child ){ unflatten( array, child ) } );                    
    }
    
    return tree;
}

tree = unflatten( arr );
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))
Run Code Online (Sandbox Code Playgroud)

要求

它假定属性'id'和'parentid'分别表示ID和父ID.必须有父ID为0的元素,否则返回一个空数组.孤儿和他们的后代'迷失'

用法示例

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
Run Code Online (Sandbox Code Playgroud)

的jsfiddle

http://jsfiddle.net/LkkwH/1/

  • 对于任何感兴趣的人,代码很容易转换为vanilla js:http://jsfiddle.net/LkkwH/853/ (4认同)
  • 你可以添加`else {parent ['children'] = []; 在第一个if子句之后,确保每个节点都有一个属性`children`(如果节点是叶节点,它将为空) (3认同)
  • 你的代码片段工作得很好,谢谢!唯一的事情是:`tree`在递归调用函数时永远不会作为参数传递,所以我认为行`tree = typeof tree!=='undefined'?tree:[];`可以用`let tree = [];`替换 (2认同)
  • 请记住,上面的答案使用了两个循环,因此可以改进。由于我找不到实现 O(n) 解决方案的 npm 模块,因此我创建了以下模块(经过单元测试、100% 代码覆盖率、大小仅为 0.5 kb 并包含类型)。也许它对某人有帮助:npmjs.com/package/performant-array-to-tree (2认同)

小智 29

有同样的问题,但我无法确定数据是否已排序.我不能使用第三方库,所以这只是香草Js; 输入数据可以从@ Stephen的例子中获取;

 var arr = [
        {'id':1 ,'parentid' : 0},
        {'id':4 ,'parentid' : 2},
        {'id':3 ,'parentid' : 1},
        {'id':5 ,'parentid' : 0},
        {'id':6 ,'parentid' : 0},
        {'id':2 ,'parentid' : 1},
        {'id':7 ,'parentid' : 4},
        {'id':8 ,'parentid' : 1}
      ];
    function unflatten(arr) {
      var tree = [],
          mappedArr = {},
          arrElem,
          mappedElem;

      // First map the nodes of the array to an object -> create a hash table.
      for(var i = 0, len = arr.length; i < len; i++) {
        arrElem = arr[i];
        mappedArr[arrElem.id] = arrElem;
        mappedArr[arrElem.id]['children'] = [];
      }


      for (var id in mappedArr) {
        if (mappedArr.hasOwnProperty(id)) {
          mappedElem = mappedArr[id];
          // If the element is not at the root level, add it to its parent array of children.
          if (mappedElem.parentid) {
            mappedArr[mappedElem['parentid']]['children'].push(mappedElem);
          }
          // If the element is at the root level, add it to first level elements array.
          else {
            tree.push(mappedElem);
          }
        }
      }
      return tree;
    }

var tree = unflatten(arr);
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))
Run Code Online (Sandbox Code Playgroud)

JS小提琴

平面阵列到树


Fur*_*anO 21

非常直接的方法是

(奖励1:可以或不可以订购节点)

(奖金2:不需要第三方图书馆,PLAIN JS)

const createDataTree = dataset => {
    let hashTable = Object.create(null)
    dataset.forEach( aData => hashTable[aData.ID] = { ...aData, childNodes : [] } )
    let dataTree = []
    dataset.forEach( aData => {
      if( aData.parentID ) hashTable[aData.parentID].childNodes.push(hashTable[aData.ID])
      else dataTree.push(hashTable[aData.ID])
    } )
    return dataTree
}
Run Code Online (Sandbox Code Playgroud)

这是对它的测试,可能会有所帮助:

it('creates a correct shape of dataTree', () => {

    let dataSet = [
        {
            "ID": 1,
            "Phone": "(403) 125-2552",
            "City": "Coevorden",
            "Name": "Grady"
        },
        {
            "ID": 2,
            "parentID": 1,
            "Phone": "(979) 486-1932",
            "City": "Che?m",
            "Name": "Scarlet"
        }
    ]

    let expectedDataTree = [ 
    {
            "ID": 1,
            "Phone": "(403) 125-2552",
            "City": "Coevorden",
            "Name": "Grady",
            childNodes : [
                {
                    "ID": 2,
                    "parentID": 1,
                    "Phone": "(979) 486-1932",
                    "City": "Che?m",
                    "Name": "Scarlet",
                    childNodes : []
                }
            ]
    } 
    ]

  expect( createDataTree(dataSet) ).toEqual(expectedDataTree)
});
Run Code Online (Sandbox Code Playgroud)

  • 如果我们仅在需要时添加“childNodes”不是更准确吗?通过从第一个“forEach”中删除它们并将它们移动到第二个“forEach”中? (3认同)

she*_*dtu 16

使用此ES6方法。像魅力一样工作

// Data Set
// One top level comment 
const comments = [{
    id: 1,
    parent_id: null
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 1
}, {
    id: 4,
    parent_id: 2
}, {
    id: 5,
    parent_id: 4
}];

const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));

console.log(
  nest(comments)
)
Run Code Online (Sandbox Code Playgroud)

  • 我认为最短和最好的答案 (5认同)
  • 与 FurkanO 的答案相比,速度较慢 (5认同)
  • 如果数组有多个 nullparentId,则不起作用 (2认同)

Wil*_*ung 15

一个更简单的函数list-to-tree-lite

npm install list-to-tree-lite

listToTree(list)

资源:

function listToTree(data, options) {
    options = options || {};
    var ID_KEY = options.idKey || 'id';
    var PARENT_KEY = options.parentKey || 'parent';
    var CHILDREN_KEY = options.childrenKey || 'children';

    var tree = [],
        childrenOf = {};
    var item, id, parentId;

    for (var i = 0, length = data.length; i < length; i++) {
        item = data[i];
        id = item[ID_KEY];
        parentId = item[PARENT_KEY] || 0;
        // every item may have children
        childrenOf[id] = childrenOf[id] || [];
        // init its children
        item[CHILDREN_KEY] = childrenOf[id];
        if (parentId != 0) {
            // init its parent's children object
            childrenOf[parentId] = childrenOf[parentId] || [];
            // push it into its parent's children object
            childrenOf[parentId].push(item);
        } else {
            tree.push(item);
        }
    };

    return tree;
}
Run Code Online (Sandbox Code Playgroud)

的jsfiddle


Ima*_*our 9

您只需两行编码即可处理此问题:

_(flatArray).forEach(f=>
           {f.nodes=_(flatArray).filter(g=>g.parentId==f.id).value();});

var resultArray=_(flatArray).filter(f=>f.parentId==null).value();
Run Code Online (Sandbox Code Playgroud)

在线测试(有关创建的树,请参阅浏览器控制台)

要求:

1-安装lodash 4(用于使用高性能方法操作对象和集合的Javascript库=>像c#中的Linq一样)Lodash

2- A flatArray如下:

    var flatArray=
    [{
      id:1,parentId:null,text:"parent1",nodes:[]
    }
   ,{
      id:2,parentId:null,text:"parent2",nodes:[]
    }
    ,
    {
      id:3,parentId:1,text:"childId3Parent1",nodes:[]
    }
    ,
    {
      id:4,parentId:1,text:"childId4Parent1",nodes:[]
    }
    ,
    {
      id:5,parentId:2,text:"childId5Parent2",nodes:[]
    }
    ,
    {
      id:6,parentId:2,text:"childId6Parent2",nodes:[]
    }
    ,
    {
      id:7,parentId:3,text:"childId7Parent3",nodes:[]
    }
    ,
    {
      id:8,parentId:5,text:"childId8Parent5",nodes:[]
    }];
Run Code Online (Sandbox Code Playgroud)

感谢Bakhshabadi先生

祝好运


Den*_*enQ 7

它可能是有用的包列表到树 安装:

bower install list-to-tree --save
Run Code Online (Sandbox Code Playgroud)

要么

npm install list-to-tree --save
Run Code Online (Sandbox Code Playgroud)

例如,有列表:

var list = [
  {
    id: 1,
    parent: 0
  }, {
    id: 2,
    parent: 1
  }, {
    id: 3,
    parent: 1
  }, {
    id: 4,
    parent: 2
  }, {
    id: 5,
    parent: 2
  }, {
    id: 6,
    parent: 0
  }, {
    id: 7,
    parent: 0
  }, {
    id: 8,
    parent: 7
  }, {
    id: 9,
    parent: 8
  }, {
    id: 10,
    parent: 0
  }
];
Run Code Online (Sandbox Code Playgroud)

使用包列表到树:

var ltt = new LTT(list, {
  key_id: 'id',
  key_parent: 'parent'
});
var tree = ltt.GetTree();
Run Code Online (Sandbox Code Playgroud)

结果:

[{
  "id": 1,
  "parent": 0,
  "child": [
    {
      "id": 2,
      "parent": 1,
      "child": [
        {
          "id": 4,
          "parent": 2
        }, {
          "id": 5, "parent": 2
        }
      ]
    },
    {
      "id": 3,
      "parent": 1
    }
  ]
}, {
  "id": 6,
  "parent": 0
}, {
  "id": 7,
  "parent": 0,
  "child": [
    {
      "id": 8,
      "parent": 7,
      "child": [
        {
          "id": 9,
          "parent": 8
        }
      ]
    }
  ]
}, {
  "id": 10,
  "parent": 0
}];
Run Code Online (Sandbox Code Playgroud)

  • 请注意,不鼓励使用 [仅链接答案](http://meta.stackoverflow.com/tags/link-only-answers/info),因此答案应该是搜索解决方案的终点(而不是参考文献的另一个停留点,随着时间的推移,这些参考文献往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考 (2认同)

Dan*_*mas 7

经过多次尝试我想出了这个:

const arrayToTree = (arr, parent = 0) => arr .filter(item => item.parent === parent).map(child => ({ ...child, children: arrayToTree(arr, child.index) }));

   
Run Code Online (Sandbox Code Playgroud)

const arrayToTree = (arr, parent = 0) => arr .filter(item => item.parent === parent).map(child => ({ ...child, children: arrayToTree(arr, child.index) }));

   
Run Code Online (Sandbox Code Playgroud)

  • (item.parent ?? 0) 如果父项为空,则添加此一项。 (2认同)

Nin*_*olz 5

2022 年更新

这是针对无序项目的提案。该函数使用单个循环和哈希表,并收集所有项目及其id. 如果找到根节点,则将该对象添加到结果数组中。

const
    getTree = (data, root) => {
        const t = {};
        data.forEach(o => ((t[o.parentId] ??= {}).children ??= []).push(Object.assign(t[o.id] ??= {}, o)));
        return t[root].children;
    },
    data = { People: [{ id: "12", parentId: "0", text: "Man", level: "1", children: null }, { id: "6", parentId: "12", text: "Boy", level: "2", children: null }, { id: "7", parentId: "12", text: "Other", level: "2", children: null }, { id: "9", parentId: "0", text: "Woman", level: "1", children: null }, { id: "11", parentId: "9", text: "Girl", level: "2", children: null }], Animals: [{ id: "5", parentId: "0", text: "Dog", level: "1", children: null }, { id: "8", parentId: "5", text: "Puppy", level: "2", children: null }, { id: "10", parentId: "13", text: "Cat", level: "1", children: null }, { id: "14", parentId: "13", text: "Kitten", level: "2", children: null }] },
    result = Object.fromEntries(Object
        .entries(data)
        .map(([k, v]) => [k, getTree(v, '0')])
    );

console.log(result);
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Run Code Online (Sandbox Code Playgroud)


Eli*_*abl 5

我编写了一个测试脚本来评估用户 shekhardtu 提出的两个最通用的解决方案的性能(意味着不必预先对输入进行排序并且代码不依赖于第三方库)(请参阅答案)和 FurkanO(见答案)。

http://playcode.io/316025?tabs=console&script.js&output

FurkanO 的解决方案似乎是最快的。

/*
** performance test for https://stackoverflow.com/questions/18017869/build-tree-array-from-flat-array-in-javascript
*/

// Data Set (e.g. nested comments)
var comments = [{
    id: 1,
    parent_id: null
}, {
    id: 2,
    parent_id: 1
}, {
    id: 3,
    parent_id: 4
}, {
    id: 4,
    parent_id: null
}, {
    id: 5,
    parent_id: 4
}];

// add some random entries
let maxParentId = 10000;
for (let i=6; i<=maxParentId; i++)
{
  let randVal = Math.floor((Math.random() * maxParentId) + 1);
  comments.push({
    id: i,
    parent_id: (randVal % 200 === 0 ? null : randVal)
  });
}

// solution from user "shekhardtu" (https://stackoverflow.com/a/55241491/5135171)
const nest = (items, id = null, link = 'parent_id') =>
  items
    .filter(item => item[link] === id)
    .map(item => ({ ...item, children: nest(items, item.id) }));
;

// solution from user "FurkanO" (https://stackoverflow.com/a/40732240/5135171)
const createDataTree = dataset => {
    let hashTable = Object.create(null)
    dataset.forEach( aData => hashTable[aData.id] = { ...aData, children : [] } )
    let dataTree = []
    dataset.forEach( aData => {
      if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
      else dataTree.push(hashTable[aData.id])
    } )
    return dataTree
};


/*
** lets evaluate the timing for both methods
*/
let t0 = performance.now();
let createDataTreeResult = createDataTree(comments);
let t1 = performance.now();
console.log("Call to createDataTree took " + Math.floor(t1 - t0) + " milliseconds.");

t0 = performance.now();
let nestResult = nest(comments);
t1 = performance.now();
console.log("Call to nest took " + Math.floor(t1 - t0) + " milliseconds.");




//console.log(nestResult);
//console.log(createDataTreeResult);

// bad, but simple way of comparing object equality
console.log(JSON.stringify(nestResult)===JSON.stringify(createDataTreeResult));
Run Code Online (Sandbox Code Playgroud)