如何在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就是这样;只是一个抽象的概念,隐藏了混乱的部分。但是,如果您发现自己经常使用这种模式,我会说您可以将代码重构成更通用的样式。你现在所拥有的只能在这一种特殊情况下起作用。如果你这样做:
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();