使用 LINQ 搜索树

我用这个类创建了一个树。

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

我想搜索所有的孩子和他们的孩子,得到符合条件的:

node.Key == SomeSpecialKey

我如何实现它?

46667 次浏览

Perhaps you need just

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

Or, if you need to search one level deeper,

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

If you need to search on all levels, take the following:

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

You can try this extension method to enumerate the tree nodes:

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

Then use that with a Where() clause:

var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);

Searching a Tree of Objects with 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;
}


}
}

A while back I wrote a codeproject article which describes how to use Linq to query tree-like structures:

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

This provides a linq-to-XML style API where you can search descendants, children, ancestors etc...

Probably overkill for your current problem, but might be of interest to others.

It's a misconception that this requires recursion. It will require a stack or a queue and the easiest way is to implement it using recursion. For sake of completeness I'll provide a non-recursive answer.

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

Use this expression for example to use it:

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

If you want to maintain Linq like syntax, you can use a method to obtain all the descendants (children + children's children etc.)

static class NodeExtensions
{
public static IEnumerable<Node> Descendants(this Node node)
{
return node.Children.Concat(node.Children.SelectMany(n => n.Descendants()));
}
}

This enumerable can then be queried like any other using where or first or whatever.

You can use this extension method to query the tree.

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

I have a generic extension method that can flatten any IEnumerable<T> and from that flattened collection, you can get the node you want.

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

Use this like this:

var q = from node in myTree.FlattenHierarchy(x => x.Children)
where node.Key == "MyKey"
select node;
var theNode = q.SingleOrDefault();
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;
}
}

And then you can search like:

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

Why not use an IEnumerable<T> extension method

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

then just do this

var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);

I use the following implementations for enumerating Tree items

    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 in implementation above uses queue of node sequences instead of nodes queue. This is not classic BFS algorithm way.

And just for fun (almost a decade later) an answer also using Generics but with a Stack and While loop, based off the accepted answer by @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);
}
}
}

Given a collection one can use like this

        var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);

or with a root object

        var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);