LINQ聚合算法解释

时间:2011-08-18 09:51:22

标签: c# .net linq

这可能听起来很蹩脚,但我无法找到Aggregate的真正好的解释。

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

12 个答案:

答案 0 :(得分:950)

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逗号和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) 取一个种子值。

5

与上面的示例非常相似,它以值10开头,并将其乘以序列50的第一个元素,得到20的结果。此结果将继续并乘以序列1000中的下一个数字,以得到Aggregate的结果。这将继续通过序列的剩余2个元素。

实例:overload
文档:http://rextester.com/ZXZ64749


<强>附录

上面的示例2使用字符串连接来创建由逗号分隔的值列表。这是解释StringBuilder使用的简单方法,这是本答案的用意。但是,如果使用此技术实际创建大量逗号分隔数据,则使用Aggregate更合适,并且这与StringBuilder完全兼容,使用种子重载来启动{ {1}}。

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://msdn.microsoft.com/en-us/library/bb548651.aspx

答案 1 :(得分:126)

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

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

您可能会发现Aggregate post in my Edulinq series很有用 - 它包含更详细的描述(包括各种重载)和实现。

一个简单的示例是使用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有用 - “量身定制”的聚合方法对我来说通常都足够好。

答案 2 :(得分:59)

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

稍长 .Max(),. Min(),. Sum(),.。Average()都遍历序列中的元素,并使用相应的聚合函数聚合它们。 .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));
}

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

<强> IMHO

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

答案 3 :(得分:31)

一张图片胜过千言万语

  

提醒:Func<A, B, C>是一个包含两个类型为AB的输入的函数,它返回C

Enumerable.Aggregate有三个重载:


超载1:

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

Aggregate1

示例:

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)

Aggregate2

示例:

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


  

插图改编自this excellent blogpost

答案 4 :(得分:14)

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

根据MSDN            “聚合函数在序列上应用累加器函数。”

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

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

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

参数解释:

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

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

1.total:这将与计算后func返回的总和值(聚合值)之前保持一致。

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

同时调试此代码可以让您更好地了解聚合的工作方式。

答案 5 :(得分:7)

Jamiec's回答了解了很多。

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

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

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

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

源代码为here

答案 6 :(得分:5)

除了这里的所有优秀答案之外,我还使用它来完成一系列转换步骤。

如果将转化实施为Func<T,T>,您可以向List<Func<T,T>>添加多个转换,并使用Aggregate在每个步骤中遍历T的实例。< / p>

更具体的例子

您希望获取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);

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

此特定渠道的最终结果是" cat "变为"A"

一旦您意识到T可以任何,这会变得非常强大。这可以用于图像转换,例如过滤器,以BitMap为例;

答案 7 :(得分:2)

  

定义

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

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

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

  

语法:

collection.Aggregate(seed, func, resultSelector);
  • 种子-默认情况下的初始值;
  • 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
  

实际用法:

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

答案 8 :(得分:0)

一个简短而重要的定义可能是这样的:Linq Aggregate扩展方法允许声明一种应用于列表元素的递归函数,其操作数是两个:元素按它们出现的顺序排列列表,一次一个元素,以及先前递归迭代的结果,如果还没有递归则没有任何内容。

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

答案 9 :(得分:0)

这是关于在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);

并且让我们看看我们想要实现一个带有一组字段的sort函数,使用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);

答案 10 :(得分:0)

聚合用于对多维整数数组中的列求和

        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来对匹配的列求和并返回一个新的数组; {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

但是计算布尔数组中的trues数量比较困难,因为累积类型(int)与源类型(bool)不同;这里有种子是必要的,以便使用第二次过载。

        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

答案 11 :(得分:0)

每个人都给出了他的解释。我的解释是这样的。

Aggregate方法将函数应用于集合的每个项目。例如,让我们有集合{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; }