Saya memiliki pohon yang dibuat dari kelas ini.
class Node
{
public string Key { get; }
public List<Node> Children { get; }
}
Saya ingin mencari pada semua anak dan semua anaknya untuk mendapatkan yang cocok dengan kondisi:
node.Key == SomeSpecialKey
Bagaimana cara menerapkannya?
Jawaban:
Ini adalah kesalahpahaman bahwa ini membutuhkan rekursi. Ini akan membutuhkan tumpukan atau antrian dan cara termudah adalah menerapkannya menggunakan rekursi. Demi kelengkapan, saya akan memberikan jawaban non-rekursif.
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); } }
Gunakan ekspresi ini misalnya untuk menggunakannya:
sumber
StackOverflowException
.Queue<Node>
(dengan perubahan yang sesuai keEnqueue
/Dequeue
dariPush
/Pop
).Mencari Pohon Objek dengan 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; } } }
sumber
head
danchildrenFunc
memecah metode menjadi dua bagian sehingga pemeriksaan parameter tidak ditangguhkan ke waktu traversal.Jika Anda ingin mempertahankan sintaks seperti Linq, Anda dapat menggunakan metode untuk mendapatkan semua turunan (anak + anak anak, dll.)
static class NodeExtensions { public static IEnumerable<Node> Descendants(this Node node) { return node.Children.Concat(node.Children.SelectMany(n => n.Descendants())); } }
Enumerable ini kemudian dapat di-query seperti yang lain menggunakan where atau first atau apapun.
sumber
Anda dapat mencoba metode ekstensi ini untuk menghitung simpul pohon:
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; } }
Kemudian gunakan itu dengan
Where()
klausa:var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);
sumber
Mungkin Anda hanya perlu
Atau, jika Anda perlu mencari satu tingkat lebih dalam,
Jika Anda perlu mencari di semua level, lakukan yang berikut:
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))); }
sumber
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; } }
Dan kemudian Anda dapat menelusuri seperti:
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");
sumber
Mengapa tidak menggunakan
IEnumerable<T>
metode ekstensipublic 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; } } }
lalu lakukan saja ini
var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);
sumber
Beberapa waktu yang lalu saya menulis artikel codeproject yang menjelaskan bagaimana menggunakan Linq untuk menanyakan struktur seperti pohon:
http://www.codeproject.com/KB/linq/LinqToTree.aspx
Ini menyediakan API gaya linq-ke-XML tempat Anda dapat mencari turunan, turunan, leluhur, dll ...
Mungkin berlebihan untuk masalah Anda saat ini, tetapi mungkin menarik bagi orang lain.
sumber
Anda dapat menggunakan metode ekstensi ini untuk menanyakan pohon.
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; }
sumber
Saya memiliki metode ekstensi generik yang dapat meratakan apa saja
IEnumerable<T>
dan dari koleksi yang diratakan itu, Anda bisa mendapatkan simpul yang Anda inginkan.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; } } } }
Gunakan ini seperti ini:
var q = from node in myTree.FlattenHierarchy(x => x.Children) where node.Key == "MyKey" select node; var theNode = q.SingleOrDefault();
sumber
Saya menggunakan implementasi berikut untuk menghitung item Pohon
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 dalam implementasi di atas menggunakan antrian urutan node, bukan antrian node. Ini bukan cara algoritma BFS klasik.
sumber
Dan hanya untuk bersenang-senang (hampir satu dekade kemudian) jawaban juga menggunakan Generik tetapi dengan loop Stack dan While, berdasarkan jawaban yang diterima oleh @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); } } }
Diberikan koleksi seseorang dapat menggunakan seperti ini
var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
atau dengan objek root
var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
sumber