2011-03-26 14 views
8
Enumerable.Range(0, int.MaxValue) 
      .Select(n => Math.Pow(n, 2)) 
      .Where(squared => squared % 2 != 0) 
      .TakeWhile(squared => squared < 10000).Sum() 

¿Este código iterará sobre todos los valores enteros desde 0 hasta max-range o solo a través de los valores enteros para satisfacer los operadores take-while, where y select? ¿Alguien puede aclarar?¿Puedo usar un rango infinito y operar sobre él?

EDIT: Mi primer intento para asegurarme de que funciona como se esperaba fue tonto. Lo revoco :)

+0

Uhhh ... superaste el rango máximo de un número entero ... ¿qué esperabas que sucediera? –

Respuesta

11

int.MaxValue + 5 desbordamiento para ser un número negativo. Inténtelo usted mismo:

unchecked 
{ 
    int count = int.MaxValue + 5; 
    Console.WriteLine(count); // Prints -2147483644 
} 

El segundo argumento para Enumerable.Range tiene que ser no negativo - de ahí la excepción.

Sin duda, puede utilizar secuencias infinitas en LINQ. He aquí un ejemplo de tal secuencia:

public IEnumerable<int> InfiniteCounter() 
{ 
    int counter = 0; 
    while (true) 
    { 
     unchecked 
     { 
      yield return counter; 
      counter++; 
     } 
    } 
} 

Eso se desbordará y, por supuesto, pero va a seguir adelante ...

Tenga en cuenta que algunos operadores LINQ (por ejemplo Reverse) necesitan lea todos los datos antes de que puedan dar su primer resultado. Otros (como Select) pueden simplemente mantener los resultados de la transmisión mientras los leen desde la entrada. Consulte mi Edulinq blog posts para obtener detalles sobre el comportamiento de cada operador (en LINQ to Objects).

+0

+1 para Eduling. Brillante conjunto de artículos. Tienes un código similar en una primera o segunda publicación allí. – gideon

+0

El enlace a sus publicaciones en el blog de Edulinq ahora está roto. ¿Tienes otra referencia que funcione? –

+0

@LucaCremonesi: reparado, gracias. –

2

Su primer código solo se repetirá mientras se cumpla la condición TakeWhile. No repetirá hasta int.MaxValue.

int.MaxValue + 5 dará como resultado un número entero negativo. Enumerable.Range arroja una ArgumentOutOfRangeException si su segundo argumento es negativo. Entonces, es por eso que obtienes la excepción (antes de que cualquier iteración tenga lugar).

+0

Gracias por una clara explicación sepp2k – suhair

3

La manera de resolver este tipo de preguntas en general, es pensar en lo que está pasando en los pasos.

Linq convierte el código linq en algo que será ejecutado por el proveedor de consultas. Esto podría ser algo como producir código SQL o todo tipo de cosas. En el caso de linq-to-objects, produce algún código .NET equivalente. Pensando en lo que será ese código .NET nos permite razonar sobre lo que sucederá *

Con su código tiene:.

Enumerable.Range(0, int.MaxValue) 
         .Select(n => Math.Pow(n, 2)) 
         .Where(squared => squared % 2 != 0) 
         .TakeWhile(squared => squared < 10000).Sum() 

Enumerable.Range es un poco más complicado que:

for(int i = start; i != start + count; ++i) 
    yield return i; 

... pero eso es lo suficientemente cerca como argumento.

Select es lo suficientemente cerca de:

foreach(T item in source) 
    yield return func(item); 

¿Dónde está lo suficientemente cerca de:

foreach(T item in source) 
    if(func(item)) 
    yield return item; 

TakeWhile es lo suficientemente cerca de:

foreach(T item in source) 
    if(func(item)) 
    yield return item; 
    else 
    yield break; 

suma es lo suficientemente cerca de:

T tmp = 0;//must be numeric type 
foreach(T x in source) 
    tmp += x; 
return tmp; 

Esto simplifica algunas optimizaciones y demás, pero está lo suficientemente cerca como para razonar. Teniendo cada una de ellas, a su vez, su código es equivalente a:

double ret = 0; // part of equivalent of sum 
for(int i = 0; i != int.MaxValue; ++i) // equivalent of Range 
{ 
    double j = Math.Pow(i, 2); // equivalent of Select(n => Math.Pow(n, 2)) 
    if(j % 2 != 0) //equivalent of Where(squared => squared %2 != 0) 
    { 
    if(j < 10000) //equivalent of TakeWhile(squared => squared < 10000) 
    { 
     ret += j; //equaivalent of Sum() 
    } 
    else //TakeWhile stopping further iteration 
    { 
     break; 
    } 
    } 
} 
return ret; //end of equivalent of Sum() 

Ahora, en cierto modo, el código anterior es más simple, y en algunos aspectos es más complicado. El objetivo de utilizar LINQ es que, en muchos aspectos, es más simple. Aún así, para responder a su pregunta "¿Este código iterará sobre todos los valores enteros de 0 a rango máximo o solo a través de los valores enteros para satisfacer los operadores take-while, where y select?" podemos ver lo anterior y ver que aquellos que no satisfacen el lugar se repiten para encontrar que no satisfacen el lugar, pero no se hace más trabajo con ellos, y una vez que el TakeWhile está satisfecho, todo el trabajo posterior está detenido (el break en mi re-escritura no LINQ).

Por supuesto, es solo el TakeWhile() en este caso, lo que significa que la llamada volverá en un período de tiempo razonable, pero también necesitamos pensar brevemente sobre los demás para asegurarnos de que ceden a medida que avanzan. Considere la siguiente variante de su código:

Enumerable.Range(0, int.MaxValue) 
    .Select(n => Math.Pow(n, 2)) 
    .Where(squared => squared % 2 != 0) 
    .ToList() 
    .TakeWhile(squared => squared < 10000).Sum() 

En teoría, esto le dará exactamente la misma respuesta, pero tomará mucho más tiempo y mucho más memoria para hacerlo (probablemente suficiente para provocar una excepción de memoria insuficiente). El código no LINQ equivalente aquí sin embargo es:

List<double> tmpList = new List<double>(); // part of ToList equivalent 
for(int i = 0; i != int.MaxValue; ++i) // equivalent of Range 
{ 
    double j = Math.Pow(i, 2); // equivalent of Select(n => Math.Pow(n, 2)) 
    if(j % 2 != 0) //equivalent of Where(squared => squared %2 != 0) 
    { 
    tmpList.Add(j);//part of equivalent to ToList() 
    } 
} 
double ret = 0; // part of equivalent of sum 
foreach(double k in tmpList) 
{ 
    if(k < 10000) //equivalent of TakeWhile(squared => squared < 10000) 
    { 
    ret += k; //equaivalent of Sum() 
    } 
    else //TakeWhile stopping further iteration 
    { 
    break; 
    } 
} 
return ret; //end of equivalent of Sum() 

Aquí podemos ver cómo la adición de ToList() a la consulta LINQ afecta enormemente la consulta para que cada artículo producido por la llamada Range() debe ser tratado. Métodos como ToList() y ToArray() rompen el encadenamiento de modo que los equivalentes que no son linq ya no encajen "dentro" entre sí y ninguno puede por lo tanto detener el funcionamiento de los anteriores. (Sum() es otro ejemplo, pero como está después de su TakeWhile() en su ejemplo, eso no es un problema).

Otra cosa que lo haría pasar por cada iteración del rango es si tenía While(x => false) porque nunca realizaría la prueba en TakeWhile.

* Aunque puede haber más optimizaciones, especialmente en el caso del código SQL y también, aunque conceptualmente, p. Ej. Count() es equivalente a:

int c = 0; 
foreach(item in src) 
    ++c; 
return c; 

que esto se convirtió en una llamada a la propiedad Count de un ICollection o la propiedad Length de una matriz significa que el O (n) anteriormente se sustituye por un O (1) (para la mayoría de las implementaciones de ICollection) call, que es una ganancia masiva para grandes secuencias.

Cuestiones relacionadas