递归 IEnumerable 没有按预期工作?

Recursive IEnumerable doesn't work as expected?

我写了一个递归函数,它产生 IEnumerable<int>

IEnumerable<int>   Go(int b  )
{
 if (b==0) yield  return 0;
   else
 foreach (var element in Go(b-1))
  {
    yield return element;
  }
}

所以如果我写

foreach (var element in Go(3))
{
    Console.WriteLine (element);
}

它应该产生

0
1
2
3

但它没有按预期工作。 (它显示 0)。

在正常的递归函数中(return int - 没有 Ienumerable)它工作正常。

问题:

如何修改代码以使其产生预期值?

注意。不,没有理由使用递归 Ienumerables。在玩了递归收益率之后,我才想到它。

我怀疑它是否会有所不同 - 因为我看到的唯一具体 yieldyield 0

我猜你想要这样的东西:

IEnumerable<int> Go(int b)
{
   if (b > 0)
   {
      foreach (var element in Go(b-1))
      {
        yield return element;
      }
   }
   yield return b;
}

但这仍然是非常低效的,并且会用更大的 bs

破坏堆栈

针对您的问题:

您的代码:

会这样做:

b=3:
  is b == 0? no ok, then enumerate and return everything from b=2...
     b=2:
       is b == 0? no ok, then enumerate and return everything from b=1...
         b=1:
           is b == 0? no ok, then enumerate everything from b=0...
             b=0:
               is b == 0? **YES** so yield a single **0**
           everything was {0}
       everything was {0}
  everything was {0}
return is {0}

因为你永远不会产生 b 本身,只会产生 0。

IEnumerable<int> Go(int b)
{
    if(b > 0) {
         foreach (var element in Go(b-1))
            yield return element;
    }
    yield return b;
}

请注意,如果您希望结果序列从 0 开始并向上移动,您必须 yield return b 之后 foreach。让我们展开对 Go(3):

的第一个调用
Go(3):
foreach(var element in Go(2)):
    yield return element;
yield return 3;

因此 3 将是序列中的最后一项(因为所有其他项都在它之前产生)。现在展开 Go(2):

Go(3):
    Go(2):
    foreach(var element in Go(1)):
        yield return element;
    yield return 2;
yield return 3;

Go(1):

Go(3):
    Go(2):
        Go(1):
            foreach(var element in Go(0))
                yield return element;
        yield return 1;
    yield return 2;
yield return 3;

如您所见,结果与调用相关联 "backwards":

Go(3) --> Go(2) --> Go(1) --> Go(0) --> 0 --> 1 --> 2 --> 3

您的代码有缺陷。 :-)

它所做的是 Go 方法只产生值 0。如果用 3 调用,那么它会下降到最后一个递归调用 returns 0。foreach 只迭代那个 0 和再次产生它。因此,零作为唯一元素在每个级别上产生。

假设您有一个名为 enumerable 的 IEnumerable。如果你写

foreach(var element in enumerable) yield return element;

和你写的完全一样

return enumerable;

如果您查看结果和 return 类型。如果你尝试

if(b == 0) yield return 0;
else return Go(b - 1);

它给出了一个编译器错误:"Iterator cannot contain return statement",因为如果你写一个带有 yield return 语句的函数,它不会编译成一个函数而是一个迭代器,所以它会不是真的 "return"。让我们修改它以获得相同的行为,但为了清楚起见使用 "real function"。要使其编译,您可以将其修改为

if (b == 0) return Enumerable.Repeat(0, 1); // or return Enumerable.Range(0, 1);
else return Go(b - 1);

但这并没有真正让它更清楚:你在那里所做的几乎就像:

return b == 0 ? 0 : Go(b-1);

但结果包装在 IEnumerable 中。我希望现在清楚为什么它 return 只有一个 0.

为什么您的代码不起作用...让我们从头开始...您想要 [0, 1, 2, 3]。显然要得到那个序列,必须有一个

yield return 0
yield return 1
yield return 2
yield return 3

但是在您的代码中您可以:

yield return 0

yield return the Go function 

没有任何代码可以产生 return 非零值!

请注意,事实上正确的代码有一个

yield return b

其中 b 是传递给函数 Go(int b) 的值,因此该函数将首先递归调用自身以 return 值 0...b-1 然后产生 b 值.

条件为 b==0 的另一个变体

static IEnumerable<int> Go(int b)
{
    if (b == 0)
    {
        yield return 0; //return 0 if b==0;
        yield break; // say that iteration end;
    }

    foreach (var el in Go(b - 1)) 
    {
        yield return el;
    }

    yield return b; //return current b as element of result collection

}

或没有yield break

static IEnumerable<int> Go(int b)
{
    if (b == 0)
    {
        yield return 0;
    }
    else
    {

        foreach (var el in Go(b - 1)) 
        {
            yield return el;
        }

        yield return b; //return current b as element of result collection
    }
}

根据 b 参数

添加可视化:(只是为了显示流程,如果产量)

我花了一些时间才看到这里发生了什么。