如何使用yield break?</t>打破递归的IEnumerable <t>循环

时间:2010-07-27 07:59:02

标签: c# recursion ienumerable yield

我有以下方法可以正常工作,但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

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

3 个答案:

答案 0 :(得分:2)

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

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方法出现错误(它没有迭代孩子的孩子)现在已经更正了

答案 1 :(得分:1)

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

这对你有用吗?

<强>更新

我已经多考虑了一下。这可能是递归的棘手问题。你需要保留一些状态变量来打破所有循环。

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

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

答案 2 :(得分:1)

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

根据这个假设,你可以使用例外(我强烈建议反对),或者返回KeyValuePair<bool, IEnumerable<Node>>,其中bool部分将用于表示早期的链。

然后,在API级别,公开一个包装器方法,该方法只返回IEnumerable<Node>部分并抛出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; 
    }
}