简体   繁体   English

如何在Linq查询中计算一系列整数的运行总和?

[英]How to compute a running sum of a series of ints in a Linq query?

I am trying to come up with a linq query to convert an IEnumerable<int> to another IEnumerable<int> , where each int in the result is the sum of all the ints up to that position from the initial list: 我试图想出一个linq查询来将IEnumerable<int>转换为另一个IEnumerable<int> ,其中结果中的每个int都是从初始列表到该位置的所有int的总和:

Given int[] a 给定int[] a
I need int[] b 我需要int[] b
Where b[0] = a[0], b[1] = a[0] + a[1], b[2] = a[0] + a[1] + a[2] and so on 其中b[0] = a[0], b[1] = a[0] + a[1], b[2] = a[0] + a[1] + a[2]等等

Alternatively, the sums above can be written as b[1] = b[0] + a[1], b[2] = b[1] + a[2] and so on, but I don't see how that would help. 或者,上面的总和可以写成b[1] = b[0] + a[1], b[2] = b[1] + a[2]等等,但我不知道那是怎么回事有助于。

I can, of course, do this with a for loop, but I obtain the a[] sequence from a query and I thought it would look nicer if I continue that query instead of suddenly adding a for there :) 当然,我可以使用for循环执行此操作,但是我从查询中获取a []序列,并且我认为如果我继续查询而不是突然添加for那样会更好看:)

Well, you can do it with side effects easily enough, although it's pretty icky... 好吧,你可以很容易地做副作用,虽然它很icky ......

int sum = 0;
int[] b = a.Select(x => (sum += x)).ToArray();

It would be nice if the framework provided a sort of "running aggregate" to encapsulate this, but it doesn't as far as I'm aware. 如果框架提供了一种“运行聚合”来封装它,那将是很好的,但它并不是我所知道的。

I wrote a function to do this a while ago. 我刚才写了一个函数来做这个。 It's similar to Haskell's scanl function. 它类似于Haskell的scanl函数。

public static IEnumerable<TResult> Scan<T, TResult>(
    this IEnumerable<T> source, 
    Func<T, T, TResult> combine)
{
    using (IEnumerator<T> data = source.GetEnumerator())
        if (data.MoveNext())
        {
            T first = data.Current;

            yield return first;

            while (data.MoveNext())
            {
                first = combine(first, data.Current);
                yield return first;
            }
        }
}

int[] b = a
    .Scan((running, current) => running + current)
    .ToArray();

An alternative to Mr. Skeet's solution: If we drop the requirement for a linq query and more literally address "convert an IEnumerable<int> to another IEnumerable<int> " we can use this: Skeet先生解决方案的替代方案:如果我们删除linq查询的要求,并且更确切地说“将IEnumerable<int>转换为另一个IEnumerable<int> ”,我们可以使用:

    static IEnumerable<int> Sum(IEnumerable<int> a)
    {
        int sum = 0;
        foreach (int i in a)
        {
            sum += i;
            yield return sum;
        }
    }

which we can apply to an infinite series: 我们可以应用于无限系列:

    foreach (int i in Sum(MyMath.NaturalNumbers))
        Console.WriteLine(i);

This is also useful if you don't want to create the whole array at once. 如果您不想一次创建整个数组,这也很有用。

The above answer doesn't quite work....and doesnt share the same signature as Haskell's scanl.... basically its an extension to the idea of linq's "aggregate"...I think this matches the Haskell implementation better 上面的答案不太有效....并且与Haskell的scanl没有共享相同的签名....基本上它是linq的“聚合”概念的扩展...我认为这与Haskell实现更好地匹配

    public static IEnumerable<TResult> Scanl<T, TResult>(
        this IEnumerable<T> source,
        TResult first,
        Func<TResult, T, TResult> combine)
    {
        using (IEnumerator<T> data = source.GetEnumerator())
        {
            yield return first;

            while (data.MoveNext())
            {
                first = combine(first, data.Current);
                yield return first;
            }
        }
    }

usage 用法

    [TestMethod]
    public void Scanl_Test()
    {
        var xs = new int[] { 1, 2, 3, 4, 5, 6, 7 };

        var lazyYs = xs.Scanl(0, (y, x) => y + x);

        var ys = lazyYs.ToArray();

        Assert.AreEqual(ys[0], 0);
        Assert.AreEqual(ys[1], 1);
        Assert.AreEqual(ys[2], 3);
        Assert.AreEqual(ys[3], 6);
        Assert.AreEqual(ys[4], 10);
        Assert.AreEqual(ys[5], 15);
        Assert.AreEqual(ys[6], 21);
        Assert.AreEqual(ys[7], 28);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM