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

本文关键字:IEnumerable 获取 集合 表达式 LINQ 一个 | 更新日期: 2023-09-27 17:56:51

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

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

经典的方法是:

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

但我一直在思考如何只用一种表达方式做到这一点,但没有成功......

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

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

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

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

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

为了在单个语句中干净利落地做到这一点,没有副作用,您可能需要使用某种Scan方法来计算输入序列的前缀和。然后你只需要第一个大于 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;
                }
            }
        }
    }
}

当然,有一种方法可以使用单个 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 表达式!玩得开心

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

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() 不用于其返回值,而是用于强制实际枚举。

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

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

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

Linq不是问题所在。在 A() 完成生成之前,您不会完成添加...而这永远不会发生。

顺便说一句,你发布的代码并没有把所有小于N的值都加起来

......它会把所有值加起来,直到找到一个小于N的值,然后它就停止寻找了。这是你的意思吗?

我相信

下面的可怕代码可以满足您的要求。

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()。其中(x => x <N)。总和();>