如何在Linq中根据运行总数进行分组和/或选择

本文关键字:选择 Linq 运行 | 更新日期: 2023-09-27 18:07:08

我有一个需求,我需要对运行量超过阈值10的事务进行分组和选择。一旦超过阈值,运行计数将被重置。

下面是我正在尝试做的一个例子…

以下是一些交易:

Id | Amount
1  | 5.50
2  | 4.10
3  | 1.20
4  | 1.05
5  | 3.25
6  | 1.25
7  | 5.15
8  | 8.15
9  | 5.15

我想达到的结果是:

第一组:

Id | Amount
1  | 5.50
2  | 4.10    
3  | 1.20

组2:

4  | 1.05
5  | 3.25    
6  | 1.25   
7  | 5.15  

集团3:

8  | 8.15
9  | 5.15

我想出了一个使用for循环和yield的解决方案。你可以在https://dotnetfiddle.net/rcSJO4和下面看到它。

我只是想知道是否有一个更优雅的解决方案,如果有一个聪明的和更可读的方式,可以使用Linq实现。

我的解决方案:

using System;
using System.Linq;
using System.Collections.Generic;
public class Transaction{
    public int Id { get; set;}
    public decimal Amount { get; set;}        
}

public class Program
{
    public static void Main()
    {
        var transactions = new Transaction [] { 
            new Transaction {Id = 1, Amount = 5.50m},
            new Transaction {Id = 2, Amount = 4.10m},
            new Transaction {Id = 3, Amount = 1.20m},
            new Transaction {Id = 4, Amount = 1.05m},
            new Transaction {Id = 5, Amount = 3.25m},
            new Transaction {Id = 6, Amount = 1.25m},
            new Transaction {Id = 7, Amount = 5.15m},
            new Transaction {Id = 8, Amount = 8.15m},
            new Transaction {Id = 9, Amount = 5.15m},
        };
        var grouped = ApplyGrouping(transactions);
        foreach(var g in grouped)
        {
            Console.WriteLine("Total:" + g.Item1);
            foreach(var t in g.Item2){
                Console.WriteLine(" " +t.Amount);
            }
            Console.WriteLine("---------");
        }
    }
    private static IEnumerable<Tuple<decimal, IEnumerable<Transaction>>> ApplyGrouping(IEnumerable<Transaction> transactions){
        decimal runningTotal = 0m;
        decimal threshold = 10m;
        var grouped = new List<Transaction>();
        foreach(var t in transactions){
            grouped.Add(t);
            runningTotal += t.Amount;
            if (runningTotal <= threshold) continue;
            yield return new Tuple<decimal, IEnumerable<Transaction>>(runningTotal, grouped);
            grouped.Clear();
            runningTotal = 0;
        }
    }
}

如何在Linq中根据运行总数进行分组和/或选择

我不知道你是否会找到一个完全令人满意的解决方案。没有内置的Linq操作符(或操作符的组合)会以一种美观的方式做到这一点。您的方法——将复杂性重构为扩展方法——是最好的方法。这并没有错。不管怎样,Linq就是这样;只是一个抽象的概念,隐藏了混乱的部分。但是,如果您发现自己经常使用这种模式,我会说您可以将代码重构成更通用的样式。你现在所拥有的只能在这一种特殊情况下起作用。如果你这样做:

public static class EnumerableExtensions
{
    public static IEnumerable<TResult> GroupUntil<TSource, TAccumulation, TResult>(
        this IEnumerable<TSource> source,
        Func<TAccumulation> seedFactory,
        Func<TAccumulation, TSource, TAccumulation> accumulator,
        Func<TAccumulation, bool> predicate,
        Func<TAccumulation, IEnumerable<TSource>, TResult> selector)
    {
        TAccumulation accumulation = seedFactory();
        List<TSource> result = new List<TSource>();
        using(IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            while(enumerator.MoveNext())
            {
                result.Add(enumerator.Current);
                accumulation = accumulator(accumulation, enumerator.Current);
                if(predicate(accumulation))
                {
                    yield return selector(accumulation, result);
                    accumulation = seedFactory();
                    result = new List<TSource>();
                }
            }
            if(result.Count > 0)
            {
                yield return selector(accumulation, result);
            }
        }
    }
}

那么,根据你的情况,你可以这样称呼它:

var grouped = 
    transactions
    .GroupUntil(
        () => 0.0m,
        (accumulation, transaction) => accumulation + transaction.Amount,
        (accumulation) => accumulation > 10.0m,
        (accumulation, group) => new { Total = accumulation, Transactions = group})

但它足够通用,可以在其他地方使用

我会坚持你的解决方案。即使你可以用LINQ修改它,它也很可能是不可读的。

你唯一应该改变的是一遍又一遍地使用相同的List<T>并在返回后清除它的方式-因为List<T>是一个引用类型,你也清除了已经返回的项。

创建新的List<T>,代替之前调用的Clear

private static IEnumerable<Tuple<decimal, IEnumerable<Transaction>>> ApplyGrouping(IEnumerable<Transaction> transactions){
    decimal runningTotal = 0m;
    decimal threshold = 10m;
    var grouped = new List<Transaction>();
    foreach(var t in transactions){
        grouped.Add(t);
        runningTotal += t.Amount;
        if (runningTotal <= threshold) continue;
        yield return new Tuple<decimal, IEnumerable<Transaction>>(runningTotal, grouped);
        grouped = new List<Transaction>();
        runningTotal = 0;
    }
}

这是LINQ的一种方法

    var runningTotal = 0.0m;
    List<List<Transaction>> list = new List<List<Transaction>>();
    List<Transaction> currentList = new List<Transaction>();
    transactions.Select(x => {
        runningTotal += x.Amount;
        currentList.Add(x);
        if (runningTotal >= 10)
        {
            Console.WriteLine("Group 1 Total {0}", runningTotal);
            runningTotal = 0;
            list.Add(currentList);
            currentList = new List<Transaction>();
        }
        return runningTotal;
        }).ToList();