将以前的值与IEnumerable相加
我有一系列数字:
var seq = new List { 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());
怎么可能呢?
这是函数式编程中的常见模式,在F#中称为扫描 。 它就像C#的Enumerable.Aggregate和F#的折叠,除了它产生累加器的中间结果以及最终结果。 我们可以使用扩展方法很好地在C#中实现扫描:
public static IEnumerable Scan(this IEnumerable input, Func next, U state) { yield return state; foreach(var item in input) { state = next(state, item); yield return state; } }
然后使用如下:
var seq = new List { 1, 3, 12, 19, 33 }; var transformed = seq.Scan(((state, item) => state + item), 0).Skip(1);
“Pure”LINQ:
var result = seq.Select((a, i) => seq.Take(i + 1).Sum());
一个“纯”LINQ 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; });
var seq = new List { 1, 3, 12, 19, 33 }; var summed = new List (); seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
要使用Linq,只能在使用自定义聚合器后迭代列表:
class Aggregator { public List List { get; set; } public int Sum { get; set; } }
..
var seq = new List { 1, 3, 12, 19, 33 }; var aggregator = new Aggregator{ List = new List (), Sum = 0 }; var aggregatorResult = seq.Aggregate(aggregator, (a, number) => { a.Sum += number; a.List.Add(a.Sum); return a; }); var result = aggregatorResult.List;
var seq = new List { 1, 3, 12, 19, 33 }; for (int i = 1; i < seq.Count; i++) { seq[i] += seq[i-1]; }
只是为了提供另一种选择,尽管不是LINQ,你可以编写一个基于yield的函数来进行聚合:
public static IEnumerable SumSoFar(this IEnumerable values) { int sumSoFar = 0; foreach (int value in values) { sumSoFar += value; yield return sumSoFar; } }
像BrokenGlass一样,这只会对数据进行一次传递,尽管与他的返回不同,迭代器不是列表。
(令人讨厌的是, 您无法轻松地在列表中的数字类型上进行此通用 。)
Stephen Swensen的答案很棒,扫描正是您所需要的。 虽然不需要种子,但还有另一种扫描版本,这对于您的确切问题更为合适。
此版本要求您的输出元素类型与输入元素类型相同(在您的情况下),并且优点是不需要传入0然后跳过第一个(0)结果。
您可以在C#中实现此版本的扫描,如下所示:
public static IEnumerable Scan (this IEnumerable Input, Func Accumulator) { using (IEnumerator 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 seq = new List { 1, 3, 12, 19, 33 }; IEnumerable transformed = seq.Scan((state, item) => state + item);