Wie kann man einen Baum effizient aus einer flachen Struktur bauen?


153

Ich habe eine Reihe von Objekten in einer flachen Struktur. Diese Objekte haben eine IDund eine ParentIDEigenschaft, sodass sie in Bäumen angeordnet werden können. Sie sind in keiner bestimmten Reihenfolge. Jede ParentIDEigenschaft stimmt nicht unbedingt mit einer IDin der Struktur überein . Daher könnten mehrere Bäume aus diesen Objekten hervorgehen.

Wie würden Sie diese Objekte verarbeiten, um die resultierenden Bäume zu erstellen?

Ich bin nicht so weit von einer Lösung entfernt, aber ich bin sicher, dass sie alles andere als optimal ist ...

Ich muss diese Bäume erstellen, um dann Daten in der richtigen Reihenfolge in eine Datenbank einzufügen.

Es gibt keine Zirkelverweise. Ein Knoten ist ein RootNode, wenn ParentID == null ist oder wenn ParentID in den anderen Objekten nicht gefunden werden kann


Was meinst du mit "erstellen"? In einer Benutzeroberfläche rendern? Hierarchisch in XML oder einer Datenbank speichern?
RedFilter

Wie definieren Sie einen Knoten ohne übergeordneten Knoten (dh einen Stammknoten)? ParentID ist null? ParentID = 0? Ich nehme an, es gibt keine korrekten Zirkelverweise?
Jason Punyon

5
Ich finde diese Frage ziemlich cool.
nes1983

Antworten:


119

Speichern Sie die IDs der Objekte in einer Hash-Tabelle, die dem jeweiligen Objekt zugeordnet ist. Führen Sie eine Aufzählung aller Objekte durch und suchen Sie deren übergeordnetes Element, falls vorhanden, und aktualisieren Sie den übergeordneten Zeiger entsprechend.

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
Welche Sprache ist das? (Ich nehme es C #)
Jason S

3
Dieses Algo ist (in informeller Notation) O (3N), wobei eine O (1N) -Lösung leicht erreichbar ist, indem entweder Teilknoten für nicht "durchquerte" Eltern instanziiert werden oder indem sekundäre Nachschlagetabellen für Kinder ohne Instanziierung geführt werden Eltern. Wahrscheinlich spielt es für die meisten realen Anwendungen keine Rolle, aber es könnte bei großen Datenmengen von Bedeutung sein.
Andrew Hanlon

15
@ AndrewHanlon vielleicht solltest du das Sol für 0 (1N)
Ced

1
Die Antwort von @Ced Martin Schmidt unten ist sehr nahe daran, wie ich sie implementieren würde. Wie zu sehen ist, wird eine einzelne Schleife verwendet, und der Rest sind Hashtabellenoperationen.
Andrew Hanlon

26
O (3N) ist nur O (N);)
JakeWilson801

34

Basierend auf der Antwort von Mehrdad Afshari und dem Kommentar von Andrew Hanlon für eine Beschleunigung, hier ist meine Meinung.

Wichtiger Unterschied zur ursprünglichen Aufgabe: Ein Stammknoten hat die ID == 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
Schön, das ist im Grunde der Ansatz, auf den ich anspielte. Ich würde jedoch nur einen Pseudo-Root-Knoten (mit ID = 0 und null Parent) verwenden und die Selbstreferenzanforderung entfernen.
Andrew Hanlon

In diesem Beispiel fehlt nur das Zuweisen des übergeordneten Felds zu jedem untergeordneten Knoten. Dazu müssen wir das übergeordnete Feld erst festlegen, nachdem wir die untergeordneten Elemente zur übergeordneten Sammlung hinzugefügt haben. So: parentNode.Children.Add (ourNode); ourNode.Parent = parentNode;
Plauriola

@plauriola Richtig, danke, ich habe das hinzugefügt. Eine Alternative wäre, einfach die Parent-Eigenschaft zu entfernen, die für den Kernalgorithmus nicht erforderlich ist.
Martin Schmidt

4
Da ich kein npm-Modul finden konnte, das eine O (n) -Lösung implementiert, habe ich das folgende erstellt (Unit-getestet, 100% Codeabdeckung, nur 0,5 KB groß und enthält Typisierungen. Vielleicht hilft es jemandem: npmjs.com/package / Performant-Array-zu-Baum
Philip Stanislaus

31

Hier ist ein einfacher JavaScript-Algorithmus zum Parsen einer flachen Tabelle in eine übergeordnete / untergeordnete Baumstruktur, die in N-Zeit ausgeführt wird:

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);

versuchen, diesen Ansatz in C # umzuwandeln.
Hakan

erkannte, dass, wenn id von etwas Großem wie 1001 beginnt, wir Index aus gebundener Ausnahme bekommen ...
hakan

2
Tipp: Verwenden Sie console.log(JSON.stringify(root, null, 2));diese Option, um die Ausgabe hübsch zu drucken.
Aloisdg wechselt zu codidact.com

14

Python-Lösung

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

Beispielsweise:

# (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)

Produziert:

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

Hallo. Wie füge ich der Ausgabe ein weiteres Attribut hinzu? dh. name, parent_id
einfacher Typ

bei weitem das eleganteste!
ccpizza

@simpleguy: Das Listenverständnis kann entfaltet werden, falls Sie mehr Kontrolle benötigen, z. B.: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-Version, die eine Wurzel oder ein Array von Wurzeln zurückgibt, von denen jede eine untergeordnete Array-Eigenschaft enthält, die die zugehörigen untergeordneten Elemente enthält. Hängt nicht von der geordneten Eingabe ab, ist anständig kompakt und verwendet keine Rekursion. Genießen!

// 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
Diese Frage ist 7 Jahre alt und hat bereits eine abgestimmte und akzeptierte Antwort. Wenn Sie der Meinung sind, dass Sie eine bessere Lösung haben, ist es hilfreich, Ihrem Code eine Erklärung hinzuzufügen.
Jordi Nebot

Dieser Ansatz eignet sich gut für diesen ungeordneten Datentyp.
Cody C

4

Hier finden Sie eine großartige JavaScript-Version: http://oskarhane.com/create-a-nested-array-recursively-in-javascript/

Angenommen, Sie haben ein Array wie dieses:

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

Und Sie möchten, dass die Objekte wie folgt verschachtelt sind:

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

Hier ist eine rekursive Funktion, die dies ermöglicht.

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;
}

Verwendung:

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);

Für jede parentId wird das gesamte Modell wiederholt - ist dies nicht O (N ^ 2)?
Ed Randall

4

Beachten Sie für alle, die an einer C # -Version von Eugenes Lösung interessiert sind, dass auf node_list als Karte zugegriffen wird. Verwenden Sie stattdessen ein Wörterbuch.

Beachten Sie, dass diese Lösung nur funktioniert, wenn die Tabelle nach parent_id sortiert ist .

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

Der Knoten ist wie folgt definiert.

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

1
Es ist zu alt , aber die Liste Punkt 8 new Node { parent_id = 7, id = 9 },verhindert node_list.Add(item.id, item);abzuschließen , weil Schlüssel nicht wiederholen können; es ist ein Tippfehler; Anstatt also id = 9 , Typ - ID = 8
Marcelo Scofano

Fest. Danke @MarceloScofano!
Joel Malone

3

Ich habe eine generische Lösung in C # geschrieben, die lose auf der Antwort von @Mehrdad Afshari basiert:

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;
  }
}

Downwähler, bitte kommentieren. Ich werde froh sein zu wissen, was ich falsch gemacht habe.
HuBeZa

2

Hier ist eine Java-Lösung der Antwort von Mehrdad Afshari.

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;
    }
}

Sie sollten ein wenig erklären, was Ihre Idee hinter dem Code ist.
Ziad Akiki

Es ist nur eine Java-Übersetzung der früheren Antwort
Vimal Bhatt

1

Vage, wie mir die Frage erscheint, würde ich wahrscheinlich eine Karte von der ID zum eigentlichen Objekt erstellen. In Pseudo-Java (ich habe nicht überprüft, ob es funktioniert / kompiliert) könnte es so etwas sein wie:

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

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

Und um jeden Elternteil nachzuschlagen:

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

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

Durch Wiederverwenden getRealObject(ID)und Erstellen einer Zuordnung von Objekt zu einer Sammlung von Objekten (oder deren IDs) erhalten Sie auch eine übergeordnete> untergeordnete Zuordnung.


1

Ich kann dies in 4 Codezeilen und O (n log n) Zeit tun, vorausgesetzt, Dictionary ist so etwas wie eine 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.

BEARBEITEN : Ok, und jetzt habe ich gelesen, dass einige Eltern-IDs gefälscht sind. Vergessen Sie also das Obige und machen Sie Folgendes:

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

Bei den meisten Antworten wird davon ausgegangen, dass Sie dies außerhalb der Datenbank tun möchten. Wenn Ihre Bäume relativ statisch sind und Sie die Bäume nur irgendwie der Datenbank zuordnen müssen, sollten Sie möglicherweise verschachtelte Mengenrepräsentationen auf der Datenbankseite verwenden. Schauen Sie sich Bücher von Joe Celko an (oder hier für eine Übersicht von Celko).

Wenn sie ohnehin an Oracle dbs gebunden sind, überprüfen Sie deren CONNECT BY auf direkte SQL-Ansätze.

Bei beiden Ansätzen können Sie die Zuordnung der Bäume vollständig überspringen, bevor Sie die Daten in die Datenbank laden. Ich dachte nur, ich würde dies als Alternative anbieten, es könnte für Ihre spezifischen Bedürfnisse völlig ungeeignet sein. Der gesamte Teil der ursprünglichen Frage "richtige Reihenfolge" impliziert etwas, dass die Reihenfolge aus irgendeinem Grund in der Datenbank "korrekt" sein muss. Dies könnte mich dazu bringen, auch dort mit den Bäumen umzugehen.


1

Es ist nicht genau das gleiche, wonach der Fragesteller gesucht hat, aber ich hatte Schwierigkeiten, mich mit den hier angegebenen mehrdeutigen Antworten zu befassen, und ich denke immer noch, dass diese Antwort unter den Titel passt.

Meine Antwort besteht darin, eine flache Struktur einem Baum direkt auf dem Objekt zuzuordnen, in dem Sie nur ein ParentIDObjekt für jedes Objekt haben. ParentIDist nulloder 0wenn es eine Wurzel ist. Gegenüber dem Fragesteller gehe ich davon aus, dass alle gültigen ParentIDPunkte auf etwas anderes in der Liste verweisen:

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

Hier ist eine Ruby-Implementierung:

Es wird nach Attributname oder Ergebnis eines Methodenaufrufs katalogisiert.

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

Die akzeptierte Antwort erscheint mir viel zu komplex, daher füge ich eine Ruby- und eine NodeJS-Version hinzu

Angenommen, die Liste der flachen Knoten hat die folgende Struktur:

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

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

Die Funktionen, die die flache Listenstruktur oben in einen Baum verwandeln, sehen folgendermaßen aus

für 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

für 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)

};

Ich glaube, der Scheck für nullmuss sein fürundefined
Ullauri

@ Ullauri null == undefined => truein NodeJS
Hirurg103

1

Eine elegante Möglichkeit, dies zu tun, besteht darin, Elemente in der Liste als Zeichenfolge darzustellen, die eine durch Punkte getrennte Liste der Eltern und schließlich einen Wert enthält:

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

Wenn Sie einen Baum zusammenbauen, erhalten Sie Folgendes:

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

Ich habe eine Konfigurationsbibliothek , die diese Überschreibungskonfiguration (Baum) aus Befehlszeilenargumenten (Liste) implementiert. Der Algorithmus zum Hinzufügen eines einzelnen Elements zur Liste zu einem Baum ist hier .


0

Verwenden Sie nur diese Attribute? Wenn nicht, kann es hilfreich sein, ein Array von untergeordneten Knoten zu erstellen, in denen Sie alle diese Objekte einmal durchlaufen können, um solche Attribute zu erstellen. Wählen Sie von dort aus den Knoten mit Kindern, aber keinen Eltern aus, und erstellen Sie Ihren Baum iterativ von oben nach unten.


0

Java-Version

// 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());
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.