【问题标题】:How to get subset of List<T> items based on their Sum of an object property如何根据对象属性的总和获取 List<T> 项的子集
【发布时间】:2020-04-23 18:11:00
【问题描述】:

我想根据其中一个对象属性的值从列表中获取对象的子集,特别是我想根据该属性的聚合值的总和来获取前几个对象。

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

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

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 的总和

你会怎么做?

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    你想要的是 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)。
    • 我正要发布几乎相同的解决方案...有点太慢了!出于好奇,我将predicate 简单地实现为Predicate&lt;A&gt;。您选择Func&lt;A, S, bool&gt; 是否有特定原因?
    • 我喜欢这个答案的一个原因是它让我接触到了一些我以前从未使用过的 C# (Func)。
    • @InBetween:C# 团队的普遍态度是 Predicate 等是不必要的错误功能。 “谓词”是自记录的,因为它告诉您“这个返回布尔值的委托用于做出决定”,因此它在逻辑上不同于从文件中反序列化布尔值的委托。所以这很好,但是我们真的需要在类型系统中进行区分吗?我们真的想让拥有Predicate&lt;int&gt; 并需要Func&lt;int, bool&gt; 来进行转换的人吗?不。只要在任何地方都使用Func,始终如一。
    • @InBetween:现在,如果您的问题是为什么 A, S 而不仅仅是 A - 您可能想要类似“拿走这些物品,直到一个物品是跑步物​​品的两倍大迄今为止的平均水平”,或类似的东西。在这种情况下,您需要在谓词中包含当前项以及累加器。
    【解决方案2】:

    对于老式的非 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;
    }
    

    【讨论】:

    • 您可以添加某种int currentSum 以避免在每次迭代时重新计算result.Sum()。这也导致了“无 Linq”解决方案。
    • @MartinBackasch 非常真实!这将比所有的求和更好。
    猜你喜欢
    • 1970-01-01
    • 2014-07-16
    • 1970-01-01
    • 2019-07-02
    • 2020-01-12
    相关资源
    最近更新 更多