LINQ聚合算法解释

Ale*_*sky 689 .net c# linq

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

良好意味着简短,描述性,全面,有一个小而明确的例子.

Jam*_*iec 980

最容易理解的定义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)
Run Code Online (Sandbox Code Playgroud)

这增加了1并使之2成为现实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
Run Code Online (Sandbox Code Playgroud)

这种方式大致相同.连接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)
Run Code Online (Sandbox Code Playgroud)

与上面的例子非常相似,它以值为开始,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);
Run Code Online (Sandbox Code Playgroud)

更新示例:http://rextester.com/YZCVXV6464

  • @JeffTian - 我建议链接一个[`TakeWhile`](http://msdn.microsoft.com/en-us/library/vstudio/bb534804%28v=vs.100%29.aspx)然后一个`Aggregate` - 这就是可枚举扩展的节拍 - 它们很容易链接.所以你最终得到了'TakeWhile(a => a =='a').Aggregate(....)`.请参阅此示例:http://rextester.com/WPRA60543 (13认同)
  • 第一个描述的另一个解释是,您提供的函数始终组合前两个成员,直到数组缩小为一个元素.所以`[1,2,3,4]`将是`[3,3,4]`然后是`[6,4]`,最后是`[10]`.但是,不是返回单个值的数组,而是获取值本身. (9认同)
  • 我可以提早退出/退出聚合函数吗?例如,chars.Aggregate((a,b)=> {if(a =='a')打破整个聚合,否则返回一个+','+ b}) (2认同)
  • 作为附录的注解,可以很容易地将整个块替换为`var csv = string.Join(“,”,chars)`(不需要聚合或字符串生成器)-但是,我知道答案的重点是给出聚合的用法示例,这很酷。但我仍然想提一提,不建议仅将其连接字符串,已经有专用于此的方法。 (2认同)
  • 另一个常见的用法(到目前为止,我在生产代码中只见过)是获取最小或最大的项目,如`var largestAccount = Accounts.Aggregate((a1,a2)=> a1.Amount> = a2.Amount?a1: A2);` (2认同)
  • 感觉就像“.net 中的聚合”是“javascript 中的缩减” (2认同)

Jon*_*eet 128

这部分取决于你所谈论的超载,但基本思路是:

  • 以种子作为"当前值"开始
  • 迭代序列.对于序列中的每个值:
    • 应用用户指定的函数变换(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);
Run Code Online (Sandbox Code Playgroud)

或者也许在字符串序列中总结字符串的所有长度:

int total = sequence.Aggregate(0, (current, item) => current + item.Length);
Run Code Online (Sandbox Code Playgroud)

就个人而言,我很少发现Aggregate有用 - "量身定制"的聚合方法对我来说通常都足够好.

  • @Jon是否有聚合的异步变体将项目拆分为树,以便可以在核心之间拆分工作?看起来该方法的设计与"减少"或"折叠"的概念是一致的,但我不知道它是否真的是在幕后做,或者只是遍历项目列表. (6认同)

Jus*_*mer 59

超短 聚合在Haskell/ML/F#中像折叠一样工作.

稍微长一些 .Max(),. Min(),. Sum(),. Average()都遍历序列中的元素并使用相应的聚合函数聚合它们..Aggregate()是通用聚合器,它允许开发人员指定开始状态(又称种子)和聚合函数.

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

带代码的长版本 一种方式可以说明如何使用foreach和使用.Aggregate 实现样本标准偏差.注意:我没有优先考虑性能,所以我不必要地在集合上多次迭代

首先是一个辅助函数,用于创建二次距离之和:

static double SumOfQuadraticDistance (double average, int value, double state)
{
    var diff = (value - average);
    return state + diff * diff;
}
Run Code Online (Sandbox Code Playgroud)

然后使用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));
}
Run Code Online (Sandbox Code Playgroud)

然后一旦使用.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));
}
Run Code Online (Sandbox Code Playgroud)

请注意,除了如何计算sumOfQuadraticDistance之外,这些函数是相同的:

var state = seed;
foreach (var value in ints)
{
    state = SumOfQuadraticDistance (average, value, state);
}
var sumOfQuadraticDistance = state;
Run Code Online (Sandbox Code Playgroud)

与:

var sumOfQuadraticDistance = ints
    .Aggregate (
        seed,
        (state, value) => SumOfQuadraticDistance (average, value, state)
        );
Run Code Online (Sandbox Code Playgroud)

那么.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;
}
Run Code Online (Sandbox Code Playgroud)

使用标准差函数看起来像这样:

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);
Run Code Online (Sandbox Code Playgroud)

恕我直言

那么.Aggregate有助于提高可读性吗?一般来说,我喜欢LINQ,因为我认为.在哪里,.选择,.OrderBy等大大提高了可读性(如果你避免内联的hierarhical.选择).由于完整性原因,Aggregate必须在Linq中,但我个人并不相信.Aggregate与一个写得很好的foreach相比增加了可读性.


3dG*_*ber 34

一张图片胜过千言万语

提醒:Func<X,?Y,?R>与类型的两个输入的函数XY,返回一个R.

Enumerable.Aggregate有三个重载:


超载1:

A Aggregate<A>(IEnumerable<A> a,?Func<A,?A,?A> f)
Run Code Online (Sandbox Code Playgroud)

Aggregate1

例:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10
Run Code Online (Sandbox Code Playgroud)


这种重载很简单,但它有以下限制:

  • 序列必须包含至少一个元素,
    否则函数将抛出一个元素InvalidOperationException.
  • 元素和结果必须是同一类型.



超载2:

B Aggregate<A,?B>(IEnumerable<A> a, B bIn,?Func<B, A,?B> f)
Run Code Online (Sandbox Code Playgroud)

Aggregate2

例:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2
Run Code Online (Sandbox Code Playgroud)


这种过载更为通用:

  • 必须提供种子值(bIn).
  • 集合可以为空,
    在这种情况下,函数将生成种子值作为结果.
  • 元素和结果可以有不同的类型.



超载3:

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)
Run Code Online (Sandbox Code Playgroud)


第三次超载对IMO来说不是很有用.
通过使用重载2后跟一个转换其结果的函数,可以更简洁地编写相同的内容.


插图改编自这篇优秀的博文.

  • [是的](https://msdn.microsoft.com/en-us/library/bb548651(v = vs.110).aspx).你在自己的答案中使用它! (4认同)

max*_*pan 15

Aggregate主要用于分组或汇总数据.

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

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

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

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

变量解释

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

nextValue:它是数组序列中的下一个值.将该值加到聚合值即总数上.

示例2:添加数组中的所有项.同时将初始累加器值设置为从10开始添加.

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

论点解释:

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

第二个参数是一个func,它是一个带2个int的函数.

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

2.nextValue ::它是数组序列中的下一个值.将该值加到聚合值即总数上.

同时调试此代码将使您更好地了解聚合的工作方式.


Rm5*_*558 7

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

如果唯一需要生成CSV字符串,您可以试试这个.

var csv3 = string.Join(",",chars);
Run Code Online (Sandbox Code Playgroud)

这是一个包含100万个字符串的测试

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 
Run Code Online (Sandbox Code Playgroud)

源代码在这里


Bra*_*ner 6

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

如果将转换实现为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);
Run Code Online (Sandbox Code Playgroud)

这将创建一系列转换:删除前导和尾随空格->删除第一个字符->删除最后一个字符->转换为大写字母。可以根据需要添加,删除或重新排序此链中的步骤,以创建所需的任何类型的转换管道。

这种特定管道的最终结果,就是" cat "变成"A"


一旦你意识到这可能会变得非常强大T可以任何东西。例如,它可以用于图像转换,例如滤镜BitMap


Yur*_*iUn 5

定义

聚合方法是泛型集合的扩展方法。聚合方法将函数应用于集合的每个项目。不仅应用一个函数,还将其结果作为下一次迭代的初始值。因此,作为结果,我们将从集合中获得一个计算值(最小值、最大值、平均值或其他统计值)。

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

Safe,因为递归将遍历集合的每个项目,并且我们无法因错误的退出条件而获得任何无限循环暂停。Recursive,因为当前函数的结果被用作下一个函数调用的参数。

句法:

collection.Aggregate(seed, func, resultSelector);
Run Code Online (Sandbox Code Playgroud)
  • 种子- 默认初始值;
  • func - 我们的递归函数。它可以是 lambda 表达式、Func 委托或函数类型 TF(T result, T nextValue);
  • resultSelector - 它可以是 func 之类的函数,也可以是用于计算、转换、更改、转换最终结果的表达式。

这个怎么运作:

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
Run Code Online (Sandbox Code Playgroud)

实际使用:

  1. 从数字 n 中求阶乘:

int n = 7;
var numbers = Enumerable.Range(1, n);
var factorial = numbers.Aggregate((result, x) => result * x);

Run Code Online (Sandbox Code Playgroud)

它与这个函数做同样的事情:

public static int Factorial(int n)
{
   if (n < 1) return 1;

   return n * Factorial(n - 1);
}
Run Code Online (Sandbox Code Playgroud)
  1. Aggregate() 是最强大的 LINQ 扩展方法之一,如 Select() 和 Where()。我们可以用它来代替 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);
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)