这听起来可能很蹩脚,但我还没能找到一个对聚合的真正好的解释。
好的意思是简短、描述性、全面,并有一个小而清晰的例子。
这听起来可能很蹩脚,但我还没能找到一个对聚合的真正好的解释。
好的意思是简短、描述性、全面,并有一个小而清晰的例子。
当前回答
一个简短而重要的定义可能是这样的:Linq Aggregate扩展方法允许声明一种应用于列表元素的递归函数,其操作数是两个:按元素在列表中的出现顺序排列的元素,一次一个元素,以及上一次递归迭代的结果,或者如果还没有递归,则什么都没有。
通过这种方式,您可以计算数字的阶乘,或连接字符串。
其他回答
超短聚合的工作方式类似于Haskell/ML/F#中的折叠。
稍长一点.Max()、.Min()、.Sum()、.Aaverage()都在序列中迭代元素,并使用各自的聚合函数聚合它们。Aggregate()是一个通用的聚合器,它允许开发人员指定开始状态(也称为种子)和聚合函数。
我知道你要求一个简短的解释,但我想当其他人给出了几个简短的回答时,我想你可能会对一个稍微长一点的解释感兴趣
带代码的长版本说明它的一种方法是显示如何使用foreach和Aggregate实现一次Sample Standard Deviation。注意:我在这里没有优先考虑性能,因此我在集合中重复了几次,这是不必要的
首先,一个用于创建二次距离总和的辅助函数:
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)
);
那幺.Agregate做的是封装了这个聚合器模式,我希望.Agregate的实现看起来像这样:
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,因为我认为。Where、.Select、.OrderBy等大大有助于可读性(如果您避免内联hierarhical.Selects)。出于完整性的原因,聚合必须在LINQ中,但就我个人而言,我不太相信这一点。与编写良好的foreach相比,聚合增加了可读性。
每个人都给出了自己的解释。我的解释是这样的。
聚合方法将函数应用于集合的每个项。例如,让我们使用集合{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的方法,而不是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; }
用于对多维整数数组中的列求和的聚合
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()
);
在Aggregate函数中使用带索引的Select对匹配的列求和并返回新的Array;{ 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)不同,因此计算布尔数组中的true数更为困难;这里需要种子以使用第二过载。
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
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、a、逗号和b连接起来形成a、b。然后用逗号和c连接a、b,形成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的用法,而Aggregate正是这个答案的意图。但是,如果使用此技术实际创建大量逗号分隔的数据,则更适合使用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
除了这里已经给出的所有好答案之外,我还用它来引导一个项目完成一系列转换步骤。
如果转换被实现为Func<T,T>,则可以将多个转换添加到List<Func<T,T>中,并使用Aggregate遍历T的每个步骤。
更具体的例子
您需要获取一个字符串值,并通过一系列可以通过编程构建的文本转换对其进行遍历。
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);
这将创建一系列转换:删除前导和尾随空格->删除第一个字符->删除最后一个字符->转换为大写。可以根据需要添加、删除或重新排序此链中的步骤,以创建所需的任何类型的转换管道。
这个特定管道的最终结果是“猫”变成了“A”。
一旦你意识到T可以是任何东西,它就会变得非常强大。这可以用于图像转换,如过滤器,使用BitMap作为示例;