Also habe ich einen einfachen Baum:
class MyNode
{
public MyNode Parent;
public IEnumerable<MyNode> Elements;
int group = 1;
}
Ich habe eine IEnumerable<MyNode>
. Ich möchte eine Liste aller MyNode
(einschließlich der Objekte des inneren Knotens ( Elements
)) als eine flache Liste erhalten Where
group == 1
. Wie mache ich so etwas über LINQ?
Elements
ist null oder leer?Antworten:
Sie können einen Baum wie folgt abflachen:
Anschließend können Sie filtern , indem Sie
group
mitWhere(...)
.Konvertieren Sie
Flatten
in eine Erweiterungsfunktion in einer statischen Klasse, um einige "Punkte für Stil" zu erhalten.Um mehr Punkte für einen "noch besseren Stil" zu erhalten, konvertieren Sie
Flatten
in eine generische Erweiterungsmethode, die einen Baum und eine Funktion verwendet, die Nachkommen von einem Knoten erzeugt:Rufen Sie diese Funktion folgendermaßen auf:
Wenn Sie das Abflachen lieber in der Vorbestellung als in der Nachbestellung bevorzugen, wechseln Sie die Seiten des
Concat(...)
.quelle
Concat
sollte seinnew[] {e}
, nichtnew[] {c}
(es würde dort nicht einmal kompilierenc
).c
. Verwendene
kompiliert nicht. Sie können auch hinzufügenif (e == null) return Enumerable.Empty<T>();
, um mit untergeordneten Nulllisten fertig zu werden.Das Problem mit der akzeptierten Antwort ist, dass es ineffizient ist, wenn der Baum tief ist. Wenn der Baum sehr tief ist, bläst er den Stapel. Sie können das Problem mithilfe eines expliziten Stapels lösen:
Unter der Annahme von n Knoten in einem Baum der Höhe h und einem Verzweigungsfaktor, der erheblich kleiner als n ist, ist diese Methode O (1) im Stapelraum, O (h) im Heapraum und O (n) in der Zeit. Der andere angegebene Algorithmus ist O (h) im Stapel, O (1) im Heap und O (nh) in der Zeit. Wenn der Verzweigungsfaktor im Vergleich zu n klein ist, liegt h zwischen O (lg n) und O (n), was zeigt, dass der naive Algorithmus eine gefährliche Menge an Stapel und eine große Zeit verwenden kann, wenn h nahe an n liegt.
Jetzt, da wir eine Durchquerung haben, ist Ihre Anfrage unkompliziert:
quelle
Traverse
alle Elemente aufrufen . Sie könnenTraverse
auch eine Sequenz ändern , um alle Elemente der Sequenz darauf zu übertragenstack
. Denken Sie daran,stack
ist "Elemente, die ich noch nicht durchlaufen habe". Oder Sie können eine "Dummy" -Wurzel erstellen, in der Ihre Sequenz aus untergeordneten Elementen besteht, und dann die Dummy-Wurzel durchlaufen.foreach (var child in current.Elements.Reverse())
Sie eine erwartete Abflachung. Insbesondere werden Kinder in der Reihenfolge angezeigt, in der sie erscheinen, und nicht das letzte Kind zuerst. Dies sollte in den meisten Fällen keine Rolle spielen, aber in meinem Fall musste die Abflachung in einer vorhersehbaren und erwarteten Reihenfolge erfolgen..Reverse
indem Sie dasStack<T>
gegen einQueue<T>
Reverse
ist, dass zusätzliche Iteratoren erstellt werden, was durch diesen Ansatz vermieden werden soll . @RubensFarias Das ErsetzenQueue
vonStack
Ergebnissen führt zu einer Durchquerung der Breite.Der Vollständigkeit halber hier die Kombination der Antworten von dasblinkenlight und Eric Lippert. Gerät getestet und alles. :-)
quelle
Aktualisieren:
Für Leute, die sich für das Verschachtelungsniveau (Tiefe) interessieren. Eines der guten Dinge bei der Implementierung eines expliziten Enumerator-Stacks ist, dass zu jedem Zeitpunkt (und insbesondere bei der Ausgabe des Elements)
stack.Count
die aktuelle Verarbeitungstiefe dargestellt wird. Wenn wir dies berücksichtigen und die C # 7.0-Wertetupel verwenden, können wir die Methodendeklaration einfach wie folgt ändern:und
yield
Aussage:Dann können wir die ursprüngliche Methode implementieren, indem wir einfach Folgendes anwenden
Select
:Original:
Überraschenderweise zeigte niemand (selbst Eric) den "natürlichen" iterativen Port einer rekursiven DFT vor der Bestellung. Hier ist es also:
quelle
e
jedes Mal wechseln, wenn Sie anrufenelementSelector
, um die Vorbestellung aufrechtzuerhalten. Wenn die Reihenfolge keine Rolle spielt, können Sie dann die Funktion ändern, um allee
einmal gestarteten Vorgänge zu verarbeiten ?Queue<T>
. Wie auch immer, die Idee hier ist, einen kleinen Stapel mit Enumeratoren zu behalten, der dem sehr ähnlich ist, was in der rekursiven Implementierung geschieht.Stack
würde zu einer Zick-Zack-Breite der ersten Durchquerung führen.Ich habe einige kleine Probleme mit den hier gegebenen Antworten gefunden:
Aufbauend auf den vorherigen Antworten und mit den folgenden Ergebnissen:
Und die Unit-Tests:
quelle
Falls jemand anderes dies findet, aber auch das Level kennen muss, nachdem er den Baum abgeflacht hat, erweitert dies Konamimans Kombination aus Dasblinkenlight und Eric Lipperts Lösungen:
quelle
Eine wirklich andere Option ist ein korrektes OO-Design.
Bitten Sie zB die
MyNode
, alle Abflachungen zurückzugeben.So was:
Jetzt können Sie den MyNode der obersten Ebene bitten, alle Knoten abzurufen.
Wenn Sie die Klasse nicht bearbeiten können, ist dies keine Option. Ansonsten könnte dies meiner Meinung nach einer separaten (rekursiven) LINQ-Methode vorgezogen werden.
Dies verwendet LINQ, daher denke ich, dass diese Antwort hier anwendbar ist;)
quelle
quelle
Kombinieren Sie die Antwort von Dave und Ivan Stoev, falls Sie die Verschachtelungsebene benötigen und die Liste "in der richtigen Reihenfolge" abgeflacht und nicht wie in der Antwort von Konamiman umgekehrt.
quelle
Aufbauend auf Konamimans Antwort und dem Kommentar, dass die Reihenfolge unerwartet ist, ist hier eine Version mit einem expliziten Sortierparameter:
Und ein Beispiel für die Verwendung:
quelle
Unten finden Sie den Code von Ivan Stoev mit der zusätzlichen Funktion, den Index jedes Objekts im Pfad zu ermitteln. ZB nach "Item_120" suchen:
würde das Element und ein int-Array [1,2,0] zurückgeben. Offensichtlich ist auch die Verschachtelungsebene als Länge des Arrays verfügbar.
quelle
Hier einige gebrauchsfertige Implementierung mit Queue und Rückgabe des Flatten-Baums zuerst an mich und dann an meine Kinder.
quelle
Hin und wieder versuche ich, dieses Problem zu lösen und eine eigene Lösung zu finden, die willkürlich tiefe Strukturen unterstützt (keine Rekursion), eine erste Durchquerung in der Breite durchführt und nicht zu viele LINQ-Abfragen missbraucht oder präventiv eine Rekursion für die Kinder ausführt. Nachdem ich mich in der .NET-Quelle umgesehen und viele Lösungen ausprobiert habe, habe ich endlich diese Lösung gefunden. Es war sehr nah an Ian Stoevs Antwort (deren Antwort ich erst jetzt gesehen habe), aber meine verwendet keine Endlosschleifen oder hat einen ungewöhnlichen Codefluss.
Ein Arbeitsbeispiel finden Sie hier .
quelle