从对象树构造平面数组

Posted

技术标签:

【中文标题】从对象树构造平面数组【英文标题】:Construct flat array from tree of objects 【发布时间】:2015-12-13 01:55:56 【问题描述】:

假设我有一棵像下面这样的对象树,可能是使用此处找到的优秀算法创建的:https://***.com/a/22367819/3123195


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

(特别是在本例中,该函数返回的数组作为 children 数组属性嵌套在一个空对象内。)

如何将其转换回平面数组?

【问题讨论】:

我发现了很多问题,询问如何从数组创建树,但没有一个可以从另一个方向转换回来,所以我发布了我想出的解决方案。 【参考方案1】:

希望你熟悉 es6:

let flatten = (children, extractChildren) => Array.prototype.concat.apply(
  children, 
  children.map(x => flatten(extractChildren(x) || [], extractChildren))
);

let extractChildren = x => x.children;

let flat = flatten(extractChildren(treeStructure), extractChildren)
               .map(x => delete x.children && x);

UPD:

抱歉,没有注意到您需要设置父级和级别。请在下面找到新功能:

let flatten = (children, getChildren, level, parent) => Array.prototype.concat.apply(
  children.map(x => ( ...x, level: level || 1, parent: parent || null )), 
  children.map(x => flatten(getChildren(x) || [], getChildren, (level || 1) + 1, x.id))
);

https://jsbin.com/socono/edit?js,console

【讨论】:

哇,我不敢相信那是多么少的代码,而且它工作得很好。 ES6 太棒了!哦,级别指示器不是必需的,我只是将它添加到我的实现中以进行说明。 是的,es6 和 next 很鼓舞人心!很高兴读到这个!此外,到目前为止,如果我们用匿名函数替换 lambda,第一个解决方案将是有效的 es5。【参考方案2】:

此功能将完成这项工作,此外它还为每个对象添加了一个级别指示器。 treeObj 的直接子级为 1 级,其子级为 2 级,依此类推。parent 属性也会更新。

function flatten(treeObj, idAttr, parentAttr, childrenAttr, levelAttr) 
    if (!idAttr) idAttr = 'id';
    if (!parentAttr) parentAttr = 'parent';
    if (!childrenAttr) childrenAttr = 'children';
    if (!levelAttr) levelAttr = 'level';

    function flattenChild(childObj, parentId, level) 
        var array = []; 

        var childCopy = angular.extend(, childObj);
        childCopy[levelAttr] = level;
        childCopy[parentAttr] = parentId;
        delete childCopy[childrenAttr];
        array.push(childCopy);

        array = array.concat(processChildren(childObj, level));

        return array;
    ;

    function processChildren(obj, level) 
        if (!level) level = 0;
        var array = [];

        obj[childrenAttr].forEach(function(childObj) 
            array = array.concat(flattenChild(childObj, obj[idAttr], level+1));
        );

        return array;
    ;

    var result = processChildren(treeObj);
    return result;
;

此解决方案利用 Angular 的 angular.extend() 函数来执行子对象的副本。将其与任何其他库的等效方法或本机函数连接起来应该是一个微不足道的更改。

上面例子的输出是:

[
    "id": 1,
    "title": "home",
    "parent": null,
    "level": 1
, 
    "id": 2,
    "title": "about",
    "parent": null,
    "level": 1
, 
    "id": 3,
    "title": "team",
    "parent": 2,
    "level": 2
, 
    "id": 4,
    "title": "company",
    "parent": 2,
    "level": 2
]

另外值得注意的是,这个函数不保证数组会按id排序;它将基于在操作过程中遇到各个对象的顺序。

Fiddle!

【讨论】:

你应该调用你的函数 flattenChildrenOf 或类似的东西,因为它总是忽略根。没有什么说根不可能是一个重要的节点。 请记住,上述答案是递归的,因此可以改进。由于我找不到实现 O(n) 解决方案的 npm 模块,因此我创建了以下模块(单元测试,100% 代码覆盖,只有 0.5 kb 大小并且包括类型。也许它可以帮助某人:npmjs.com/package /performant-array-to-tree【参考方案3】:

这是我的贡献:

function flatNestedList(nestedList, childrenName, parentPropertyName, idName, newFlatList, parentId) 

        if (newFlatList.length === 0)
            newFlatList = [];

        $.each(nestedList, function (i, item) 
            item[parentPropertyName] = parentId;
            newFlatList.push(item);
            if (item[childrenName] && item[childrenName].length > 0) 
                //each level
                flatNestedList(item[childrenName], childrenName, parentPropertyName, idName, newFlatList, item[idName]);
            
        );

        for (var i in newFlatList)
            delete (newFlatList[i][childrenName]);
    

【讨论】:

【参考方案4】:

仅假设每个项目都具有子属性,请尝试遵循此操作

class TreeStructureHelper 
   public toArray(nodes: any[], arr: any[]) 
    if (!nodes) 
      return [];
    
    if (!arr) 
      arr = [];
    
    for (var i = 0; i < nodes.length; i++) 
      arr.push(nodes[i]);
      this.toArray(nodes[i].children, arr);
    
  return arr;
 

用法

 let treeNode =
 
   children: [
    id: 1,
    title: "home",
    parent: null,
    children: []
   , 
    id: 2,
    title: "about",
    parent: null,
    children: [
        id: 3,
        title: "team",
        parent: 2,
        children: []
     , 
        id: 4,
        title: "company",
        parent: 2,
        children: []
     ]
   ]
 ;
 let flattenArray = _treeStructureHelper.toArray([treeNode], []);

【讨论】:

【参考方案5】:

这是数据:

 const data = 
      id: '1',
      children: [
        
          id: '2',
          children: [
            
              id: '4',
              children: [
                
                  id: '5'
                ,
                
                  id: '6'
                
              ]
            ,
            
              id: '7'
            
          ]
        ,
        
          id: '3',
          children: [
            
              id: '8'
            ,
            
              id: '9'
            
          ]
        
      ]
    

在 React.JS 中,只需在 state 中声明一个数组字段并将项目推送到该数组。

  const getAllItemsPerChildren = item => 
    array.push(item);
    if (item.children) 
      return item.children.map(i => getAllItemsPerChildren(i));
    
  

函数调用后,您的数组状态将保存所有项目,如下所示:

【讨论】:

【参考方案6】:

再来一个??

function flatten(root, parent=null, depth=0, key='id', flat=[], pick=() => ) 
    flat.push(
        parent,
        [key]: root[key],
        depth: depth++,
        ...pick(root, parent, depth, key, flat)
    );
    
    if(Array.isArray(root.children)) 
        root.children.forEach(child => flatten(child, root[key], depth, key, flat, pick));
    


let sample = 
    "id": 0,
    "children": [
        "id": 1,
        "title": "home",
        "parent": null,
        "children": []
    , 
        "id": 2,
        "title": "about",
        "parent": null,
        "children": [
            "id": 3,
            "title": "team",
            "parent": 2,
            "children": []
        , 
            "id": 4,
            "title": "company",
            "parent": 2,
            "children": []
        ]
    ]
;

let flat = [];

flatten(sample, null, 0, 'id', flat, root => ( title: root.title ));

let expected = [
    
        "id": 0,
        "parent": null,
        "depth": 0
    ,
    
        "id": 1,
        "parent": 0,
        "depth": 1,
        "title": "home"
    ,
    
        "id": 2,
        "parent": 0,
        "depth": 1,
        "title": "about"
    ,
    
        "id": 3,
        "parent": 2,
        "depth": 2,
        "title": "team"
    ,
    
        "id": 4,
        "parent": 2,
        "depth": 2,
        "title": "company"
    
];

【讨论】:

以上是关于从对象树构造平面数组的主要内容,如果未能解决你的问题,请参考以下文章

从平面对象数组构建对象树数组[重复]

React Native,从对象或数组构造初始状态值

JavaScript - 从多个数组填充对象构造函数

将数组从另一个类的构造函数传递给类函数

从列表中构造树(非二进制,不平衡)的最低时间复杂度?

构造哈夫曼树的过程