c# - IEnumerable<T> Salta en secuencia ilimitada
linq fibonacci (3)
La respuesta de CodeCaster es acertada. Me gustaría señalar que realmente no necesita implementar su propio enumerable para algo como esto:
public IEnumerable<BigInteger> FibonacciSequence()
{
var previous = BigInteger.One;
var current = BigInteger.Zero;
while (true)
{
yield return current;
var temp = current;
current += previous;
previous = temp;
}
}
El compilador creará tanto el enumerador como el enumerable para usted. Para una enumeración simple como esta, la diferencia no es realmente tan grande (solo evitas toneladas de repetitivo), pero si realmente necesitas algo más complicado que una simple función recursiva, hace una gran diferencia.
Tengo una implementación simple de la secuencia de Fibonacci usando 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();}
}
Es una secuencia ilimitada ya que MoveNext()
siempre devuelve true.
Cuando se llama usando
var fs = new FibonacciSequence();
fs.Take(10).ToList().ForEach(_ => Console.WriteLine(_));
la salida es la esperada (1,1,2,3,5,8, ...)
Quiero seleccionar 10 artículos pero comenzando en la posición 100. Intenté llamarlo via
fs.Skip(100).Take(10).ToList().ForEach(_ => Console.WriteLine(_));
pero esto no funciona, ya que genera diez elementos desde el principio (es decir, la salida es de nuevo 1,1,2,3,5,8, ...).
Puedo saltearlo llamando a SkipWhile
fs.SkipWhile((b,index) => index < 100).Take(10).ToList().ForEach(_ => Console.WriteLine(_));
que produce correctamente 10 elementos a partir del elemento 100.
¿Hay algo más que necesite / pueda implementarse en el enumerador para hacer que el Skip(...)
funcione?
Mueve tu lógica a 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;
}
}
Omitir (10) simplemente llama a MoveNext
10 veces, y luego a Current
. También tiene más sentido lógico que la operación se realice en MoveNext
, en lugar de actual.
Skip(n)
no accede a Current
, solo llama MoveNext()
n
veces.
Por lo tanto, debe realizar el incremento en MoveNext()
, que es el lugar lógico para esa operación de todos modos :
Actual no mueve la posición del enumerador, y las llamadas consecutivas a Actual devuelven el mismo objeto hasta que se llama MoveNext o Reset.