Це може звучати кульгаво, але мені не вдалося знайти справді хорошого пояснення 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 () - це узагальнений агрегатор тим, що дозволяє розробнику вказати стартовий стан (він же насіння) та функцію агрегату.
Я знаю, що ви попросили короткого пояснення, але я зрозумів, як інші дали пару коротких відповідей, я вважав, що вас, можливо, зацікавить трохи довший
Довга версія з кодом Один із способів проілюструвати, що це може показати, як ви реалізуєте 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;
Проти:
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, тому що я думаю. Агрегат повинен бути в Linq з міркувань повноти, але особисто я не настільки впевнений, що. Агрегат додає читабельності порівняно з добре написаним передбаченням.
SampleStandardDeviation_Aggregate()
і SampleStandardDeviation_ForEach()
не може бути private
(за замовчуванням за відсутності кваліфікуючого доступу), тому повинні були бути нараховані або одним, public
або internal
, мені здається
Нагадування:
Func<X, Y, R>
це функція з двома входами типуX
іY
, яка повертає результат типуR
.
Численні. Агрегат має три перевантаження:
Перевантаження 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, яка займає a 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.
змінне пояснення
Всього: воно буде містити суму підсумків (агреговану величину), повернуту функцією.
nextValue: це наступне значення в послідовності масиву. Ця величина перевищує сукупну величину, тобто загальну.
Приклад 2: Додайте всі елементи в масив. Також встановіть початкове значення акумулятора, щоб почати додавати з 10.
int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);
пояснення аргументів:
перший аргумент - це початкове (початкове значення, тобто початкове значення), яке буде використано для початку додавання із наступним значенням у масиві.
другий аргумент - функція, яка є функцією, яка займає 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 або інше статистичне значення) з колекції.
Тому метод Агрегат є формою безпечної реалізації рекурсивної функції.
Безпечно , тому що рекурсія буде повторюватися над кожним елементом колекції, і ми не зможемо отримати нескінченну призупинення циклу за умови неправильного виходу. Рекурсивний , оскільки результат поточної функції використовується як параметр для наступного виклику функції.
Синтаксис:
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.
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()
);
Вибір з індексом використовується в функції агрегування для підсумовування відповідних стовпців і повернення нового масиву; {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]
. Але замість повернення масиву одного значення ви просто отримаєте саме значення.