协慌网

登录 贡献 社区

LINQ 聚合算法介绍

这听起来有些 la 脚,但是我无法找到关于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 。然后将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

这以几乎相同的方式工作。 a a 与逗号和b串联, a,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 使用字符串连接来创建由逗号分隔的值列表。这是解释使用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

这部分取决于您在说的是哪种重载,但是基本思想是:

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

您可能会在我的 Edulinq 系列文章中Aggregate帖子很有用 - 它包含更详细的描述(包括各种重载)和实现。

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

或者,也许将一个字符串序列中的所有字符串长度求和:

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

我个人很少发现Aggregate有用 -“量身定制” 的聚集方法通常对我来说足够好。

超短聚集体的工作方式类似于 Haskell / ML / F#中的折叠。

稍长。最大(),.Min(),.SUM(),。平均()并且使用相应的聚合函数在一个序列中的元素和它们的聚集体的所有迭代。 .Aggregate()是广义的聚合器,它允许开发人员指定起始状态(即种子)和聚合函数。

我知道您要求简短的解释,但我认为其他人给出了一些简短的答案,我认为您可能会对更长的答案感兴趣

带代码的长版本一种方法来说明它是什么,该示例说明如何使用一次 foreach 和一次使用. Aggregate 来实现示例标准偏差。注意:我在这里没有优先考虑性能,因此我不必要地在上课中进行了多次迭代

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

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

然后使用. 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));
}

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

恕我直言

那么. Aggregate 有助于提高可读性吗?总的来说,我喜欢 LINQ,因为我认为. Where,.Select,.OrderBy 等可以极大地提高可读性(如果避免使用内联的层级. Selects)。出于完整性原因,Aggregate 必须位于 Linq 中,但我个人并不相信. Aggregate 与编写良好的 foreach 相比,增加了可读性。