使用 linq 计算列表属性的总和,不包括最小值和最大值
calculate sum of list properties excluding min and max value with linq
这是我目前拥有的:
decimal? total = list.Sum(item => item.Score);
我想做的是排除列表中的最小值和最大值,然后得到总值。
是否可以在一个 linq 语句中完成所有这些操作?
您应该在求和之前预处理列表以排除最小值和最大值。
如果要排除 所有 最小值和最大值,请预先计算这两个值,然后使用 È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);
您可以尝试先对列表进行排序,然后跳过第一项(最少)并从其余项中取出除最后一项(最多)以外的所有项:
decimal? total = list.OrderBy(x => x.Score)
.Skip(1)
.Take(list.Count - 2)
.Sum(x => x.Score);
list.OrderBy(item => item.Score)
.Skip(1)
.Reverse()
.Skip(1)
.Sum(item => item.Score);
这不是可以想象的最好的代码,但它确实具有
- 仅枚举整个集合一次(虽然它确实获得了第一个值三次)。
- 不需要比保存 IEnumerator 和两个
Tuple<int, int, long, long>
对象多得多的内存(如果使用 OrderBy
、ToList
和排序等,则不需要。 ).这让它可以处理任意大的 IEnumerable 集合。
- 单个 Linq 表达式(这就是您想要的)。
- 正确处理边缘情况 (
values.Count() < 2
):
- 当没有值时,在 IEnumerable 上使用
Min()
和 Max()
将抛出 InvalidOperationException
- 当只有一个值时,天真的实现会在 IEnumerable 上做类似
Sum() - Min() - Max()
的事情,returns 单个值,取反。
我知道你已经接受了一个答案,但它是:我正在使用单个调用 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;
}
这是我目前拥有的:
decimal? total = list.Sum(item => item.Score);
我想做的是排除列表中的最小值和最大值,然后得到总值。
是否可以在一个 linq 语句中完成所有这些操作?
您应该在求和之前预处理列表以排除最小值和最大值。
如果要排除 所有 最小值和最大值,请预先计算这两个值,然后使用 È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);
您可以尝试先对列表进行排序,然后跳过第一项(最少)并从其余项中取出除最后一项(最多)以外的所有项:
decimal? total = list.OrderBy(x => x.Score)
.Skip(1)
.Take(list.Count - 2)
.Sum(x => x.Score);
list.OrderBy(item => item.Score)
.Skip(1)
.Reverse()
.Skip(1)
.Sum(item => item.Score);
这不是可以想象的最好的代码,但它确实具有
- 仅枚举整个集合一次(虽然它确实获得了第一个值三次)。
- 不需要比保存 IEnumerator 和两个
Tuple<int, int, long, long>
对象多得多的内存(如果使用OrderBy
、ToList
和排序等,则不需要。 ).这让它可以处理任意大的 IEnumerable 集合。 - 单个 Linq 表达式(这就是您想要的)。
- 正确处理边缘情况 (
values.Count() < 2
):- 当没有值时,在 IEnumerable 上使用
Min()
和Max()
将抛出InvalidOperationException
- 当只有一个值时,天真的实现会在 IEnumerable 上做类似
Sum() - Min() - Max()
的事情,returns 单个值,取反。
- 当没有值时,在 IEnumerable 上使用
我知道你已经接受了一个答案,但它是:我正在使用单个调用 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;
}