Algorytm agregacji LINQ wyjaśniony

To może zabrzmieć kiepsko, ale nie udało mi się znaleźć naprawdę dobrego wyjaśnienia Aggregate.

Dobry oznacza krótki, opisowy, wyczerpujący z małym i jasnym przykładem.

 603
Author: Jamiec, 2011-08-18

11 answers

Najprostsza do zrozumienia definicja Aggregate jest taka, że wykonuje operację na każdym elemencie listy, biorąc pod uwagę operacje, które przeszły wcześniej. Oznacza to, że wykonuje działanie na pierwszym i drugim elemencie i przenosi wynik do przodu. Następnie działa na poprzednim wyniku i trzeciego elementu i przenosi do przodu. itd.

Przykład 1. Sumowanie liczb

var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)

To dodaje 1 i 2 do 3. Następnie dodaje 3 (wynik poprzedniego) i 3 (następny element w sekwencji), aby 6. Następnie dodaje 6 i 4, aby utworzyć 10.

Przykład 2. tworzenie pliku csv z tablicy łańcuchów

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d
To działa w ten sam sposób. Połącz a przecinek i b, Aby a,b. Następnie łączy a,b z przecinkiem i c, aby utworzyć a,b,c. i tak dalej.

Przykład 3. Mnożenie liczb za pomocą ziarna

Dla kompletności istnieje przeciążenie z Aggregate, które przyjmuje wartość zalążkową.

var multipliers = new []{10,20,30,40};
var multiplied = multipliers.Aggregate(5, (a,b) => a * b);
Console.WriteLine(multiplied); //Output 1200000 ((((5*10)*20)*30)*40)

Podobnie jak w powyższych przykładach, zaczyna się od wartości {[21] } i mnoży ją przez pierwszy element ciągu 10 dając wynik 50. Wynik ten jest przenoszony do przodu i mnożony przez następną liczbę w sekwencji 20, aby dać wynik 1000. Kontynuuje się to przez pozostałe 2 elementy sekwencji.

Przykłady na żywo: http://rextester.com/ZXZ64749
Docs: http://msdn.microsoft.com/en-us/library/bb548651.aspx


Dodatek

Przykład 2 powyżej wykorzystuje konkatenację łańcuchową do tworzenia listy wartości oddzielonych przecinkiem. Jest to uproszczony sposób, aby wyjaśnić użycie Aggregate, które było intencją tej odpowiedzi. Jednak, jeśli użycie tej techniki do stworzenia dużej ilości danych oddzielonych przecinkami, byłoby bardziej odpowiednie użycie StringBuilder, a to jest całkowicie zgodne z Aggregate przy użyciu / align = "left" /

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate(new StringBuilder(), (a,b) => {
    if(a.Length>0)
        a.Append(",");
    a.Append(b);
    return a;
});
Console.WriteLine(csv);

Zaktualizowany przykład: http://rextester.com/YZCVXV6464

 881
Author: Jamiec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-06 02:10:24

To częściowo zależy od tego, o którym przeciążeniu mówisz, ale podstawowa idea brzmi:

  • zacznij od ziarna jako"bieżącej wartości"
  • iteracja nad sekwencją. Dla każdej wartości w sekwencji:
    • zastosuj funkcję określoną przez użytkownika, aby przekształcić (currentValue, sequenceValue) w (nextValue)
    • Zestaw currentValue = nextValue
  • Return the final currentValue

Możesz znaleźć Aggregate post w mojej serii Edulinq przydatny-zawiera bardziej szczegółowy opis (w tym różne przeciążenia) i implementacje.

Jednym z prostych przykładów jest użycie Aggregate jako alternatywy dla Count:

// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);

A może sumowanie wszystkich długości strun w sekwencję strun:

int total = sequence.Aggregate(0, (current, item) => current + item.Length);

Osobiście ja rzadko uważam Aggregate za przydatne - "dopasowane" metody agregacji są dla mnie zazwyczaj wystarczająco dobre.

 117
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-16 15:08:24

Super short Agregat działa jak fold w Haskell / ML / F#.

Nieco dłużej .Max (),Min (),Sum (),Average() wszystkie iteruje nad elementami w sekwencji i agreguje je za pomocą odpowiedniej funkcji agregującej. .Agregat () jest uogólnionym agregatorem, ponieważ pozwala deweloperowi określić stan początkowy (aka seed) i funkcję agregatu.

wiem, że prosiłeś o krótkie wyjaśnienie, ale pomyślałem, że inni Dali kilka krótkich odpowiedzi pomyślałem, że może zainteresuje cię nieco dłuższa

Wersja długa z kodem Jednym ze sposobów zilustrowania tego, co robi, może być pokazanie, w jaki sposób zaimplementujesz przykładowe odchylenie standardowe raz za pomocą foreach i raz za pomocą ./ Align = "left" / uwaga: nie nadałem tu priorytetu wydajności, więc wielokrotnie powtarzałem nad kolekcją niepotrzebnie

Najpierw funkcja pomocnicza używana do tworzenia sumy kwadratowej odległości:

static double SumOfQuadraticDistance (double average, int value, double state)
{
    var diff = (value - average);
    return state + diff * diff;
}

Następnie przykładowe odchylenie standardowe za pomocą ForEach:

static double SampleStandardDeviation_ForEach (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var state = seed;
    foreach (var value in ints)
    {
        state = SumOfQuadraticDistance (average, value, state);
    }
    var sumOfQuadraticDistance = state;

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

Następnie raz za pomocą .Agregat:

static double SampleStandardDeviation_Aggregate (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var sumOfQuadraticDistance = ints
        .Aggregate (
            seed,
            (state, value) => SumOfQuadraticDistance (average, value, state)
            );

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

Zauważ, że te funkcje są identyczne z wyjątkiem sposobu obliczania sumOfQuadraticDistance:

var state = seed;
foreach (var value in ints)
{
    state = SumOfQuadraticDistance (average, value, state);
}
var sumOfQuadraticDistance = state;

Kontra:

var sumOfQuadraticDistance = ints
    .Aggregate (
        seed,
        (state, value) => SumOfQuadraticDistance (average, value, state)
        );
No i co z tego .Agregat robi to, że enkapsuje ten wzór agregatora i oczekuję, że implementacja .Agregat wyglądałby mniej więcej tak:
public static TAggregate Aggregate<TAggregate, TValue> (
    this IEnumerable<TValue> values,
    TAggregate seed,
    Func<TAggregate, TValue, TAggregate> aggregator
    )
{
    var state = seed;

    foreach (var value in values)
    {
        state = aggregator (state, value);
    }

    return state;
}

Korzystanie z funkcji odchylenia standardowego wyglądałoby to mniej więcej tak:

var ints = new[] {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
var average = ints.Average ();
var sampleStandardDeviation = ints.SampleStandardDeviation_Aggregate ();
var sampleStandardDeviation2 = ints.SampleStandardDeviation_ForEach ();

Console.WriteLine (average);
Console.WriteLine (sampleStandardDeviation);
Console.WriteLine (sampleStandardDeviation2);

IMHO

Tak samo .Zbiorcza czytelność pomocy? Ogólnie kocham LINQ, bo myślę .Gdzie, .Wybierz,.OrderBy i tak dalej znacznie ułatwia czytelność (jeśli unikniesz inkrustowanych hierarhical .Selects). Agregat musi być w Linq ze względów kompletności, ale osobiście nie jestem tego tak przekonany .Agregat dodaje czytelność w porównaniu do dobrze napisanego foreach.
 56
Author: Just another metaprogrammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-18 10:39:39

Obraz jest wart tysiąca słów

Reminder: Func<A, B, C> jest funkcją z dwoma wejściami typu A i B, która zwraca C.

Wyliczyć.Agregat ma trzy przeciążenia:


przeciążenie 1:

A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)

Agregat1

przykład:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10


To przeciążenie jest proste, ale ma następujące ograniczenia:

  • sekwencja musi zawierać co najmniej jeden element,
    w przeciwnym razie funkcja rzuci InvalidOperationException.
  • elementy i wynik muszą być tego samego typu.



Przeciążenie 2:

B Aggregate<A, B>(IEnumerable<A> a, B bIn, Func<B, A, B> f)

Agregat2

przykład:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2


To przeciążenie jest bardziej ogólne:

  • należy podać wartość zalążkową (bIn).
  • zbiór może być pusty,
    w tym przypadku funkcja uzyska wartość seed jako wynik.
  • elementy i wynik mogą mieć różne typy.



Przeciążenie 3:

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)


Trzecie przeciążenie nie jest zbyt przydatne IMO.
To samo można zapisać bardziej zwięźle, używając przeciążenia 2, po którym następuje funkcja, która przekształca jego wynik.


Ilustracje są zaadaptowane z tego doskonałego posta blogowego .

 21
Author: 3dGrabber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-08 09:23:53

Agregat jest zasadniczo używany do grupowania lub sumowania danych.

Według MSDN "Funkcja agregująca stosuje funkcję akumulatora nad sekwencją."

Przykład 1: Dodaj wszystkie liczby w tablicy.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);

* ważne: początkową wartością zbiorczą jest domyślnie 1 element w sekwencji zbioru. tzn.: całkowita wartość początkowa zmiennej będzie domyślnie równa 1.

Wyjaśnienie zmiennej

Total: przechowuje wartość sumaryczną (zagregowaną wartość) zwracana przez func.

NextValue: jest następną wartością w sekwencji tablicy. Wartość ta jest następnie dodawana do wartości zagregowanej, tj. całkowitej.

Przykład 2: Dodaj wszystkie elementy w tablicy. Ustaw także początkową wartość akumulatora, aby rozpocząć dodawanie od 10.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);

Wyjaśnienie argumentów:

Pierwszy argument jest początkową (początkową wartością tj. wartością początkową), która zostanie użyta do rozpoczęcia dodawania z następną wartością w tablicy.

Drugi argument to func, który jest func, który zajmuje 2 int.

1.total: będzie to miało taką samą wartość jak przed wartością sumaryczną (sumaryczną) zwróconą przez func po obliczeniu.

2.nextValue:: jest następną wartością w sekwencji tablicy. Wartość ta jest następnie dodawana do wartości zagregowanej, tj. całkowitej.

Debugowanie tego kodu pozwoli Ci lepiej zrozumieć, jak działa agregat.

 14
Author: maxspan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-19 01:29:12

Wiele się nauczyłem zodpowiedzi Jamieca .

Jeśli potrzebujesz tylko wygenerować ciąg CSV, możesz spróbować tego.

var csv3 = string.Join(",",chars);

Oto test z 1 milionem strun

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 

Kod źródłowy to tutaj

 6
Author: Rm558,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Oprócz wszystkich świetnych odpowiedzi tutaj, użyłem go również do przejścia przedmiotu przez serię kroków transformacji.

Jeśli transformacja jest zaimplementowana jako Func<T,T>, możesz dodać kilka przekształceń do List<Func<T,T>> i użyć Aggregate, aby przejść instancję T przez każdy krok.

Bardziej konkretny przykład

Chcesz wziąć wartość string i przejść ją przez serię przekształceń tekstu, które można zbudować programowo.

var transformationPipeLine = new List<Func<string, string>>();
transformationPipeLine.Add((input) => input.Trim());
transformationPipeLine.Add((input) => input.Substring(1));
transformationPipeLine.Add((input) => input.Substring(0, input.Length - 1));
transformationPipeLine.Add((input) => input.ToUpper());

var text = "    cat   ";
var output = transformationPipeLine.Aggregate(text, (input, transform)=> transform(input));
Console.WriteLine(output);

Spowoduje to utworzenie łańcucha przekształceń: Usuń początkowe i końcowe spacje -> Usuń pierwszy znak -> Usuń ostatni znak- > przekonwertuj na wielkie litery. Kroki w tym łańcuchu mogą być dodawane, usuwane lub zmieniane w zależności od potrzeb, aby utworzyć dowolny rodzaj rurociągu transformacji.

Efektem końcowym tego konkretnego potoku jest to, że " cat " staje się "A".


To może stać się bardzo potężne, gdy uświadomisz sobie, że T może być cokolwiek . To może być użyte do transformacji obrazu, jak filtry, używając BitMap jako przykład;

 3
Author: Bradley Uffner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-08 20:19:35

Krótka i istotna definicja może być następująca: metoda LINQ Aggregate extension pozwala zadeklarować rodzaj rekurencyjnej funkcji zastosowanej na elementach listy, których operandami są dwa: elementy w kolejności, w jakiej są obecne na liście, jeden element na raz, i wynik poprzedniej rekurencyjnej iteracji lub nic, jeśli jeszcze nie rekurencyjna.

W ten sposób można obliczyć iloczyn liczb, lub łączyć ciągi.

 0
Author: Ciro Corvino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-12 08:54:16

Jest to wyjaśnienie dotyczące używania Aggregate na płynnym API, takim jak sortowanie Linq.

var list = new List<Student>();
var sorted = list
    .OrderBy(s => s.LastName)
    .ThenBy(s => s.FirstName)
    .ThenBy(s => s.Age)
    .ThenBy(s => s.Grading)
    .ThenBy(s => s.TotalCourses);

I zobaczmy chcemy zaimplementować funkcję sortowania, która zajmuje zbiór pól, jest to bardzo proste używając Aggregate zamiast pętli for, jak to:

public static IOrderedEnumerable<Student> MySort(
    this List<Student> list,
    params Func<Student, object>[] fields)
{
    var firstField = fields.First();
    var otherFields = fields.Skip(1);

    var init = list.OrderBy(firstField);
    return otherFields.Skip(1).Aggregate(init, (resultList, current) => resultList.ThenBy(current));
}

I możemy go używać tak:

var sorted = list.MySort(
    s => s.LastName,
    s => s.FirstName,
    s => s.Age,
    s => s.Grading,
    s => s.TotalCourses);
 0
Author: Jaider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-17 17:52:05

Agregat używany do sumowania kolumn w wielowymiarowej tablicy całkowitej

        int[][] nonMagicSquare =
        {
            new int[] {  3,  1,  7,  8 },
            new int[] {  2,  4, 16,  5 },
            new int[] { 11,  6, 12, 15 },
            new int[] {  9, 13, 10, 14 }
        };

        IEnumerable<int> rowSums = nonMagicSquare
            .Select(row => row.Sum());
        IEnumerable<int> colSums = nonMagicSquare
            .Aggregate(
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray()
                );

Select z indeksem jest używany w Func do sumowania pasujących kolumn i zwracania nowej tablicy; { 3 + 2 = 5, 1 + 4 = 5, 7 + 16 = 23, 8 + 5 = 13 }.

        Console.WriteLine("rowSums: " + string.Join(", ", rowSums)); // rowSums: 19, 27, 44, 46
        Console.WriteLine("colSums: " + string.Join(", ", colSums)); // colSums: 25, 24, 45, 42

Ale liczenie liczby trues w tablicy Boolean jest trudniejsze, ponieważ nagromadzony typ (int) różni się od typu źródłowego (bool); tutaj ziarno jest konieczne, aby użyć drugiego przeciążenia.

        bool[][] booleanTable =
        {
            new bool[] { true, true, true, false },
            new bool[] { false, false, false, true },
            new bool[] { true, false, false, true },
            new bool[] { true, true, false, false }
        };

        IEnumerable<int> rowCounts = booleanTable
            .Select(row => row.Select(value => value ? 1 : 0).Sum());
        IEnumerable<int> seed = new int[booleanTable.First().Length];
        IEnumerable<int> colCounts = booleanTable
            .Aggregate(seed,
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + (currentRow[index] ? 1 : 0)).ToArray()
                );

        Console.WriteLine("rowCounts: " + string.Join(", ", rowCounts)); // rowCounts: 3, 1, 2, 2
        Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2
 0
Author: Dan M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-28 20:16:25

Każdy dał swoje wyjaśnienie. Moje wyjaśnienie jest takie.

Metoda agregująca stosuje funkcję do każdego elementu zbioru. Na przykład, niech będzie kolekcja { 6, 2, 8, 3 } i funkcja Add (operator+) robi (((6+2)+8)+3) i zwraca 19

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: (result, item) => result + item);
// sum: (((6+2)+8)+3) = 19

W tym przykładzie zamiast wyrażenia lambda jest przekazywana metoda o nazwie Add.

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: Add);
// sum: (((6+2)+8)+3) = 19

private static int Add(int x, int y) { return x + y; }
 0
Author: user2983359,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-16 13:49:42