我想根据一个对象属性的值从列表中获取一个对象子集,具体来说,我想根据该属性的聚合值得到前几个对象。
我可以手动迭代列表,添加/求和属性的值,并将结果与我想要的值进行比较,但是有更好的方法吗?
例如,假设我有以下列表:
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 <= 5的总和,这将只返回前2个对象。
你会怎么做?
发布于 2017-11-09 21:17:21
您想要的是聚合和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);我可能弄错了元组语法;我现在手头没有。但你知道这个主意。聚合生成(sum,item)元组的序列,现在我们可以对它使用普通的序列运算符。
发布于 2017-11-09 21:18:29
对于传统的非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;
}https://stackoverflow.com/questions/47211254
复制相似问题