首页 文章

LINQ聚合算法解释

提问于
浏览
622

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

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

11 回答

  • 0

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

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

  • 6

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

    • 从种子开始"current value"

    • 迭代序列 . 对于序列中的每个值:

    • 应用用户指定的函数将 (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 很有用 - "tailored"聚合方法对我来说通常都足够好 .

  • 28

    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 ::它是数组序列中的下一个值 . 将该值加到聚合值上,即总数 .

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

  • 13

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

    如果将转换实现为 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);
    

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

    这个特定管道的最终结果是 " cat " 变为 "A" .


    一旦你意识到 T 可以是任何东西,这会变得非常强大 . 这可以用于图像转换,如过滤器,使用 BitMap 作为示例;

  • 117

    Jamiec's回答了很多 .

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

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

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

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

    源代码是here

  • 0

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

    Aggregate方法将函数应用于集合的每个项目 . 例如,让我们有集合{6,2,8,3}和函数Add(运算符)它(((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; }
    
  • 56

    这是关于在诸如Linq Sorting之类的Fluent API上使用 Aggregate 的说明 .

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

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

    Slightly longer .Max(),. Min(),. Sum(),. . Average()都迭代序列中的元素,并使用相应的聚合函数聚合它们 . .Aggregate()是通用聚合器,它允许开发人员指定开始状态(又名种子)和聚合函数 .

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

    Long version with code 一种方式来说明它可以显示如何使用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

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

    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 func中使用带索引的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
    

    但是计算布尔数组中的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
    
  • 0

    Aggregate 最容易理解的定义是它对列表的每个元素执行一个操作,同时考虑到之前的操作 . 也就是说它对第一个和第二个元素执行操作并向前传递结果 . 然后它对前一个结果和第三个元素进行操作并继续前进 . 等等

    Example 1. Summing numbers

    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 .

    Example 2. create a csv from an array of strings

    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 . 然后用逗号和 c 连接 a,b 以制作 a,b,c . 等等 .

    Example 3. Multiplying numbers using a seed

    为了完整性,_452200_的_452200_取一个种子值 .

    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


    Addendum

    上面的示例2使用字符串连接来创建由逗号分隔的值列表 . 这是解释 Aggregate 使用的简单方法,这是本答案的意图 . 但是,如果使用此技术实际创建大量逗号分隔数据,则使用 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);
    

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

  • 910

    一张图片胜过千言万语

    提醒:Func <A,B,C>是一个带有两个A和B类型输入的函数,它返回一个C.

    Enumerable.Aggregate有三个重载:

    Overload 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 .

    • 元素和结果必须属于同一类型 .


    Overload 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 ) .

    • 该集合可以为空,
      在这种情况下,函数将生成种子值作为结果 .

    • 元素和结果可以有不同的类型 .


    Overload 3:

    C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)
    

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

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

相关问题