首页 > 解决方案 > 使用 LINQ 有效地验证累积和验证属性

问题描述

我想验证我的列表中元素的总和(非负数)是否低于某些值。而且我不想计算没有必要的整个总和。(如果我们证明第一个元素的总和不尊重该属性,我们将停止计算)

所以我想要一个 LINQ 命令来验证累积和的每个元素是否低于某个值,只要它看到不等式成立。

var b = a.Aggregate(new List<int> { 0 }, (ls, x) => { ls.Add(x + ls.Last()); return ls; }).All(x => x < 4);

这种方法行不通。当它看到累积和的第 i 个元素不满足属性但整个累积和是计算时,所有的都停止。

你有更好的方法吗?(我知道我们可以用循环有效地做到这一点,但我想用 LINQ 做到这一点)

如果我使用循环:

var s = 0;

var b = true;

foreach(var x in list) { s=s+x; if(s>4){ b= false; break;} }

谢谢

标签: c#linq

解决方案


你不需要使用 LINQ 方法来做你想做的事。您可以使用枚举器和循环编写自己的代码。毕竟,LINQ-to-Objects 操作本身是使用循环实现的。例如TakeWhile被实现为一个迭代器,它遍历源并产生匹配的元素:

    static IEnumerable<TSource> TakeWhileIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {
        int index = -1;
        foreach (TSource element in source) {
            checked { index++; }
            if (!predicate(element, index)) break;
            yield return element;
        }
    }

缺点是这会为迭代器生成一个状态机并返回所有匹配的元素,无论它们是否被使用。

您可以编写自己的扩展方法,计算循环中的总和,并true在循环完成但未达到限制时返回:

public static bool SumBelow(this IEnumerable<int> source, int limit) 
{
    int sum=0;
    foreach (var element in source) 
    {
        sum+=element;
        if (sum>limit)
        {
            return false;
        }
    }
    return true;
}

并将其用作扩展方法:

var isSumBelow = someEnumerable.SumBelow(5);

为什么不是通用方法?

无法指定运算符约束或 IAddable 接口,这就是Sum()本身为每种类型分别实现的原因,例如:

public static int Sum(this IEnumerable<int> source) {
    if (source == null) throw Error.ArgumentNull("source");
    int sum = 0;
    checked {
        foreach (int v in source) sum += v;
    }
    return sum;
}

功能性方式

将累加器和条件检查器作为函数传递可用于创建一种通用的、可重用的方法,该方法可以处理任何转换和条件:

public static bool AccWithinLimit<T>(
                        this IEnumerable<T> source, 
                        Func<T,T,T> accumulator,
                        Func<T,bool> terminator, 
                        T seed=default) 
{
    T total=seed;
    foreach (var element in source) 
    {
        total = accumulator(element,total);            
        if (terminator(total))
        {
            return false;
        }
    }
    return true;
}

这可用于检查整数数组的部分和:

var myArray=new []{1,2,3};
var limit = 5;
var totalBelowLimit = myArray.AccWithinLimit(myArray,
                                  (sum,elm)=>sum+elm,
                                  sum=>sum>limit);

或带有双打列表的部分产品:

var myList = new List<double>{1.0, 2.0, 3.0};
var limit = 10;
var totalBelowLimit = myList.AccWithinLimit(myArray,
                                  (sum,elm)=>sum*elm,
                                  sum=>sum>limit,
                                  1);

推荐阅读