如何根据对象属性的总和获取List <T>项的子集

Adr*_*n K 3 c# linq

我想基于其中一个对象属性的值从List中获取对象的子集,具体来说,我希望根据其属性的聚合值的总和来获取前几个对象.

我可以手动迭代列表,添加/求和属性的值,并将结果与​​我想要的值进行比较,但有更好的方法吗?

例如,假设我有这个列表:

List<MyObj> MyObjList;
Run Code Online (Sandbox Code Playgroud)

MyObj看起来像这样:

public class MyObj
{
  public int MyValue { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

MyObjList按以下顺序具有以下对象和值:

MyObjList[0].MyValue = 1;
MyObjList[1].MyValue = 3;
MyObjList[2].MyValue = 2;
MyObjList[3].MyValue = 3;
MyObjList[4].MyValue = 2;
Run Code Online (Sandbox Code Playgroud)

例如,我可能想要获得前几个项目,其中MyValue <= 5的集合总和,它只返回前两个对象.

你会怎么做?

Eri*_*ert 11

你想要的是Aggregate和TakeWhile的组合,所以让我们写一下.

public static IEnumerable<S> AggregatingTakeWhile<S, A>(
  this IEnumerable<S> items,
  A initial,
  Func<A, S, A> accumulator,
  Func<A, S, bool> predicate) 
{
  A current = initial;
  foreach(S item in items)
  {
    current = accumulator(current, item);
    if (!predicate(current, item))
      break;
    yield return item;
  }
}
Run Code Online (Sandbox Code Playgroud)

所以现在你可以说

var items = myObjList.AggregatingTakeWhile(
  0,
  (a, s) => a + s.MyValue,
  (a, s) => a <= 5);
Run Code Online (Sandbox Code Playgroud)

请注意,我已经决定在累加器更新查询谓词; 根据您的应用程序,您可能需要稍微调整一下.

另一种解决方案是将聚合与枚举结合起来:

public static IEnumerable<(A, S)> RunningAggregate<S, A>(
  this IEnumerable<S> items,
  A initial,
  Func<A, S, A> accumulator) 
{
  A current = initial;
  foreach(S item in items)
  {
    current = accumulator(current, item);
    yield return (current, item);
  }
}
Run Code Online (Sandbox Code Playgroud)

现在你想要的操作是

var result = myObjList
  .RunningAggregate(0, (a, s) => a + s.MyValue)
  .TakeWhile( ((a, s)) => a <= 5)
  .Select(((a, s)) => s);
Run Code Online (Sandbox Code Playgroud)

我可能在那里弄​​错了元组语法; 我现在还没有Visual Studio方便.但是你明白了.聚合产生一系列(sum,item)元组,现在我们可以在那个东西上使用正常的序列运算符.


Ruf*_*s L 5

对于老式的非 Linq 方法,您可以为此编写一个简单的方法:

static List<MyObj> GetItemsUntilSumEquals(List<MyObj> items, int maxSum)
{    
    if (items == null) return null;

    var result = new List<MyObj>();
    var sum = 0;

    foreach (var item in items)
    {
        if (sum + item.MyValue > maxSum) break;
        sum += item.MyValue;
        result.Add(item);
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)