Как эффективно построить дерево из плоской конструкции?


153

У меня есть куча объектов в плоской структуре. Эти объекты имеют IDи в ParentIDсобственность , чтобы они могли быть расположены на деревьях. Они не в определенном порядке. Каждое ParentIDсвойство не обязательно совпадает с IDв структуре. Поэтому их может быть несколько деревьев, выходящих из этих объектов.

Как бы вы обработали эти объекты для создания результирующих деревьев?

Я не так далек от решения, но уверен, что оно далеко от оптимального ...

Мне нужно создать эти деревья, чтобы затем вставить данные в базу данных в правильном порядке.

Там нет циркулярных ссылок. Узел является RootNode, когда ParentID == null или когда ParentID не может быть найден в других объектах


Что вы подразумеваете под «создавать»? Визуализировать в пользовательском интерфейсе? Хранить иерархически в XML или в базе данных?
RedFilter

Как вы определяете узел без родителя (то есть корневой узел). ParentID имеет значение null? ParentID = 0? Я предполагаю, что нет циклических ссылок правильно?
Джейсон Пуньон

5
Я нахожу этот вопрос довольно крутым.
nes1983

1
проверить эту статью: scip.be/index.php?Page=ArticlesNET23&Lang=NL
Эбрам Халил

Ответы:


120

Сохраните идентификаторы объектов в хэш-таблице, сопоставленной с конкретным объектом. Перечислите все объекты и найдите их родителей, если они существуют, и соответственно обновите указатель их родителей.

class MyObject
{ // The actual object
    public int ParentID { get; set; }
    public int ID { get; set; }
}

class Node
{
    public List<Node> Children = new List<Node>();
    public Node Parent { get; set; }
    public MyObject AssociatedObject { get; set; }
}

IEnumerable<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
{
    Dictionary<int, Node> lookup = new Dictionary<int, Node>();
    actualObjects.ForEach(x => lookup.Add(x.ID, new Node { AssociatedObject = x }));
    foreach (var item in lookup.Values) {
        Node proposedParent;
        if (lookup.TryGetValue(item.AssociatedObject.ParentID, out proposedParent)) {
            item.Parent = proposedParent;
            proposedParent.Children.Add(item);
        }
    }
    return lookup.Values.Where(x => x.Parent == null);
}

5
какой это язык? (Я так понимаю, C #)
Джейсон С

3
Этот алгоритм представляет собой (в неформальной записи) O (3N), где решение O (1N) легко достижимо либо путем создания экземпляров частичных узлов для непересекаемых родителей, либо путем сохранения вторичных таблиц поиска для детей, не являющихся экземплярами. родители. Вероятно, не имеет значения для большинства реальных применений, но оно может быть значительным для больших наборов данных.
Эндрю Ханлон

15
@AndrewHanlon, может быть, вы должны опубликовать Sol за 0 (1N)
Ced

1
Ответ @Ced Martin Schmidt ниже очень близок к тому, как бы я его реализовал. Как видно, он использует один цикл, а остальные являются хэш-операциями.
Эндрю Хэнлон

26
O (3N) это просто O (N);)
JakeWilson801

34

Основываясь на ответе Мехрдада Афшари и комментариях Эндрю Хэнлона для ускорения, вот мое мнение.

Важное отличие от исходной задачи: корневой узел имеет идентификатор == parentID.

class MyObject
{   // The actual object
    public int ParentID { get; set; }
    public int ID { get; set; }
}

class Node
{
    public List<Node> Children = new List<Node>();
    public Node Parent { get; set; }
    public MyObject Source { get; set; }
}

List<Node> BuildTreeAndGetRoots(List<MyObject> actualObjects)
{
    var lookup = new Dictionary<int, Node>();
    var rootNodes = new List<Node>();

    foreach (var item in actualObjects)
    {
        // add us to lookup
        Node ourNode;
        if (lookup.TryGetValue(item.ID, out ourNode))
        {   // was already found as a parent - register the actual object
            ourNode.Source = item;
        }
        else
        {
            ourNode = new Node() { Source = item };
            lookup.Add(item.ID, ourNode);
        }

        // hook into parent
        if (item.ParentID == item.ID)
        {   // is a root node
            rootNodes.Add(ourNode);
        }
        else
        {   // is a child row - so we have a parent
            Node parentNode;
            if (!lookup.TryGetValue(item.ParentID, out parentNode))
            {   // unknown parent, construct preliminary parent
                parentNode = new Node();
                lookup.Add(item.ParentID, parentNode);
            }
            parentNode.Children.Add(ourNode);
            ourNode.Parent = parentNode;
        }
    }

    return rootNodes;
}

1
Хорошо, это в основном подход, о котором я говорил. Однако я бы просто использовал псевдо-корневой узел (с ID = 0 и нулевым Parent) и удалил требование самоссылки.
Эндрю Хэнлон,

Единственное, чего не хватает в этом примере, - это назначить родительское поле каждому дочернему узлу. Для этого нам нужно установить поле «Родитель» только после добавления детей в его «Родительскую коллекцию». Вот так: parentNode.Children.Add (ourNode); ourNode.Parent = parentNode;
Плауриола

@plauriola Правда, спасибо, я добавил это. Альтернативой может быть просто удалить свойство Parent, это не обязательно для основного алгоритма.
Мартин Шмидт

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

32

Вот простой алгоритм JavaScript для разбора плоской таблицы на структуру родительского / дочернего дерева, которая выполняется за N раз:

var table = [
    {parent_id: 0, id: 1, children: []},
    {parent_id: 0, id: 2, children: []},
    {parent_id: 0, id: 3, children: []},
    {parent_id: 1, id: 4, children: []},
    {parent_id: 1, id: 5, children: []},
    {parent_id: 1, id: 6, children: []},
    {parent_id: 2, id: 7, children: []},
    {parent_id: 7, id: 8, children: []},
    {parent_id: 8, id: 9, children: []},
    {parent_id: 3, id: 10, children: []}
];

var root = {id:0, parent_id: null, children: []};
var node_list = { 0 : root};

for (var i = 0; i < table.length; i++) {
    node_list[table[i].id] = table[i];
    node_list[table[i].parent_id].children.push(node_list[table[i].id]);
}

console.log(root);

пытаясь преобразовать этот подход в C #.
Hakan

понял , что если идентификатор начинается с чего - то большого , как 1001 , то мы получаем индекс из связанного исключения ...
Hakan

2
Совет: используйте, console.log(JSON.stringify(root, null, 2));чтобы красиво распечатать результат.
aloisdg переходит на codidact.com

14

Решение Python

def subtree(node, relationships):
    return {
        v: subtree(v, relationships) 
        for v in [x[0] for x in relationships if x[1] == node]
    }

Например:

# (child, parent) pairs where -1 means no parent    
flat_tree = [
     (1, -1),
     (4, 1),
     (10, 4),
     (11, 4),
     (16, 11),
     (17, 11),
     (24, 17),
     (25, 17),
     (5, 1),
     (8, 5),
     (9, 5),
     (7, 9),
     (12, 9),
     (22, 12),
     (23, 12),
     (2, 23),
     (26, 23),
     (27, 23),
     (20, 9),
     (21, 9)
    ]

subtree(-1, flat_tree)

Производит:

{
    "1": {
        "4": {
            "10": {}, 
            "11": {
                "16": {}, 
                "17": {
                    "24": {}, 
                    "25": {}
                }
            }
        }, 
        "5": {
            "8": {}, 
            "9": {
                "20": {}, 
                "12": {
                    "22": {}, 
                    "23": {
                        "2": {}, 
                        "27": {}, 
                        "26": {}
                    }
                }, 
                "21": {}, 
                "7": {}
            }
        }
    }
}

Здравствуй. Как добавить еще один атрибут в вывод? то есть. имя, parent_id
простой парень

безусловно, самый элегантный!
ccpizza

@simpleguy: понимание списка можно развернуть, если вам нужен больший контроль, например:def recurse(id, pages): for row in rows: if row['id'] == id: print(f'''{row['id']}:{row['parent_id']} {row['path']} {row['title']}''') recurse(row['id'], rows)
ccpizza

8

Версия JS, которая возвращает один корень или массив корней, каждый из которых будет иметь свойство массива Children, содержащее связанные дочерние элементы. Не зависит от упорядоченного ввода, прилично компактен и не использует рекурсию. Наслаждайтесь!

// creates a tree from a flat set of hierarchically related data
var MiracleGrow = function(treeData, key, parentKey)
{
    var keys = [];
    treeData.map(function(x){
        x.Children = [];
        keys.push(x[key]);
    });
    var roots = treeData.filter(function(x){return keys.indexOf(x[parentKey])==-1});
    var nodes = [];
    roots.map(function(x){nodes.push(x)});
    while(nodes.length > 0)
    {

        var node = nodes.pop();
        var children =  treeData.filter(function(x){return x[parentKey] == node[key]});
        children.map(function(x){
            node.Children.push(x);
            nodes.push(x)
        });
    }
    if (roots.length==1) return roots[0];
    return roots;
}


// demo/test data
var treeData = [

    {id:9, name:'Led Zep', parent:null},
    {id:10, name:'Jimmy', parent:9},
    {id:11, name:'Robert', parent:9},
    {id:12, name:'John', parent:9},

    {id:8, name:'Elec Gtr Strings', parent:5},
    {id:1, name:'Rush', parent:null},
    {id:2, name:'Alex', parent:1},
    {id:3, name:'Geddy', parent:1},
    {id:4, name:'Neil', parent:1},
    {id:5, name:'Gibson Les Paul', parent:2},
    {id:6, name:'Pearl Kit', parent:4},
    {id:7, name:'Rickenbacker', parent:3},

    {id:100, name:'Santa', parent:99},
    {id:101, name:'Elf', parent:100},

];
var root = MiracleGrow(treeData, "id", "parent")
console.log(root)

2
На этот вопрос уже 7 лет, и на него уже проголосовал и принят ответ. Если вы думаете, что у вас есть лучшее решение, было бы замечательно добавить некоторые пояснения к вашему коду.
Джорди Небот

Этот подход хорошо работает для этого неупорядоченного типа данных.
Коди C

4

Нашел отличную версию JavaScript здесь: http://oskarhane.com/create-a-nested-array-recursively-in-javascript/

Допустим, у вас есть такой массив:

const models = [
    {id: 1, title: 'hello', parent: 0},
    {id: 2, title: 'hello', parent: 0},
    {id: 3, title: 'hello', parent: 1},
    {id: 4, title: 'hello', parent: 3},
    {id: 5, title: 'hello', parent: 4},
    {id: 6, title: 'hello', parent: 4},
    {id: 7, title: 'hello', parent: 3},
    {id: 8, title: 'hello', parent: 2}
];

И вы хотите, чтобы объекты были вложены так:

const nestedStructure = [
    {
        id: 1, title: 'hello', parent: 0, children: [
            {
                id: 3, title: 'hello', parent: 1, children: [
                    {
                        id: 4, title: 'hello', parent: 3, children: [
                            {id: 5, title: 'hello', parent: 4},
                            {id: 6, title: 'hello', parent: 4}
                        ]
                    },
                    {id: 7, title: 'hello', parent: 3}
                ]
            }
        ]
    },
    {
        id: 2, title: 'hello', parent: 0, children: [
            {id: 8, title: 'hello', parent: 2}
        ]
    }
];

Вот рекурсивная функция, которая делает это возможным.

function getNestedChildren(models, parentId) {
    const nestedTreeStructure = [];
    const length = models.length;

    for (let i = 0; i < length; i++) { // for-loop for perf reasons, huge difference in ie11
        const model = models[i];

        if (model.parent == parentId) {
            const children = getNestedChildren(models, model.id);

            if (children.length > 0) {
                model.children = children;
            }

            nestedTreeStructure.push(model);
        }
    }

    return nestedTreeStructure;
}

Usuage:

const models = [
    {id: 1, title: 'hello', parent: 0},
    {id: 2, title: 'hello', parent: 0},
    {id: 3, title: 'hello', parent: 1},
    {id: 4, title: 'hello', parent: 3},
    {id: 5, title: 'hello', parent: 4},
    {id: 6, title: 'hello', parent: 4},
    {id: 7, title: 'hello', parent: 3},
    {id: 8, title: 'hello', parent: 2}
];
const nestedStructure = getNestedChildren(models, 0);

Для каждого parentId это цикл всей модели - это не O (N ^ 2)?
Эд Рэндалл

4

Для всех, кто интересуется версией решения Юджина на C #, обратите внимание, что node_list доступен как карта, поэтому используйте вместо него словарь.

Имейте в виду, что это решение работает, только если таблица отсортирована по parent_id .

var table = new[]
{
    new Node { parent_id = 0, id = 1 },
    new Node { parent_id = 0, id = 2 },
    new Node { parent_id = 0, id = 3 },
    new Node { parent_id = 1, id = 4 },
    new Node { parent_id = 1, id = 5 },
    new Node { parent_id = 1, id = 6 },
    new Node { parent_id = 2, id = 7 },
    new Node { parent_id = 7, id = 8 },
    new Node { parent_id = 8, id = 9 },
    new Node { parent_id = 3, id = 10 },
};

var root = new Node { id = 0 };
var node_list = new Dictionary<int, Node>{
    { 0, root }
};

foreach (var item in table)
{
    node_list.Add(item.id, item);
    node_list[item.parent_id].children.Add(node_list[item.id]);
}

Узел определяется следующим образом.

class Node
{
    public int id { get; set; }
    public int parent_id { get; set; }
    public List<Node> children = new List<Node>();
}

1
Он слишком старый, но пункт 8 списка new Node { parent_id = 7, id = 9 },не node_list.Add(item.id, item);может быть завершен, потому что ключ не может повторяться; это опечатка; поэтому вместо id = 9 введите id = 8
Марсело Скофано

Исправлена. Спасибо @MarceloScofano!
Джоэл Мэлоун

3

Я написал общее решение на C #, основанное на ответе @Mehrdad Afshari:

void Example(List<MyObject> actualObjects)
{
  List<TreeNode<MyObject>> treeRoots = actualObjects.BuildTree(obj => obj.ID, obj => obj.ParentID, -1);
}

public class TreeNode<T>
{
  public TreeNode(T value)
  {
    Value = value;
    Children = new List<TreeNode<T>>();
  }

  public T Value { get; private set; }
  public List<TreeNode<T>> Children { get; private set; }
}

public static class TreeExtensions
{
  public static List<TreeNode<TValue>> BuildTree<TKey, TValue>(this IEnumerable<TValue> objects, Func<TValue, TKey> keySelector, Func<TValue, TKey> parentKeySelector, TKey defaultKey = default(TKey))
  {
    var roots = new List<TreeNode<TValue>>();
    var allNodes = objects.Select(overrideValue => new TreeNode<TValue>(overrideValue)).ToArray();
    var nodesByRowId = allNodes.ToDictionary(node => keySelector(node.Value));

    foreach (var currentNode in allNodes)
    {
      TKey parentKey = parentKeySelector(currentNode.Value);
      if (Equals(parentKey, defaultKey))
      {
        roots.Add(currentNode);
      }
      else
      {
        nodesByRowId[parentKey].Children.Add(currentNode);
      }
    }

    return roots;
  }
}

Пожалуйста, прокомментируйте. Я буду рад узнать, что я сделал не так.
HuBeZa

2

Вот Java-решение ответа Мехрдада Афшари.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class Tree {

    Iterator<Node> buildTreeAndGetRoots(List<MyObject> actualObjects) {
        Map<Integer, Node> lookup = new HashMap<>();
        actualObjects.forEach(x -> lookup.put(x.id, new Node(x)));
        //foreach (var item in lookup.Values)
        lookup.values().forEach(item ->
                {
                    Node proposedParent;
                    if (lookup.containsKey(item.associatedObject.parentId)) {
                        proposedParent = lookup.get(item.associatedObject.parentId);
                        item.parent = proposedParent;
                        proposedParent.children.add(item);
                    }
                }
        );
        //return lookup.values.Where(x =>x.Parent ==null);
        return lookup.values().stream().filter(x -> x.parent == null).iterator();
    }

}

class MyObject { // The actual object
    public int parentId;
    public int id;
}

class Node {
    public List<Node> children = new ArrayList<Node>();
    public Node parent;
    public MyObject associatedObject;

    public Node(MyObject associatedObject) {
        this.associatedObject = associatedObject;
    }
}

Вы должны немного объяснить, что вы думаете о коде.
Зиад Акики

Это просто Java-перевод предыдущего ответа
Вимал Бхатт

1

Мне кажется, что вопрос неопределенный, я бы, вероятно, создал карту из идентификатора фактического объекта. В псевдо-Java (я не проверял, работает ли он / компилируется), это может быть что-то вроде:

Map<ID, FlatObject> flatObjectMap = new HashMap<ID, FlatObject>();

for (FlatObject object: flatStructure) {
    flatObjectMap.put(object.ID, object);
}

И посмотреть каждого родителя:

private FlatObject getParent(FlatObject object) {
    getRealObject(object.ParentID);
}

private FlatObject getRealObject(ID objectID) {
    flatObjectMap.get(objectID);
}

Повторно используя getRealObject(ID)и делая карту из объекта в коллекцию объектов (или их идентификаторов), вы также получаете карту parent-> children.


1

Я могу сделать это за 4 строки кода и за O (n log n), предполагая, что Dictionary - это что-то вроде TreeMap.

dict := Dictionary new.
ary do: [:each | dict at: each id put: each].
ary do: [:each | (dict at: each parent) addChild: each].
root := dict at: nil.

РЕДАКТИРОВАТЬ : Хорошо, и теперь я прочитал, что некоторые родительские идентификаторы являются поддельными, так что забудьте об этом и сделайте следующее:

dict := Dictionary new.
dict at: nil put: OrderedCollection new.
ary do: [:each | dict at: each id put: each].
ary do: [:each | 
    (dict at: each parent ifAbsent: [dict at: nil]) 
          add: each].
roots := dict at: nil.

1

Большинство ответов предполагают, что вы хотите сделать это за пределами базы данных. Если ваши деревья относительно статичны по своей природе и вам просто нужно каким-то образом отобразить деревья в базу данных, вы можете рассмотреть возможность использования представлений вложенных множеств на стороне базы данных. Проверьте книги Джо Селко (или здесь для обзора Селко). При любом подходе вы можете полностью пропустить отображение деревьев, прежде чем загружать данные в базу данных. Просто подумал, что я предложу это в качестве альтернативы, это может быть совершенно не подходит для ваших конкретных потребностей. Вся часть «правильного порядка» исходного вопроса в некоторой степени подразумевает, что вам нужен порядок, чтобы быть «правильным» в БД по какой-то причине? Это может подтолкнуть меня к обработке деревьев там же.

Если вы все равно привязаны к Oracle dbs, проверьте их CONNECT BY на подходы прямого SQL.


1

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

Мой ответ - для отображения плоской структуры в дереве непосредственно на объекте, где все, что у вас есть, - ParentIDна каждом объекте. ParentIDесть nullили 0если это корень. Напротив спрашивающего, я предполагаю, что все действительные ParentIDуказывают на что-то еще в списке:

var rootNodes = new List<DTIntranetMenuItem>();
var dictIntranetMenuItems = new Dictionary<long, DTIntranetMenuItem>();

//Convert the flat database items to the DTO's,
//that has a list of children instead of a ParentID.
foreach (var efIntranetMenuItem in flatIntranetMenuItems) //List<tblIntranetMenuItem>
{
    //Automapper (nuget)
    DTIntranetMenuItem intranetMenuItem =
                                   Mapper.Map<DTIntranetMenuItem>(efIntranetMenuItem);
    intranetMenuItem.Children = new List<DTIntranetMenuItem>();
    dictIntranetMenuItems.Add(efIntranetMenuItem.ID, intranetMenuItem);
}

foreach (var efIntranetMenuItem in flatIntranetMenuItems)
{
    //Getting the equivalent object of the converted ones
    DTIntranetMenuItem intranetMenuItem = dictIntranetMenuItems[efIntranetMenuItem.ID];

    if (efIntranetMenuItem.ParentID == null || efIntranetMenuItem.ParentID <= 0)
    {
        rootNodes.Add(intranetMenuItem);
    }
    else
    {
        var parent = dictIntranetMenuItems[efIntranetMenuItem.ParentID.Value];
        parent.Children.Add(intranetMenuItem);
        //intranetMenuItem.Parent = parent;
    }
}
return rootNodes;

1

вот реализация ruby:

Он будет каталогизирован по имени атрибута или результату вызова метода.

CatalogGenerator = ->(depth) do
  if depth != 0
    ->(hash, key) do
      hash[key] = Hash.new(&CatalogGenerator[depth - 1])
    end
  else
    ->(hash, key) do
      hash[key] = []
    end
  end
end

def catalog(collection, root_name: :root, by:)
  method_names = [*by]
  log = Hash.new(&CatalogGenerator[method_names.length])
  tree = collection.each_with_object(log) do |item, catalog|
    path = method_names.map { |method_name| item.public_send(method_name)}.unshift(root_name.to_sym)
  catalog.dig(*path) << item
  end
  tree.with_indifferent_access
end

 students = [#<Student:0x007f891d0b4818 id: 33999, status: "on_hold", tenant_id: 95>,
 #<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
 #<Student:0x007f891d0b42c8 id: 37220, status: "on_hold", tenant_id: 6>,
 #<Student:0x007f891d0b4020 id: 3444, status: "ready_for_match", tenant_id: 15>,
 #<Student:0x007f8931d5ab58 id: 25166, status: "in_partnership", tenant_id: 10>]

catalog students, by: [:tenant_id, :status]

# this would out put the following
{"root"=>
  {95=>
    {"on_hold"=>
      [#<Student:0x007f891d0b4818
        id: 33999,
        status: "on_hold",
        tenant_id: 95>]},
   6=>
    {"on_hold"=>
      [#<Student:0x007f891d0b4570 id: 7635, status: "on_hold", tenant_id: 6>,
       #<Student:0x007f891d0b42c8
        id: 37220,
        status: "on_hold",
        tenant_id: 6>]},
   15=>
    {"ready_for_match"=>
      [#<Student:0x007f891d0b4020
        id: 3444,
        status: "ready_for_match",
        tenant_id: 15>]},
   10=>
    {"in_partnership"=>
      [#<Student:0x007f8931d5ab58
        id: 25166,
        status: "in_partnership",
        tenant_id: 10>]}}}

1

Принятый ответ выглядит слишком сложным для меня, поэтому я добавляю его версии для Ruby и NodeJS

Предположим, что список плоских узлов имеет следующую структуру:

nodes = [
  { id: 7, parent_id: 1 },
  ...
] # ruby

nodes = [
  { id: 7, parentId: 1 },
  ...
] # nodeJS

Функции, которые превратят структуру плоского списка выше в дерево, выглядят следующим образом

для Ruby:

def to_tree(nodes)

  nodes.each do |node|

    parent = nodes.find { |another| another[:id] == node[:parent_id] }
    next unless parent

    node[:parent] = parent
    parent[:children] ||= []
    parent[:children] << node

  end

  nodes.select { |node| node[:parent].nil? }

end

для NodeJS:

const toTree = (nodes) => {

  nodes.forEach((node) => {

    const parent = nodes.find((another) => another.id == node.parentId)
    if(parent == null) return;

    node.parent = parent;
    parent.children = parent.children || [];
    parent.children = parent.children.concat(node);

  });

  return nodes.filter((node) => node.parent == null)

};

Я считаю, что проверка nullдолжна бытьundefined
Уллаури

@Ullauri null == undefined => trueв NodeJS
Hirurg103

1

один из элегантных способов сделать это - представить элементы в списке в виде строки, содержащей список родителей, разделенных точками, и, наконец, значение:

server.port=90
server.hostname=localhost
client.serverport=90
client.database.port=1234
client.database.host=localhost

При сборке дерева вы получите что-то вроде:

server:
  port: 90
  hostname: localhost
client:
  serverport=1234
  database:
    port: 1234
    host: localhost

У меня есть библиотека конфигурации, которая реализует эту конфигурацию переопределения (дерево) из аргументов командной строки (список). Алгоритм добавления одного элемента в список в дереве находится здесь .


0

Вы застряли, используя только эти атрибуты? Если нет, то было бы неплохо создать массив дочерних узлов, где вы можете циклически пройти через все эти объекты один раз, чтобы создать такие атрибуты. Оттуда выберите узел с детьми, но без родителей и итеративно постройте свое дерево сверху вниз.


0

Java-версия

// node
@Data
public class Node {
    private Long id;
    private Long parentId;
    private String name;
    private List<Node> children = new ArrayList<>();
}

// flat list to tree
List<Node> nodes = new ArrayList();// load nodes from db or network
Map<Long, Node> nodeMap = new HashMap();
nodes.forEach(node -> {
  if (!nodeMap.containsKey(node.getId)) nodeMap.put(node.getId, node);
  if (nodeMap.containsKey(node.getParentId)) {
    Node parent = nodeMap.get(node.getParentId);
    node.setParentId(parent.getId());
    parent.getChildren().add(node);
  }
});

// tree node
List<Node> treeNode = nodeMap .values().stream().filter(n -> n.getParentId() == null).collect(Collectors.toList());
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.