Алгоритм LINQ Aggregate объяснен

Это может показаться хромым, но я не смог найти действительно хорошее объяснение Aggregate.

Хорошее означает короткий, описательный, всеобъемлющий с небольшим и ясным примером.

Ответ 1

Простейшим для понимания определением Aggregate является то, что он выполняет операцию над каждым элементом списка с учетом ранее проведенных операций. То есть он выполняет действие над первым и вторым элементом и переносит результат вперед. Затем он работает с предыдущим результатом и третьим элементом и переносится вперед. и др.

Пример 1. Суммирование чисел

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

Это добавляет 1 и 2, чтобы сделать 3. Затем добавляет 3 (результат предыдущего) и 3 (следующий элемент в последовательности), чтобы сделать 6. Затем добавляет 6 и 4, чтобы сделать 10.

Пример 2. Создайте csv из массива строк

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d

Это работает почти так же. Соедините a запятую и b, чтобы сделать a,b. Затем конкатенирует a,b с запятой и c, чтобы сделать a,b,c. и т.д.

Пример 3. Умножение чисел с использованием семени

Для полноты существует перегрузка Aggregate, которая принимает начальное значение.

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)

Как и в приведенных выше примерах, это начинается со значения 5 и умножает его на первый элемент последовательности 10, давая результат 50. Этот результат переносится вперед и умножается на следующее число в последовательности 20, чтобы получить результат 1000. Это продолжается через оставшийся 2 элемента последовательности.

Примеры в реальном времени: http://rextester.com/ZXZ64749
Документы: http://msdn.microsoft.com/en-us/library/bb548651.aspx


Добавление

Пример 2, выше, использует конкатенацию строк для создания списка значений, разделенных запятой. Это упрощенный способ объяснить использование Aggregate, которое было целью этого ответа. Однако, если использовать этот метод для фактического создания большого количества разделенных запятыми данных, было бы более целесообразно использовать StringBuilder, и это полностью совместимо с Aggregate, используя перегруженную серию, чтобы инициировать StringBuilder.

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

Пример: http://rextester.com/YZCVXV6464

Ответ 2

Отчасти это зависит от того, о какой перегрузке вы говорите, но основная идея:

  • Начните с семени как "текущее значение"
  • Итерации по последовательности. Для каждого значения в последовательности:
    • Применить заданную пользователем функцию для преобразования (currentValue, sequenceValue) в (nextValue)
    • Установить currentValue = nextValue
  • Вернуть окончательный currentValue

Вы можете найти сообщение Aggregate в моей серии Edulinq полезное - оно включает более подробное описание (включая различные перегрузки) и реализации.

Один простой пример - использование Aggregate в качестве альтернативы 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);

Или, возможно, суммируя все длины строк в последовательности строк:

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

Лично я редко нахожу Aggregate полезным - методы "адаптированного" агрегации обычно достаточно хороши для меня.

Ответ 3

Супер короткий Агрегат работает как сгиб в Haskell/ML/F #.

Немного длиннее .Max(),.Min(),.Sum(),.Average() выполняет итерацию по элементам в последовательности и агрегирует их с использованием соответствующей агрегатной функции..Aggregate() является обобщенным агрегатором, поскольку он позволяет разработчику указывать начальное состояние (aka seed) и агрегатную функцию.

Я знаю, что вы попросили краткое объяснение, но я подумал, что другие дали несколько коротких ответов, которые, по моему мнению, вам интереснее немного длиннее.

Длинная версия с кодом Один из способов проиллюстрировать, что это может показать, как вы реализуете Sample Standard Deviation один раз, используя foreach и один раз используя .Aggregate. Примечание. У меня нет приоритета производительности здесь, поэтому я повторяю несколько раз над сборником без необходимости

Сначала вспомогательная функция, используемая для создания суммы квадратичных расстояний:

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

Затем стандартное отклонение выборки с использованием параметра 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));
}

Затем после использования .Aggregate:

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

Обратите внимание, что эти функции идентичны, за исключением того, как вычисляется sumOfQuadraticDistance:

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

Versus:

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

Итак, что. Агрегат - это то, что он инкапсулирует этот шаблон агрегатора, и я ожидаю, что реализация .Aggregate будет выглядеть примерно так:

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

Использование стандартных функций отклонения будет выглядеть примерно так:

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

ИМХО

Так делает. Объясняет читаемость справки? В общем, я люблю LINQ, потому что думаю. Где бы то ни было .Select,.OrderBy и т.д. Значительно облегчает читаемость (если вы избегаете встроенных иерархий.Выбирает). Агрегат должен быть в Linq по причинам полноты, но лично я не настолько убежден, что. Агрегат добавляет читаемость по сравнению с хорошо написанным foreach.

Ответ 4

Картинка стоит тысячи слов

Напоминание:
Func<X, Y, R> - это функция с двумя входами типа X и Y, которая возвращает результат типа R.

Enumerable.Aggregate имеет три перегрузки:


Перегрузка 1:

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

Aggregate1

Пример:

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


Эта перегрузка проста, но имеет следующие ограничения:

  • последовательность должна содержать хотя бы один элемент,
    в противном случае функция выдаст InvalidOperationException.
  • элементы и результат должны быть одного типа.



Перегрузка 2:

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

Aggregate2

Пример:

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


Эта перегрузка носит более общий характер:

  • необходимо указать начальное значение (bIn).
  • коллекция может быть пустой,
    в этом случае функция выдаст начальное значение как результат.
  • элементы и результат могут иметь разные типы.



Перегрузка 3:

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


Третья перегрузка не очень полезна для ИМО.
То же самое можно записать более кратко, используя перегрузку 2, за которой следует функция, которая преобразует свой результат.


Иллюстрации взяты из этого отличного поста.

Ответ 5

Агрегат в основном используется для группировки или суммирования данных.

Согласно MSDN           "Агрегатная функция Применяет функцию аккумулятора по последовательности".

Пример 1: добавьте все числа в массив.

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

* important: начальное значение агрегата по умолчанию - это 1 элемент в последовательности сбора. i.e: общее начальное значение переменной будет по умолчанию равно 1.

описание переменных

total: он будет содержать суммарное значение (агрегированное значение), возвращаемое функцией func.

nextValue: это следующее значение в последовательности массива. Это значение добавляется к агрегированному значению i.e total.

Пример 2: добавьте все элементы в массив. Также установите начальное значение аккумулятора, чтобы начать добавление с 10.

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

аргументы:

первым аргументом является начальное (начальное значение i.e начальное значение), которое будет использоваться для начала добавления со следующим значением в массиве.

второй аргумент является func, который является func, который принимает 2 int.

1.Это будет сохраняться так же, как и до значения суммирования (агрегированного значения), возвращаемого func после вычисления.

2.nextValue:: это следующее значение в последовательности массива. Это значение добавляется к агрегированному значению i.e total.

Также отладка этого кода даст вам лучшее представление о том, как работает агрегат.

Ответ 6

Выучил много из ответ Жамеца.

Если нужна только генерация CSV-строки, вы можете попробовать это.

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

Вот тест с 1 миллионом строк

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

Исходный код здесь

Ответ 7

В дополнение ко всем великим ответам здесь, я также использовал его, чтобы пройти элемент через последовательность шагов преобразования.

Если преобразование реализовано как Func<T,T>, вы можете добавить несколько преобразований в List<Func<T,T>> и использовать Aggregate для перехода к экземпляру T через каждый шаг.

Более конкретный пример

Вы хотите взять значение string и пройти его через ряд текстовых преобразований, которые могут быть построены программно.

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

Это создаст цепочку преобразований: Удалите передние и конечные пробелы → удалите первый символ → удалите последний символ → преобразуйте в верхний регистр. Шаги в этой цепочке могут быть добавлены, удалены или переупорядочены по мере необходимости, чтобы создать какой-либо конвейер преобразования.

Конечным результатом этого конкретного конвейера является то, что " cat " становится "A".


Это может стать очень мощным, если вы поймете, что T может быть чем угодно. Это можно использовать для преобразования изображений, например, в фильтры, используя BitMap в качестве примера;

Ответ 8

Каждый дал свое объяснение. Мое объяснение таково.

Агрегатный метод применяет функцию к каждому элементу коллекции. Например, пусть набор {6, 2, 8, 3} и функция Add (оператор +) он делает (((6 + 2) +8) +3) и возвращает 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

В этом примере передается именованный метод 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; }

Ответ 9

Коротким и существенным определением может быть следующее: метод расширения Linq Aggregate позволяет объявить некоторую рекурсивную функцию, применяемую к элементам списка, операндами которых являются два: элементы в том порядке, в котором они присутствуют в список, один элемент за раз и результат предыдущей рекурсивной итерации или ничего, если не рекурсия.

Таким образом вы можете вычислить факториал чисел или объединить строки.

Ответ 10

Это объяснение использования Aggregate в Fluent API, таком как сортировка 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);

и мы видим, что мы хотим реализовать функцию сортировки, которая принимает набор полей, это очень просто, используя Aggregate вместо цикла for, например:

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

И мы можем использовать его следующим образом:

var sorted = list.MySort(
    s => s.LastName,
    s => s.FirstName,
    s => s.Age,
    s => s.Grading,
    s => s.TotalCourses);

Ответ 11

Агрегат, используемый для суммирования столбцов в многомерном целочисленном массиве

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

Выбор с помощью индекса используется в функции агрегации для суммирования совпадающих столбцов и возврата нового массива; {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

Но подсчет количества истин в булевом массиве сложнее, так как накопленный тип (int) отличается от типа источника (bool); здесь требуется семя, чтобы использовать вторую перегрузку.

        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

Ответ 12

  Определение

Агрегатный метод - это метод расширения для универсальных коллекций. Метод Aggregate применяет функцию к каждому элементу коллекции. Не только применяет функцию, но и принимает ее результат в качестве начального значения для следующей итерации. Таким образом, в результате мы получим вычисленное значение (min, max, avg или другое статистическое значение) из коллекции.

Следовательно, метод Aggregate - это форма безопасной реализации рекурсивной функции.

Безопасный, потому что рекурсия будет повторяться для каждого элемента коллекции, и мы не можем получить приостановку бесконечного цикла из-за неправильного условия выхода. Рекурсивно, поскольку текущий результат функции используется в качестве параметра для следующего вызова функции.

Syntax:

collection.Aggregate(seed, func, resultSelector);
  • seed - начальное значение по умолчанию;
  • func - наша рекурсивная функция Это может быть лямбда-выражение, делегат Func или тип функции T F (T result, T nextValue);
  • resultSelector - это может быть функция типа func или выражение для вычисления, преобразования, изменения, преобразования окончательного результата.

Как это работает:

var nums = new[]{1, 2};
var result = nums.Aggregate(1, (result, n) => result + n); //result = (1 + 1) + 2 = 4
var result2 = nums.Aggregate(0, (result, n) => result + n, response => (decimal)response/2.0); //result2 = ((0 + 1) + 2)*1.0/2.0 = 3*1.0/2.0 = 3.0/2.0 = 1.5

Practical usage:

  1. Найти Факториал из числа n:

int n = 7;
var numbers = Enumerable.Range(1, n);
var factorial = numbers.Aggregate((result, x) => result * x);

который выполняет ту же функцию, что и эта функция:

public static int Factorial(int n)
{
   if (n < 1) return 1;

   return n * Factorial(n - 1);
}
  1. Aggregate() является одним из самых мощных методов расширения LINQ, таких как Select() и Where(). Мы можем использовать его для замены Sum(), Min(). Max(), Avg() или изменить его, добавив дополнительный контекст:
    var numbers = new[]{3, 2, 6, 4, 9, 5, 7};
    var avg = numbers.Aggregate(0.0, (result, x) => result + x, response => (double)response/(double)numbers.Count());
    var min = numbers.Aggregate((result, x) => (result < x)? result: x);
  1. Более сложное использование методов расширения:
    var path = @'c:\path-to-folder';

    string[] txtFiles = Directory.GetFiles(path).Where(f => f.EndsWith('.txt')).ToArray<string>();
    var output = txtFiles.Select(f => File.ReadAllText(f, Encoding.Default)).Aggregate<string>((result, content) => result + content);

    File.WriteAllText(path + 'summary.txt', output, Encoding.Default);

    Console.WriteLine('Text files merged into: {0}', output); //or other log info