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

Questo codice verrà iterato su tutti i valori interi da 0 a max-range o solo attraverso i valori interi per soddisfare il take-while, dove e selezionare gli operatori? Qualcuno può chiarire?Posso usare un raggio infinito e operare su di esso?

MODIFICA: Il mio primo tentativo per assicurarsi che funzioni come previsto è stato stupido. Lo revoco :)

+0

Uhhh ... hai superato il limite massimo per un intero ... cosa ti aspettavi che accadesse? –

risposta

11

int.MaxValue + 5 overflow deve essere un numero negativo. Provate voi stessi:

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

Il secondo argomento per Enumerable.Range deve essere non negativo - da qui l'eccezione.

Puoi comunque usare infinite sequenze in LINQ. Ecco un esempio di una tale sequenza:

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

che traboccherà come pure, naturalmente, ma sarà andare avanti ...

Nota che alcuni operatori LINQ (ad esempio Reverse) devono leggi tutti i i dati prima che possano ottenere il loro primo risultato. Altri (come Select) possono semplicemente mantenere in streaming i risultati mentre li leggono dall'input. Vedere il mio Edulinq blog posts per i dettagli sul comportamento di ciascun operatore (in LINQ agli oggetti).

+0

+1 per Eduling. Brillante serie di articoli. Hai un codice simile in un primo o secondo post lì. – gideon

+0

Il collegamento ai post del blog Edulinq è ora interrotto. Hai un altro riferimento che funziona? –

+0

@LucaCremonesi: risolto, grazie. –

2

Il tuo primo codice verrà iterato solo fino a quando è soddisfatta la condizione TakeWhile. Non verrà iterato fino a int.MaxValue.

int.MaxValue + 5 restituirà un numero intero negativo. Enumerable.Range genera un'eccezione ArgumentOutOfRangeException se il suo secondo argomento è negativo. Ecco perché ottieni l'eccezione (prima che avvenga l'iterazione).

+0

Grazie per la chiara spiegazione di sepp2k – suhair

3

Il modo per risolvere questo tipo di domande in generale, è quello di pensare a cosa sta succedendo nei passaggi.

Linq trasforma il codice linq in qualcosa che verrà eseguito dal provider di query. Questo potrebbe essere qualcosa come la produzione di codice SQL, o ogni sorta di cose. Nel caso di linq-to-objects, produce un codice .NET equivalente. Pensando a quello che sarà il codice .NET ci permette di ragionare su ciò che accadrà *

con il codice si ha:.

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

Enumerable.Range è leggermente più complicato di:

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

... ma è abbastanza vicino per l'argomento.

Select è abbastanza vicino a:

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

Dove è abbastanza vicino a:

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

TakeWhile è abbastanza vicino a:

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

somma è abbastanza vicino a:

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

Questo semplifica alcune ottimizzazioni e così via, ma è abbastanza vicino da ragionare. Prendendo ciascuno di questi a sua volta, il codice è 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() 

Ora, in qualche modo il codice di cui sopra è più semplice, e per certi versi è più complicato. L'intero punto di utilizzo di LINQ è che per molti aspetti è più semplice. Ancora, per rispondere alla tua domanda "Questo codice verrà iterato su tutti i valori interi da 0 a max-range o solo attraverso i valori interi per soddisfare il take-while, dove e selezionare gli operatori?" possiamo osservare quanto sopra e vedere che quelli che non soddisfano il punto in cui sono passati per scoprire che non soddisfano il dove, ma non c'è più lavoro con loro, e una volta che il TakeWhile è soddisfatto, tutto il resto viene interrotto (lo break nella mia riscrittura non LINQ).

Ovviamente è solo lo TakeWhile() in questo caso, il che significa che la chiamata verrà restituita in un lasso di tempo ragionevole, ma dobbiamo anche riflettere brevemente sugli altri per assicurarsi che cedano mentre vanno. Si consideri la seguente variante del codice:

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

In teoria, questo darà esattamente la stessa risposta, ma ci vorrà molto più tempo e di gran lunga più memoria a farlo (probabilmente sufficiente a causare un'eccezione di memoria). Il codice non LINQ equivalente qui però è:

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() 

Qui possiamo vedere come l'aggiunta ToList() alla query LINQ influisce notevolmente la query in modo che ogni articolo prodotto dalla chiamata Range() deve essere affrontato. Metodi come ToList() e ToArray() interrompono il concatenamento in modo che gli equivalenti non-linq non si adattino più "dentro" l'uno con l'altro e nessuno può quindi interrompere l'operazione di quelli che vengono prima. (Sum() è un altro esempio, ma poiché è il tuo TakeWhile() nell'esempio, non è un problema).

Un'altra cosa che farebbe passare attraverso ogni iterazione dell'intervallo è se si avesse While(x => false) perché non avrebbe mai effettivamente eseguito il test in TakeWhile.

* Anche se potrebbero esserci ulteriori ottimizzazioni, esp nel caso del codice SQL e anche mentre concettualmente, ad es. Count() è equivalente a:

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

Che questo sarà trasformata in una chiamata al Count proprietà di un ICollection o Length proprietà di un array intende la O (n) di cui sopra è sostituito da un O (1) (per la maggior parte delle implementazioni di ICollection), che è un enorme guadagno per grandi sequenze.

Problemi correlati