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

时间:2017-11-09 20:48:11

标签: c# linq

我希望根据其中一个对象属性的值从List中获取对象的子集,特别是我想根据其属性的聚合值之和得到前几个对象。

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

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

List<MyObj> MyObjList;

MyObj看起来像这样:

public class MyObj
{
  public int MyValue { get; set; }
}

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

MyObjList[0].MyValue = 1;
MyObjList[1].MyValue = 3;
MyObjList[2].MyValue = 2;
MyObjList[3].MyValue = 3;
MyObjList[4].MyValue = 2;

例如,我可能希望得到前几个项目,它们的集合总和为MyValue&lt; = 5,这将只返回前两个对象。

你会怎么做?

2 个答案:

答案 0 :(得分: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;
  }
}

现在你可以说

var items = myObjList.AggregatingTakeWhile(
  0,
  (a, s) => a + s.MyValue,
  (a, s) => a <= 5);

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

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

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);
  }
}

现在你想要的操作是

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

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

答案 1 :(得分:4)

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

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

    var result = new List<MyObj>();

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

    return result;
}

好吧,我猜那里有 Linq ......