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

もう 1 つの「純粋な」LINQ O(n):

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

状態を維持するもう 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 ではありませんが、集計を行うために利回りベースの関数を作成できます。

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

BrokenGlass の場合と同様に、これはデータに対して 1 回のパスのみを行いますが、リストではなく反復子を返すのとは異なります。

(厄介なことに、リスト内の数値型でこれを簡単にジェネリックにすることはできません。)

于 2011-07-01T17:59:12.680 に答える
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
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

スティーブン・スウェンセンの答えは素晴らしいです.スキャンはまさにあなたが必要としているものです. シードを必要としない別のバージョンのスキャンがありますが、これは正確な問題により少し適しています。

このバージョンでは、出力要素のタイプが入力要素のタイプと同じである必要があり、これはあなたの場合であり、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 に答える