LINQ聚合算法解释

这可能听起来很蹩脚,但我还没能找到一个很好的解释Aggregate

好意味着简短,描述性,全面,有一个小而清晰的例子。

317951 次浏览

这在一定程度上取决于你所说的重载,但基本思想是:

  • 以种子作为“当前值”开始
  • 迭代序列。对于序列中的每个值:
    • 应用用户指定的函数将(currentValue, sequenceValue)转换为(nextValue)
    • 设置currentValue = nextValue
  • 返回最终currentValue

您可能会发现#0文章在我的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很有用——“量身定制”的聚合方法通常对我来说已经足够好了。

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)

这将12添加到3。然后将3(前一个结果)和3(顺序中的下一个元素)添加到6。然后将64添加到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

<强>超短聚合的工作方式类似于Haskell/ML/F#中的折叠。

稍长. Max(),. Min(),. Sum(),. A平均()所有迭代序列中的元素并使用各自的聚合函数聚合它们。. 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));}

然后使用一次。聚合:

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

请注意,这些函数是相同的,除了sum OfQuadaticDetance是如何计算的:

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

IMHO

一般来说,我喜欢LINQ,因为我认为。在哪里,。选择,。OrderBy等大大有助于易读性(如果你避免内联的层次结构。选择)。由于完整性的原因,Aggregate必须在Linq中,但我个人不太相信这一点。与写得很好的Foreach相比,Aggregate增加了易读性。

Aggregate基本上用于对数据进行分组或求和。

根据MSDN"聚合函数在序列上应用累加器函数。"

示例1:将数组中的所有数字相加。

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

*重要:默认情况下,初始聚合值是集合序列中的1个元素。即:总变量初始值默认为1。

变量解释

总计:它将保存func返回的总和值(聚合值)。

nextValue:它是数组序列中的下一个值。此值被添加到聚合值即总计中。

示例2:在数组中添加所有项目。还设置初始累加器值以从10开始添加。

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

论据解释:

第一个参数是初始值(起始值即种子值),它将用于从数组中的下一个值开始加法。

第二个参数是一个func,它是一个接受2 int的func。

1.total:这将保持与计算后func返回的总和值(聚合值)之前相同。

2.nextValue::它是数组序列中的下一个值。此值被添加到聚合值即总计中。

此外,调试此代码将使您更好地了解聚合的工作方式。

从答案Jamiec的学到了很多。

如果只需要生成CSV字符串,您可以尝试此操作。

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

这是一个100万测试

0.28 seconds = Aggregate w/ String Builder0.30 seconds = String.Join

源代码是这里

一个简短而基本的定义可能是这样的:Linq Aggregate扩展方法允许声明一种应用于列表元素的递归函数,其操作数是两个:元素按它们存在于列表中的顺序,一次一个元素,以及之前递归迭代的结果,如果还没有递归,则什么都没有。

通过这种方式,您可以计算数字的阶乘,或连接字符串。

这是关于在Fluent API(例如Linq Sorting)上使用Aggregate的解释。

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

选择with index在Aggregate func中用于对匹配的列进行求和并返回一个新数组;{3+2=5,1+4=5,7+16=23,8+5=13}。

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

但是计算布尔数组中的true数量更加困难,因为累积类型(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, 2Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2

一张照片胜过千言万语

提醒:
Func<X, Y, R>是一个有两个输入类型XY的函数,返回类型R的结果。

Aggregate有三个重载:


重载1:

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

Aggregate 1

示例:

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)

Aggregate 2

示例:

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)


第三个重载在IMO中不是很有用。
通过使用重载2后跟转换其结果的函数,可以更简洁地编写相同的内容。


插图改编自这个优秀的博客

除了这里已经有的所有好答案之外,我还用它来引导一个项目完成一系列转换步骤。

如果转换实现为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为例;

每个人都给出了他的解释。我的解释是这样的。

Aggregate方法将一个函数应用于集合的每个项目。例如,让我们有集合{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而不是lambda表达式。

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

定义

Aggregate方法是泛型集合的扩展方法。Aggregate方法将函数应用于集合的每个项。不仅应用一个函数,而且将其结果作为下一次迭代的初始值。因此,我们将从集合中获得计算值(min、max、avg或其他统计值)。

因此,聚合方法是递归函数的一种安全实现形式。

安全,因为递归将迭代集合的每个项目,并且我们不能通过错误的退出条件获得任何无限循环暂停。递归,因为当前函数的结果被用作下一个函数调用的参数。

语法:

collection.Aggregate(seed, func, resultSelector);
  • 种子-默认初始值;
  • func-我们的递归函数。它可以是lambda表达式、Func委托或函数类型T F(T结果,T nextValue);
  • 结果选择器-它可以是一个函数,如func或一个表达式来计算,转换,更改,转换最终结果。

它是如何工作的:

var nums = new[]{1, 2};var result = nums.Aggregate(1, (result, n) => result + n); //result = (1 + 1) + 2 = 4var 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

实际用法:

  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扩展方法之一,就像选择()和在哪里()一样。我们可以用它来替换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