• C# Linq增强扩展MoreLinq之Aggregate(对序列应用累加器)


    如项目所说,LINQ to Objects 缺失了一些理想的功能。但MoreLinq将强大的linq进一步增强了,扩展出了将近100+的功能,使编写代码效率提高。

    MoreLINQ项目的主要目标是为LINQ提供更多的功能和灵活性,以满足不同场景下的需求。该项目由一些微软的工程师创建和维护,他们利用自己的业余时间开发并分享这个开源项目。

    本系列文章将逐个介绍MoreLinq的使用方法。

    Aggregate 扩展

    Aggregate可实现对序列应用累加器,其原生方法是Linq的Aggregate,MoreLinq在其基础上扩展了更多功能。
    原生方法是这样的:

    public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);
    public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func);
    public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector);
    
    • 1
    • 2
    • 3

    MoreLinq中Aggregate 的方法:

    /// 
            /// Applies two accumulators sequentially in a single pass over a
            /// sequence.
            /// 
            /// The type of elements in .
            /// The type of first accumulator value.
            /// The type of second accumulator value.
            /// The type of the accumulated result.
            /// The source sequence
            /// The seed value for the first accumulator.
            /// The first accumulator.
            /// The seed value for the second accumulator.
            /// The second accumulator.
            /// 
            /// A function that projects a single result given the result of each
            /// accumulator.
            /// The value returned by .
            /// 
            /// This operator executes immediately.
            /// 
    
            public static TResult Aggregate<T, TAccumulate1, TAccumulate2, TResult>(
                this IEnumerable<T> source,
                TAccumulate1 seed1, Func<TAccumulate1, T, TAccumulate1> accumulator1,
                TAccumulate2 seed2, Func<TAccumulate2, T, TAccumulate2> accumulator2,
                Func<TAccumulate1, TAccumulate2, TResult> resultSelector)
            {
                if (source == null) throw new ArgumentNullException(nameof(source));
                if (accumulator1 == null) throw new ArgumentNullException(nameof(accumulator1));
                if (accumulator2 == null) throw new ArgumentNullException(nameof(accumulator2));
                if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
    
                var a1 = seed1;
                var a2 = seed2;
    
                foreach (var item in source)
                {
                    a1 = accumulator1(a1, item);
                    a2 = accumulator2(a2, item);
                }
    
                return resultSelector(a1, a2);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    该扩展方法在一次遍历序列中连续应用累加器,上面的代码是对两个初始值和两个累加器的扩展,且sead1只应用accumulator1,同理sead2只应用accumulator2,然后对结果应用resultSelector(a1,a2)方法。
    该扩展方法有多个重载,最多可以在一次遍历中依次应用八个累加器序列。

    /// 
            /// Applies two accumulator queries sequentially in a single
            /// pass over a sequence.
            /// 
            /// The type of elements in .
            /// The type of the result of the first accumulator.
            /// The type of the result of the second accumulator.
            /// The type of the accumulated result.
            /// The source sequence
            /// The first accumulator.
            /// The second accumulator.
            /// 
            /// A function that projects a single result given the result of each
            /// accumulator.
            /// The value returned by .
            /// 
            /// An  returned by an accumulator function
            /// produced zero or more than a single aggregate result.
            /// 
            /// 
            /// This operator executes immediately.
            /// 
            /// Each accumulator argument is a function that receives an
            /// , which when subscribed to, produces the
            /// items in the  sequence and in original
            /// order; the function must then return an 
            /// that produces a single aggregate on completion (when
            ///  is called. An error is raised
            /// at run-time if the  returned by an
            /// accumulator function produces no result or produces more than a
            /// single result.
            /// 
            /// 
    
            public static TResult Aggregate<T, TResult1, TResult2, TResult>(
                this IEnumerable<T> source,
                Func<IObservable<T>, IObservable<TResult1>> accumulator1,
                Func<IObservable<T>, IObservable<TResult2>> accumulator2,
                Func<TResult1, TResult2, TResult> resultSelector)
            {
                if (source == null) throw new ArgumentNullException(nameof(source));
                if (accumulator1 == null) throw new ArgumentNullException(nameof(accumulator1));
                if (accumulator2 == null) throw new ArgumentNullException(nameof(accumulator2));
                if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
    
                var r1 = new (bool, TResult1)[1];
                var r2 = new (bool, TResult2)[1];
    
                var subject = new Subject<T>();
    
                using (SubscribeSingle(accumulator1, subject, r1, nameof(accumulator1)))
                using (SubscribeSingle(accumulator2, subject, r2, nameof(accumulator2)))
                {
                    foreach (var item in source)
                        subject.OnNext(item);
    
                    subject.OnCompleted();
                }
    
                return resultSelector
                (
                    GetAggregateResult(r1[0], nameof(accumulator1)),
                    GetAggregateResult(r2[0], nameof(accumulator2))
                );
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    该方法在一个序列上的一次传递中按顺序应用累加器,上面的代码会分别订阅流数据,将执行结果应用resultSelector方法。

    用两个示例演示Aggregate 的基本用法

    运行以下七个累加器:

    • 所有数字的总和
    • 偶数的总和
    • 数字的个数
    • 最小的数字
    • 最大的数字
    • 所有数字中不同数字的个数
    • 数字列表

    原生

    Enumerable
        .Range(1, 10)
        .Shuffle()
        .Select(n => new { Num = n, Str = n.ToString(CultureInfo.InvariantCulture) })
        .Aggregate(
            0, (s, e) => s + e.Num,
            0, (s, e) => e.Num % 2 == 0 ? s + e.Num : s,
            0, (s, _) => s + 1,
            (int?)null, (s, e) => s is int n ? Math.Min(n, e.Num) : e.Num,
            (int?)null, (s, e) => s is int n ? Math.Max(n, e.Num) : e.Num,
            new HashSet<int>(), (s, e) => { s.Add(e.Str.Length); return s; },
            new List<(int, string)>(), (s, e) => { s.Add((e.Num, e.Str)); return s; },
            (sum, esum, count, min, max, lengths, items) => new
            {
                Sum           = sum,
                EvenSum       = esum,
                Count         = count,
                Average       = (double)sum / count,
                Min           = min is int mn ? mn : throw new InvalidOperationException(),
                Max           = max is int mx ? mx : throw new InvalidOperationException(),
                UniqueLengths = "[" + string.Join(", ", lengths) + "]",
                Items         = "[" + string.Join(", ", items)   + "]",
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    但用这种方式编写每个聚合器可能很繁琐、重复且容易出错,因为不能重复使用 Enumerable.Sum。Aggregate 扩展允许使用响应性推导式编写聚合器。将上边的代码改一下。

    Enumerable
        .Range(1, 10)
        .Shuffle()
        .Select(n => new { Num = n, Str = n.ToString(CultureInfo.InvariantCulture) })
        .Aggregate(
            s => s.Sum(e => e.Num),
            s => s.Select(e => e.Num).Where(n => n % 2 == 0).Sum(),
            s => s.Count(),
            s => s.Min(e => e.Num),
            s => s.Max(e => e.Num),
            s => s.Select(e => e.Str.Length).Distinct().ToArray(),
            s => s.ToList(),
            (sum, esum, count, min, max, lengths, items) => new
            {
                Sum           = sum,
                EvenSum       = esum,
                Count         = count,
                Average       = (double)sum / count,
                Min           = min,
                Max           = max,
                UniqueLengths = "[" + string.Join(", ", lengths) + "]",
                Items         = "[" + string.Join(", ", items)   + "]",
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这样是不是就好看很多了呢?

    MoreLinq开源地址:https://github.com/morelinq

  • 相关阅读:
    Python将.arff文件转换为.mat文件
    WMS系统是什么——史上最全WMS介绍
    SpringBoot生成Excel文件并下载到浏览器
    nginx配置详解
    双端队列--二叉树 Z 字层序遍历
    基础算法篇——前缀和与差分
    SAP 内表数据转换为JSON格式
    [NewStarCTF 2023] web题解
    基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的自动驾驶目标检测系统详解(深度学习+Python代码+PySide6界面+训练数据集)
    Java之多态
  • 原文地址:https://blog.csdn.net/sd2208464/article/details/134164753