2013-06-11 15 views
5

Ho una raccolta che ha 96 valori. Vorrei sommare i valori per 4 indici sequenziali. Come posso farlo usando Linq?Utilizzare Linq per sommare per indice

Esempio

collection = {100, 101, 200, 150, 103, 105, 100, 104, .........., 20, 40, 60, 80}; 

Somma di (100, 101, 200, 150;) poi somma di (103, 105, 100, 104;) ... poi somma di (20, 40, 60, 80;) Questo significa che ora la mia nuova collezione avrà 24 valori.

Come posso farlo utilizzando Linq?

+0

I dati devono essere effettivamente raggruppati quando vengono inseriti? Per esempio. 'Elenco >' –

risposta

11

Possiamo cominciare con questa funzione di utilità per Batch oggetti fino basate su un determinato dimensione del lotto:

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize) 
{ 
    List<T> buffer = new List<T>(batchSize); 

    foreach (T item in source) 
    { 
     buffer.Add(item); 

     if (buffer.Count >= batchSize) 
     { 
      yield return buffer; 
      buffer = new List<T>(batchSize); 
     } 
    } 
    if (buffer.Count > 0) 
    { 
     yield return buffer; 
    } 
} 

Dopo di che è il più semplice:

var query = data.Batch(4) 
    .Select(batch => batch.Sum()); 
+0

Mi manca qualcosa, o è intenzione di restituire i primi 4 elementi _ogni volta_? – DonBoitnott

+1

@DonBoitnott Ti manca qualcosa. Cerca "blocchi iteratori". – Servy

+0

Molto bello. Grazie, ho imparato qualcosa oggi! – DonBoitnott

4

È possibile raggruppare da index/4 per ottenere le tue somme, in questo modo:

var res = collection 
    .Select((v,i) => new {v, i}) 
    .GroupBy(p => p.i/4) 
    .Select(g => g.Sum(p.v)); 
+0

@Servy Sapevo che mi mancava qualcosa! Era troppo carino per avere ragione! Grazie! – dasblinkenlight

3

È possibile calcolare un indice di gruppo dal indice, gruppo su questo, e ottenere la somma dei valori di ogni gruppo:

var sums = collection 
.Select((n, i) => new { Group = i/4, Value = n }) 
.GroupBy(x => x.Group) 
.Select(g => g.Sum(y => y.Value)); 
1

si avrebbe bisogno di un nuovo metodo di estensione Partition:

public static IEnumerable<IEnumerable<T>> Partition<T>(
    this IEnumerable<T> source, int partitionSize) 
{ 
    var counter = 0; 
    var result = new T[partitionSize]; 
    foreach(var item in source) 
    { 
     result[counter] = item; 
     ++counter; 
     if(counter >= partitionSize) 
     { 
      yield return result; 
      counter = 0; 
      result = new T[partitionSize]; 
     } 
    } 

    if(counter != 0) 
     yield return result.Take(counter); 
} 

Uso sarebbero:

collection.Partition(4).Select(x => x.Sum()) 

Questo è un metodo alternativo al metodo Batch pubblicato da Servy.

1

Prima di tutto, crea un modo per raggruppare i tuoi set per indice. In questo caso ho scelto di usare la divisione intero per creare elementi 0-3 gruppo 0, 4-7 gruppo 1, ecc.

Successivamente, raggruppa i tuoi elementi in diversi set che necessiteranno di sommare (dal raggruppamento chiave).

Infine, selezionare la somma degli elementi che appartengono a ciascun gruppo.

values.Select((x, i) => new { GroupingKey = i/4, Value = x }) 
     .GroupBy(x => x.GroupingKey) 
     .Select(x => new { Group = x.Key, Sum = x.Sum() }); 
0

Questo farlo:

static IEnumerable<int> BatchSum(int batchSize, IEnumerable<int> collection) 
{ 
    var batch = collection.Take(batchSize).ToList(); 
    if (batch.Count == 0) yield break; 

    yield return batch.Sum(); 

    var rest = collection.Skip(batchSize); 
    foreach (var sum in BatchSum(batchSize, rest)) yield return sum; 
} 

E per usarlo:

var collection = new[] { 100, 101, 200, 150, 103, 105, 100, 104, 20, 40, 60, 80, 11, 13 }; 

foreach (var sum in BatchSum(4, collection)) Show(sum); 

E l'output sarà:

551 
412 
200 
24 

Come si vede, la lunghezza collezione non deve essere un fattore di batchSize.

+0

In genere è preferibile evitare i blocchi iteratori ricorsivi, laddove possibile. I metodi tradizionali hanno un overhead piuttosto piccolo ogni volta che viene chiamato un metodo, ma i blocchi iteratore (come pure i metodi 'async') hanno un overhead molto maggiore per il" plumbing "che viene impostato per far funzionare la macchina a stati. Una funzione ricorsiva come questa è l'aggiunta di un sacco di overhead aggiuntivo per la creazione di tutte quelle macchine con stato extra e la resa di ciascuno dei valori nidificati fino in cima. Inoltre, non c'è alcun vantaggio reale nell'usare questo approccio su un approccio iterativo. Oh, e iterate 'collection' più volte. – Servy

+1

Non sono d'accordo; i blocchi iteratori ricorsivi non sono di cattivo gusto. F #, ad esempio, ha anche una sintassi per appiattire i blocchi iteratori ricorsivi, che qui viene ottenuto mediante un 'foreach'. E a proposito di overhead, ovviamente si dovrebbe andare con l'una o l'altra implementazione basata sulle sue priorità; prestazione? sviluppo veloce? In questa domanda si dice che ci sono solo 96 elementi. Non sono sicuro di suggerire la soluzione adeguata, ma può essere corretto se (ad esempio) le prestazioni non sono un problema qui. Se le prestazioni fossero la principale preoccupazione nello sviluppo del software, allora Ruby non esisterebbe. –

+0

@KavehShahbazian: Rimane comunque il problema che 'collection' viene ripetuto più volte. In tal caso, il tuo metodo non dovrebbe prendere un 'IEnumerable ' ma qualcosa come 'ICollection ' o simile. –

Problemi correlati