仅在一个LINQ表达式中获取IEnumerable集合的总和

时间:2011-05-17 22:57:01

标签: c# linq extension-methods ienumerable

假设我有一个inifite生成器A()。我想要的是获取A返回的所有数字的总和,使得总和在一个LINQ表达式中不超过值N

我想知道是否有一种扩展方法可以帮助我吗?

经典的方式是:

int sum = 0;
foreach (int x in A()) {
    sum += x;
    if (sum > N) {
        break;
    }
}

return sum;

但是我一直在考虑如何只在一个表达中做到这一点而没有成功......

7 个答案:

答案 0 :(得分:3)

如果A是无限生成器,则只能使用内置的LINQ方法在单个语句中执行此操作。

要在一个语句中干净利落地完成任务,没有任何副作用,您可能需要使用某种Scan方法来计算输入序列的prefix sum。然后你只需要大于N的第一个元素。简单!

int sum = A().Scan((s, x) => s + x).First(s => s > N);

// ...

public static class EnumerableExtensions
{
    public static IEnumerable<T> Scan<T>(
        this IEnumerable<T> source, Func<T, T, T> func)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (func == null) throw new ArgumentNullException("func");

        using (var e = source.GetEnumerator())
        {
            if (e.MoveNext())
            {
                T accumulator = e.Current;
                yield return accumulator;

                while (e.MoveNext())
                {
                    accumulator = func(accumulator, e.Current);
                    yield return accumulator;
                }
            }
        }
    }
}

答案 1 :(得分:3)

使用标准惯用语LINQ,这是不可能的。您需要的语义是Aggregate()TakeWhile()的组合。否则,你需要有副作用,这在LINQ中是禁止的。

以下是使用副作用的一种方法示例:

var temp = 0;
var sum = A().TakeWhile(i =>
{
    var res = !(temp > N);
    temp += i;
    return res;
}).Sum();

答案 2 :(得分:3)

当然有一种方法可以使用单个LINQ表达式来完成此操作。最简单的我可以提出并仍然有一些普遍性和优雅是:

public static int SumWhile(this IEnumerable<int> collection, Func<int, bool> condition)
{
    int sum = 0;
    foreach (int i in collection)
    {
        sum += i;
        if (!condition(sum))
            break;
    }
    return sum;
}

可以被称为:

int sum = A().SumWhile(i => i <= N);

是的,只需一个LINQ表达式!玩得开心

答案 3 :(得分:1)

可能最接近你最初的想法:

int sum = 0;
int limit = 500;
A().TakeWhile(i => (sum += i) < limit).Count();
//Now the variable named sum contains the smaller sum of elements being >= limit

Count()不用于返回值,而是强制实际枚举。

答案 4 :(得分:0)

让我们看看我的要求是否正确。

A()是无限生成器。根据定义,它会永远生成值(在本例中为整数)。

您希望查找小于N的所有值,并将它们一起添加。

Linq不是问题。

,在A()完成生成之前,你不会完成添加。

顺便说一句,你发布的代码并不是所有的值都小于N ......它会将所有值相加,直到找到小于N的值,然后退出查找。那是你的意思吗?

答案 5 :(得分:0)

我相信下面的可怕代码可以满足您的要求。 : - )

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication12 {
  public class Program {
    public static void Main(string[] args) {
      const int N=100;

      int sum;
      try {
        sum=A().Aggregate((self, next) => {
          if(self+next<=N)
            return self+next;
          else
            throw new ResultException(self);
        });
      } catch(ResultException re) {
        sum=re.Value;
      }
      Debug.Print("Sum="+sum);
    }

    private class ResultException : Exception {
      public readonly int Value;

      public ResultException(int value) {
        Value=value;
      }
    }

    private static IEnumerable<int> A() {
      var i=0;
      while(true) {
        yield return i++;
      }
    }
  }
}

答案 6 :(得分:-1)

int sum = A()。其中​​(x =&gt; x&lt; N).Sum();