17

我有一个数字序列:

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());

还能怎么做?

4

7 回答 7

22

这是函数式编程中的一种常见模式,在 F# 中称为scan。这就像 C# 的Enumerable.Aggregate和 F# 的折叠,只是它产生累加器的中间结果以及最终结果。我们可以使用扩展方法在 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);
于 2011-07-01T18:01:24.833 回答
8

“纯”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; });
于 2011-07-01T17:36:55.477 回答
3
var seq = new List<int> { 1, 3, 12, 19, 33 };

var summed = new List<int>();

seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
于 2011-07-01T17:35:55.850 回答
2

只是为了提供另一种选择,虽然不是真正的 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 一样,这仅对数据进行一次传递,尽管与他的返回不同的是迭代器而不是列表。

(令人讨厌的是,您不能轻易地在列表中的数字类型上使其通用。)

于 2011-07-01T17:59:12.680 回答
1
var seq = new List<int> { 1, 3, 12, 19, 33 }; 

for (int i = 1; i < seq.Count; i++)
{
   seq[i] += seq[i-1];
}
于 2011-07-01T17:34:57.937 回答
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;
于 2011-07-01T17:38:59.930 回答
1

Stephen Swensen 的回答很棒,扫描正是您所需要的。还有另一种版本的扫描虽然不需要种子,但它更适合您的确切问题。

此版本要求您的输出元素类型与您的输入元素类型相同,在您的情况下,它的优点是不需要您传入 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);
于 2014-01-28T00:35:23.977 回答