LINQ,简化表达式 – 取得的总和不超过给定值

鉴于这样的设置..

class Product { int Cost; // other properties unimportant } var products = new List { new Product { Cost = 5 }, new Product { Cost = 10 }, new Product { Cost = 15 }, new Product { Cost = 20 } }; var credit = 15; 

假设列表将按给定顺序排序。 我希望基本上迭代列表中的每个项目,保持成本的总和值,并且只要总成本不超过credit就继续获取产品。

我可以通过一些循环和东西来做到这一点,但我想知道是否有办法将其压缩成更简单的LINQ查询。

不是“完全”linq,因为它需要一个额外的变量,但它是我能想到的最容易的:

 int current=0; var selection = products.TakeWhile(p => (current = current + p.Cost) <= credit); 

其他人已经指出了捕获的变量方法,并且可以说正确的观点认为这种方法很糟糕,因为它会改变状态。 另外,捕获的变量方法只能迭代一次,并且因为a而危险。 你可能会忘记这个事实并尝试迭代两次; 湾 捕获的变量不反映所采取项目的总和。

要避免这些问题,只需创建一个扩展方法:

 public static IEnumerable TakeWhileAggregate( this IEnumerable source, TAccumulate seed, Func func, Func predicate ) { TAccumulate accumulator = seed; foreach (TSource item in source) { accumulator = func(accumulator, item); if (predicate(accumulator)) { yield return item; } else { yield break; } } } 

用法:

 var taken = products.TakeWhileAggregate( 0, (cost, product) => cost + product.Cost, cost => cost <= credit ); 

请注意,现在您可以迭代两次(但是如果您的TAccumulate是可变的引用类型,请小心)。

如果您想要一个没有外部变量的解决方案,可以执行此操作

 var indexQuery = products.Select((x,index) => new { Obj = x, Index = index }); var query = from p in indexQuery let RunningTotal = indexQuery.Where(x => x.Index <= p.Index) .Sum(x => x.Obj.Cost) where credit >= RunningTotal select p.Obj; 

好的,请在@ Aducci的回答中重新评论,这是使用Scan的版本

  var result=products.Scan(new {Product=(Product)null, RunningSum=0}, (self, next) => new {Product=next, RunningSum=self.RunningSum+next.Cost}) .Where(x=>x.RunningSum<=credit) .Select(x => x.Product); 

这是我对Scan的实现(我假设它类似于Rx框架中的内容,但我没有检查过)

  public static IEnumerable Scan(this IEnumerable source, TAccumulate seed, Func accumulator) { foreach(var item in source) { seed=accumulator(seed, item); yield return seed; } } 

使用捕获的变量来跟踪到目前为止所采用的数量。

 int sum = 0; IEnumerable query = products.TakeWhile(p => { bool canAfford = (sum + p.Cost) <= credit; sum = canAfford ? sum + p.Cost : sum; return canAfford; });