LINQ聚合algorithm解释

这可能听起来很蹩脚,但我一直没能find一个很好的解释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 。 然后加6410

示例2.从一个string数组中创build一个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来创builda,b 。 然后将a,b与逗号和ca,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使用string连接来创build由逗号分隔的值列表。 这是一个简单的方法来解释这个答案的目的是使用Aggregate 。 但是,如果使用这种技术来实际创build大量逗号分隔的数据,那么使用StringBuilder会更合适,而且这与使用种子重载启动StringBuilder Aggregate完全兼容。

 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

您可能会发现我的Edulinq系列中的Aggregatepost很有用 – 它包含更详细的描述(包括各种重载)和实现。

一个简单的例子是使用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); 

或者也许总结string序列中所有长度的string:

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

就我个人而言,我很less发现Aggregate有用 – “量身定制”的聚合方法通常对我来说足够好。

超级短集合像Haskell / ML / F#中的折叠一样工作。

Max(),.Min(),.Sum(),.Average()全部遍历序列中的元素,并使用各自的聚合函数进行聚合。 .Aggregate()是泛化的聚合器,它允许开发人员指定开始状态(又名种子)和聚合函数。

我知道你问了一个简短的解释,但我认为其他人给了几个简短的答案我想你可能会感兴趣的稍长

使用代码的长版本一种方法可以说明如何使用foreach和一次使用.Aggregate来实现Sample Standard Deviation 。 注意:在这里我没有优先考虑性能,所以我不必要地多次迭代集合

首先使用一个辅助函数来创build二次距离的总和:

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

请注意,除了如何计算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); 

恕我直言

所以呢。整合帮助可读性? 一般来说,我喜欢LINQ,因为我认为。在哪里,.Select,.OrderBy等大大有助于可读性(如果你避免内联层次select)。 为了完整性原因,总结必须在Linq中,但是我个人并不这么认为.Aggregate增加了可读性,而且写得比较好。

聚合基本上用于分组或总结数据。

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

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

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

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

variables的解释

总数:它将保存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。

总计:这与计算后由func返回的总计值(合计值)保持一致。

2.nextValue:它是数组序列中的下一个值。 这个值被加到总值上,即总和。

另外debugging这段代码会让你更好的理解聚合的工作方式。

一张图片胜过千言万语

提醒: Func<A, B, C>是一个带有两个ABinput的函数,返回一个C

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 

这个过载很简单,但它有以下限制:

  • 该序列必须包含至less一个元素,
    否则该函数将抛出一个InvalidOperationException
  • 元素和结果必须是相同的types。


超载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 )。
  • 收集可以是空的,
    在这种情况下,函数将产生结果的种子值。
  • 元素和结果可以有不同的types。


过载3:

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

IMO的第三次过载并不是很有用。
通过使用重载2,然后转换其结果的函数,可以更简洁地编写相同的代码。

插图是从这个优秀的博客post改编的。

从Jamiec的回答中学到了很多东西。

如果唯一的需要是生成CSVstring,你可以试试这个。

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

这是一个100万string的testing

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

源代码在这里

除了这里所有的重要答案外,我还用它来通过一系列的转换步骤来演示一个项目。

如果以Func<T,T>forms实现转换,则可以向List<Func<T,T>>添加多个转换List<Func<T,T>>并使用Aggregate遍历每个步骤的T实例。

一个更具体的例子

你想要一个string值,并通过一系列可以编程方式构build的文本转换。

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

这将创build一个转换链:删除前导和尾随空格 – >删除第一个字符 – >删除最后一个字符 – >转换为大写。 可以根据需要添加,删除或重新sorting此链中的步骤,以创build任何types的转换pipe道。

这个特定pipe道的最终结果是, " cat "变成了"A"


一旦你意识到T可以是任何东西 ,这可以变得非常强大。 这可以用于图像转换,比如filter,以BitMap为例;

一个简短而重要的定义可能是这样的:Linq Aggregate扩展方法允许声明一个recursion函数应用于列表的元素,其操作数是两个:列表中元素的顺序,一次一个元素,以及之前的recursion迭代的结果,或者如果还没有recursion,则什么都不是。

用这种方法可以计算数字的阶乘,或者连接string。

这是关于在Fluent API上使用Aggregate的解释,例如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); 

让我们看看我们想要实现一个采用一组字段的sorting函数,这是非常容易的使用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() ); 

使用索引select在聚合函数中使用来对匹配列求和并返回一个新的数组; {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 

但是,由于累积types(int)与源types(bool)不同,因此计算布尔数组中的trues数量会更困难。 这里为了使用第二次过载,种子是必要的。

  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