枚举本质上不是 IEnumerable 的集合?

发布于 2024-08-12 13:44:59 字数 1781 浏览 7 评论 0 原文

当您想要递归枚举一个分层对象,根据某些条件选择一些元素时,有许多技术示例,例如“扁平化”,然后使用 Linq 进行过滤:就像在这里找到的那​​些示例:

链接文本

但是,当您枚举诸如 Form 的 Controls 集合或 TreeView 的 Nodes 集合之类的内容时,我无法使用这些类型技术,因为它们似乎需要一个参数(扩展方法),该参数是 IEnumerable 集合:传入 SomeForm.Controls 无法编译。

我发现的最有用的东西是:

链接文本< /a>

这确实为您提供了 Control.ControlCollection 的扩展方法,其中包含 IEnumerable 结果,然后您可以将其与 Linq 一起使用。

我已经修改了上面的示例来毫无问题地解析 TreeView 的节点。

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

这就是我现在使用扩展方法编写的代码:

    var theNodes = treeView1.Nodes.GetNodesRecursively();

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

我认为可能有一种更优雅的方法可以在传入约束的情况下执行此操作。

我想知道是否可以定义这样的过程是通用的,这样:在运行时我可以将集合的类型以及实际的集合传递给通用参数,因此代码独立于它是 TreeNodeCollection 还是 Controls.Collection。

我还想知道是否有比第二个链接(上面)中显示的方法更便宜?更快?)以 Linq 可用的形式获取 TreeNodeCollection 或 Control.ControlCollection 。

Leppie 在链接到第一个(上面)的 SO 帖子中关于“SelectMany”的评论似乎是一个线索。

我对 SelectMany 的实验是:好吧,称它们为“灾难”。 :)

感谢任何指点。我花了几个小时阅读我能找到的所有涉及这些领域的 SO 帖子,并漫无目的地进入了“y-combinator”这样的奇特事物。我可能会补充说,这是一次“谦卑”的经历:)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

無心 2024-08-19 13:44:59

这段代码应该可以解决问题,

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

这是一个如何使用它的示例

TreeView view = new TreeView();

// ...

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

更新:响应 Eric Lippert 的帖子。

这是使用 中讨论的技术的一个大大改进的版本关于迭代器的一切

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

我使用以下基准测试技术进行了简单的性能测试。结果不言而喻。树的深度对第二种解决方案的性能只有边际影响;而第一个解决方案的性能会迅速下降,当树的深度变得太大时,最终会导致 StackOverflowException 。

基准测试

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

心碎无痕… 2024-08-19 13:44:59

您似乎走在正确的道路上,上面的答案有一些好主意。但我注意到所有这些递归解决方案都有一些深刻的缺陷。

假设所讨论的树总共有 n 个节点,最大树深度为 d <= n。

首先,它们消耗树深度的系统堆栈空间。如果树结构非常深,那么这可能会破坏堆栈并使程序崩溃。树深度 d 为 O(lg n),取决于树的分支因子。更糟糕的情况是根本没有分支——只有一个链表——在这种情况下,只有几百个节点的树将破坏堆栈。

其次,您在这里所做的是构建一个迭代器,该迭代器调用一个迭代器,该迭代器调用另一个迭代器……这样顶部迭代器上的每个 MoveNext() 实际上都会执行一系列调用,成本又是 O(d)。如果在每个节点上执行此操作,则调用的总成本为 O(nd),最坏情况为 O(n^2),最好情况为 O(n lg n)。你可以比两者都做得更好;没有理由不能在时间上呈线性。

诀窍是停止使用小型、脆弱的系统堆栈来跟踪下一步要做什么,并开始使用堆分配的堆栈来显式跟踪。

您应该将 Wes Dyer 的相关文章添加到您的阅读列表中:

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

他在最后给出了一些编写递归迭代器的好技巧。

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.

格子衫的從容 2024-08-19 13:44:59

我不确定 TreeNodes,但您可以使用 System.Linq 来创建表单 IEnumerable 的 Controls 集合,例如

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"

抱歉,我不知道如何进行递归,关闭我的头顶。

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.

瑾夏年华 2024-08-19 13:44:59

基于 mrydengren 的解决方案:

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

编辑:for BillW

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

小红帽 2024-08-19 13:44:59

我猜你正在要求这样的东西。

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

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();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文