web-dev-qa-db-ja.com

foreachを逆方向に反復することは可能ですか?

forステートメントを使用して同じ効果を達成できることは知っていますが、C#でforeachループを逆方向にループできますか?

112
JL.

リストを使用して作業する場合(直接インデックス付け)、forループを使用するほど効率的に行うことはできません。

編集:一般的に、あなたがableforループを使用する場合、このタスクの正しい方法である可能性が高いことを意味します。さらに、foreachが順序どおりに実装される限り、構成要素自体は、要素のインデックスと反復順序に依存しないループを表現するために構築されます。これは、 並列プログラミング で特に重要です。 私の意見は、順序に依存する反復はループにforeachを使用すべきではないということです。

74
Sam Harwell

.NET 3.5を使用している場合、これを行うことができます。

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

基本的に列挙子をすべて処理してスタックに配置し、すべてを逆順にポップアウトする必要があるため、あまり効率的ではありません。

直接インデックス可能なコレクション(IListなど)がある場合は、代わりにforループを必ず使用する必要があります。

.NET 2.0を使用しており、forループを使用できない場合(つまり、IEnumerableがある場合)、独自のReverse関数を記述する必要があります。これは動作するはずです:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

これは、おそらくそれほど明白ではないいくつかの動作に依存しています。 IEnumerableをスタックコンストラクターに渡すと、IEnumerableを繰り返し処理し、アイテムをスタックにプッシュします。その後、スタックを反復処理すると、逆順にポップアウトします。

これと.NET 3.5 Reverse()拡張メソッドは、アイテムを返すことを決して止めないIEnumerableをフィードすると明らかに爆発します。

134
Matt Howells

280Z28にあるように、IList<T>にはインデックスを使用できます。拡張メソッドでこれを隠すことができます:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

これは、最初にすべてのデータをバッファリングするEnumerable.Reverse()よりも高速です。 (ReverseにはCount()のように最適化が適用されるとは思わない。)このバッファリングは、最初に反復を開始したときにデータが完全に読み取られるのに対して、FastReverseは繰り返しながらリスト。 (繰り返しの間に複数のアイテムを削除した場合にも破損します。)

一般的なシーケンスの場合、逆に反復する方法はありません。たとえば、シーケンスは無限になります。

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

それを逆に反復しようとした場合、何が起こると思いますか?

50
Jon Skeet

反復に「foreach」を使用する前に、「reverse」メソッドでリストを逆にします。

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
8
Prem

List <T>を使用する場合、次のコードも使用できます。

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

これは、リスト自体を逆に書き込むメソッドです。

今foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

出力は次のとおりです。

3
2
1
4
th3s0urc3

インデックスを作成する余裕がない場合や、Linqクエリの結果を元に戻したい場合や、ソースコレクションを変更したくない場合があります。これらのいずれかが当てはまる場合、Linqが役立ちます。

Linq Selectで匿名型を使用してLinq OrderByDescendingの並べ替えキーを提供するLinq拡張メソッド。

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

使用法:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

「反転」と同義であり、リスト反転の実装とのあいまいさを解消できるため、「反転」という名前が付けられています。

Int32.MinValueとInt32.MaxValueはあらゆる種類のコレクションインデックスの範囲外であるため、コレクションの特定の範囲を逆にすることも可能です。それらを順序付けプロセスに活用できます。要素のインデックスが指定された範囲を下回っている場合、Int32.MaxValueが割り当てられ、OrderByDescendingを使用するときに順序が変わらないようにします。同様に、指定された範囲より大きいインデックスの要素にはInt32.MinValueが割り当てられ、注文プロセスの最後に表示されます。指定された範囲内のすべての要素には通常のインデックスが割り当てられ、それに応じて反転されます。

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

使用法:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

これらのLinq実装のパフォーマンスヒットが、一時的なリストを使用してコレクションを反転用にラップすることとは対照的です。


この記事を書いている時点では、Linqの独自のReverse実装を認識していませんでしたが、それでもうまくいきました。 https://msdn.Microsoft.com/en-us/library/vstudio/bb358497(v = vs.100).aspx

4
Vorspire

コレクションコードを変更できる場合は、IEnumerableまたはIEnumerableを実装します(たとえば、IListの独自の実装)。

イテレータ を作成します。たとえば、 IEnumerable インターフェイスを介した次の実装のように、このジョブを実行します(「items」がこのサンプルのListフィールドであると仮定)。

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

このため、リストはリストを逆順に繰り返します。

ヒント:ドキュメント内でリストのこの特別な動作を明確に述べる必要があります(StackやQueueのような自己説明的なクラス名を選択することでさらに良くなります)。

2
Beachwalker

いいえ。ForEachは各アイテムのコレクションを反復処理するだけで、IEnumerableまたはGetEnumerator()を使用するかどうかによって順序が異なります。

2
Josip Medved

Jon Skeet によるNiceの回答について少し詳しく説明しますが、これは多用途です:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

そして、として使用

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}
0
Eske Rahn