Mam drzewo utworzone z tej klasy.
class Node
{
public string Key { get; }
public List<Node> Children { get; }
}
Chcę przeszukać wszystkie dzieci i wszystkie ich dzieci, aby uzyskać te pasujące do warunku:
node.Key == SomeSpecialKey
Jak mogę to zaimplementować?
Odpowiedzi:
To błędne przekonanie, że wymaga to rekurencji. To będzie wymagać stos lub kolejkę i najprostszym sposobem jest wykonanie go za pomocą rekursji. Ze względu na kompletność podam nierekurencyjną odpowiedź.
static IEnumerable<Node> Descendants(this Node root) { var nodes = new Stack<Node>(new[] {root}); while (nodes.Any()) { Node node = nodes.Pop(); yield return node; foreach (var n in node.Children) nodes.Push(n); } }
Użyj tego wyrażenia na przykład, aby go użyć:
źródło
StackOverflowException
.Queue<Node>
(z odpowiednimi zmianami doEnqueue
/Dequeue
zPush
/Pop
).Przeszukiwanie drzewa obiektów za pomocą Linq
public static class TreeToEnumerableEx { public static IEnumerable<T> AsDepthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc) { yield return head; foreach (var node in childrenFunc(head)) { foreach (var child in AsDepthFirstEnumerable(node, childrenFunc)) { yield return child; } } } public static IEnumerable<T> AsBreadthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc) { yield return head; var last = head; foreach (var node in AsBreadthFirstEnumerable(head, childrenFunc)) { foreach (var child in childrenFunc(node)) { yield return child; last = child; } if (last.Equals(node)) yield break; } } }
źródło
head
ichildrenFunc
podzielić metody na dwie części, aby sprawdzanie parametrów nie zostało odroczone do czasu przejścia.Jeśli chcesz zachować składnię podobną do Linq, możesz użyć metody do uzyskania wszystkich potomków (dzieci + dzieci dzieci itp.)
static class NodeExtensions { public static IEnumerable<Node> Descendants(this Node node) { return node.Children.Concat(node.Children.SelectMany(n => n.Descendants())); } }
Ten wyliczalny można następnie przeszukiwać jak każdy inny, używając gdzie lub najpierw lub cokolwiek.
źródło
Możesz wypróbować tę metodę rozszerzenia, aby wyliczyć węzły drzewa:
static IEnumerable<Node> GetTreeNodes(this Node rootNode) { yield return rootNode; foreach (var childNode in rootNode.Children) { foreach (var child in childNode.GetTreeNodes()) yield return child; } }
Następnie użyj tego z
Where()
klauzulą:var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);
źródło
Być może potrzebujesz tylko
Jeśli chcesz przeszukać jeden poziom głębiej,
Jeśli chcesz wyszukiwać na wszystkich poziomach, wykonaj następujące czynności:
IEnumerable<Node> FlattenAndFilter(Node source) { List<Node> l = new List(); if (source.Key == SomeSpecialKey) l.Add(source); return l.Concat(source.Children.SelectMany(child => FlattenAndFilter(child))); }
źródło
public class Node { string key; List<Node> children; public Node(string key) { this.key = key; children = new List<Node>(); } public string Key { get { return key; } } public List<Node> Children { get { return children; } } public Node Find(Func<Node, bool> myFunc) { foreach (Node node in Children) { if (myFunc(node)) { return node; } else { Node test = node.Find(myFunc); if (test != null) return test; } } return null; } }
Następnie możesz wyszukiwać:
Node root = new Node("root"); Node child1 = new Node("child1"); Node child2 = new Node("child2"); Node child3 = new Node("child3"); Node child4 = new Node("child4"); Node child5 = new Node("child5"); Node child6 = new Node("child6"); root.Children.Add(child1); root.Children.Add(child2); child1.Children.Add(child3); child2.Children.Add(child4); child4.Children.Add(child5); child5.Children.Add(child6); Node test = root.Find(p => p.Key == "child6");
źródło
Dlaczego nie skorzystać z
IEnumerable<T>
metody rozszerzeniapublic static IEnumerable<TResult> SelectHierarchy<TResult>(this IEnumerable<TResult> source, Func<TResult, IEnumerable<TResult>> collectionSelector, Func<TResult, bool> predicate) { if (source == null) { yield break; } foreach (var item in source) { if (predicate(item)) { yield return item; } var childResults = SelectHierarchy(collectionSelector(item), collectionSelector, predicate); foreach (var childItem in childResults) { yield return childItem; } } }
to po prostu zrób to
var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);
źródło
Jakiś czas temu napisałem artykuł dotyczący codeproject, w którym opisano, jak używać Linq do wykonywania zapytań w strukturach drzewiastych:
http://www.codeproject.com/KB/linq/LinqToTree.aspx
Zapewnia to interfejs API w stylu linq-to-XML, w którym można wyszukiwać potomków, dzieci, przodków itp.
Prawdopodobnie przesada jak na twój obecny problem, ale może zainteresować innych.
źródło
Możesz użyć tej metody rozszerzenia, aby zapytać o drzewo.
public static IEnumerable<Node> InTree(this Node treeNode) { yield return treeNode; foreach (var childNode in treeNode.Children) foreach (var flattendChild in InTree(childNode)) yield return flattendChild; }
źródło
Mam ogólną metodę rozszerzenia, która może spłaszczyć dowolną
IEnumerable<T>
iz tej spłaszczonej kolekcji, możesz uzyskać żądany węzeł.public static IEnumerable<T> FlattenHierarchy<T>(this T node, Func<T, IEnumerable<T>> getChildEnumerator) { yield return node; if (getChildEnumerator(node) != null) { foreach (var child in getChildEnumerator(node)) { foreach (var childOrDescendant in child.FlattenHierarchy(getChildEnumerator)) { yield return childOrDescendant; } } } }
Użyj tego w ten sposób:
var q = from node in myTree.FlattenHierarchy(x => x.Children) where node.Key == "MyKey" select node; var theNode = q.SingleOrDefault();
źródło
Używam następujących implementacji do wyliczania elementów Tree
public static IEnumerable<Node> DepthFirstUnfold(this Node root) => ObjectAsEnumerable(root).Concat(root.Children.SelectMany(DepthFirstUnfold)); public static IEnumerable<Node> BreadthFirstUnfold(this Node root) { var queue = new Queue<IEnumerable<Node>>(); queue.Enqueue(ObjectAsEnumerable(root)); while (queue.Count != 0) foreach (var node in queue.Dequeue()) { yield return node; queue.Enqueue(node.Children); } } private static IEnumerable<T> ObjectAsEnumerable<T>(T obj) { yield return obj; }
BreadthFirstUnfold w powyższej implementacji używa kolejki sekwencji węzłów zamiast kolejki węzłów. To nie jest klasyczny sposób algorytmu BFS.
źródło
I dla zabawy (prawie dziesięć lat później) odpowiedź również przy użyciu Generics, ale z pętlą Stack and While, na podstawie zaakceptowanej odpowiedzi przez @vidstige.
public static class TypeExtentions { public static IEnumerable<T> Descendants<T>(this T root, Func<T, IEnumerable<T>> selector) { var nodes = new Stack<T>(new[] { root }); while (nodes.Any()) { T node = nodes.Pop(); yield return node; foreach (var n in selector(node)) nodes.Push(n); } } public static IEnumerable<T> Descendants<T>(this IEnumerable<T> encounter, Func<T, IEnumerable<T>> selector) { var nodes = new Stack<T>(encounter); while (nodes.Any()) { T node = nodes.Pop(); yield return node; if (selector(node) != null) foreach (var n in selector(node)) nodes.Push(n); } } }
Biorąc pod uwagę kolekcję, można z niej korzystać
var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
lub z obiektem root
var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
źródło