2009-11-09 2 views
10

Ho un ciclo simile al seguente, posso fare lo stesso utilizzando più SUM?Più SUM utilizzando LINQ

foreach (var detail in ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload && 
                   pd.InventoryType == InventoryTypes.Finished)) 
{ 
    weight += detail.GrossWeight; 
    length += detail.Length; 
    items += detail.NrDistaff; 
} 
+1

LINQ non è l'ideale e la fine della manipolazione dei dati e non c'è ancora niente di sbagliato in un ciclo for. – SLaks

+0

Divertente anche se @SLaks è uno dei rari casi in cui LINQ non offre una soluzione ragionevole. – PeterX

risposta

7

Tecnicamente parlando, quello che hai è probabilmente il modo più efficace per fai quello che stai chiedendo Tuttavia, è possibile creare un metodo di estensione sul IEnumerable <T> chiamato Ciascun che potrebbe rendere più semplice:

public static class EnumerableExtensions 
{ 
    public static void Each<T>(this IEnumerable<T> col, Action<T> itemWorker) 
    { 
     foreach (var item in col) 
     { 
      itemWorker(item); 
     } 
    } 
} 

e chiamarlo in questo modo:

// Declare variables in parent scope 
double weight; 
double length; 
int items; 

ArticleLedgerEntries 
    .Where(
     pd => 
      pd.LedgerEntryType == LedgerEntryTypeTypes.Unload && 
      pd.InventoryType == InventoryTypes.Finished 
    ) 
    .Each(
     pd => 
     { 
      // Close around variables defined in parent scope 
      weight += pd.GrossWeight; 
      lenght += pd.Length; 
      items += pd.NrDistaff; 
     } 
    ); 

UPDATE: Solo una nota aggiuntiva. L'esempio sopra si basa su una chiusura. Le variabili peso, lunghezza e voci devono essere dichiarate nell'ambito genitore, consentendo loro di persistere oltre ogni chiamata all'azione itemWorker. Ho aggiornato l'esempio per riflettere questo per chiarezza.

+0

Soluzione molto elegante. – Alessandro

+0

Felice di essere al servizio. :-) – jrista

+0

+1 Tecnica molto interessante. –

4

È possibile chiamare Sum tre volte, ma sarà più lento perché renderà tre anelli.

Ad esempio:

var list = ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload 
            && pd.InventoryType == InventoryTypes.Finished)) 

var totalWeight = list.Sum(pd => pd.GrossWeight); 
var totalLength = list.Sum(pd => pd.Length); 
var items = list.Sum(pd => pd.NrDistaff); 

A causa di esecuzione ritardata, sarà anche rivalutare il Where chiamata ogni volta, anche se questo non è una questione nel tuo caso. Questo potrebbe essere evitato chiamando lo ToArray, ma ciò causerà un'allocazione dell'array. (E gestirà ancora tre loop)

Tuttavia, a meno che non si disponga di un numero molto elevato di voci o si esegua questo codice in un ciclo chiuso, non è necessario preoccuparsi delle prestazioni.


EDIT: Se davvero vuole usare LINQ, si potrebbe abusare Aggregate, in questo modo:

int totalWeight, totalLength, items; 

list.Aggregate((a, b) => { 
    weight += detail.GrossWeight; 
    length += detail.Length; 
    items += detail.NrDistaff; 
    return a; 
}); 

Questo è fenomenale codice brutto, ma necessario eseguire quasi come un anello dritto.

Si può anche sommare l'accumulatore, (vedi esempio sotto), ma questo alloca un oggetto temporaneo per ogni elemento della lista, che è un'idea stupida. (Tipi anonimi sono immutabili)

var totals = list.Aggregate(
    new { Weight = 0, Length = 0, Items = 0}, 
    (t, pd) => new { 
     Weight = t.Weight + pd.GrossWeight, 
     Length = t.Length + pd.Length, 
     Items = t.Items + pd.NrDistaff 
    } 
); 
+0

Ok. Mi rendo conto che non esiste un modo semplice per farlo utilizzando LINQ. Prenderò un ciclo foreach perché ho capito che non è così male. Grazie a tutti voi. – Alessandro

+0

Puoi commentare la risposta dall'utente805138? Che aspetto ha la performance nel suo approccio? – gisek

+0

@gisek: Il 'gruppo x per 1' è completamente inutile e molto stupido; introduce la sintassi LINQ senza alcun motivo. A parte questo, è identico al mio primo codice; usa due anelli extra. – SLaks

0

Ok. Mi rendo conto che non esiste un modo semplice per farlo utilizzando LINQ. Prenderò un ciclo foreach perché ho capito che non è così male. Grazie a tutti voi

+0

Non si dovrebbe pubblicare un rispondi a SO come faresti per pubblicare una risposta su un argomento in un forum. Fallo solo se rispondi alla tua stessa domanda. Di solito aggiungi * AGGIORNA * alla tua domanda originale come una sorta di risposta alle risposte e ai commenti sulla tua domanda. –

2

Si potrebbe anche gruppo dal vero - 1 (che in realtà è compresa qualsiasi degli elementi e poi li hanno contati o estivazione):

var results = from x in ArticleLedgerEntries 
         group x by 1 
         into aggregatedTable 
         select new 
            { 
             SumOfWeight = aggregatedTable.Sum(y => y.weight), 
             SumOfLength = aggregatedTable.Sum(y => y.Length), 
             SumOfNrDistaff = aggregatedTable.Sum(y => y.NrDistaff) 
            }; 

Per quanto riguarda Tempo di esecuzione, è quasi buono come il ciclo (con un'aggiunta costante).

+0

Il 'group by' è completamente inutile e piuttosto confuso. Basta fare 'var results = new {... = ArticleLedgerEntries.Sum (...), ...}' – SLaks