7

私は、IEnumerable オブジェクトを頻繁に処理し、そのオブジェクトをループして、直前および直後の n 個のオブジェクトに依存する各要素の計算を実行する必要があります。

一般的な例の 1 つはローリング平均の計算ですが、計算がそれよりも複雑で、リストの各要素の複数のフィールドに依存する場合もあります。

ループを構成する最良の方法については、私にはよくわかりません。効率は重要ですが、保守性と可読性はより重要です。

  • 時々、List に変換してから for ループを使用して要素 [i-1]、[i]、[i+1] を取得し、計算を実行します。

  • それ以外の場合は、IEnumerable として保持しますが、前のいくつかの要素を「キャッシュ」して、foreach ループで [i+1] に到達するまで i の計算を行わないようにします。

  • .Previous メソッドと .Next メソッドを使用できるように、リンク リストの使用も検討しました。

どのテクニックを使用するのが最適かについての提案はありますか?

4

2 に答える 2

6

1 つのオプションは、使用できるローリング「ウィンドウ」を提供する拡張メソッドを作成することです。これにより、ループを簡単な方法で記述できます。

IEnumerable<IList<T>> CreateRollingWindow(IEnumerable<T> items, int size)
{
    LinkedList<T> list = new LinkedList<T>();

    foreach(var item in items)
    {
        list.AddLast(item);
        if (list.Count == size)
        {
            yield return list.ToList();
            list.RemoveFirst();
        }
    }
}

これにより、アルゴリズムを次のように簡単に記述できます。

foreach(var window as collection.CreateRollingWindow(5))
{
    double rollingAverage = window.Average(); // window is IList<T> here
}
于 2012-08-27T23:29:03.153 に答える
2

簡単な実装を次に示します。

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count)
{
    var queue = new Queue<double>();
    foreach (var v in values)
    {
        queue.Enqueue(v);
        if (queue.Count == count)
        {
            yield return queue.Average();
            queue.Dequeue();
        }
    }
}

おそらく改善される可能性がありますが、うまくいくようです...

編集: これはわずかに優れたバージョンです (平均を計算するためにキューを列挙する必要はありません):

public static IEnumerable<double> RollingAverage(this IEnumerable<double> values, int count)
{
    var queue = new Queue<double>();
    double sum = 0.0;
    foreach (var v in values)
    {
        sum += v;
        queue.Enqueue(v);
        if (queue.Count == count)
        {
            yield return sum / count;
            sum -= queue.Dequeue();
        }
    }
}
于 2012-08-27T23:28:25.483 に答える