Enumerating Collections that are not inherently IEnumerable?

后端 未结 5 1586
予麋鹿
予麋鹿 2020-11-29 03:45

When you want to recursively enumerate a hierarchical object, selecting some elements based on some criteria, there are numerous examples of techniques like \"flattening\" a

相关标签:
5条回答
  • 2020-11-29 03:58

    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

    0 讨论(0)
  • 2020-11-29 04:17

    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:

    https://blogs.msdn.microsoft.com/wesdyer/2007/03/23/all-about-iterators/

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

    0 讨论(0)
  • 2020-11-29 04:20

    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

    0 讨论(0)
  • 2020-11-29 04:21

    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();
    
    0 讨论(0)
  • 2020-11-29 04:22

    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.

    0 讨论(0)
提交回复
热议问题