Преобразование массива parent-child в дерево - программирование
Подтвердить что ты не робот

Преобразование массива parent-child в дерево

Может ли кто-нибудь помочь преобразовать следующий список объектов parent-child:

[
   {
      "name":"root",
      "_id":"root_id",
   },
   {
      "name":"a1",
      "parentAreaRef":{
         "id":"root_id",
      },
      "_id":"a1_id",
   },
   {
      "name":"a2",
      "parentAreaRef":{
         "id":"a1_id",
      },
      "_id":"a2_id",
   },
   {
      "name":"a3",
      "parentAreaRef":{
         "id":"a2_id",
      },
      "_id":"a3_id",
   },
   {
      "name":"b1",
      "parentAreaRef":{
         "id":"root_id",
      },
      "_id":"b1_id",
   },
   {
      "name":"b2",
      "parentAreaRef":{
         "id":"b1_id",
      },
      "_id":"b2_id",
   },
   {
      "name":"b3",
      "parentAreaRef":{
         "id":"b1_id",
      },
      "_id":"b3_id",
   }
]

в древовидную структуру, показывающую отношения родитель-потомок:

[
    {
        "name": "root",
        "_id":"root_id",
        "children": [
            {
                "name": "a1",
                "_id":"a1_id",
                "children" : [
                    {
                        "name" : "a2",
                        "_id":"a2_id",
                        "children" : [
                            {
                                "name" : "a3"
                                "_id":"a3_id"
                            }
                        ]
                    }
                ]
            }, 
            {
                "name": "b1",
                "_id":"b1_id",
                "children" : [
                    {
                        "name" : "b2"
                        "_id":"b2_id"
                    },
                    {
                        "name" : "b3"
                        "_id":"b3_id"
                    }
                ]
            }
        ]
    }
]

(Структура вывода - это массив, позволяющий использовать несколько корней, но если мы сможем получить решение, которое отлично справится с одним корнем).

Дерево вывода выглядит следующим образом:

root
  |
  -- a1
  |   |
  |   -- a2
  |       |
  |       -- a3
  | 
  -- b1
      |
      -- b2
      -- b3


Спасибо!

4b9b3361

Ответ 1

У меня есть решение, которое работает. Я могу дать вам подсказки, чтобы решить это. Хорошо, что ваши данные не содержат прямых ссылок на узлы. Таким образом, вы можете создать свое дерево всего за один проход через массив. Если вы заметили, вам нужно будет сначала пройти весь массив, чтобы создать карту идентификаторов для узлов.

Ваш алгоритм будет выглядеть следующим образом.

  • Создайте карту, которая сопоставляет идентификатор узлам. Это упростит поиск узлов.
  • Цикл через массив узлов.
  • Для каждого элемента.
    • Добавить запись на карту.
    • Добавьте свойство children (массив) к этому node.
    • Есть ли у элемента родительский элемент? Если нет, это должен быть корень, поэтому назначьте этот элемент корню дерева.
    • Этот элемент имеет родительский элемент, поэтому найдите родительский node, а затем добавьте этот текущий node в качестве дочернего элемента родительского node (добавьте его в массив children).

Это поможет вам решить проблему. Если у вас возникают определенные проблемы с этим алгоритмом, я могу указать, где находятся проблемы и как его решить, или опубликовать решение и объяснить, как я его решил.

UPDATE

Я посмотрел на решение, которое у вас есть. На самом деле вам не нужна рекурсия для этого, и вы можете сделать это итеративно, используя алгоритм, описанный выше. Вы также изменяете структуру на месте, что усложняет алгоритм. Но вы на правильном пути. Вот как я это решил:

var idToNodeMap = {}; //Keeps track of nodes using id as key, for fast lookup
var root = null; //Initially set our loop to null

//loop over data
data.forEach(function(datum) {

    //each node will have children, so let give it a "children" poperty
    datum.children = [];

    //add an entry for this node to the map so that any future children can
    //lookup the parent
    idToNodeMap[datum._id] = datum;

    //Does this node have a parent?
    if(typeof datum.parentAreaRef === "undefined") {
        //Doesn't look like it, so this node is the root of the tree
        root = datum;        
    } else {        
        //This node has a parent, so let look it up using the id
        parentNode = idToNodeMap[datum.parentAreaRef.id];

        //We don't need this property, so let delete it.
        delete datum.parentAreaRef;

        //Let add the current node as a child of the parent node.
        parentNode.children.push(datum);        
    }
});

Теперь root указывает на все дерево.

Fiddle.

В случае, когда массив элементов находится в произвольном порядке, вам сначала нужно инициализировать idToNodeMap. Остальная часть алгоритма остается более-менее одинаковой (за исключением строки, в которой вы храните node на карте, что не нужно, потому что вы сделали это уже в первом проходе):

var idToNodeMap = data.reduce(function(map, node) {
    map[node._id] = node;
    return map;
}, {});

Ответ 2

Я знаю это поздно, но я только что закончил этот алгоритм и, возможно, он может помочь другим людям, которые хотят решить одну и ту же проблему: http://jsfiddle.net/akerbeltz/9dQcn/

Хорошая вещь в том, что для оригинального объекта он не требует особого вида.

Если вам необходимо адаптировать его к вашим потребностям, измените следующие строки:

  1. Измените _id и parentAreaRef.id в зависимости от вашей структуры.

    if (String(tree[i]._id) === String(item.parentAreaRef.id)) {

  2. Измените parentAreaRef в зависимости от вашей структуры.

    if (tree[idx].parentAreaRef) buildTree(tree, tree.splice(idx, 1)[0])

Надеюсь, поможет!

ОБНОВИТЬ

Добавление кода здесь на основе комментария @Gerfried:

var buildTree = function(tree, item) {
    if (item) { // if item then have parent
        for (var i=0; i<tree.length; i++) { // parses the entire tree in order to find the parent
            if (String(tree[i]._id) === String(item.parentAreaRef.id)) { // bingo!
                tree[i].childs.push(item); // add the child to his parent
                break;
            }
            else buildTree(tree[i].childs, item); // if item doesn't match but tree have childs then parses childs again to find item parent
        }
    }
    else { // if no item then is a root item, multiple root items are supported
        var idx = 0;
        while (idx < tree.length)
            if (tree[idx].parentAreaRef) buildTree(tree, tree.splice(idx, 1)[0]) // if have parent then remove it from the array to relocate it to the right place
            else idx++; // if doesn't have parent then is root and move it to the next object
    }
}

for (var i=0; i<data.length; i++) { // add childs to every item
    data[i].childs = [];
}
buildTree(data);
console.log(data);

Спасибо!

Ответ 3

Я знаю, что я слишком поздно, но так как я только что закончил свой вклад в примерную реализацию того, как это можно сделать, я думал, что поделюсь им, так как он может оказаться полезным или вдохновить на альтернативное решение.

Реализация можно найти здесь: http://jsfiddle.net/sw_lasse/9wpHa/

Основная идея реализации сосредотачивается вокруг следующей рекурсивной функции:

// Get parent of node (recursive)
var getParent = function (rootNode, rootId) {

    if (rootNode._id === rootId)
        return rootNode;

    for (var i = 0; i < rootNode.children.length; i++) {
        var child = rootNode.children[i];
        if (child._id === rootId)
            return child;

        if (child.children.length > 0)
            var childResult = getParent(child, rootId);

        if (childResult != null) return childResult;
    }
    return null;
};

..., который используется для построения дерева.

Ответ 4

Вы можете использовать array-to-tree модуль из npm.

Ответ 5

Попробуйте:

   var obj = {};
   obj.rootElements = [];
   var currentRoot;
   var currentParent;
   for (s in a) {
       var t = a[s];
       var id = t._id;
       if (t.parentAreaRef) {
           var parentId = t.parentAreaRef.id;
           if (parentId == currentParent._id) {
               //add children
               if (!currentParent.children) {
                   currentParent.children = [];
               }
               currentParent.children.push(t);
           }
           else {
               addChildToParent(t, parentId);
           }

       }
       else // is root
       {
           currentRoot = t;
           currentParent = t;
           obj.rootElements.push(currentRoot);
       }
   }

   var t = currentRoot

   function addChildToParent(child, parentId, root) {
       for (p in a) {
           if (a[p]._id.toString() == parentId.toString()) {
               if (!a[p].children) {
                   a[p].children = [];
               }
               a[p].children.push(t);
           }
       }
   }

Ответ 6

В вашей строке

есть ошибка.
a[p].children.push(t);

Это должно быть

a[p].children.push(child);

Также я немного оптимизирую его:

var data = [{"id":1,"name":"X","parentId":null},{"id":2,"name":"Y","parentId":1},{"id":3,"name":"D","parentId":2},{"id":2,"name":"S","parentId":1},{"id":5,"name":"K","parentId":4}]
    var obj = {};
    obj.rootElements = [];
    for (i in data) {
        var _elem = data[i];
        if (_elem.parentId) {
            var _parentId = _elem.parentId;
            if (_parentId == _elem.id) {
                // check children, if false - add
                if (!_elem.children) {
                    _elem.children = [];
                }
                _elem.children.push(_elem);
            }
            else {
                addChildToParent(_elem, _parentId);
            }
        }
        else // is root
        {
            obj.rootElements.push(_elem);
        }
    }
    function addChildToParent(child, parentId, root) {
        for (j in data) {
            if (data[j].id.toString() == parentId.toString()) {
                if (!data[j].children) {
                    data[j].children = [];
                }
                data[j].children.push(child);
            }
        }
    }
    res.send(obj.rootElements); 

Ответ 7

Заимствуя логику кэширования из ответа Вивина Палиата, я создал функцию многократного использования для преобразования списка данных с дочерними и родительскими отношениями в дерево.

var data = [
  { "id" : "root"                     },
  { "id" : "a1",   "parentId" : "root", },
  { "id" : "a2",   "parentId" : "a1",   },
  { "id" : "a3",   "parentId" : "a2",   },
  { "id" : "b1",   "parentId" : "root", },
  { "id" : "b2",   "parentId" : "b1",   },
  { "id" : "b3",   "parentId" : "b1",   }
];
var options = {
  childKey  : 'id',
  parentKey : 'parentId'
};
var tree = walkTree(listToTree(data, options), pruneChildren);

document.body.innerHTML = '<pre>' + JSON.stringify(tree, null, 4) + '</pre>';

function listToTree(list, options) {
  options = options || {};
  var childKey    = options.childKey    || 'child';
  var parentKey   = options.parentKey   || 'parent';
  var childrenKey = options.childrenKey || 'children';
  var nodeFn      = options.nodeFn      || function(node, name, children) {
    return { name : name, children : children };
  };
  var nodeCache = {};
  return list.reduce(function(tree, node) {
    node[childrenKey] = [];
    nodeCache[node[childKey]] = node;
    if (typeof node[parentKey] === 'undefined' || node[parentKey] === '') {
      tree = nodeFn(node, node[childKey], node[childrenKey]);
    } else {
      parentNode = nodeCache[node[parentKey]];
      parentNode[childrenKey].push(nodeFn(node, node[childKey], node[childrenKey]));
    }
    return tree;
  }, {});
}

function walkTree(tree, visitorFn, parent) {
  if (visitorFn == null || typeof visitorFn !== 'function') {
    return tree;
  }
  visitorFn.call(tree, tree, parent);
  if (tree.children && tree.children.length > 0) {
    tree.children.forEach(function(child) {
      walkTree(child, visitorFn, tree);
    });
  }
  return tree;
}

function pruneChildren(node, parent) {
  if (node.children.length < 1) {
    delete node.children;
  }
}