2013-06-11 16 views
5

Mam kolekcję, która ma 96 wartości. Chciałbym podsumować wartości dla 4 kolejnych indeksów. Jak mogę to zrobić, używając Linq?Użyj Linq do sumowania według indeksu

Przykład

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

Suma (100, 101, 200, 150;) następnie suma (103, 105, 100, 104;) ... wtedy suma (20, 40, 60, 80;) Oznacza to teraz moja nowa kolekcja będzie miał 24 wartości.

Jak mogę to zrobić, używając Linq?

+0

Czy dane powinny zostać zgrupowane, gdy zostaną wstawione? Na przykład. 'Lista >' –

Odpowiedz

11

Możemy zacząć z tej funkcji narzędzie do Batch pozycji w górę w oparciu o danej wielkości partii:

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; 
    } 
} 

Po że to tak proste, jak:

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

Czy brakuje mi czegoś, czy to będzie zwracać pierwsze 4 przedmioty _czasowy czas? – DonBoitnott

+1

@DonBoitnott Tęsknisz. Wyszukaj "bloki iteratora". – Servy

+0

Bardzo ładne. Dzięki, nauczyłem się czegoś dzisiaj! – DonBoitnott

4

można grupować według index/4 dostać Twoje sumy, tak:

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

@Servy Wiedziałem, że czegoś brakuje! To było zbyt urocze, żeby mieć rację! Dzięki! – dasblinkenlight

3

Możesz obliczyć indeks grupy z Indeks, grupa na tym i uzyskać sumę od wartości w każdej grupie:

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

będzie trzeba nową metodę rozszerzenia 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); 
} 

Wykorzystanie byłoby:

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

To jest alternatywny sposób do metody Batch opublikowanej przez Servy.

1

Najpierw utwórz grupę według indeksu. W tym przypadku wybrałem podział liczb całkowitych, aby utworzyć elementy 0-3 grupa 0, 4-7 grupa 1 itd.

Następnie pogrupuj swoje elementy w różne zestawy, które będą wymagać zsumowania (według grupowania klawisz).

Na koniec wybierz sumę elementów należących do każdej grupy.

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

to zrobić:

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; 
} 

I z niego korzystać:

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

a wyjście będzie:

551 
412 
200 
24 

Jak widać, swoją długość zbiórki nie musi być czynnikiem batchSize.

+0

W miarę możliwości najlepiej unikać rekurencyjnych bloków iteracyjnych. Tradycyjne metody mają dość niewielki narzut za każdym razem, gdy wywoływana jest metoda, ale bloki iteratorów (jak również metody "asynchroniczne") mają znacznie większy narzut dla "hydraulika", który jest ustawiony, aby maszyna stan pracowała. Funkcja rekursywna, taka jak ta, dodaje wiele dodatkowych narzutów, tworząc wszystkie te dodatkowe maszyny stanów i generując każdą z zagnieżdżonych wartości aż do samej góry. Nie ma także żadnej realnej korzyści z używania tego w iteracyjnym podejściu. Aha, i wielokrotnie powtarzasz 'collection'. – Servy

+1

Nie zgadzam się; rekursywne bloki iteratorów nie są złym smakiem projektu. Na przykład F # ma nawet składnię do spłaszczania rekursywnych bloków iteracyjnych, którą tutaj uzyskuje się za pomocą 'foreach'. I o ponad głową, oczywiście należy iść z jednym lub innym wdrożeniem w oparciu o jej/jego priorytety; wydajność? szybki rozwój? W tym pytaniu wspomina się, że istnieje tylko 96 pozycji. Nie jestem pewien, czy sugeruję właściwe rozwiązanie, ale może być właściwe, jeśli (na przykład) wydajność nie jest tutaj problemem. Gdyby wydajność była głównym problemem w tworzeniu oprogramowania, Ruby nie istniałaby. –

+0

@KavehShahbazian: To nadal pozostawia problem, że 'collection' jest iterowane wiele razy. W takim przypadku twoja metoda nie powinna przyjmować wartości "IEnumerable ", ale może być podobna do 'ICollection ' lub podobnej. –