views:

664

answers:

6

When you want to recursively enumerate a hierarchical object, selecting some elements based on some criteria, there are numerous examples of techniques like "flattening" and then filtering using Linq : like those found here :

link text

But, when you are enumerating something like the Controls collection of a Form, or the Nodes collection of a TreeView, I have been unable to use these types of techniques because they seem to require an argument (to the extension method) which is an IEnumerable collection : passing in SomeForm.Controls does not compile.

The most useful thing I found was this :

link text

Which does give you an extension method for Control.ControlCollection with an IEnumerable result you can then use with Linq.

I've modified the above example to parse the Nodes of a TreeView with no problem.

public static IEnumerable<TreeNode> GetNodesRecursively(this TreeNodeCollection nodeCollection)
{
    foreach (TreeNode theNode in nodeCollection)
    {
        yield return theNode;

        if (theNode.Nodes.Count > 0)
        {
            foreach (TreeNode subNode in theNode.Nodes.GetNodesRecursively())
            {
                yield return subNode;
            }
        }
    }
}

This is the kind of code I'm writing now using the extension method :

    var theNodes = treeView1.Nodes.GetNodesRecursively();

    var filteredNodes = 
    (
        from n in theNodes
            where n.Text.Contains("1")
                select n
    ).ToList();

And I think there may be a more elegant way to do this where the constraint(s) are passed in.

What I want to know if it is possible to define such procedures generically, so that : at run-time I can pass in the type of collection, as well as the actual collection, to a generic parameter, so the code is independent of whether it's a TreeNodeCollection or Controls.Collection.

It would also interest me to know if there's any other way (cheaper ? fastser ?) than that shown in the second link (above) to get a TreeNodeCollection or Control.ControlCollection in a form usable by Linq.

A comment by Leppie about 'SelectMany in the SO post linked to first (above) seems like a clue.

My experiments with SelectMany have been : well, call them "disasters." :)

Appreciate any pointers. I have spent several hours reading every SO post I could find that touched on these areas, and rambling my way into such exotica as the "y-combinator." A "humbling" experience, I might add :)

+3  A: 

I'm not sure about TreeNodes, but you can make the Controls collection of a form IEnumerable by using System.Linq and, for example

var ts = (from t in this.Controls.OfType<TextBox>
                 where t.Name.Contains("fish")
                 select t);
//Will get all the textboxes whose Names contain "fish"

Sorry to say I don't know how to make this recursive, off the top of my head.

Matt Ellen
.OfType<T> may be more useful than .Cast<T> because you can specify the type that you want; if the collection is heterogenous (as a collection of controls would be) you can safely collect a specific type of control with .OfType<T>. .Cast<T> may throw a casting exception if there's a mismatch. Also, if you know a collection is homogenous, then .AsEnumerable() will do the trick nicely.
Cylon Cat
Thanks Cylon Cat. I'll update the code to reflect that
Matt Ellen
@Matt Thanks for responding ! fyi : WinForms (VS Studio 2010 beta 2 and FrameWork 4.0 only tested) : the only extension methods exposed by a Form's ControlCollection beginning with "As" are 'AsParallel and 'AsQueryable. A standard WinForms TreeViews's TreeNodeCollection also exposes the same extension methods. Whether any other WinForms native control exposes an inner collection with an 'AsEumerable extension method is unknown to me.
BillW
You're quite correct BillW. I misinterpreted Cylon Cat's reply and didn't test my code. I've changed the code to something more suitable.
Matt Ellen
+14  A: 

This code should do the trick

public static class Extensions
{
    public static IEnumerable<T> GetRecursively<T>(this IEnumerable collection,
        Func<T, IEnumerable> selector)
    {
        foreach (var item in collection.OfType<T>())
        {
            yield return item;

            IEnumerable<T> children = selector(item).GetRecursively(selector);
            foreach (var child in children)
            {
                yield return child;
            }
        }
    }
}

Here's an example of how to use it

TreeView view = new TreeView();

// ...

IEnumerable<TreeNode> nodes = view.Nodes.
    .GetRecursively<TreeNode>(item => item.Nodes);

Update: In response to Eric Lippert's post.

Here's a much improved version using the technique discussed in All About Iterators.

public static class Extensions
{
    public static IEnumerable<T> GetItems<T>(this IEnumerable collection,
        Func<T, IEnumerable> selector)
    {
        Stack<IEnumerable<T>> stack = new Stack<IEnumerable<T>>();
        stack.Push(collection.OfType<T>());

        while (stack.Count > 0)
        {
            IEnumerable<T> items = stack.Pop();
            foreach (var item in items)
            {
                yield return item;

                IEnumerable<T> children = selector(item).OfType<T>();
                stack.Push(children);
            }
        }
    }
}

I did a simple performance test using the following benchmarking technique. The results speak for themselves. The depth of the tree has only marginal impact on the performance of the second solution; whereas the performance decreases rapidly for the first solution, eventually leadning to a StackOverflowException when the depth of the tree becomes too great.

benchmarking

mrydengren
In "selector(item).OfType<T>()" the OfType is not mandatory ,as it should already return T.
Dennis Cheung
No it's mandatory because the selector returns an IEnumerable and not and IEnumerable<T>.
mrydengren
Oh, I see the problem now. I've update the code to reflect the changes. Good catch.
mrydengren
The TreeNodeCollection class implements the IEnumerable interface. You should be able to get the nodes like this: var nodes = treeView.Nodes.GetItems<TreeNode>(node => node.Nodes).Where(node => node.Text.Contains("1"));
mrydengren
@mrydengren Many thanks for your hard work on this and responsiveness to my queries ! I cannot explain why yesterday I could not get your code to compile in VS2010 beta 2 against FrameWork 4.0 unless I tweaked the declaration in the ways I commented on yesterday and added code to the recursive call that used 'OfType(). But, today, starting afresh, I was able to get it to compile, and validate your sample usage test above. So it's my pleasure to now accept your answer (again). Please come to Chiang Mai and let me treat you to a one-hour elephant ride :) best,
BillW
+1  A: 

I guess you are asking for something like this.

public static IEnumerable<T> <T,TCollection> GetNodesRecursively(this TCollection nodeCollection, Func<T, TCollection> getSub)
 where T, TCollection: IEnumerable
{   
    foreach (var theNode in )
    {
        yield return theNode;
        foreach (var subNode in GetNodesRecursively(theNode, getSub))
        {
            yield return subNode;
        }
    }
}

var all_control = GetNodesRecursively(control, c=>c.Controls).ToList();
Dennis Cheung
+2  A: 

Based on mrydengren's solution:

public static IEnumerable<T> GetRecursively<T>(this IEnumerable collection,
    Func<T, IEnumerable> selector,
    Func<T, bool> predicate)
{
    foreach (var item in collection.OfType<T>())
    {
        if(!predicate(item)) continue;

        yield return item;

        IEnumerable<T> children = selector(item).GetRecursively(selector, predicate);
        foreach (var child in children)
        {
            yield return child;
        }
    }
}


var theNodes = treeView1.Nodes.GetRecursively<TreeNode>(
    x => x.Nodes,
    n => n.Text.Contains("1")).ToList();

Edit: for BillW

Yannick M.
Thanks for the opportunity to study your code : in order to use your code I had to alter the example use-case shown like so :var theNodes = winTV.Nodes.GetRecursively<TreeNode>(x => x.Nodes, n => n.Text.Contains("1")).ToList();thanks,
BillW
I corrected the mistake. But I strongly suggest to consider mrydengren's stack-based implementation. If need be I can alter my response to include the predicate parameter.
Yannick M.
@Yannick My bad : I just voted up your answer above, forgetting I had already voted it up, and it just erased my up-vote and now won't let me reset it unless you edit the original. I've written to the SO team about this before : it would seem trivial to have a dialog box appear to ask you to confirm you were changing an already cast vote : but maybe that's just my inability to see "orange" :) I am intensively testing Mrydengren's answer, which I did accept, and it's "gracious" of you to mention it.
BillW
I have edited. I was wondering where those 10 points went ;-)
Yannick M.
+15  A: 

You seem to be on the right track and the answers above have some good ideas. But I note that all these recursive solutions have some deep flaws.

Let's suppose the tree in question has a total of n nodes with a max tree depth of d <= n.

First off, they consume system stack space in the depth of the tree. If the tree structure is very deep, then this can blow the stack and crash the program. Tree depth d is O(lg n), depending on the branching factor of the tree. Worse case is no branching at all -- just a linked list -- in which case a tree with only a few hundred nodes will blow the stack.

Second, what you're doing here is building an iterator that calls an iterator that calls an iterator ... so that every MoveNext() on the top iterator actually does a chain of calls that is again O(d) in cost. If you do this on every node, then the total cost in calls is O(nd) which is worst case O(n^2) and best case O(n lg n). You can do better than both; there's no reason why this cannot be linear in time.

The trick is to stop using the small, fragile system stack to keep track of what to do next, and to start using a heap-allocated stack to explicitly keep track.

You should add to your reading list Wes Dyer's article on this:

http://blogs.msdn.com/wesdyer/archive/2007/03/23/all-about-iterators.aspx

He gives some good techniques at the end for writing recursive iterators.

Eric Lippert
Thanks for the great post, I've updated my solution to reflect the lesson learnt here.
mrydengren
Thanks, Eric! I struggle to get a "mental model" of how Linq makes internal structure and its "costs": with "classic recursion": at least I feel "grounded." I go over and over Skeet's excellent book; I have Albahari's book (imho a series of advanced notes with little explanation). When I find: IEnumerable<TreeNode> nodes1 = treeView1.Nodes.OfType<TreeNode>();andIEnumerable<TreeNode> nodes2 = treeView1.Nodes.Cast<TreeNode>().Select(node => node); both return top-level nodes of a TreeView : the "scientist" in me wants to know what's "under the hood" : and, which technique to use "when."
BillW
A: 

I have an elegant, efficient and reusable solution for recusrive iterators. See my blog post about it.

Thanks, Arnon.

PS: I'd really like to know what you think about my solution. Please let me know :-)

Arnon Axelrod