Это может показаться хромым, но я не смог найти действительно хорошее объяснение Aggregate
.
Хорошее означает короткий, описательный, всеобъемлющий с небольшим и ясным примером.
Это может показаться хромым, но я не смог найти действительно хорошее объяснение Aggregate
.
Хорошее означает короткий, описательный, всеобъемлющий с небольшим и ясным примером.
Простейшим для понимания определением 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
Отчасти это зависит от того, о какой перегрузке вы говорите, но основная идея:
(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
полезным - методы "адаптированного" агрегации обычно достаточно хороши для меня.
Супер короткий Агрегат работает как сгиб в 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.
Напоминание:
Func<X, Y, R>
- это функция с двумя входами типаX
иY
, которая возвращает результат типаR
.
Enumerable.Aggregate имеет три перегрузки:
Перегрузка 1:
A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)
Пример:
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)
Пример:
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, за которой следует функция, которая преобразует свой результат.
Иллюстрации взяты из этого отличного поста.
Агрегат в основном используется для группировки или суммирования данных.
Согласно 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.
Также отладка этого кода даст вам лучшее представление о том, как работает агрегат.
Выучил много из ответ Жамеца.
Если нужна только генерация CSV-строки, вы можете попробовать это.
var csv3 = string.Join(",",chars);
Вот тест с 1 миллионом строк
0.28 seconds = Aggregate w/ String Builder
0.30 seconds = String.Join
Исходный код здесь
В дополнение ко всем великим ответам здесь, я также использовал его, чтобы пройти элемент через последовательность шагов преобразования.
Если преобразование реализовано как 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
в качестве примера;
Каждый дал свое объяснение. Мое объяснение таково.
Агрегатный метод применяет функцию к каждому элементу коллекции. Например, пусть набор {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; }
Коротким и существенным определением может быть следующее: метод расширения Linq Aggregate позволяет объявить некоторую рекурсивную функцию, применяемую к элементам списка, операндами которых являются два: элементы в том порядке, в котором они присутствуют в список, один элемент за раз и результат предыдущей рекурсивной итерации или ничего, если не рекурсия.
Таким образом вы можете вычислить факториал чисел или объединить строки.
Это объяснение использования 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);
Агрегат, используемый для суммирования столбцов в многомерном целочисленном массиве
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
Определение
Агрегатный метод - это метод расширения для универсальных коллекций. Метод Aggregate применяет функцию к каждому элементу коллекции. Не только применяет функцию, но и принимает ее результат в качестве начального значения для следующей итерации. Таким образом, в результате мы получим вычисленное значение (min, max, avg или другое статистическое значение) из коллекции.
Следовательно, метод Aggregate - это форма безопасной реализации рекурсивной функции.
Безопасный, потому что рекурсия будет повторяться для каждого элемента коллекции, и мы не можем получить приостановку бесконечного цикла из-за неправильного условия выхода. Рекурсивно, поскольку текущий результат функции используется в качестве параметра для следующего вызова функции.
Syntax:
collection.Aggregate(seed, func, resultSelector);
Как это работает:
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:
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);
}
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);
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