92

I have a tree created from this class.

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

I want to search in all children and all their children to get the ones matching a condition:

node.Key == SomeSpecialKey

How can I implement it?

Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
  • Interesting, I think you can accomplish this using the SelectMany function, Remember having to do somthing similar a while ago. – Jethro Aug 15 '11 at 08:26

12 Answers12

186

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)
ahsteele
  • 26,243
  • 28
  • 134
  • 248
vidstige
  • 12,492
  • 9
  • 66
  • 110
  • 32
    +1. And this method will continue to work when the tree is so deep that a recursive traversal would blow the call stack and cause a `StackOverflowException`. – LukeH Aug 15 '11 at 09:26
  • 3
    @LukeH Although it is useful to have alternatives like this for those situations, that would mean a very large tree. Unless you're tree is very deep, the recursive methods are normally simpler/more readable. – ForbesLindesay Aug 15 '11 at 16:44
  • 3
    @Tuskan: Using recursive iterators also has performance implications, see "The Cost of Iterators" section of http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx (admittedly the trees still need to be fairly deep for this to be noticeable). And, fwiw, I find vidstige's answer to be just as readable as the recursive answers here. – LukeH Aug 15 '11 at 16:51
  • 4
    Yeah, don't pick my solution because of performance. Readability is always first, unless proven a bottleneck. Although my solution is pretty straight forward, so I guess it's a matter of taste... I actually posted my answer merely as a complement to the recursive answers, but I'm glad people liked it. – vidstige Aug 15 '11 at 18:17
  • 12
    I think it's worth mentioning that the above presented solution performs a (last-child-first) depth-first search. If you wanted a (first-child-first) breadth-first search, you can change the type of the nodes collection to `Queue` (with the corresponding changes to `Enqueue`/`Dequeue` from `Push`/`Pop`). – Andrew Coonce Aug 19 '13 at 22:33
  • 1
    @vidstige Looking at your code, it is possible to create a non recursive Extension method that accepts an Action to be performed on node & Function to retrieve children of node. The only issue here is debugging, when you have exception other than Stack Overflow. You don't have call stack & state of local variables at each depth. For simpler logic it is readable, you are just iterating, but when you have further conditions to evaluate, it becomes difficult. How do you know depth at current iteration? Try printing tree with indention equal to depth and see how dirty code becomes. – Akash Kava Aug 20 '13 at 09:02
  • 1
    good question. I highly recommend decoupling code using the powerful enumerable syntax. Instead of putting everything in the same method - Use this method only for enumerating all children, then use e.g. Where() or foreach to process children further. Just like in the example code in my answer! This gives cleaner code and also better stack traces in case something goes wrong without any significant performance penalties. For printing with indentation I recommend yielding a new type e.g. NodeWidthDepth – vidstige Aug 20 '13 at 11:04
  • Is there any clean sane way to make this handle circular references? – Marie Sep 13 '18 at 16:17
  • 1
    Too late to edit, I was also curious if Descendants is a good name considering this method also returns the root? – Marie Sep 13 '18 at 16:25
  • @Marie yes, there is a clean way to handle circular graphs (rather than trees). Please post a new question for this to avoid confusion. You can ping me here and I'l try to answer. :-) – vidstige Sep 13 '18 at 16:30
  • As for the naming, you are correct - This was an oversight on my side. Do you have any suggestions for alternative names? – vidstige Sep 13 '18 at 16:31
  • 2
    I actually didnt realize the distinction between trees and graphs, no worries regarding that answer. As for naming I am not sure. I had considered `SelectNodes`, `TraverseTree`, and `Flatten`. I actually ended up leaving it and inversing the `where` as a `do ... where` to exclude the root node because it worked for my use-case. – Marie Sep 13 '18 at 18:10
16

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

    }
}
Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
CD..
  • 72,281
  • 25
  • 154
  • 163
  • 1
    +1 Solves the problem in general. The linked article provided a great explanation. – John Jesus Mar 16 '14 at 23:55
  • To be complete you need null checking on parameters `head` and `childrenFunc` to break the methods into two parts so the parameter checking is not deferred to traversal time. – ErikE Aug 19 '15 at 00:12
16

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.

ForbesLindesay
  • 10,482
  • 3
  • 47
  • 74
3

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);
dlev
  • 48,024
  • 5
  • 125
  • 132
  • 2
    Note that this technique is inefficient if the tree is deep and can throw an exception if the tree is very deep. – Eric Lippert Aug 15 '11 at 13:01
  • 1
    @Eric Good point. And welcome back from vacation? (It's hard to tell what with this internet thing spanning the globe.) – dlev Aug 15 '11 at 13:33
2

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);
Dean Chalk
  • 20,076
  • 6
  • 59
  • 90
1

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);
George Albertyn
  • 267
  • 4
  • 11
1

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)));
}
Vlad
  • 35,022
  • 6
  • 77
  • 199
  • Will that search the childrens childrens? – Jethro Aug 15 '11 at 08:28
  • I think this wont work, as this searches only on one level in the tree and doesn't do a complete tree traversal – lunactic Aug 15 '11 at 08:28
  • @Ufuk: the 1st line works only 1 level deep, the second only 2 levels deep. If you need to search on _all_ levels, you need a recursive function. – Vlad Aug 15 '11 at 08:32
1
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");
Varun Chatterji
  • 5,059
  • 1
  • 23
  • 24
  • Because the input of Find is Func myFunc you could use this method to filter by any other property that you might define in Node as well. For example in Node had a Name property and you wanted to find a Node by Name, you could just pass in p => p.Name == "Something" – Varun Chatterji Aug 15 '11 at 08:53
0

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.

Valentyn Zakharenko
  • 2,710
  • 1
  • 21
  • 47
0

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.

ColinE
  • 68,894
  • 15
  • 164
  • 232
0

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;
    }
BitKFu
  • 3,649
  • 3
  • 28
  • 43
0

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();
Mikael Östberg
  • 16,982
  • 6
  • 61
  • 79