在 C# 中使用 LINQ 求数字数组的累积和

问题描述 投票:0回答:12

我有一个包含双精度数的csv字符串(例如“0.3,0.4,0.3”),并且我希望能够输出包含这些数字的累积和的双精度数组(例如[0.3,0.7,1.0])。

到目前为止,我已经

double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();

它将数字作为数组给出,但不是数字的累积和。

有没有办法继续这个表达式来得到我想要的,或者我是否需要使用迭代从我已有的数组创建一个新数组?

c# arrays linq cumulative-sum
12个回答
59
投票

有时需要概括性,有时需要解决实际提出的问题。这是后者之一。如果您想创建一个将双精度序列转换为部分和序列的方法,那么就这样做:

public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence)
{
    double sum = 0;
    foreach(var item in sequence)
    {
        sum += item;
        yield return sum;
    }        
}

简单。不要搞乱聚合和复杂的查询等等。易于理解、易于调试、易于使用:

textBox_f.Text
    .Split(new char[]{','})
    .Select(s => double.Parse(s))
    .CumulativeSum()
    .ToArray();

现在,我注意到,如果这是用户输入,则 double.Parse 可能会抛出异常;这样做可能是一个更好的主意:

public static double? MyParseDouble(this string s)
{
    double d;
    if (double.TryParse(s, out d))
        return d;
    return null;
}

public static IEnumerable<double?> CumulativeSum(this IEnumerable<double?> sequence)
{
    double? sum = 0;
    foreach(var item in sequence)
    {
        sum += item;
        yield return sum;
    }        
}
...
textBox_f.Text
    .Split(new char[]{','})
    .Select(s => s.MyParseDouble())
    .CumulativeSum()
    .ToArray();

现在,如果用户输入错误,您不会收到异常;你得到空值。


26
投票

我前段时间也有类似的需求。基本上,我需要进行聚合,但我还需要选择每个中间值。所以我写了一个名为

SelectAggregate
的扩展方法(可能不是最合适的名称,但我当时找不到更好的方法),可以这样使用:

double[] numbers = new [] { 0.3, 0.4, 0.3 };
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray();

这是代码:

    public static IEnumerable<TAccumulate> SelectAggregate<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        source.CheckArgumentNull("source");
        func.CheckArgumentNull("func");
        return source.SelectAggregateIterator(seed, func);
    }

    private static IEnumerable<TAccumulate> SelectAggregateIterator<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        TAccumulate previous = seed;
        foreach (var item in source)
        {
            TAccumulate result = func(previous, item);
            previous = result;
            yield return result;
        }
    }

6
投票

您想要使用

Aggregate
运算符,并使用
List<double>
作为聚合累加器。这样你就可以产生一个投影,它本身就是一个和的序列。

这是一个帮助您入门的示例:

double[] runningTotal = textBox_f.Text
            .Split(new char[]{','})
            .Select(s => double.Parse(s))
            .Aggregate((IEnumerable<double>)new List<double>(), 
                       (a,i) => a.Concat(new[]{a.LastOrDefault() + i}))
            .ToArray();

3
投票
var input=new double[]{ ... }
double sum=0;

var output=input
    .Select(w=>sum+=w);

2
投票

为什么需要是 LINQ?

var cumulative = new double[probabilities.Length];
for (int i = 0; i < probabilities.Length; i++)
    cumulative[i] = probabilities[i] + (i == 0 ? 0 : cumulative[i-1]);

2
投票

首先,我认为这对于 Linq 来说不是一个好任务。普通的旧

foreach
会做得更好。但作为拼图来说还是可以的。

第一个想法是使用子查询,但我不喜欢它,因为它是 O(n^2)。这是我的线性解决方案:

        double[] probabilities = new double[] { 0.3, 0.4, 0.3};
        probabilities
            .Aggregate(
                new {sum=Enumerable.Empty<double>(), last = 0.0d},
                (a, c) => new {
                    sum = a.sum.Concat(Enumerable.Repeat(a.last+c,1)),
                    last = a.last + c
                },
                a => a.sum
            );

1
投票

使用 RX :

var input=new double[]{ ... }
var output = new List<double>();
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add);

1
投票

使用生成器进行泛化实际上非常简单。这是一种名为

Accumulate
的新扩展方法,其工作原理类似于
Select
Aggregate
的组合。它通过将二元函数应用于序列中的每个元素和迄今为止的累积值来返回一个新序列。

 public static class EnumerableHelpers 
 {
    public static IEnumerable<U> Accumulate<T, U>(this IEnumerable<T> self, U init, Func<U, T, U> f) 
    {
        foreach (var x in self)
            yield return init = f(init, x);
    }

    public static IEnumerable<T> Accumulate<T>(this IEnumerable<T> self, Func<T, T, T> f)
    {
        return self.Accumulate(default(T), f);
    }

    public static IEnumerable<double> PartialSums(this IEnumerable<double> self)
    {
        return self.Accumulate((x, y) => x + y);
    }

    public static IEnumerable<int> PartialSums(this IEnumerable<int> self)
    {
        return self.Accumulate((x, y) => x + y);
    }
 }

1
投票

这是我的解决方案:

  • Linq
  • 线性时间
  • 线性记忆
  • 无副作用

唯一需要注意的是,它不适用于空列表(处理起来很简单)。

    var doublesSummed  = doubles.Skip(1).Aggregate(
        new {
            sum = doubles.First(),
            doubles = new [] {doubles.First()}.AsEnumerable()
        },  
        (acc, nextDouble) => new {
            sum = acc.sum + nextDouble,
            doubles = acc.doubles.Append(acc.sum + nextDouble)
        }
    );

演示


0
投票

这是使用 LINQ 实现此操作的 a 方法:

double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 };
var doublesSummed = new List<double>();

Enumerable.Aggregate(doubles, (runningSum, nextFactor) => {
    double currentSum = runningSum + nextFactor;
    doublesSummed.Add(currentSum);
    return currentSum;
});

doublesSummed.Dump();

在 LINQPad 中:

  • 4
  • 5.9
  • 10
  • 12.9

0
投票

来自这里的 JavaScript 技巧:https://stackoverflow.com/a/55259065/7126740

使用柯里化来存储总和

拉姆达:

Func<int, Func<int, int>> cumulative = sum => value => sum += value;
var sums = array.Select(cumulative(0));

本地功能:

Func<int, int> cumulative(int sum) => value => sum += value;
var sums = array.Select(cumulative(0));

单行:

var sums = array.Select(((Func<int, Func<int, int>>)(sum => value => sum += value))(0));

结果,编译器会生成一个类来存储和:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public int sum;

    internal int <Main>b__1(int value)
    {
        return sum += value;
    }
}

我不建议使用此代码,但这对我来说是一个奇迹


-1
投票

List<double>
的累计金额:

var nums = new List<double>() { 0.3, 0.0, 0.4, 1.1 };
var cumsum = nums.Aggregate(new List<double> (), 
              (list, next) => { list.Add(list.LastOrDefault() + next); return list; });
© www.soinside.com 2019 - 2024. All rights reserved.