使用LINQsearch树

我有一个从这个类创build的树。

class Node { public string Key { get; } public List<Node> Children { get; } } 

我想search所有的孩子和他们的孩子,以获得匹配的条件:

 node.Key == SomeSpecialKey 

我怎样才能实现它?

这是一个误解,这需要recursion。 这需要一个堆栈或队列,最简单的方法是使用recursion来实现它。 为了完整起见,我将提供一个非recursion的答案。

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

例如使用这个expression式来使用它:

 root.Descendants().Where(node => node.Key == SomeSpecialKey) 

用Linqsearch对象树

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

如果你想维护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); 

也许你需要

 node.Children.Where(child => child.Key == SomeSpecialKey) 

或者,如果您需要更深层次的search,

 node.Children.SelectMany( child => child.Children.Where(child => child.Key == SomeSpecialKey)) 

如果您需要在各个层面进行search,请采取以下措施:

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

然后你可以像这样search:

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

这不会没有recursion。

尝试这个:

 IEnumerable<Node> GetMatchingNodes(Node parent, string key) { var result = new List<Node>(); if(parent.Key == key) result.Add(parent) result.AddRange(parent.Children.Where(c => GetMatchingNodes(c, key))); return result; } 

这样称呼它:

 Node rootNode = ...; var allMatchingNodes = GetMatchingNodes(rootNode, key); 

我有一个通用的扩展方法,可以扁平化任何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(); 

后来我写了一篇描述如何使用Linq来查询树状结构的codeproject文章:

http://www.codeproject.com/KB/linq/LinqToTree.aspx

这提供了一个linq到XML风格的API,您可以search后代,子代,祖先等…

可能是为了解决目前的问题,但可能会引起其他人的兴趣。

您可以使用此扩展方法来查询树。

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

我使用以下实现来枚举树项目

  public static IEnumerable<Node> DepthFirstUnfold(this Node root) => ObjectAsEnumerable(root).Concat(root.Children.SelectMany(DepthFirstUnfold)); public static IEnumerable<UIMapTreeNode> 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使用节点序列队列而不是节点队列。 这不是经典的BFSalgorithm方式。