Apa cara terbaik untuk melakukan putaran mundur di C / C # / C ++?

101

Saya perlu bergerak mundur melalui array, jadi saya memiliki kode seperti ini:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

Apakah ada cara yang lebih baik untuk melakukan ini?

Pembaruan: Saya berharap mungkin C # memiliki beberapa mekanisme bawaan untuk ini seperti:

foreachbackwards (int i in myArray)
{
    // so easy
}

Update 2: Ada yang cara yang lebih baik. Rune mengambil hadiah dengan:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

yang terlihat lebih baik di C biasa (berkat Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}
MusiGenesis
sumber
Saya tidak yakin saya mengerti mengapa salah satu alternatif lebih baik, jika kebaikan mencakup kejelasan atau pemeliharaan.
dkretz
Benar. Saya berharap menemukan cara yang lebih masuk akal untuk melakukan ini, karena saya harus melakukannya cukup sering.
MusiGenesis
3
Sebagian besar pertanyaan ini adalah semacam review dari jawaban-jawaban di bawah ini. Sarankan itu dipersingkat secara signifikan.
einpoklum
Hapus C dan C ++ dari judul dan hashtag.
jaskmar

Jawaban:

149

Meskipun memang agak tidak jelas, saya akan mengatakan bahwa cara tipografi yang paling menyenangkan untuk melakukan ini adalah

for (int i = myArray.Length; i --> 0; )
{
    //do something
}
MusiGenesis
sumber
32
Ketika saya pertama kali membaca jawaban Anda, sepertinya itu bahkan tidak dapat dikompilasi, jadi saya berasumsi bahwa Anda adalah orang gila. Tapi itulah yang saya cari: cara yang lebih baik untuk menulis backwards for loop.
MusiGenesis
3
Saya pikir i -> 0; sengaja. Itulah yang dia maksud dengan "secara tipografis menyenangkan"
Johannes Schaub - litb
16
Saya sendiri merasa "membingungkan secara tipografis". Bagi saya, "-" tidak terlihat benar kecuali berdekatan dengan variabel yang dipengaruhinya.
MusiGenesis
26
Itu terlalu kabur dan tidak jelas. Saya tidak pernah menulis sesuatu seperti ini dalam kode produksi ...
Mihai Todor
118

Di C ++ pada dasarnya Anda memiliki pilihan antara iterasi menggunakan iterator, atau indeks. Bergantung pada apakah Anda memiliki array biasa, atau std::vector, Anda menggunakan teknik yang berbeda.

Menggunakan std :: vector

Menggunakan iterator

C ++ memungkinkan Anda melakukan ini menggunakan std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Menggunakan indeks

Jenis integral unsigned dikembalikan oleh std::vector<T>::sizeyang tidak selalu std::size_t. Bisa lebih besar atau lebih kecil. Ini penting agar loop berfungsi.

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

Ini berfungsi, karena nilai tipe integral unsigned ditentukan dengan menggunakan modulo jumlah bitnya. Jadi, jika Anda menyetel -N, Anda berakhir di(2 ^ BIT_SIZE) -N

Menggunakan Array

Menggunakan iterator

Kami menggunakan std::reverse_iteratoruntuk melakukan iterasi.

for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
    it != itb; 
    ++it) {
    /* std::cout << *it; .... */
}

Menggunakan indeks

Kami dapat menggunakan dengan aman di std::size_tsini, sebagai lawan di atas, karena sizeofselalu mengembalikan std::size_tmenurut definisi.

for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
   /* std::cout << a[i]; ... */
}

Menghindari jebakan dengan ukuran yang diterapkan pada pointer

Sebenarnya cara di atas untuk menentukan ukuran array menyebalkan. Jika a sebenarnya adalah sebuah pointer dan bukan sebuah array (yang sering terjadi, dan pemula akan bingung), itu akan gagal secara diam-diam. Cara yang lebih baik adalah dengan menggunakan yang berikut ini, yang akan gagal pada waktu kompilasi, jika diberi pointer:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Ia bekerja dengan mendapatkan ukuran dari larik yang diteruskan terlebih dahulu, lalu mendeklarasikan untuk mengembalikan referensi ke larik bertipe char dengan ukuran yang sama. chardidefinisikan memiliki sizeof: 1. Jadi array yang dikembalikan akan memiliki sizeof: N * 1, yang kita cari, dengan hanya evaluasi waktu kompilasi dan overhead runtime nol.

Daripada melakukan

(sizeof a / sizeof *a)

Ubah kode Anda sehingga sekarang bisa

(sizeof array_size(a))
Johannes Schaub - litb
sumber
Selain itu, jika penampung Anda tidak memiliki iterator terbalik, Anda dapat menggunakan implementasi reverse_iterator boost
MP24
array_size Anda tampaknya berfungsi untuk array yang dialokasikan secara statis, tetapi gagal untuk saya ketika a adalah 'new int [7]' misalnya.
Nate Parsons
2
ya itulah maksudnya :) new int [7] mengembalikan sebuah pointer. jadi sizeof (new int [7]) mengembalikan bukan 7 * sizeof (int), tetapi akan mengembalikan sizeof (int *). array_size menyebabkan kesalahan kompilasi untuk kasus itu alih-alih bekerja secara diam-diam.
Johannes Schaub - litb
juga coba array_size (+ statically_allocated_array), yang juga gagal, karena operator + membuat array menjadi pointer ke elemen pertamanya. menggunakan ukuran biasa akan memberi Anda ukuran pointer lagi
Johannes Schaub - litb
Untuk hal pertama - mengapa tidak menggunakan enddan begindalam urutan terbalik?
Tomáš Zato - Kembalikan Monica
54

Di C # , menggunakan Visual Studio 2005 atau yang lebih baru, ketik 'forr' dan tekan [TAB] [TAB] . Ini akan meluas ke forloop yang mundur melalui koleksi.

Sangat mudah untuk salah (setidaknya bagi saya), sehingga saya pikir memasukkan potongan ini adalah ide yang bagus.

Meskipun demikian, saya suka Array.Reverse()/ Enumerable.Reverse()dan kemudian mengulanginya ke depan dengan lebih baik - mereka menyatakan niat dengan lebih jelas.

Jay Bazuzi
sumber
41

Saya selalu lebih suka kode yang jelas daripada kode yang 'secara tipografis menyenangkan '. Jadi, saya akan selalu menggunakan:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Anda dapat menganggapnya sebagai cara standar untuk memutar mundur.
Hanya dua sen saya ...

Jack Griffin
sumber
Bekerja. Belum melakukan ini di C #, tapi terima kasih.
PCPGMR
Jadi bagaimana jika arraynya sangat besar (jadi indeksnya harus bertipe unsigned)? size_t sebenarnya unsigned, bukan?
lalala
Anda dapat menyatakan saya sebagai uint. Lihat postingan Marc Gravell.
Jack Griffin
Tidak akan berfungsi untuk tipe unsigned karena membungkus, tidak seperti tipografi yang menyenangkan. Tidak baik.
Ocelot
17

Di C # menggunakan Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}
Keltex
sumber
Ini pasti yang paling sederhana, tetapi perhatikan bahwa dalam versi CLR saat ini, membalik daftar selalu merupakan operasi O (n) daripada operasi O (1), bisa jadi jika itu adalah IList <T>; lihat connect.microsoft.com/VisualStudio/feedback/…
Greg Beech
1
Sepertinya .Reverse () membuat salinan lokal dari larik dan kemudian melakukan iterasi mundur melaluinya. Tampaknya agak tidak efisien untuk menyalin larik hanya agar Anda dapat mengulanginya. Saya kira setiap posting dengan "Linq" di dalamnya layak untuk dipilih. :)
MusiGenesis
Ada kemungkinan pengorbanan, tetapi kemudian Anda mengalami masalah bahwa "memberikan suara pada jawaban" (i -> 0) mungkin menghasilkan kode yang lebih lambat karena (i) harus diperiksa terhadap ukuran larik setiap kali itu digunakan seperti pada index.
Keltex
1
Meskipun saya setuju bahwa LINQ hebat, masalah dengan pendekatan ini adalah bahwa iterator tidak memungkinkan Anda untuk memilih item larik secara selektif - pertimbangkan situasi di mana Anda ingin menavigasi melalui sebagian Array, tetapi tidak keseluruhan hal ...
jesses.co.tt
11

Itu pasti cara terbaik untuk larik apa pun yang panjangnya merupakan tipe integral bertanda. Untuk array yang panjangnya merupakan tipe integral unsigned (misalnya std::vectordalam C ++), maka Anda perlu sedikit mengubah kondisi akhir:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Jika Anda baru saja mengatakan i >= 0, ini selalu benar untuk integer yang tidak bertanda tangan, jadi loop akan menjadi loop tanpa batas.

Adam Rosenfield
sumber
Dalam C ++ "i--" secara otomatis akan membungkus ke nilai tertinggi jika i adalah 0? Maaf, saya cacat C ++.
MusiGenesis
Luar biasa. Kalian baru saja membantu saya memahami penyebab bug pengisian hard drive pada sesuatu yang saya tulis 12 tahun lalu. Untung saya tidak bekerja di C ++.
MusiGenesis
kondisi terminasi harus: i <myArray.size (). Hanya bergantung pada properti overflow dari unsigned int; bukan detail implementasi integer dan cast. Akibatnya lebih mudah dibaca dan berfungsi dalam bahasa dengan representasi bilangan bulat alternatif.
ejgottl
Saya pikir solusi yang lebih baik bagi saya adalah tetap berada di dunia C # di mana saya tidak dapat menyakiti siapa pun.
MusiGenesis
4

Terlihat bagus untukku. Jika pengindeks tidak ditandatangani (uint dll), Anda mungkin harus mempertimbangkannya. Panggil saya malas, tetapi dalam kasus (unsigned) itu, saya mungkin hanya menggunakan variabel-counter:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(sebenarnya, bahkan di sini Anda harus berhati-hati terhadap kasus seperti arr.Length = uint.MaxValue ... mungkin a! = di suatu tempat ... tentu saja, itu kasus yang sangat tidak mungkin!)

Marc Gravell
sumber
Masalah "--pos" memang rumit. Saya pernah memiliki rekan kerja yang suka "mengoreksi" hal-hal secara acak dalam kode yang tidak terlihat benar bagi mereka.
MusiGenesis
1
Kemudian gunakan .arr.Length-1 dan pos--
Marc Gravell
4

Di CI suka melakukan ini:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

Contoh C # ditambahkan oleh MusiGenesis:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}
Twotymz
sumber
Saya suka ini. Saya butuh satu atau dua detik untuk menyadari bahwa metode Anda berhasil. Harap Anda tidak keberatan dengan versi C # yang ditambahkan (tanda kurung kurawal ekstra sehingga indeks memiliki cakupan yang sama seperti di loop for).
MusiGenesis
Saya tidak yakin saya akan menggunakan ini dalam kode produksi, karena ini akan menghasilkan "WTF apakah ini?" Universal reaksi dari siapa pun yang harus mempertahankannya, tetapi sebenarnya lebih mudah untuk mengetik daripada loop for normal, dan tidak ada tanda minus atau> = simbol.
MusiGenesis
1
Sayang sekali versi C # membutuhkan ekstra "> 0".
MusiGenesis
Saya tidak yakin bahasa apa itu. tetapi potongan kode pertama Anda tentu BUKAN C :) hanya untuk memberi tahu Anda hal yang sudah jelas. mungkin Anda ingin menulis C # dan html gagal untuk menguraikannya atau sesuatu?
Johannes Schaub - litb
@ litb: Saya kira "myArray.Length" tidak valid C (?), tetapi bagian loop sementara berfungsi dan tampak hebat.
MusiGenesis
3

Cara terbaik untuk melakukannya di C ++ mungkin dengan menggunakan adaptor iterator (atau lebih baik, range), yang dengan malas akan mengubah urutan saat sedang dilalui.

Pada dasarnya,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

Menampilkan rentang "rentang" (di sini, kosong, tapi saya cukup yakin Anda dapat menambahkan elemen sendiri) dalam urutan terbalik. Tentu saja mengiterasi rentang tersebut tidak banyak gunanya, tetapi meneruskan rentang baru itu ke algoritme dan hal-hal lainnya cukup keren.

Mekanisme ini juga dapat digunakan untuk penggunaan yang jauh lebih kuat:

range | transformed(f) | filtered(p) | reversed

Akan dengan malas menghitung rentang "rentang", di mana fungsi "f" diterapkan ke semua elemen, elemen yang "p" tidak benar akan dihapus, dan akhirnya rentang yang dihasilkan dibalik.

Sintaks pipa adalah IMO yang paling mudah dibaca, mengingat infiksnya. Pembaruan perpustakaan Boost.Range menunggu tinjauan mengimplementasikan ini, tetapi cukup mudah untuk melakukannya sendiri juga. Bahkan lebih keren dengan lambda DSEL untuk menghasilkan fungsi f dan predikat p in-line.


sumber
dapatkah Anda memberitahu kami dari mana Anda memiliki "foreach"? Apakah ini #tentukan untuk BOOST_FOREACH? Terlihat lucu sepanjang jalan.
Johannes Schaub - litb
1
// this is how I always do it
for (i = n; --i >= 0;){
   ...
}
Mike Dunlavey
sumber
1

Saya lebih suka loop sementara. Ini lebih jelas bagi saya daripada mengurangi idalam kondisi loop for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}
Petko Petkov
sumber
0

Saya akan menggunakan kode di pertanyaan awal, tetapi jika Anda benar-benar ingin menggunakan foreach dan memiliki indeks integer di C #:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}
xyz
sumber
-1

Saya akan mencoba menjawab pertanyaan saya sendiri di sini, tetapi saya juga tidak terlalu suka ini:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}
MusiGenesis
sumber
Daripada melakukan .Length - 1 - i setiap kali, mungkin pertimbangkan variabel kedua? Lihat pos [diperbarui] saya.
Marc Gravell
Tidak setuju dengan pertanyaan saya sendiri. Keras. Ini tidak lebih kikuk dari aslinya.
MusiGenesis
-4

CATATAN: Posting ini akhirnya menjadi jauh lebih rinci dan karena itu keluar dari topik, saya minta maaf.

Itu dikatakan rekan-rekan saya membacanya dan percaya itu berharga 'di suatu tempat'. Utas ini bukan tempatnya. Saya sangat menghargai umpan balik Anda tentang ke mana ini harus pergi (saya baru di situs ini).


Bagaimanapun ini adalah versi C # di .NET 3.5 yang luar biasa karena ia bekerja pada semua jenis koleksi menggunakan semantik yang ditentukan. Ini adalah ukuran default (gunakan kembali!), Bukan kinerja atau minimalisasi siklus CPU dalam skenario dev yang paling umum meskipun tampaknya tidak pernah terjadi di dunia nyata (pengoptimalan prematur).

*** Metode ekstensi bekerja pada semua jenis koleksi dan mengambil delegasi tindakan yang mengharapkan satu nilai dari jenis tersebut, semuanya dijalankan secara terbalik pada setiap item **

Requres 3.5:

public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
      {
          foreach (var contextItem in sequenceToReverse.Reverse())
              doForEachReversed(contextItem);
      }

Versi .NET yang lebih lama atau Anda ingin memahami internal Linq dengan lebih baik? Baca terus .. Atau tidak ..

ASUMSI: Dalam sistem tipe .NET, tipe Array mewarisi dari antarmuka IEnumerable (bukan IEnumerable generik hanya IEnumerable).

Ini semua yang Anda butuhkan untuk mengulang dari awal hingga akhir, namun Anda ingin bergerak ke arah yang berlawanan. Karena IEnumerable bekerja pada Array tipe 'objek', tipe apa pun valid,

TINDAKAN KRITIS: Kami berasumsi jika Anda dapat memproses urutan apa pun dalam urutan terbalik yang 'lebih baik' maka hanya dapat melakukannya pada bilangan bulat.

Solusi a untuk .NET CLR 2.0-3.0:

Deskripsi: Kami akan menerima semua contoh implementasi IEnumerable dengan mandat bahwa setiap contoh yang dikandungnya adalah dari jenis yang sama. Jadi jika kita menerima sebuah array, seluruh array berisi instance tipe X. Jika ada instance lain yang bertipe! = X pengecualian dilemparkan:

Layanan tunggal:

public class ReverserService {private ReverserService () {}

    /// <summary>
    /// Most importantly uses yield command for efficiency
    /// </summary>
    /// <param name="enumerableInstance"></param>
    /// <returns></returns>
    public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
    {
        if (enumerableInstance == null)
        {
            throw new ArgumentNullException("enumerableInstance");
        }

        // First we need to move forwarad and create a temp
        // copy of a type that allows us to move backwards
        // We can use ArrayList for this as the concrete
        // type

        IList reversedEnumerable = new ArrayList();
        IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();

        while (tempEnumerator.MoveNext())
        {
            reversedEnumerable.Add(tempEnumerator.Current);
        }

        // Now we do the standard reverse over this using yield to return
        // the result
        // NOTE: This is an immutable result by design. That is 
        // a design goal for this simple question as well as most other set related 
        // requirements, which is why Linq results are immutable for example
        // In fact this is foundational code to understand Linq

        for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
        {
            yield return reversedEnumerable[i];
        }
    }
}



public static class ExtensionMethods
{

      public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
      {
          return ReverserService.ToReveresed(enumerableInstance);
      }
 }

[TestFixture] kelas publik Testing123 {

    /// <summary>
    /// .NET 1.1 CLR
    /// </summary>
    [Test]
    public void Tester_fornet_1_dot_1()
    {
        const int initialSize = 1000;

        // Create the baseline data
        int[] myArray = new int[initialSize];

        for (var i = 0; i < initialSize; i++)
        {
            myArray[i] = i + 1;
        }

        IEnumerable _revered = ReverserService.ToReveresed(myArray);

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
    }

    [Test]
    public void tester_why_this_is_good()
    {

        ArrayList names = new ArrayList();
        names.Add("Jim");
        names.Add("Bob");
        names.Add("Eric");
        names.Add("Sam");

        IEnumerable _revered = ReverserService.ToReveresed(names);

        Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));


    }

    [Test]
    public void tester_extension_method()
  {

        // Extension Methods No Linq (Linq does this for you as I will show)
        var enumerableOfInt = Enumerable.Range(1, 1000);

        // Use Extension Method - which simply wraps older clr code
        IEnumerable _revered = enumerableOfInt.ToReveresed();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }


    [Test]
    public void tester_linq_3_dot_5_clr()
    {

        // Extension Methods No Linq (Linq does this for you as I will show)
        IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);

        // Reverse is Linq (which is are extension methods off IEnumerable<T>
        // Note you must case IEnumerable (non generic) using OfType or Cast
        IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }



    [Test]
    public void tester_final_and_recommended_colution()
    {

        var enumerableOfInt = Enumerable.Range(1, 1000);
        enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));

    }



    private static object TestAndGetResult(IEnumerable enumerableIn)
    {
      //  IEnumerable x = ReverserService.ToReveresed(names);

        Assert.IsTrue(enumerableIn != null);
        IEnumerator _test = enumerableIn.GetEnumerator();

        // Move to first
        Assert.IsTrue(_test.MoveNext());
        return _test.Current;
    }
}
tim domain.dot.net
sumber
2
Dude or dudette: Saya hanya mencari cara penulisan yang tidak terlalu kikuk "for (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis
1
tidak ada nilai dalam balasan ini sama sekali
Matt Melton