如何摆脱递归 IEnumerable使用yieldbreak循环?

发布于 2024-09-11 17:24:47 字数 1683 浏览 9 评论 0 原文

我有以下效果很好的方法,除了yield break 语句仅突破当前枚举器之外。我明白为什么会出现这种情况,但我对如何通过递归堆栈传播产量分解一无所知。

    private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText) {
        var en = nodes.GetEnumerator();
        var targetFound = false;
        while (en.MoveNext())  {
            var node = en.Current as Node;
            if (node != null) 
            {
                if (node.Parent == null && string.IsNullOrEmpty(parentText))
                {
                    //Returns the top level nodes if an empty parentIdis entered
                    targetFound = true;
                    yield return node;
                }
                else if (node.Parent != null && node.Parent.Text == parentText)
                {
                    //returns the nodes belonging to the parent
                    yield return node;
                }
                else
                {
                    //Recurse into the children to see whether one of these is the node to find
                    foreach (var nd in FindChildrenById(node.Nodes, parentText))
                    {
                        yield return nd;
                    }
                }
            }
        }
        if (targetFound)
        {
            yield break;
        }
    }

因此,当我有以下节点并传递“Top 2 a”作为parentText...

Top 1
    Top 1 a
    Top 1 b
Top 2
    Top 2 a
       Top 2 aa
       Top 2 ab
       Top 2 ac
    Top 2 b
Top 3
    Top 3 a
    Top 3 b
Top 4

...然后我得到结果:

Top 2 aa
Top 2 ab
Top 2 ac

这是正确的结果,但是,当我单步执行代码时,最外层循环继续处理 Top 3 和 Top 4。如何跳出这个外循环?

I have the following method that works well, except the yield break statement only breaks out of the current enumerator. I understand why this is the case, but I am drawing a blank over how to propogate the yield break up through the recursive stack.

    private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText) {
        var en = nodes.GetEnumerator();
        var targetFound = false;
        while (en.MoveNext())  {
            var node = en.Current as Node;
            if (node != null) 
            {
                if (node.Parent == null && string.IsNullOrEmpty(parentText))
                {
                    //Returns the top level nodes if an empty parentIdis entered
                    targetFound = true;
                    yield return node;
                }
                else if (node.Parent != null && node.Parent.Text == parentText)
                {
                    //returns the nodes belonging to the parent
                    yield return node;
                }
                else
                {
                    //Recurse into the children to see whether one of these is the node to find
                    foreach (var nd in FindChildrenById(node.Nodes, parentText))
                    {
                        yield return nd;
                    }
                }
            }
        }
        if (targetFound)
        {
            yield break;
        }
    }

So when I have the following nodes and pass "Top 2 a" as the parentText...

Top 1
    Top 1 a
    Top 1 b
Top 2
    Top 2 a
       Top 2 aa
       Top 2 ab
       Top 2 ac
    Top 2 b
Top 3
    Top 3 a
    Top 3 b
Top 4

... then I get the result:

Top 2 aa
Top 2 ab
Top 2 ac

This is the correct result, however, when I step through my code, the outer-most loop continues to process Top 3 and Top 4. How do I break out of this outer loop?

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

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

发布评论

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

评论(3

牛↙奶布丁 2024-09-18 17:24:47

如果我的代码正确,我想下面的代码将解决您的问题

private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText)
    {
        var result =
               (from node in nodes
                where (node.Parent == null && string.IsNullOrEmpty(parentText))
                      || (node.Parent != null && node.Parent.Text == parentText)
                select node).TakeWhile(node => !(node.Parent == null && string.IsNullOrEmpty(parentText)));
        return result;
    }

它是基于两个扩展方法构建的(见下文),并且应该只迭代直到满足您的目标找到标准

public static class IEnumerablExtensions
        {
            //Will iterate the graph in depth first order
            public static IEnumerable<TResult> Select<TResult>(this IEnumerable collection, Func<Node, TResult> selector)
            {
                foreach (var obj in collection)
                {
                    var node = obj as Node;
                    if (node != null)
                    {
                        yield return selector(node);
                        foreach (var n in node.Nodes.Select(selector))
                        {
                           yield return n;
                        }
                    }
                }
            }

        public static IEnumerable<Node> Where(this IEnumerable collection, Predicate<Node> pred)
        {
            foreach (var node in collection.Select(x => x)) //iterate the list in graph first order
            {
                if (pred(node))
                    yield return node;
            }
        }
    }

编辑:有一个错误在原始发布的 Select 方法中(它没有迭代子项的子项),现在已更正

If I got your code right, I guess the code below will solve your problem

private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText)
    {
        var result =
               (from node in nodes
                where (node.Parent == null && string.IsNullOrEmpty(parentText))
                      || (node.Parent != null && node.Parent.Text == parentText)
                select node).TakeWhile(node => !(node.Parent == null && string.IsNullOrEmpty(parentText)));
        return result;
    }

It's built on two extension methods (see below) and should only iterate until your target found criteria is met

public static class IEnumerablExtensions
        {
            //Will iterate the graph in depth first order
            public static IEnumerable<TResult> Select<TResult>(this IEnumerable collection, Func<Node, TResult> selector)
            {
                foreach (var obj in collection)
                {
                    var node = obj as Node;
                    if (node != null)
                    {
                        yield return selector(node);
                        foreach (var n in node.Nodes.Select(selector))
                        {
                           yield return n;
                        }
                    }
                }
            }

        public static IEnumerable<Node> Where(this IEnumerable collection, Predicate<Node> pred)
        {
            foreach (var node in collection.Select(x => x)) //iterate the list in graph first order
            {
                if (pred(node))
                    yield return node;
            }
        }
    }

EDIT: There was an error in the Select method in the original posting (it didn't iterate the children of children) that is now corrected

何以畏孤独 2024-09-18 17:24:47

我假设该函数实际上名为 FindChildrenById,否则我看不到任何递归发生。

在这种假设下,您可以使用异常(我强烈建议不要这样做),或者返回一个 KeyValuePair> 其中 bool 部分将是用于表示链上的早期退出。

然后,在 API 级别上,公开一个包装器方法,该方法仅返回 IEnumerable 部分并抛出 bool 部分。

这是一个示例,给定类 Node

public class Node
{
    List<Node> children;
    public string Text { get; set; }
    public List<Node> Children { get { return children ?? (children = new List<Node>()); } }
}

您可以像这样遍历和快捷方式:

public class NodeTraverser
{
    private static KeyValuePair<bool, IEnumerable<Node>> GetChildrenById(string text, Node node)
    {
        if(node.Text == text)
        {
            return new KeyValuePair<bool,IEnumerable<Node>>(true, node.Children);
        }
        foreach(var child in node.Children)
        {
            var result = GetChildrenById(text, child);
            if(result.Key)
            {
                return result; // early out
            }
        }
        return new KeyValuePair<bool,IEnumerable<Node>>(false, Enumerable.Empty<Node>());
    }

    public static IEnumerable<Node> FindChildrenbyId(string text, Node root)
    {
        return GetChildrenById(text, root).Value; 
    }
}

I'm assuming that the function is actually named FindChildrenById, otherwise I can't see any recursion going on.

Under this assumption you can either use exceptions (which I would strongly recommend against), or return a KeyValuePair<bool, IEnumerable<Node>> where the bool part will be used to signal an early out up the chain.

Then, on the API level, expose a wrapper method that simply returns the IEnumerable<Node> part and throws way the bool part.

Here's an example, given the class Node:

public class Node
{
    List<Node> children;
    public string Text { get; set; }
    public List<Node> Children { get { return children ?? (children = new List<Node>()); } }
}

You can traverse and shortcut like this:

public class NodeTraverser
{
    private static KeyValuePair<bool, IEnumerable<Node>> GetChildrenById(string text, Node node)
    {
        if(node.Text == text)
        {
            return new KeyValuePair<bool,IEnumerable<Node>>(true, node.Children);
        }
        foreach(var child in node.Children)
        {
            var result = GetChildrenById(text, child);
            if(result.Key)
            {
                return result; // early out
            }
        }
        return new KeyValuePair<bool,IEnumerable<Node>>(false, Enumerable.Empty<Node>());
    }

    public static IEnumerable<Node> FindChildrenbyId(string text, Node root)
    {
        return GetChildrenById(text, root).Value; 
    }
}
提笔落墨 2024-09-18 17:24:47
//Returns the top level nodes if an empty parentIdis entered
targetFound = true;
yield return node;
yield break;

这对你有用吗?

更新:

我已经考虑了更多。这对于递归来说可能很棘手。您将需要保留一些状态变量来打破所有循环。

如果 C# 有尾递归,我建议将代码转换为 CPS

你总是可以用 MSIL 编写它:)

//Returns the top level nodes if an empty parentIdis entered
targetFound = true;
yield return node;
yield break;

Will that work for you?

Update:

I have given it some more thought. This might be tricky with recursion. You will need to keep some state variable to break out of all the loops.

If C# had tail-recursion, I would suggest converting the code to CPS.

You could always write it in MSIL :)

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