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)
这增加了1
和2
使3
。 然后加上3
(前一个结果)和3
(序列中的下一个元素) 6
。 然后加6
和4
做10
。
示例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
与逗号和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使用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系列中的Aggregate
post很有用 – 它包含更详细的描述(包括各种重载)和实现。
一个简单的例子是使用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>
是一个带有两个A
和B
input的函数,返回一个C
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
这个过载很简单,但它有以下限制:
- 该序列必须包含至less一个元素,
否则该函数将抛出一个InvalidOperationException
。 - 元素和结果必须是相同的types。
超载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
)。 - 收集可以是空的,
在这种情况下,函数将产生结果的种子值。 - 元素和结果可以有不同的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