【问题标题】:How to Group and or Select in Linq based on a running total如何根据运行总计在 Linq 中进行分组和/或选择
【发布时间】:2015-08-09 17:03:32
【问题描述】:

我有一个需求,我需要对运行量超过阈值 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;
        }

    }
}

【问题讨论】:

    标签: c# linq linq-to-objects


    【解决方案1】:

    我不知道你是否会找到一个完全令人满意的解决方案。没有内置的 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})
    

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

    【讨论】:

      【解决方案2】:

      我会坚持你的解决方案。即使您可以使用 LINQ 破解它,它也很可能无法读取。

      您唯一应该更改的是反复使用相同的List&lt;T&gt; 并在返回后清除它的方式 - 因为List&lt;T&gt; 是一个引用类型,您也正在清除已返回的项目。

      创建新的List&lt;T&gt;,而不是在前一个上调用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
      【解决方案3】:

      这是一种 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();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-26
        • 1970-01-01
        • 2019-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-14
        • 2017-12-02
        相关资源
        最近更新 更多