这听起来可能很蹩脚,但我还没能找到一个对聚合的真正好的解释。

好的意思是简短、描述性、全面,并有一个小而清晰的例子。


当前回答

超短聚合的工作方式类似于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相比,聚合增加了可读性。

其他回答

一张图片胜过千言万语

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

可枚举。聚合有三个重载:

过载1:

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

例子:

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)

例子:

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后跟一个转换其结果的函数,可以更简洁地编写相同的代码。

插图改编自这篇优秀的博客文章。

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

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

你可能会发现我的Edulinq系列中的Aggregate文章很有用——它包含了更详细的描述(包括各种重载)和实现。

一个简单的例子是使用聚合作为计数的替代:

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

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

释义

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

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

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

语法:

collection.Aggregate(seed, func, resultSelector);

seed—默认初始值;func-我们的递归函数。它可以是lambda表达式、Func委托或函数类型T F(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

实际用途:

从数字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);
}

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

扩展方法的更复杂用法:

    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

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

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

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

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

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

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

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

源代码在这里