我希望根据其中一个对象属性的值从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,这将只返回前两个对象。
你会怎么做?
答案 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 ......