.net 对IEnumerable中以前的值求和

dced5bon  于 2023-02-26  发布在  .NET
关注(0)|答案(8)|浏览(142)

我有一串数字:

var seq = new List<int> { 1, 3, 12, 19, 33 };

我想把它转换成一个新的序列把这个数字加到前面的数字上来创建一个新的序列

{ 1, 3, 12, 19, 33 } --> {1, 4, 16, 35, 68 }

我想出了下面的方法,但是我不喜欢状态变量'count',我也不喜欢我使用值Enumerable而不对它进行操作的事实。

int count = 1;
var summed = values.Select(_ => values.Take(count++).Sum());

还能怎么办呢?

rt4zxlrg

rt4zxlrg1#

这是函数式编程中的一种常见模式,在F#中称为scan。它类似于C#的Enumerable.Aggregate和F#的fold,只是它在生成最终结果的沿着生成累加器的中间结果。我们可以用一个扩展方法很好地在C#中实现scan:

public static IEnumerable<U> Scan<T, U>(this IEnumerable<T> input, Func<U, T, U> next, U state) {
    yield return state;
    foreach(var item in input) {
        state = next(state, item);
        yield return state;
    }
}

然后按如下方式使用它:

var seq = new List<int> { 1, 3, 12, 19, 33 };
var transformed = seq.Scan(((state, item) => state + item), 0).Skip(1);
pgpifvop

pgpifvop2#

“纯”LINQ:
var result = seq.Select((a, i) => seq.Take(i + 1).Sum());
O(n)时间复杂度:

var res = Enumerable.Range(0, seq.Count)
    .Select(a => a == 0 ? seq[a] : seq[a] += seq[a - 1]);

还有一个LINQ,带有状态维护:

var tmp = 0;
var result = les.Select(a => { tmp += a; return tmp; });
efzxgjgh

efzxgjgh3#

Stephen Swensen的回答很好,scan正是你所需要的,还有另一个版本的scan,虽然它不需要种子,但它会稍微更适合你的问题。
这个版本要求输出元素类型与输入元素类型相同,在您的例子中就是这样,并且提供了不需要传入0然后跳过第一个(0)结果的优点。
您可以在C#中实现此版本的扫描,如下所示:

public static IEnumerable<T> Scan<T>(this IEnumerable<T> Input, Func<T, T, T> Accumulator)
{
    using (IEnumerator<T> enumerator = Input.GetEnumerator())
    {
        if (!enumerator.MoveNext())
            yield break;
        T state = enumerator.Current;
        yield return state;
        while (enumerator.MoveNext())
        {
            state = Accumulator(state, enumerator.Current);
            yield return state;
        }
    }
}

然后按如下方式使用它:

IEnumerable<int> seq = new List<int> { 1, 3, 12, 19, 33 };
IEnumerable<int> transformed = seq.Scan((state, item) => state + item);
jslywgbw

jslywgbw4#

var seq = new List<int> { 1, 3, 12, 19, 33 };

var summed = new List<int>();

seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
xzabzqsa

xzabzqsa5#

为了提供另一种替代方法(虽然不是真正的LINQ),您可以编写一个基于yield的函数来进行聚合:

public static IEnumerable<int> SumSoFar(this IEnumerable<int> values)
{
  int sumSoFar = 0;
  foreach (int value in values)
  {
    sumSoFar += value;
    yield return sumSoFar;
  }
}

像BrokenGlass一样,这只对数据进行了一次传递,尽管与他不同的是,返回的是迭代器而不是列表。
(真烦人。)

epggiuax

epggiuax6#

要使用Linq并且只在可以使用自定义聚合器时遍历列表:

class Aggregator
{
    public List<int> List { get; set; }
    public int Sum { get; set; }
}

..

var seq = new List<int> { 1, 3, 12, 19, 33 };
var aggregator = new Aggregator{ List = new List<int>(), Sum = 0 };
var aggregatorResult = seq.Aggregate(aggregator, (a, number) => { a.Sum += number; a.List.Add(a.Sum); return a; });
var result = aggregatorResult.List;
sqserrrh

sqserrrh7#

var seq = new List<int> { 1, 3, 12, 19, 33 }; 

for (int i = 1; i < seq.Count; i++)
{
   seq[i] += seq[i-1];
}
dddzy1tm

dddzy1tm8#

所有已经发布的答案都运行良好。我只想补充两件事:

  • MoreLinq已经有了一个Scan方法,其中有很多非常有用的函数,这些函数在LINQ中并不是本机的。对于这类应用程序来说,这是一个很好的库,所以我想回忆一下。
  • 在看到上面的答案之前,我写了我自己的扫描:
public static IEnumerable<T> Scan<T>(IEnumerable<T> input, Func<T, T, T> accumulator)

{ if(!input.Any())收益率中断;

T state = input.First();
  yield return state;

  foreach (var value in input.Skip(1))
  {
      state = accumulator(state, value);
      yield return state;
  }

}
运行良好,但由于对集合的多次访问,很可能比使用@Nathan菲利普斯版本效率低。枚举可能做得更好。

相关问题