У меня есть дерево, созданное из этого класса.
class Node
{
public string Key { get; }
public List<Node> Children { get; }
}
Я хочу найти всех детей и всех их детей, чтобы получить те, которые соответствуют условию:
node.Key == SomeSpecialKey
Как я могу это реализовать?
Ответы:
Заблуждение, что это требует рекурсии. Для этого потребуется стек или очередь, и самый простой способ - реализовать это с помощью рекурсии. Для полноты картины я дам нерекурсивный ответ.
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); } }
Используйте это выражение, например, чтобы использовать его:
источник
StackOverflowException
.Queue<Node>
(с соответствующими изменениями наEnqueue
/Dequeue
изPush
/Pop
).Поиск в дереве объектов с помощью 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; } } }
источник
head
иchildrenFunc
разделение методов на две части, чтобы проверка параметров не откладывалась на время обхода.Если вы хотите сохранить синтаксис, подобный Linq, вы можете использовать метод для получения всех потомков (детей + детей и т. Д.)
static class NodeExtensions { public static IEnumerable<Node> Descendants(this Node node) { return node.Children.Concat(node.Children.SelectMany(n => n.Descendants())); } }
Затем это перечислимое число можно запросить, как и любое другое, с помощью where, first или чего угодно.
источник
Вы можете попробовать этот метод расширения для перечисления узлов дерева:
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; } }
Затем используйте это с
Where()
предложением:var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);
источник
Возможно вам нужно просто
Или, если вам нужно поискать на один уровень глубже,
Если вам нужно искать на всех уровнях, возьмите следующее:
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))); }
источник
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; } }
И тогда вы можете искать, например:
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");
источник
Почему бы не использовать
IEnumerable<T>
метод расширенияpublic 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; } } }
тогда просто сделай это
var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);
источник
Некоторое время назад я написал статью кода проекта, в которой описывается, как использовать Linq для запроса древовидных структур:
http://www.codeproject.com/KB/linq/LinqToTree.aspx
Это предоставляет API стиля linq-to-XML, где вы можете искать потомков, потомков, предков и т. Д.
Возможно, излишний для вашей текущей проблемы, но может быть интересен другим.
источник
Вы можете использовать этот метод расширения для запроса дерева.
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; }
источник
У меня есть общий метод расширения, который может сгладить любой объект,
IEnumerable<T>
и из этой сглаженной коллекции вы можете получить нужный узел.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; } } } }
Используйте это так:
var q = from node in myTree.FlattenHierarchy(x => x.Children) where node.Key == "MyKey" select node; var theNode = q.SingleOrDefault();
источник
Я использую следующие реализации для перечисления элементов дерева
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 в реализации выше использует очередь последовательностей узлов вместо очереди узлов. Это не классический способ алгоритма BFS.
источник
И просто для удовольствия (почти десять лет спустя) ответ также с использованием Generics, но с циклом Stack и While, основанный на принятом ответе @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); } } }
Учитывая коллекцию, можно использовать вот так
var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
или с корневым объектом
var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
источник