聚合值,直到达到限制

时间:2015-02-10 08:38:25

标签: c# linq linq-to-objects

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

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

这是一个例子。我们有一个带有累加器的List { 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

我需要一个纯粹的功能解决方案,因此关闭临时变量不是一种选择。

5 个答案:

答案 0 :(得分:3)

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

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); 

这很难看,所以写一个新的AggregateWhile会更好:

public static TSource AggregateWhile<TSource>(this IEnumerable<TSource> source, 
                                         Func<TSource, TSource, TSource> func,
                                         Func<TSource, bool> predicate)
{
   using (IEnumerator<TSource> e = source.GetEnumerator()) {
       TSource result = e.Current;
       TSource tmp = default(TSource);
       while (e.MoveNext() && predicate(tmp = func(result, e.Current))) 
            result = tmp;
       return result;
   }
}

(没有错误检查以简洁)

答案 1 :(得分:2)

这不会起作用吗?

int total = list.Aggregate(0, (a, x) => (a + x) > 12 ? a : a + x);

使用Tuple<bool, int>作为累加器类型,在第一次溢出时中断:

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;

但不幸的是,它不是那么好。


开始使用F#。 ;)

let list = [ 1; 2; 3; 4; 5; 1 ]
let predicate = fun a -> a > 12 
let total = list |> List.fold (fun (aval, astate) x ->
    if astate || predicate (aval + x)
    then (aval, true)
    else (aval + x, false)) (0, false)

Tuple解包,没有new臃肿。当你编码它时,类型推断使它变得轻而易举。

答案 2 :(得分:2)

您可以编写自己的扩展方法。这不像普通的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;
  }
}

答案 3 :(得分:1)

我在遇到问题后回答了这个问题,后来我重新考虑了不需要AggregateWhile的问题。但是现在我遇到了一个稍微不同的问题,无疑需要AggregateWhile或直接替代它。

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

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

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;
}

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

答案 4 :(得分:1)

以下是AggregateWhile seed

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;
}