简体   繁体   中英

Recursive IEnumerable doesn't work as expected?

I've written a recursive function which yields IEnumerable<int>

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

So If I write

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

It should yield

0
1
2
3

But it doesn't work as expected. ( it displays 0).

In normal recursive function ( which return int - without Ienumerable) it works fine.

Question:

How can I fix the code so it yields the expected value ?

nb. No there's no reason for using recursive Ienumerables. It's just came to my mind after playing with recursive yields.

Because you never yield b itself but only yield 0.

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

Note that if you want the resulting sequence to start from 0 and go upwards you have to yield return b after the foreach . Let's unroll the first call for Go(3) :

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

So 3 will be the last item in the sequence (because all the others are yieled before it). Let's now unroll 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;

As you can see, result are chained "backwards" with respect to the calls:

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

I doubt that it would work anything different - because the only concrete yield I see is yield 0

I guess you want something like this:

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

but still this is highly inefficient and will blow the stack with bigger b s


For your question:

Your code:

will do this:

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}

Yet another variant with condition 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

}

or without 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
    }
}

Why your code doesn't work... Let's start from the end... You want [0, 1, 2, 3]. Clearly to obtain that sequence, there must be a

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

But in your code you can:

yield return 0

or

yield return the Go function 

nowhere you have some code that can yield return the non-zero value!

Note in fact that correct code has a

yield return b

where b is the value passed to the function Go(int b) , so that the function will first call itself recursively to return the valeus 0...b-1 and then yield the b value.

Let's assume you have an IEnumerable called enumerable. If you write

foreach(var element in enumerable) yield return element;

is exactly the same as if you write

return enumerable;

if you look at the result and the return type. If you try

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

It gives a compiler error: "Iterator cannot contain return statement", because if you write a function with a yield return statement in it, it won't compile to a function but an iterator, so it does not really "return". Let's modify it to get the same behavior, but with a "real function" for clarity. To make it compile, you could modify it to

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

but it doesn't really make it more clear: What you did up there was almost like:

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

but the result is wrapped in an IEnumerable. I hope it's clear now why it returns only one 0.

Adding visualization : ( just to show flow if yields) method name according to the b param

took me a while to see what's going on here.

在此输入图像描述

Your code is flawed. :-)

What it does is that the Go method only ever yields the value 0. If called with 3, then it gets down to the last recursive call whicht returns 0. The foreach iterates just that one 0 and yields it again. SO the zero is yielded on every level as the only element.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM