我有一系列数字:
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());
怎么办呢?
答案 0 :(得分:19)
这是函数式编程中的常见模式,在F#中称为scan。它就像C#的Enumerable.Aggregate和F#的fold,除了它产生累加器的中间结果以及最终结果。我们可以使用扩展方法很好地在C#中实现扫描:
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);
答案 1 :(得分:7)
“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; });
答案 2 :(得分:3)
var seq = new List<int> { 1, 3, 12, 19, 33 };
var summed = new List<int>();
seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
答案 3 :(得分:1)
var seq = new List<int> { 1, 3, 12, 19, 33 };
for (int i = 1; i < seq.Count; i++)
{
seq[i] += seq[i-1];
}
答案 4 :(得分:1)
要使用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;
答案 5 :(得分:1)
只是为了提供另一种选择,尽管不是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一样,这只会对数据进行一次传递,尽管与他的返回不同,迭代器不是列表。
(恼人的you can't easily make this generic on the numeric type in the list。)
答案 6 :(得分:1)
此版本要求您的输出元素类型与您的输入元素类型相同,并且它的优点是不需要传递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);