LINQ Aggregate

Większość z was prawdopodobnie kojarzy LINQ. Z doświadczenia jednak wiem, że mało kto używa jego możliwości w pełni. Dziś chciałbym się skupić na funkcji agregującej o wdzięcznej nazwie Aggregate. Ze zrozumieniem jej bywają problemy. Sam pamiętam, że wiele razy musiałem patrzeć na example, dopóki nie weszło mi to w krew. Chciałbym przybliżyć dziś nieco i ułatwić zrozumienie tej niewdzięcznej metody.

Zacznijmy więc od najprostszego przykładu – sumowania. Wiadomo, że można użyć .Sum() (albo i nie wiadomo? 😉 ). Nie psujmy jednak sobie zabawy.
var numbers = new[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
var sum = 0;
foreach (var number in numbers)
{
    sum = sum + number;
}
Oto jeden z prostszych sposobów. Prawdopodobnie większość z nas uczyła się dodawania liczb z listy w ten, czy podobny sposób (np. for zamiast foreach). W naszym przypadku chodzi głównie o użycie zmienniej sum oraz number. Zobaczmy tą samą funkcjonalność zapisaną przy pomocy funkcji Aggregate
var numbers = new[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
var aggregate = numbers.Aggregate((sum, number) => sum + number);
Widać pewną analogię, prawda? Ten overload metody Aggregate przyjmuje jako parametr funkcję z dwoma parametrami tego samego typu, co element z tablicy oraz zwraca również ten sam typ. W naszym przypadku jest to Func<int, int, int>. Pierwszym parametrem jest nasz sum czyli analogicznie do tego, co zapisaliśmy w podstawowym przykładzie sumowania, aktualny stan po pojedynczej iteracji pętli. Rozpiszmy kolejne kroki i wartości zmiennych z naszego przykładu:
sum  |  number | sum + number
0    |    1    |   1
1    |    2    |   3
3    |    3    |   6
6    |    4    |   10
10   |    5    |   15
15   |    6    |   21
21   |    7    |   28
28   |    8    |   36
36   |    9    |   45
Należy zapamiętać jedynie tyle, że pierwszym parametrem lambdy jest aktualny stan dla każdej iteracji, a drugi to kolejny element tablicy. Funkcja może operować na tych dwóch wartościach zwracając zawsze nowy stan.

Kolejny overload Aggregate pozwola nam na modyfikowanie typu oraz wartości początkowej naszego „stanu”. Załóżmy, że chcemy zliczyć ilość wystąpień pewnego wyrazu w naszej tablicy. Tak oczywiście zrobilibyśmy bez użycia funkcji agregującej:
var myStory = new[] { "Ala", "ma", "kota", "ale", "Ala", "nie", "ma", "psa" };

var sum = 0;
foreach (var element in myStory)
{
    if (element == "Ala")
    {
        sum++;
    }
}
Natomiast z aggregate ten sam kawałek kodu mógłby wyglądać następująco:
var myStory = new[] { "Ala", "ma", "kota", "ale", "Ala", "nie", "ma", "psa" };

var list = myStory.Aggregate(
    seed: 0,
    func: (sum, element) => element == "Ala" ? sum + 1 : sum);
Użycie seeda pozwoliło nam w tym przypadku na zmodyfikowanie typu zwracanego przez Aggregate. Gdyby nie to, to zamiast w func mieć Func<int, string, int> mielibyśmy Func<string, string, string>. O wiele trudniej zwraca się ilość wystąpień przy pomocy stringa 😉

Ostatni overload daje nam możliwość operacji na elemencie wynikowym funkcji. Najprościej jest tu podać przykład liczenia średniej. Standardowo napisalibyśmy to w następujący sposób:
var grades = new [] { 4, 5, 3, 4, 4, 6, 5};
var sum = 0;
foreach (var grade in grades)
{
    sum += grade;
}

var average = (double)sum / grades.Length;
Natomiast z wykorzystaniem LINQ:
var grades = new [] { 4, 5, 3, 4, 4, 6, 5};

var average = grades.Aggregate(
    seed: 0,
    func: (sum, grade) => sum + grade,
    resultSelector: sum => (double)sum / grades.Length
);
Ktoś mógłby powiedzieć, że przecież to niepotrzebne i zamiast tego możnaby było użyć drugiego overloada i na nim wykonać ostateczną operację dzielenia. Oczywiście, sporo w tym racji. Prawdopodobnie jedyne zastosowanie, które ma tu sens, to gdy wynikiem zwracanym jest jakaś nowa lista, na której dalej będziemy wykonywać operacje LINQ. Może operacje matematyczne ze wzorów dla zwiększenia czytelności? Osobiście jednak nie miałem nigdy potrzeby korzystania z tej wersji Aggregate.

Najprościej zrozumieć Aggregate samemu coś pisząc. Osobiście polecam do tego stronkę CodeWars.com, gdzie można znaleźć inspirujące zadania i ich rozwiązania. Aggregate można użyć między innymi w: Prawdopodobnie większość problemów z agregacją danych da się rozwiązać przy pomocy standardowych metod typu Max(), Min(), Sum(), Count(), Average() jednak warto wiedzieć o istnieniu i co najważniejsze rozumieć Aggregate. A nóż przyjdzie wam liczyć odchylenia standardowe i wtedy rozwiązanie gotowe! 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *