Mungkinkah untuk beralih mundur melalui foreach?


129

Saya tahu saya bisa menggunakan forpernyataan dan mencapai efek yang sama, tetapi bisakah saya memutar mundur melalui foreachloop di C #?


6
Anda dapat membalikkan elemen (daftar. Balikkan ()) dalam daftar sebelum melakukannya untuk masing-masing.
Akanthan


Anda harus instantiate semua elemen dalam enumerasi sehingga Anda dapat membalik urutannya. Itu mungkin tidak mungkin. Pertimbangkan urutannya: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Bagaimana Anda membalikkan itu?
Suncat2000

Jawaban:


84

Ketika bekerja dengan daftar (pengindeksan langsung), Anda tidak dapat melakukannya seefisien menggunakan forloop.

Sunting: Yang secara umum berarti, ketika Anda dapat menggunakan forperulangan, kemungkinan metode yang tepat untuk tugas ini. Plus, sebanyak foreachditerapkan dalam urutan, konstruksi itu sendiri dibangun untuk mengekspresikan loop yang independen dari indeks elemen dan urutan iterasi, yang sangat penting dalam pemrograman paralel . Ini adalah pendapat saya bahwa iterasi mengandalkan agar tidak harus menggunakan foreachuntuk perulangan.


3
Saya menemukan pernyataan terakhir Anda agak terlalu umum. Tentunya ada kasus di mana, misalnya, suatu IEnumerable dari beberapa jenis perlu diulang melalui agar? Apakah Anda tidak menggunakan foreach dalam kasus itu? Apa yang akan Anda gunakan
avl_sweden

1
UPDATE: Lihat [Jawaban Bryan untuk pertanyaan serupa [( stackoverflow.com/a/3320924/199364 ), untuk jawaban yang lebih modern, menggunakan Linq, dan mendiskusikan kedua daftar, dan enumerable lainnya.
ToolmakerSteve

UPDATE: Jon Skeet memiliki jawaban yang bahkan lebih elegan, yang sekarang mungkin, menggunakan " yield return".
ToolmakerSteve

2
Saya memiliki reaksi awal yang sama dengan @avl_sweden - "iterasi yang mengandalkan pesanan tidak boleh menggunakan foreach" terdengar terlalu luas. Ya, foreach baik untuk mengekspresikan tugas paralel independen-pesanan. TETAPI juga merupakan bagian penting dari pemrograman berbasis iterator modern . Mungkin intinya adalah bahwa akan lebih jelas / lebih aman jika ada dua kata kunci yang berbeda , untuk memperjelas apakah seseorang menegaskan bahwa iterasi itu tidak tergantung pesanan? [Diberi bahasa seperti Eiffel yang dapat menyebarkan kontrak, pernyataan seperti itu bisa dibuktikan benar atau salah, untuk kode yang diberikan.]
ToolmakerSteve

2
Gagasan bahwa foreach "adalah membangun untuk mengekspresikan loop yang independen dari indeks elemen dan urutan iterasi" tidak benar. Spesifikasi bahasa c # mensyaratkan agar elemen proses memeriksa secara berurutan. Baik dengan menggunakan MoveNext di iterator atau dengan memproses indeks mulai dari nol dan bertambah satu pada setiap iterasi array.
Donald Rich

145

Jika Anda menggunakan .NET 3.5, Anda dapat melakukan ini:

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

Ini tidak terlalu efisien karena pada dasarnya harus melalui pencacah ke depan meletakkan segala sesuatu di atas tumpukan kemudian muncul semuanya kembali dalam urutan terbalik.

Jika Anda memiliki koleksi yang dapat diindeks secara langsung (mis. IList), Anda harus menggunakan forloop.

Jika Anda menggunakan .NET 2.0 dan tidak dapat menggunakan for loop (yaitu Anda hanya memiliki IEnumerable) maka Anda hanya perlu menulis fungsi Reverse Anda sendiri. Ini seharusnya bekerja:

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

Ini bergantung pada beberapa perilaku yang mungkin tidak terlalu jelas. Ketika Anda melewati sebuah IEnumerable ke konstruktor stack itu akan beralih melalui itu dan mendorong item ke tumpukan. Ketika Anda kemudian beralih melalui tumpukan itu muncul hal-hal kembali dalam urutan terbalik.

Ini dan Reverse()metode ekstensi .NET 3.5 jelas akan meledak jika Anda memberinya IEnumerable yang tidak pernah berhenti mengembalikan item.


Saya juga lupa menyebutkan .net v2 saja
JL.

4
Solusi .NET 2.0 yang menarik.
RichardOD

11
Apakah saya melewatkan sesuatu atau apakah solusi .Net 3.5 Anda tidak benar-benar berfungsi? Membalikkan () membalikkan daftar di tempat dan tidak mengembalikannya. Sayang sekali, karena saya berharap untuk solusi seperti itu.
user12861

2
Beberapa admin menandai jawaban ini sebagai SALAH SALAH. Reverse () adalah void [1] dan contoh di atas mengarah ke kesalahan kompilasi. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD

6
@ user12861, Micky Duncan: jawabannya tidak salah, Anda kehilangan sesuatu. Ada metode Reverse pada System.Collections.Generic.List <T> yang melakukan reverse di tempat. Di. Net 3.5 ada metode ekstensi pada IEnumerable <T> bernama Reverse. Saya telah mengubah contoh dari var ke IEnumerable <int> untuk menjadikan ini lebih eksplisit.
Matt Howells

55

Seperti yang dikatakan 280Z28, IList<T>Anda hanya bisa menggunakan indeks. Anda dapat menyembunyikan ini dalam metode ekstensi:

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

Ini akan lebih cepat daripada Enumerable.Reverse()yang buffer semua data terlebih dahulu. (Saya tidak yakin Reverseada optimasi yang diterapkan seperti itu Count().) Perhatikan bahwa buffering ini berarti bahwa data dibaca sepenuhnya ketika Anda pertama kali memulai iterasi, sedangkan FastReverseakan "melihat" setiap perubahan yang dibuat ke daftar saat Anda iterate. (Ini juga akan pecah jika Anda menghapus beberapa item di antara iterasi.)

Untuk urutan umum, tidak ada cara untuk mengulang secara terbalik - urutannya bisa tak terbatas, misalnya:

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

Apa yang Anda harapkan terjadi jika Anda mencoba mengulanginya secara terbalik?


Hanya rasa ingin tahu, mengapa menggunakan "> = 0" bukannya "> -1"?
Chris S

15
> mengapa menggunakan "> = 0" bukannya "> -1"? Karena> = 0 lebih baik mengkomunikasikan maksud kepada manusia membaca kode. Kompiler harus dapat mengoptimalkannya ke yang setara> -1 jika hal itu akan meningkatkan kinerja.
Mark Maslar

1
FastReverse (item IList ini <T>) harus FastReverse <T> (item IList ini <T>. :)
Rob

Tongkat pemisah 0 <= i bahkan lebih baik. Setelah mengajar cukup banyak matematika anak-anak selama bertahun-tahun, dan membantu orang dengan pengkodean, saya menemukan bahwa itu jauh mengurangi kesalahan yang dibuat orang jika mereka SELALU bertukar a> b ke b <a ketika segala sesuatunya muncul dalam urutan alami sebuah garis angka (Atau sumbu X dari sistem koordinat) - satu-satunya kelemahan adalah jika Anda perlu menempelkan persamaan ke dalam XML, ucapkan .config
Eske Rahn

13

Sebelum menggunakan foreachuntuk iterasi, balikkan daftar dengan reversemetode:

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


Sepatah kata hati tentang apa myListyang akan membantu. IEnumerable.Reverse tidak bekerja di sini.
nawfal

2
@ MA-Maddin tidak keduanya berbeda. Saya kira penjawabnya mengandalkan Daftar <T>. Balikkan yang sudah ada.
nawfal

Sebenarnya, saya tidak tahu mengapa saya mengatakan itu. Saya seharusnya menambahkan rincian lebih lanjut ... Pokoknya ini adalah kode yang membingungkan karena myListtampaknya bertipe System.Collections.Generic.List<System.Windows.Documents.List>(atau Listjenis kustom lainnya ) kalau tidak, kode ini tidak berfungsi: P
Martin Schneider

5

Kadang-kadang Anda tidak memiliki kemewahan pengindeksan, atau mungkin Anda ingin membalikkan hasil kueri Linq, atau mungkin Anda tidak ingin mengubah kumpulan sumber, jika ada yang benar, Linq dapat membantu Anda.

Metode ekstensi Linq menggunakan tipe anonim dengan Linq Select untuk memberikan kunci penyortiran untuk Linq OrderByDescending;

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

Pemakaian:

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

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

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

Itu bernama "Balikkan" karena ini identik dengan "Balikkan" dan memungkinkan disambiguasi dengan implementasi Daftar Balikkan.

Dimungkinkan untuk membalikkan rentang koleksi tertentu juga, karena Int32.MinValue dan Int32.MaxValue berada di luar kisaran jenis indeks pengumpulan apa pun, kami dapat memanfaatkannya untuk proses pemesanan; jika indeks elemen di bawah rentang yang diberikan, itu diberikan Int32.MaxValue sehingga pesanannya tidak berubah saat menggunakan OrderByDescending, sama halnya, elemen pada indeks yang lebih besar dari rentang yang diberikan, akan diberikan Int32.MinValue, sehingga mereka muncul di akhir proses pemesanan. Semua elemen dalam rentang yang diberikan diberi indeks normal dan dibalik sesuai.

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

Pemakaian:

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

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

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

Saya tidak yakin dengan kinerja hit implementasi Linq ini versus menggunakan Daftar sementara untuk membungkus koleksi untuk dibalik.


Pada saat penulisan, saya tidak mengetahui implementasi Reverse Linq sendiri, tetap saja, ini sangat menyenangkan. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

Jika Anda menggunakan Daftar <T>, Anda juga dapat menggunakan kode ini:

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

Ini adalah metode yang menulis daftar terbalik dengan sendirinya.

Sekarang kedepan:

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

Outputnya adalah:

3
2
1

3

Hal ini mungkin jika Anda dapat mengubah kode koleksi yang menerapkan IEnumerable atau IEnumerable (misalnya implementasi sendiri dari IList).

Buat Iterator melakukan pekerjaan ini untuk Anda, misalnya seperti implementasi berikut melalui antarmuka IEnumerable (dengan asumsi 'item' adalah bidang Daftar dalam sampel ini):

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

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

Karena ini Daftar Anda akan diulang dalam urutan terbalik melalui daftar Anda.

Sekedar petunjuk: Anda harus dengan jelas menyatakan perilaku khusus dari daftar ini di dalam dokumentasi (bahkan lebih baik dengan memilih nama kelas yang menjelaskan sendiri seperti Stack atau Queue, juga).


2

Tidak. ForEach hanya mengulang melalui pengumpulan untuk setiap item dan pesanan tergantung apakah ia menggunakan IEnumerable atau GetEnumerator ().


1
Yah ada yang jaminan ketertiban, tergantung pada jenis koleksi.
Jon Skeet

1

Menguraikan sedikit tentang jawaban yang bagus dari Jon Skeet , ini bisa serbaguna:

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];
}

Dan kemudian gunakan sebagai

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

-1

Saya telah menggunakan kode ini yang berfungsi

                if (element.HasAttributes) {

                    foreach(var attr in element.Attributes().Reverse())
                    {

                        if (depth > 1)
                        {
                            elements_upper_hierarchy_text = "";
                            foreach (var ancest  in element.Ancestors().Reverse())
                            {
                                elements_upper_hierarchy_text += ancest.Name + "_";
                            }// foreach(var ancest  in element.Ancestors())

                        }//if (depth > 1)
                        xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                    }// foreach(var attr in element.Attributes().Reverse())

                }// if (element.HasAttributes) {

2
Ini buruk karena .Reverse()sebenarnya memodifikasi daftar.
Colin Basnett

-8

Ini bekerja dengan cukup baik

List<string> list = new List<string>();

list.Add("Hello");
list.Add("Who");
list.Add("Are");
list.Add("You");

foreach (String s in list)
{
    Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
}

9
Ini terdengar sangat tidak efisien bagi saya.
Jean Azzopardi
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.