C# 递归枚举的逻辑约简

C# 递归枚举的逻辑约简,c#,recursion,ienumerable,C#,Recursion,Ienumerable,我发现自己经常编写递归的IEnumerable迭代器来实现相同的“后代”模式,例如。我一直在实现的模式如下所示,给定一个类型Foo,带有一个名为Children的单级迭代器: public static IEnumerable<Foo> Descendants(this Foo root) { foreach (var child in root.Children()) { yield return child; foreach (var su

我发现自己经常编写递归的
IEnumerable
迭代器来实现相同的“后代”模式,例如。我一直在实现的模式如下所示,给定一个类型
Foo
,带有一个名为
Children
的单级迭代器:

public static IEnumerable<Foo> Descendants(this Foo root) {
    foreach (var child in root.Children()) {
        yield return child;
        foreach (var subchild in child.Descendants()) {
            yield return subchild;
        }
    }
}

要扩展我的评论,这应该是可行的:

public static IEnumerable<Foo> Descendants(this Foo node)
{
    yield return node; // return branch nodes
    foreach (var child in node.Children())
        foreach (var c2 in child.Descendants())
            yield return c2; // return leaf nodes
}
公共静态IEnumerable子体(此Foo节点)
{
收益返回节点;//返回分支节点
foreach(node.Children()中的var child)
foreach(child.substands()中的变量c2)
yield return c2;//返回叶节点
}
这将返回所有分支节点和叶节点。如果只想返回叶节点,请删除第一个yield返回


在回答您的问题时,是的,它是一个算法原语,因为您肯定需要调用node.Children(),并且您肯定需要对每个子级调用child.substands()。我同意有两个“foreach”循环看起来很奇怪,但第二个循环实际上只是继续整个枚举,而不是迭代子循环。

我认为这是一个好问题。对于为什么需要两个循环,我有一个最好的解释:我们需要认识到,每个项都转换为多个项(自身及其所有子项)。这意味着我们不映射一对一(如
Select
),而是一对多(
SelectMany

我们可以这样写:

public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 foreach (var item in items) {
  yield return item;
  foreach (var subitem in item.Children().Descendants())
   yield return subitem;
 }
}
public static IEnumerable<Foo> Descendants(Foo root) {
 var children = root.Children();
 var subchildren = children.SelectMany(c => c.Descendants());
 return children.Concat(subchildren);
}
public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 var children = items.SelectMany(c => c.Descendants());
 return items.Concat(children);
}
公共静态IEnumerable子体(此IEnumerable项){
foreach(项目中的var项目){
收益回报项目;
foreach(item.Children().substands()中的var子项)
收益回报子项;
}
}
或者像这样:

public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 foreach (var item in items) {
  yield return item;
  foreach (var subitem in item.Children().Descendants())
   yield return subitem;
 }
}
public static IEnumerable<Foo> Descendants(Foo root) {
 var children = root.Children();
 var subchildren = children.SelectMany(c => c.Descendants());
 return children.Concat(subchildren);
}
public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 var children = items.SelectMany(c => c.Descendants());
 return items.Concat(children);
}
公共静态IEnumerable子体(Foo root){
var children=root.children();
var subchildren=children.SelectMany(c=>c.substands());
返回子项。Concat(子项);
}
或者像这样:

public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 foreach (var item in items) {
  yield return item;
  foreach (var subitem in item.Children().Descendants())
   yield return subitem;
 }
}
public static IEnumerable<Foo> Descendants(Foo root) {
 var children = root.Children();
 var subchildren = children.SelectMany(c => c.Descendants());
 return children.Concat(subchildren);
}
public static IEnumerable<Foo> Descendants(this IEnumerable<Foo> items) {
 var children = items.SelectMany(c => c.Descendants());
 return items.Concat(children);
}
公共静态IEnumerable子体(此IEnumerable项){
var children=items.SelectMany(c=>c.substands());
返回项目。Concat(儿童);
}
必须在
root.Children()
上调用使用
IEnumerable
的版本


我认为所有这些重写暴露了一种看待问题的不同方式。另一方面,它们都有两个嵌套循环。循环可以隐藏在helper函数中,但它们仍然存在。

我将使用以下方法来管理它:

公共静态IEnumerable子体(此Foo根){
List todo=新列表();
todo.AddRange(root.Children());
而(todo.Count>0)
{
var first=todo[0];
todo.RemoveAt(0);
InsertRange(0,first.Children());
收益率优先;
}
}
不是递归的,所以不应该破坏堆栈。您只需在列表的前面为自己添加更多的工作,就可以实现深度优先遍历。

尝试以下方法:

private static IEnumerable<T> Descendants<T>(
    this IEnumerable<T> children, Func<T, IEnumerable<T>> enumerator)
{
    Func<T, IEnumerable<T>> getDescendants =
        child => enumerator(child).Descendants(enumerator);

    Func<T, IEnumerable<T>> getChildWithDescendants =
        child => new[] { child }.Concat(getDescendants(child));

    return children.SelectMany(getChildWithDescendants);
}

可以使用显式堆栈而不是隐式使用线程的调用堆栈来存储正在使用的数据。这甚至可以推广到只接受委托来表示“get my children”调用的
Traverse
方法:

公共静态IEnumerable遍历(
这是一个数不清的来源
,Func childrenSelector)
{
var stack=新堆栈(源);
while(stack.Any())
{
var next=stack.Pop();
其次是收益率;
foreach(childrenSelector中的var child(下一个))
栈.推(子);
}
}
因为这不是递归的,因此不会不断地创建状态机,所以它的性能会更好一些

请注意,如果您想进行呼吸优先搜索,只需使用
队列
而不是
堆栈
。如果您想要最佳优先搜索,请使用优先级队列


为了确保兄弟姐妹的返回顺序与selecor的返回顺序相同,而不是相反,只需在
childrenSelector
的结果中添加一个
Reverse
调用

不信者Damien_和Servy都提出了一种算法版本,该算法避免使用一种或另一种类型的集合创建递归调用堆栈。Damien使用
列表
可能会导致列表开头插入的性能不佳,而Servy使用堆栈会导致嵌套元素以相反的顺序返回。我相信手动实现单向链表将保持Servy的性能,同时仍将按原始顺序返回所有项目。唯一棘手的部分是通过迭代根来初始化第一个
ForwardLink
s。为了保持
Traverse
干净,我将其移动到
ForwardLink
上的一个构造函数

public static IEnumerable<T> Traverse<T>(
    this T root, 
    Func<T, IEnumerable<T>> childrenSelector) {

    var head = new ForwardLink<T>(childrenSelector(root));

    if (head.Value == null) yield break; // No items from root iterator

    while (head != null)
    {
        var headValue = head.Value;
        var localTail = head;
        var second = head.Next;

        // Insert new elements immediately behind head.
        foreach (var child in childrenSelector(headValue))
            localTail = localTail.Append(child);

        // Splice on the old tail, if there was one
        if (second != null) localTail.Next = second;

        // Pop the head
        yield return headValue;
        head = head.Next; 
    }
}

public class ForwardLink<T> {
    public T Value { get; private set; }
    public ForwardLink<T> Next { get; set; }

    public ForwardLink(T value) { Value = value; }

    public ForwardLink(IEnumerable<T> values) { 
        bool firstElement = true;
        ForwardLink<T> tail = null;
        foreach (T item in values)
        {
            if (firstElement)
            {
                Value = item;
                firstElement = false;
                tail = this;
            }
            else
            {
                tail = tail.Append(item);
            }
        }
    }

    public ForwardLink<T> Append(T value) {
        return Next = new ForwardLink<T>(value);
    } 
}
公共静态IEnumerable遍历(
这个T根,
Func儿童选择器){
var head=新的前向链接(childrenSelector(root));
if(head.Value==null)产生中断;//根迭代器中没有项
while(head!=null)
{
var headValue=head.Value;
var localTail=头部;
var second=头。下一个;
//将新元素直接插入头部后面。
foreach(childrenSelector中的变量child(头值))
localTail=localTail.Append(子级);
//在旧尾巴上剪接,如果有的话
如果(秒!=null)localTail.Next=second;
//爆头
收益率、收益率和总价值;
头=头。下一步;
}
}
公共类前向链接{
公共T值{get;私有集;}
公共转发链接下一个{get;set;}
公共转发链接(T值){value=value;}
公共转发链接(IEnumerable值){
bool firstElement=true;
前向链接尾部=空;
foreach(值中的T项)
{
if(第一元素)
{
价值=项目;
firstElement=false;
尾巴=这个;
}
其他的
{
tail=tail.Append(项目);
    public class RecursiveControlEnumerator : RecursiveEnumerator, IEnumerator {
        public RecursiveControlEnumerator(Control.ControlCollection controlCollection)
            : base(controlCollection) { }

        protected override ICollection GetChildCollection(object c) {
            return (c as Control).Controls;
        }
    }