Это может звучать глупо, но я не смог найти действительно хорошего объяснения 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
которого было целью этого ответа. Однако, если использовать эту технику для создания большого количества данных, разделенных запятыми, было бы более целесообразно использовать a 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
TakeWhile
тогда Aggregate
- то будет beatuty расширений перечислимого - они легко цепные. Итак, вы в конечном итоге TakeWhile(a => a == 'a').Aggregate(....)
. Посмотрите этот пример: rextester.com/WPRA60543
var csv = string.Join(",", chars)
(без необходимости в агрегате или строителях строк) - но да, я знаю, смысл ответа был в том, чтобы привести пример использования агрегата, так что это круто. Но я все же хотел упомянуть, что это не рекомендуется для простого объединения строк, для этого уже есть метод, выделенный для этого ...
var biggestAccount = Accounts.Aggregate((a1, a2) => a1.Amount >= a2.Amount ? a1 : a2);
Отчасти это зависит от того, о какой перегрузке вы говорите, но основная идея такова:
(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 () является обобщенным агрегатором в том смысле, что он позволяет разработчику указать начальное состояние (также называемое семенем) и функцию агрегирования.
Я знаю, что вы попросили короткого объяснения, но я решил, что другие дали пару коротких ответов, и я подумал, что вас, возможно, заинтересует чуть более длинный.
Длинная версия с кодом Один из способов проиллюстрировать, что это может быть, - показать, как вы реализуете стандартное отклонение образца, однажды используя 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;
Против:
var sumOfQuadraticDistance = ints
.Aggregate (
seed,
(state, value) => SumOfQuadraticDistance (average, value, state)
);
Так что .Aggregate делает то, что он инкапсулирует этот шаблон агрегатора, и я ожидаю, что реализация .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);
ИМХО
Так .Aggregate помогает читабельности? В общем, я люблю LINQ, потому что я думаю, что .Where, .Select, .OrderBy и т. Д. Очень помогают удобочитаемости (если вы избегаете встроенных иерархических .Selects). Агрегат должен быть в Linq по причинам полноты, но лично я не настолько убежден, что .Agregate добавляет удобочитаемость по сравнению с хорошо написанным foreach.
SampleStandardDeviation_Aggregate()
и SampleStandardDeviation_ForEach()
не может быть private
(по умолчанию при отсутствии квалификатора доступа), поэтому должен был быть накоплен либо, public
либо internal
, как мне кажется
Напоминание:
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, за которой следует функция, которая преобразует свой результат.
Иллюстрации взяты из этого отличного поста .
Aggegate
в .net нет перегрузки, которая принимает Func<T, T, T>
.
seed
, применяет функцию аккумулятора N -1 раз; в то время как другие перегрузки (что делать потратьте seed
) применить агрегатную функцию N раз.
Агрегат в основном используется для группировки или суммирования данных.
Согласно MSDN «Агрегатная функция применяет функцию накопителя к последовательности».
Пример 1: Добавить все числа в массив.
int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);
* важно: начальное совокупное значение по умолчанию - 1 элемент в последовательности сбора. т.е.: общее начальное значение переменной будет 1 по умолчанию.
объяснение переменной
total: он будет содержать итоговое значение (агрегированное значение), возвращаемое функцией.
nextValue: это следующее значение в последовательности массива. Это значение затем добавляется к агрегированному значению, т.е.
Пример 2: Добавить все элементы в массив. Также установите начальное значение аккумулятора, чтобы начать добавлять с 10.
int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);
Аргументация объяснения:
первый аргумент - это начальный (начальное значение, т.е. начальное значение), который будет использоваться для начала сложения со следующим значением в массиве.
Второй аргумент - это func, который принимает 2 int.
1.total: он будет таким же, как и до суммирования (агрегированного значения), возвращаемого функцией после вычисления.
2.nextValue :: это следующее значение в последовательности массивов. Это значение затем добавляется к агрегированному значению, т.е.
Также отладка этого кода даст вам лучшее понимание того, как работает агрегат.
Много узнал из ответа Джеймица .
Если вам нужно только создать строку CSV, вы можете попробовать это.
var csv3 = string.Join(",",chars);
Вот тест с 1 миллионом строк
0.28 seconds = Aggregate w/ String Builder
0.30 seconds = String.Join
Исходный код здесь
В дополнение ко всем отличным ответам здесь, я также использовал это, чтобы пройти элемент через ряд шагов преобразования.
Если преобразование реализовано как a Func<T,T>
, вы можете добавить несколько преобразований в a 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
в качестве примера;
Определение
Агрегатный метод - это метод расширения для универсальных коллекций. Метод агрегирования применяет функцию к каждому элементу коллекции. Не только применяет функцию, но и принимает ее результат в качестве начального значения для следующей итерации. Таким образом, в результате мы получим вычисленное значение (min, max, avg или другое статистическое значение) из коллекции.
Следовательно, метод Aggregate является формой безопасной реализации рекурсивной функции.
Безопасно , потому что рекурсия будет повторяться для каждого элемента коллекции, и мы не можем получить приостановку бесконечного цикла из-за неправильного условия выхода. Рекурсивно , потому что результат текущей функции используется в качестве параметра для следующего вызова функции.
Синтаксис:
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
Практическое использование:
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
Это объяснение использования Aggregate
в Fluent API, таком как Linq Sorting.
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);
Каждый дал свое объяснение. Мое объяснение таково.
Метод агрегирования применяет функцию к каждому элементу коллекции. Например, у нас есть коллекция {6, 2, 8, 3} и функция Add (operator +), которую она выполняет (((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 позволяет объявить своего рода рекурсивную функцию, применяемую к элементам списка, операндов которых два: элементы в том порядке, в котором они присутствуют в списке, один элемент за раз, и результат предыдущей рекурсивной итерации или ничего, если еще не рекурсия.
Таким образом, вы можете вычислить факториал чисел или объединить строки.
Агрегат, используемый для суммирования столбцов в многомерном целочисленном массиве
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 with index используется в функции Aggregate для суммирования совпадающих столбцов и возврата нового массива; {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
[1,2,3,4]
будет[3,3,4]
потом[6,4]
и наконец[10]
. Но вместо того, чтобы возвращать массив одного значения, вы просто получаете само значение.