2015-09-16 4 views
3

Предположим, у меня есть дерево объектов, как в следующем, возможно, созданный с использованием отличный алгоритм здесь: https://stackoverflow.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 внутри пустого объекта.)

Как преобразовать его обратно в плоский массив?

+0

Я нашел много вопросов о том, как создать дерево из массива, но ни один преобразовывая назад в другом направлении, поэтому я разместил решение I придумал. –

ответ

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.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!

+1

Вы должны называть свою функцию 'flattenChildrenOf' или что-то подобное, так как она всегда игнорирует корень. Ничто не говорит, что корень не может быть важным узлом. – plalx

+0

Имейте в виду, что вышеупомянутый ответ рекурсивный и, следовательно, может быть улучшен. Так как я не мог найти модуль npm, который реализует решение O (n), я создал следующий (проверенный по модулю, 100% -ный охват кода, всего 0,5 килобайта) и включает в себя типизацию. Возможно, это помогает кому-то: npmjs.com/package/performant-array-to-tree –

2

Здесь идет мой вклад:

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]); 
    } 
2

Надежда ваши знакомы с 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:

К сожалению, не заметили что вам нужно установить паре nt и уровень. Вы можете найти новую функцию ниже:

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

+0

Ничего себе, я не могу поверить, как мало кода было, и он отлично работает. ES6 отлично! О, и индикатор уровня не был необходим, я просто добавил его к моей реализации для иллюстративные цели –

+0

Да, es6 и далее вдохновляют! Рад это прочесть! Кроме того, до сих пор первое решение будет действительным es5, если мы заменим lambdas анонимными функциями. – slowkot