使用Linq总结一个数字(并跳过其余部分)

Nic*_*ick 43 c# linq

如果我们有一个包含这样的数字的类:

class Person 
{
  public string Name {get; set;}
  public int Amount {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

然后是一群人:

IList<Person> people;
Run Code Online (Sandbox Code Playgroud)

那包含,比方说10个随机名称和数量的人是否有一个Linq表达式,它将返回一个Person对象的子集合,其总和满足条件?

例如,我想要第一个数量总和低于1000的x人.我可以通过传统方式做到这一点

 var subgroup = new List<Person>();

 people.OrderByDescending(x => x.Amount);

 var count = 0;
 foreach (var person in people)
 {
    count += person.Amount;
    if (count < requestedAmount)
    {
        subgroup.Add(person);
    }
    else  
    {
        break;
    }
 }
Run Code Online (Sandbox Code Playgroud)

但是我一直想知道是否有一种优雅的Linq方式使用Sum做一些这样的事情然后像Take这样的其他功能?

UPDATE

这是太棒了:

var count = 0;
var subgroup = people
                  .OrderByDescending(x => x.Amount)
                  .TakeWhile(x => (count += x.Amount) < requestedAmount)
                  .ToList();
Run Code Online (Sandbox Code Playgroud)

但我想知道我是否可以以某种方式进一步改变它以便抓住人员列表中的下一个人并将余数添加到总和中,以便总金额等于请求的金额.

Gio*_*sos 42

你可以使用TakeWhile:

int s = 0;
var subgroup  = people.OrderBy(x => x.Amount)
                      .TakeWhile(x => (s += x.Amount) < 1000)
                      .ToList();
Run Code Online (Sandbox Code Playgroud)

注意:您在帖子中首先提到x个人.人们可以将其解释为具有最小量的量,直到1000达到.所以,我用过OrderBy.但是,OrderByDescending如果您想从具有最高金额的人开始提取,您可以替换它.


编辑:

要使其从列表中再选择一项,您可以使用:

.TakeWhile(x => {
                   bool bExceeds = s > 1000;
                   s += x.Amount;                                 
                   return !bExceeds;
                })
Run Code Online (Sandbox Code Playgroud)

TakeWhile这里检查s从值以前的迭代,因此将需要一个,只是要确定1000已超出.

  • 请不要修改LINQ查询中的变量; 这是一个非常糟糕的编程习惯.它可以导致一些真正奇怪的场景. (13认同)

Nik*_*asJ 24

我不喜欢这些在linq查询中改变状态的方法.

编辑:我没有声明我以前的代码是未经测试的,并且有些伪y.我也错过了Aggregate实际上同时吃掉整个东西的观点 - 正确指出它没有用.这个想法是正确的,但我们需要一个替代Aggreage.

令人遗憾的是,LINQ没有正在运行的聚合.我建议在这篇文章中来自user2088029的代码:如何在Linq查询中计算一系列整数的运行总和?.

然后使用它(经过测试并且是我的意图):

var y = people.Scanl(new { item = (Person) null, Amount = 0 },
    (sofar, next) => new { 
        item = next, 
        Amount = sofar.Amount + next.Amount 
    } 
);       
Run Code Online (Sandbox Code Playgroud)

这里有长寿的被盗代码:

public static IEnumerable<TResult> Scanl<T, TResult>(
    this IEnumerable<T> source,
    TResult first,
    Func<TResult, T, TResult> combine)
    {
        using (IEnumerator<T> data = source.GetEnumerator())
        {
            yield return first;

            while (data.MoveNext())
            {
                first = combine(first, data.Current);
                yield return first;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

以前的错误代码:

我有另一个建议; 从列表开始

people

[{"a", 100}, 
 {"b", 200}, 
 ... ]
Run Code Online (Sandbox Code Playgroud)

计算运行总数:

people.Aggregate((sofar, next) => new {item = next, total = sofar.total + next.value})


[{item: {"a", 100}, total: 100}, 
 {item: {"b", 200}, total: 300},
 ... ]
Run Code Online (Sandbox Code Playgroud)

然后使用TakeWhile和Select返回只是项目;

people
 .Aggregate((sofar, next) => new {item = next, total = sofar.total + next.value})
 .TakeWhile(x=>x.total<1000)
 .Select(x=>x.Item)
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 16

我不喜欢这个问题的所有答案.他们要么在查询中改变一个变量 - 一个导致意外结果的坏习惯 - 或者在Niklas(其他好的)解决方案的情况下,返回一个错误类型的序列,或者,在Jeroen的答案中,代码是正确的,但可以解决更普遍的问题.

我会通过制作一个返回正确类型的实际通用解决方案来改进Niklas和Jeroen的工作:

public static IEnumerable<T> AggregatingTakeWhile<T, U>(
  this IEnumerable<T> items, 
  U first,
  Func<T, U, U> aggregator,
  Func<T, U, bool> predicate)
{
  U aggregate = first;
  foreach (var item in items)
  {
    aggregate = aggregator(item, aggregate);
    if (!predicate(item, aggregate))
      yield break;
    yield return item; 
  }
}
Run Code Online (Sandbox Code Playgroud)

我们现在可以使用它来实现特定问题的解决方案:

var subgroup = people
  .OrderByDescending(x => x.Amount)
  .AggregatingTakeWhile(
    0, 
    (item, count) => count + item.Amount, 
    (item, count) => count < requestedAmount)
  .ToList();
Run Code Online (Sandbox Code Playgroud)


Jer*_*gen 7

尝试:

int sumCount = 0;

var subgroup = people
    .OrderByDescending(item => item.Amount)           // <-- you wanted to sort them?
    .Where(item => (sumCount += item.Amount) < requestedAmount)
    .ToList();
Run Code Online (Sandbox Code Playgroud)

但它并不迷人......它的可读性会降低.