Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable<T> Skip on unlimited sequence

I have a simple implementation of Fibonacci sequence using BigInteger:

internal class FibonacciEnumerator : IEnumerator<BigInteger>
    {
        private BigInteger _previous = 1;
        private BigInteger _current = 0;

        public void Dispose(){}

        public bool MoveNext() {return true;}

        public void Reset()
        {
            _previous = 1;
            _current = 0;
        }

        public BigInteger Current
        {
            get
            {
                var temp = _current;
                _current += _previous;
                _previous = temp;
                return _current;
            }
        }

        object IEnumerator.Current { get { return Current; }
        }
    }

    internal class FibonacciSequence : IEnumerable<BigInteger>
    {
        private readonly FibonacciEnumerator _f = new FibonacciEnumerator();

        public IEnumerator<BigInteger> GetEnumerator(){return _f;}

        IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}
    }

It is an unlimited sequence as the MoveNext() always returns true.

When called using

var fs = new FibonacciSequence();
fs.Take(10).ToList().ForEach(_ => Console.WriteLine(_));

the output is as expected (1,1,2,3,5,8,...)

I want to select 10 items but starting at 100th position. I tried calling it via

fs.Skip(100).Take(10).ToList().ForEach(_ => Console.WriteLine(_));

but this does not work, as it outputs ten elements from the beginning (i.e. the output is again 1,1,2,3,5,8,...).

I can skip it by calling SkipWhile

fs.SkipWhile((b,index) => index < 100).Take(10).ToList().ForEach(_ => Console.WriteLine(_));

which correctly outputs 10 elements starting from the 100th element.

Is there something else that needs/can be implemented in the enumerator to make the Skip(...) work?

like image 334
rbm Avatar asked Sep 04 '15 12:09

rbm


3 Answers

Skip(n) doesn't access Current, it just calls MoveNext() n times.

So you need to perform the increment in MoveNext(), which is the logical place for that operation anyway:

Current does not move the position of the enumerator, and consecutive calls to Current return the same object until either MoveNext or Reset is called.

like image 131
CodeCaster Avatar answered Sep 21 '22 21:09

CodeCaster


CodeCaster's answer is spot on - I'd just like to point out that you don't really need to implement your own enumerable for something like this:

public IEnumerable<BigInteger> FibonacciSequence() {   var previous = BigInteger.One;   var current = BigInteger.Zero;    while (true)   {     yield return current;      var temp = current;     current += previous;     previous = temp;   } } 

The compiler will create both the enumerator and the enumerable for you. For a simple enumerable like this the difference isn't really all that big (you just avoid tons of boilerplate), but if you actually need something more complicated than a simple recursive function, it makes a huge difference.

like image 37
Luaan Avatar answered Sep 21 '22 21:09

Luaan


Move your logic into MoveNext:

public bool MoveNext() 
{
    var temp = _current;
     _current += _previous;
     _previous = temp;
    return true;
}

public void Reset()
{
    _previous = 1;
    _current = 0;
}

public BigInteger Current
{
    get
    {
        return _current;
    }
}

Skip(10) is simply calling MoveNext 10 times, and then Current. It also makes more logical sense to have the operation done in MoveNext, rather than current.

like image 44
Rob Avatar answered Sep 21 '22 21:09

Rob