【问题标题】:calculate sum of list properties excluding min and max value with linq使用 linq 计算列表属性的总和,不包括最小值和最大值
【发布时间】:2015-02-11 22:03:24
【问题描述】:

这是我目前所拥有的:

decimal? total = list.Sum(item => item.Score);

我想做的是排除列表中的最小值和最大值,然后得到总值。

是否有可能在一个 linq 语句中完成所有这些操作?

【问题讨论】:

  • 你可以试试 list.OrderBy(item => item.Score).Skip(1).Take(list.Count - 2).Sum(item => item.Score);作为参考,我现在讨厌自己,这可能是最未经优化的垃圾。
  • 你绝对可以用一个累加器来做到这一点,该累加器对可枚举进行 one 评估。 (我的意思是使用聚合)。
  • 你为什么想要一个单一的声明?一个简单的list.Sum(item => item.Score) - list.Max() - list.Min() 不是更具可读性吗?我怀疑三次迭代列表会成为您应用程序的瓶颈。
  • ... 请参阅下面我的答案以获得仅使用 O(1) 内存枚举整个 Enumerable 一次的答案。

标签: c# linq list linq-to-objects


【解决方案1】:
list.OrderBy(item => item.Score)
    .Skip(1)
    .Reverse()
    .Skip(1)
    .Sum(item => item.Score);

【讨论】:

    【解决方案2】:

    您可以尝试先对列表进行排序,然后跳过第一项(最少)并从其余项中取出除最后一项(最多)以外的所有项:

    decimal? total = list.OrderBy(x => x.Score)
                         .Skip(1)
                         .Take(list.Count - 2)
                         .Sum(x => x.Score);
    

    【讨论】:

      【解决方案3】:

      这不是可以想象的最好的代码,但它确实有以下好处

      • 仅枚举整个集合一次(尽管它确实获得了第一个值 3 次)。
      • 不需要比保存 IEnumerator 和两个 Tuple<int, int, long, long> 对象(如果使用 OrderByToList 和排序等)所需的更多内存。这让它可以处理任意大的 IEnumerable 集合。
      • 单个 Linq 表达式(这是您想要的)。
      • 正确处理边缘情况 (values.Count() < 2):
        • 当没有值时,在 IEnumerable 上使用 Min()Max() 将抛出 InvalidOperationException
        • 当只有一个值时,天真的实现会在 IEnumerable 上执行类似Sum() - Min() - Max() 的操作,返回单个值,取反。

      我知道你已经接受了一个答案,但这里是:我正在使用单个呼叫 Enumerable.Aggregate

      public static long SumExcludingMinAndMax(IEnumerable<int> values)
      {
          // first parameter: seed (Tuple<running minimum, running maximum, count, running total>)
          // second parameter: func to generate accumulate
          // third parameter: func to select final result
          var result = values.Aggregate(
                  Tuple.Create<int, int, long, long>(int.MaxValue, int.MinValue, 0, 0),
                  (accumulate, value) => Tuple.Create<int, int, long, long>(Math.Min(accumulate.Item1, value), Math.Max(accumulate.Item2, value), accumulate.Item3 + 1, accumulate.Item4 + value),
                  accumulate => accumulate.Item3 < 2 ? 0 : accumulate.Item4 - accumulate.Item1 - accumulate.Item2);
      
          return result;
      }
      

      【讨论】:

      • 您可以使用int.MaxValueint.MinValue 作为启动值将调用交换到First,以避免在此处多次访问IEnumerable。此外,如果 OP 关心性能,那么在这里使用可变结构作为累加器会更便宜,如果他真的关心性能,那么在简单的foreach 循环中完成所有这些操作会更便宜.不过,我同意这一点的精神,尽管对于不经常调用的小型集合来说,公认的答案有点优雅和好,但在空间和时间复杂性方面却是可怕的。
      • 你对结构是完全正确的。我想不出 BCL 中有任何现有的struct 符合要求。更糟糕的是,如果使用我的方法没有值,你会得到一个InvalidOperationException。更糟糕的是,当它返回总和时,我最初称它为AverageExcludingMinAndMax。我已经相应地更新了我的答案,以使用int.MinValueint.MaxValue 处理零值案例和种子。
      【解决方案4】:

      如果您想排除所有个最小值和最大值,请预先计算这两个值,然后使用Ènumerable.Where 排除它们:

      decimal? min = list.Min(item => item.Score);
      decimal? max = list.Max(item => item.Score);
      decimal? total = list
          .Where(item=> item.Score != min && item.Score != max)
          .Sum(item =>  item.Score);
      

      【讨论】:

      • 这引出了他是否想排除所有实例或单个实例的最小/最大分数的问题。
      • 如果我有两个或三个分数是最小值或最大值,那会排除所有三个还是只排除一个?我只需要删除一个。感谢您的帮助@Tim
      • @Laziale:上述方法排除了所有最小值和最大值。这是我了解您的要求。
      • 这个 O(n) 很好,与@AlexeiLevenkov 一致。但是,为什么不直接写第三行decimal? total = list.Sum(o =&gt; o.Score) - (min + max);?然后它完全符合 OP 的要求。
      【解决方案5】:

      您应该在 sum 之前预处理列表以排除最小值和最大值。

      【讨论】:

        猜你喜欢
        • 2021-01-13
        • 2014-11-20
        • 1970-01-01
        • 2022-01-03
        • 1970-01-01
        • 2018-04-16
        • 2014-09-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多