【问题标题】:How to compute a running sum of a series of ints in a Linq query?如何计算 Linq 查询中一系列整数的运行总和?
【发布时间】:2013-05-09 18:08:17
【问题描述】:

我正在尝试提出一个 linq 查询来将 IEnumerable<int> 转换为另一个 IEnumerable<int>,其中结果中的每个 int 都是从初始列表到该位置的所有 int 的总和:

给定int[] a
我需要int[] b
在哪里b[0] = a[0], b[1] = a[0] + a[1], b[2] = a[0] + a[1] + a[2]等等

或者,上面的总和可以写成b[1] = b[0] + a[1], b[2] = b[1] + a[2] 等等,但我不明白这会有什么帮助。

当然,我可以使用for 循环来执行此操作,但是我从查询中获得了 a[] 序列,我认为如果我继续该查询而不是突然在那里添加for 会更好看:)

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    好吧,你可以很容易地解决副作用,虽然它很恶心......

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

    如果框架提供一种“运行聚合”来封装它会很好,但据我所知并没有。

    【讨论】:

    • 你真是太棒了 :) 是的,这有点恶心,但已经足够好了,我最近正在寻求删除 for 语句。
    【解决方案2】:

    前段时间我写了一个函数来做到这一点。类似于 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();
    

    【讨论】:

    • 你在yield return first有问题,first如果是T类型,而不是TResult
    【解决方案3】:

    Skeet 先生的解决方案的替代方案:如果我们放弃对 linq 查询的要求,而是更直接地解决“将一个 IEnumerable&lt;int&gt; 转换为另一个 IEnumerable&lt;int&gt;”,我们可以使用这个:

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

    我们可以将其应用于无限级数:

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

    如果您不想一次创建整个数组,这也很有用。

    【讨论】:

    • 是的,我使用数组语法是因为它更容易解释需求,但参数实际上是一个 IEnumerable。但是,Skeet 先生在这种情况下仍然有效,只是没有 .ToArray() 调用,而且它更紧凑,所以我仍然投票给那个 :)
    【解决方案4】:

    上面的答案不太有效....并且与 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;
                }
            }
        }
    

    用法

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-29
      • 2021-10-23
      • 2018-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多