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

C# 用LINQ求C中数字数组的累积和,c#,arrays,linq,cumulative-sum,C#,Arrays,Linq,Cumulative Sum,我有一个包含双精度的csv字符串,例如0.3,0.4,0.3,我希望能够输出一个双精度数组,其中包含这些数字的累积和,例如[0.3,0.7,1.0] 到目前为止,我已经 double[]probabilities=textBox_f.Text.Splitnew char[]{',}.Selects=>double.Parses.ToArray 它以数组的形式给出数字,但不是数字的累积和 是否有任何方法可以继续此表达式以获得所需内容,或者是否需要使用迭代从我已有的数组中创建新数组?是否要使用聚合运

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

到目前为止,我已经

double[]probabilities=textBox_f.Text.Splitnew char[]{',}.Selects=>double.Parses.ToArray

它以数组的形式给出数字,但不是数字的累积和

是否有任何方法可以继续此表达式以获得所需内容,或者是否需要使用迭代从我已有的数组中创建新数组?

是否要使用聚合运算符,并使用列表作为聚合累加器。这样你就可以产生一个投影,它本身就是一系列的和

下面是一个让您开始学习的示例:

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

为什么它必须是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]);

不久前我也有类似的要求。基本上,我需要进行聚合,但我还需要选择每个中间值。因此,我编写了一个名为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;
        }
    }

首先,我认为这对Linq来说不是一个好任务。普通的老弗雷奇会做得更好。但作为一个谜,它是好的

第一个想法是使用子查询,但我不喜欢它,因为它在^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
            );
这里有一种使用LINQ的方法:

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
这是一个普遍性的时刻,也是解决实际提出的问题的时刻。这是后一个时代。如果您想制作一种方法,将一系列的双精度数转换为一系列的部分和,那么只需执行以下操作:

public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence)
{
    double sum = 0;
    foreach(var item in sequence)
    {
        sum += item;
        yield return sum;
    }        
}
现在,我注意到,如果这是用户输入,那么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();
现在,如果用户输入错误,就不会出现异常;您将获得空值。

使用RX:

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

实际上,使用generator进行泛化非常简单。这里有一个新的扩展方法,名为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);
    }
 }
列表的累积金额:

以下是我的解决方案:

        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
            );
林克 线性时间 线性存储器 无副作用 唯一需要注意的是,它不适用于处理琐碎的空列表

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

我喜欢学习新技术和做事方式。很可能其他方法更好或更快,但这是我不知道怎么做的,所以我想问一下为什么?如果一个没有LINQ的解决方案输入和执行速度都更快,那么您为什么要对LINQ解决方案感兴趣呢?还有,为什么特别是LINQ呢?为什么不询问一个使用泛型、动态或任何其他不需要回答问题的随机特性的解决方案呢?Splitnew char[]{',}可以等效地写为Split',,因为参数是用params声明的。旧问题,但我必须评论。问题是关于你如何在LINQ中做到这一点,而不是没有LINQ你如何做到这一点。@simonalexander2005我为你提出这个问题而鼓掌,但我想知道为什么你选择了目前分数为-3分的Blindy的答案?这绝对不是一个好答案。就我个人而言,我更喜欢埃里克·利珀特或安德烈的答案。我只是想知道你在差不多5年后对这个问题的看法。这是低效的,每次在^2伟大的答案上都会计算出总和,让我看了总量——但最后我选择了另一个来使用。谢谢though@Andrey-实际上,我最初的实现是不正确的。我想用LastOrDefault。。。因为运行总数中的最后一个项目已经是之前所有项目的累计总和。剩下的唯一不足之处是创建中间序列。。。对于大多数用例,这应该是最小的。该实现现在编译并生成预期结果。仅因LINQ查询的副作用而使用LINQ查询是一个坏主意,原因有很多——其中最重要的一个原因是,代码的未来读者不会期望这样做。。。这违反了最不出人意料的原则。这是一个非常非常糟糕的主意。伊布什金是对的。请不要编写有副作用的查询。查询应该查询数据,而不是修改数据。@Blindy:您能解释一下Select和map的区别吗?作为查询理解的副作用,变量的变异是如何起作用的呢?作为一个具体的例子,说明为什么这是一个如此糟糕的想法,事实上var l1=output.ToList;var l2=output.ToList;不同内容列表中的结果可能非常令人惊讶。@Blindy:我希望Haskell程序员会对你所做的事情感到震惊。Haskell完全是为了避免像这样的副作用和突变。+1-这是一个很好的解决方案,它还教你如何编写可重用的代码!也许是出于同样的原因
累积和,一个数字数组和C?虽然代码不错,但它确实需要额外的导入。我知道这是七年后的事了,但我直到现在才看到这个解决方案。虽然这个解决方案在时间上是线性的,但在堆栈空间的使用上也是线性的,这真的很糟糕;相对较小的输入数组将导致堆栈溢出。你知道为什么它在堆栈空间是线性的吗?提示:溢出在您没有编写的代码中。编写代码,看看会发生什么@这是一句有趣的话。我不确定什么会导致线性堆栈空间分配。Concat是否通过将序列存储在堆栈中来有效地生成序列?这里有一个小程序。在运行它之前,请尝试预测输出结果。然后试试看;你感到惊讶吗?现在应该清楚堆栈的使用位置了。@EricLippert是的,看来我的猜测是正确的,ConcatIterator使用堆栈遍历列表。谢谢你的指点,我不知道在长列表中使用Concat是不明智的。只是澄清一下你的评论:问题是当你有大量的连接时。将两个序列连接在一起就可以了。当您将一个项目连接到一个序列上,然后将一个项目连接到该序列上,然后将一个项目连接到该序列上,然后将一个项目连接到该序列上时,就会出现问题,…您可以看到这是如何进行的。我们在堆上构建了一个对象链,当对每个对象进行迭代时,会触发一个方法调用,并且调用会在堆栈上进行?用下面的代码加倍?总和=0;总和+=1;总和+=零;Console.WriteLine$sum={sum}。;。总和变为null而不是1。怎么了?@MoneyOrientedProgrammer:没什么不对的;没错。null double表示我不知道值是什么。什么是零加一加上你不知道的东西?@MoneyOrientedProgrammer:如果你的意图是零,那么使用x??0,其中x可以为零。好的。上面的代码不需要写成sum+=item±0?@MoneyOrientedProgrammer:这取决于业务逻辑。就个人而言,我不希望在没有警告的情况下忽略格式错误的数据。
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; });
    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)
        }
    );