开发者

Obtain the Sum of an IEnumerable collection in only one LINQ expression

Let's suppose I have an inifite generator A(). What I want is to obtain the sum of all the numbers returned by A such that the sum does not exceed a value N in only one LINQ expression.

I'm wondering if there is an extension method that will help me with that?

The classic way would be:

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

return sum;

but I've bee开发者_StackOverflow社区n thinking about how to do it in only one expression without success...


Using standard idiomatic LINQ, this would be impossible. The semantics you would need is a combination of Aggregate() and TakeWhile(). Otherwise, you'd need to have side-effects which is a no-no in LINQ.

Here's an example of one way to do it with side-effects:

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


If A is an infinite generator then there's no way of doing this in a single statement using only the built-in LINQ methods.

To do it cleanly, in a single statement, with no side-effects, you'd probably need to use some sort of Scan method to compute the prefix sum of the input sequence. Then you just need the first element greater than N. Easy!

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


Sure there is a way to do this with a single LINQ-expression. The simplest I could come up with and still have some generality and elegance is:

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

which can be called like:

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

Yeah, just a single LINQ-expression! Have fun with it


Probably the most near your initial idea :

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

The Count() isn't used for its returning value but to force the actual enumerating.


Let's see if I have the requirements correct.

A() is an infinite generator. By definition, then, it generates values (in this case integers) forever.

You want to look for all of the values that are less than N, and add them together.

Linq isn't the issue. You won't be done adding until A() is finished generating... and that never happens.

BTW, the code you posted doesn't all up all of the values less than N... it adds up all the values until it finds one less than N, and then it quits looking. Is that what you meant?


I believe the horrific code below satisfies your requirements. :-)

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


int sum = A().Where(x => x < N).Sum();

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜