C# 聚合值,直到达到限制

C# 聚合值,直到达到限制,c#,linq,linq-to-objects,C#,Linq,Linq To Objects,我需要类似于AggregateWhile方法的东西。标准的System.Linq.Enumerable类没有提供它。到目前为止,我一直能够利用标准LINQ方法来解决我遇到的每一个问题。所以我想知道在这种情况下这是否仍然可行,或者我是否真的需要用非标准方法扩展LINQ 假设的AggregateWhile方法将迭代序列并应用累加器。一旦谓词返回false,聚合就完成了。结果是元素的聚合,直到但不包括谓词失败的元素 这里有一个例子。我们有一个列表{1,2,3,4,5},其中有一个累加器将两个输入数字相

我需要类似于
AggregateWhile
方法的东西。标准的
System.Linq.Enumerable
类没有提供它。到目前为止,我一直能够利用标准LINQ方法来解决我遇到的每一个问题。所以我想知道在这种情况下这是否仍然可行,或者我是否真的需要用非标准方法扩展LINQ

假设的
AggregateWhile
方法将迭代序列并应用累加器。一旦谓词返回false,聚合就完成了。结果是元素的聚合,直到但不包括谓词失败的元素

这里有一个例子。我们有一个
列表{1,2,3,4,5}
,其中有一个累加器将两个输入数字相加,还有一个谓词说明累加器必须小于12
AggregateWhile
将返回10,因为这是1+2+3+4的结果,加上最后的5将使总数超过限制。代码:

var list = new List<int> { 1, 2, 3, 4, 5 };
int total = list.AggregateWhile( (x, y) => x + y, a => a < 12 ); // returns 10
var list=新列表{1,2,3,4,5};
int total=list.AggregateWhile((x,y)=>x+y,a=>a<12);//返回10
我需要一个纯函数式的解决方案,所以关闭一个临时变量不是一个选项。

这不管用吗

int total = list.Aggregate(0, (a, x) => (a + x) > 12 ? a : a + x);
使用
Tuple
作为累加器类型,在第一次溢出时中断:

int total = list.Aggregate(new Tuple<bool, int>(false, 0),
    (a, x) => a.Item1 || (a.Item2 + x) > 12
    ? new Tuple<bool, int>(true, a.Item2)
    : new Tuple<bool, int>(false, a.Item2 + x)
).Item2;

Tuple
解包,无
new
bloat。当您编写代码时,类型推断使它变得轻而易举。

您可以自己编写函数,也可以在累加器中携带一个标志:

int total = list.Aggregate(new { value = 0, valid = true }, 
                          (acc, v) => acc.value + v < 12 && acc.valid ?
                                      new { value = acc.value + v, valid = true } :
                                      new { value = acc.value, valid = false },
                            acc => acc.value); 

(为了简洁起见没有错误检查)

您可以编写自己的扩展方法。这并不像普通的Linq方法那么完美,我作弊是因为我已经知道你的要求,让它更简单。实际上,您可能希望
a
有一个可选的起始值,而T或其他东西的输入和输出类型可能不同:

public static class Linq
{
  public static T AggregateWhile<T>(this IEnumerable<T> sequence, Func<T, T, T> aggregate, Func<T, bool> predicate)
  {
     T a;
     foreach(var value in sequence)
     {
        T temp = aggregate(a, value);
        if(!predicate(temp)) break;
        a = temp;
     }
     return a;
  }
}
公共静态类Linq
{
公共静态T AggregateWhile(此IEnumerable序列、Func聚合、Func谓词)
{
Tα;
foreach(序列中的var值)
{
T温度=骨料(a,值);
如果(!谓词(temp))中断;
a=温度;
}
返回a;
}
}

不久前我遇到一个问题时问了这个问题,后来我将这个问题重新定义为不需要
AggregateWhile
。但现在我遇到了一个稍有不同的问题,这无疑需要
AggregateWhile
或直接替代它

@sloth和@rkrahl提出的解决方案很有帮助。但它们的不足之处在于聚合逻辑(在本例中为加法)重复了两次。对于这个问题的小例子来说,这似乎没什么大不了的。但对于我真正的问题,计算是复杂的,所以写两次是不可接受的

以下是我喜欢的解决方案(缺少实际的
AggregateWhile
方法):

类程序
{
静态void Main(字符串[]args){new Program();}
公共计划()
{
var list=newint[]{1,2,3,4,5};
整数总计=列表
.总计(新累加器(0),(a,i)=>a.下一个(i),a=>a.总计);
}
}
类累加器
{
公共累加器(整数合计)
{
这个.总计=总计;
}
公共累加器下一个(int i)
{
中频(isDone)
归还这个;
否则{
int-total=this.total+i;
如果(总数<12)
返回新的蓄能器(总计);
否则{
isDone=true;
归还这个;
}
}
}
布尔伊斯通;
公共整数总计
{
获取{return total;}
}
只读整数合计;
}

理想的解决方案是完全实现和测试的
AggregateWhile
方法,它们对应于三种
Aggregate
重载。除此之外,上述模式的优点是它可以利用.NET framework中已经存在的(有些缺乏)功能。

这里是一个带有
种子的
聚合while

public static TAccumulate AggregateWhile<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, bool> predicate)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    if (func == null)
        throw new ArgumentNullException(nameof(func));

    if (predicate == null)
        throw new ArgumentNullException(nameof(predicate));

    var accumulate = seed;
    foreach (var item in source)
    {
        var tmp = func(accumulate, item);
        if (!predicate(tmp)) break;
        accumulate = tmp;
    }
    return accumulate;
}
public static TAccumulate AggregateWhile(
这是一个数不清的来源,
玉米粒,
Func Func,
Func谓词)
{
if(source==null)
抛出新ArgumentNullException(nameof(source));
如果(func==null)
抛出新ArgumentNullException(nameof(func));
if(谓词==null)
抛出新ArgumentNullException(nameof(谓词));
var=种子;
foreach(源中的var项)
{
var tmp=func(累计,项目);
如果(!谓词(tmp))中断;
累积=tmp;
}
回报累积;
}

您可以始终使用循环而不是Linq…或者查看聚合的引用源,然后将其修改为包含谓词。@MatthewWatson我需要使用Linq,它必须是纯函数性的。如果您想知道原因,请查看上的最后一个条目。@Dirk编写我自己的方法不是问题。对于像
新列表{1,2,3,4,5,1}这样的输入,这将失败然后使用元组作为累加器类型。@rkrahl您可以提供带有F的示例吗#如果您建议的话?:-)@给你格伦迪。我希望我能选择两个答案,但只能选择这一个。第一个例子将不必要地遍历所有元素。
public static class Linq
{
  public static T AggregateWhile<T>(this IEnumerable<T> sequence, Func<T, T, T> aggregate, Func<T, bool> predicate)
  {
     T a;
     foreach(var value in sequence)
     {
        T temp = aggregate(a, value);
        if(!predicate(temp)) break;
        a = temp;
     }
     return a;
  }
}
class Program
{
    static void Main( string[] args ) { new Program(); }

    public Program()
    {
        var list = new int[] { 1, 2, 3, 4, 5 };
        int total = list
            .Aggregate( new Accumulator( 0 ), ( a, i ) => a.Next( i ), a => a.Total );
    }
}

class Accumulator
{
    public Accumulator( int total )
    {
        this.total = total;
    }

    public Accumulator Next( int i )
    {
        if ( isDone )
            return this;
        else {
            int total = this.total + i;
            if ( total < 12 )
                return new Accumulator( total );
            else {
                isDone = true;
                return this;
            }
        }
    }
    bool isDone;

    public int Total
    {
        get { return total; }
    }
    readonly int total;
}
public static TAccumulate AggregateWhile<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, bool> predicate)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    if (func == null)
        throw new ArgumentNullException(nameof(func));

    if (predicate == null)
        throw new ArgumentNullException(nameof(predicate));

    var accumulate = seed;
    foreach (var item in source)
    {
        var tmp = func(accumulate, item);
        if (!predicate(tmp)) break;
        accumulate = tmp;
    }
    return accumulate;
}