Untuk array dengan banyak dimensi, biasanya kita perlu menulis for
loop untuk setiap dimensinya. Sebagai contoh:
vector< vector< vector<int> > > A;
for (int k=0; k<A.size(); k++)
{
for (int i=0; i<A[k].size(); i++)
{
for (int j=0; j<A[k][i].size(); j++)
{
do_something_on_A(A[k][i][j]);
}
}
}
double B[10][8][5];
for (int k=0; k<10; k++)
{
for (int i=0; i<8; i++)
{
for (int j=0; j<5; j++)
{
do_something_on_B(B[k][i][j]);
}
}
}
Anda sering melihat for-for-for
loop semacam ini di kode kami. Bagaimana cara menggunakan makro untuk menentukan for-for-for
loop sehingga saya tidak perlu menulis ulang kode semacam ini setiap saat? Apakah ada cara yang lebih baik untuk melakukan ini?
O(n) = n^3
kode potensial ...Jawaban:
Hal pertama adalah Anda tidak menggunakan struktur data seperti itu. Jika Anda membutuhkan matriks tiga dimensi, tentukan salah satunya:
Atau jika Anda ingin mengindeks menggunakan
[][][]
, Anda memerlukanoperator[]
yang mengembalikan proxy.Setelah Anda melakukan ini, jika Anda menemukan bahwa Anda terus-menerus harus mengulang seperti yang Anda berikan, Anda mengekspos sebuah iterator yang akan mendukungnya:
Kemudian Anda tinggal menulis:
(atau hanya:
jika Anda memiliki C ++ 11.)
Dan jika Anda memerlukan tiga indeks selama iterasi seperti itu, Anda dapat membuat iterator yang memaparkannya:
sumber
vector<vector<vector<double> > >
untuk mewakili bidang 3 dimensi. Menulis ulang kode yang setara dengan solusi di atas menghasilkan percepatan 10.Matrix3D
mungkin harus berupa template, tetapi ini adalah template yang sangat mudah.) Dan Anda hanya perlu melakukan debugMatrix3D
, tidak setiap kali Anda membutuhkan matriks 3D, sehingga Anda menghemat banyak waktu dalam debugging. Adapun kejelasan: bagaimanastd::vector<std::vector<std::vector<int>>>
lebih jelas dariMatrix3D
? Belum lagi yangMatrix3D
memberlakukan fakta bahwa Anda memiliki matriks, sedangkan vektor bersarang bisa compang-camping, dan bahwa di atas mungkin jauh lebih cepat.Menggunakan makro untuk menyembunyikan
for
loop bisa sangat membingungkan, hanya untuk menyimpan beberapa karakter. Saya akan menggunakan loop range-for sebagai gantinya:Tentu saja Anda dapat mengganti
auto&
denganconst auto&
jika Anda sebenarnya tidak memodifikasi data.sumber
int
variabel.do_something_on_A(*j)
?auto
untukk
dani
dapat dibenarkan. Kecuali bahwa itu masih memecahkan masalah pada tingkat yang salah; masalah sebenarnya adalah dia menggunakan vektor bersarang.)k
adalah seluruh vektor vektor (baik referensi untuk itu), bukan indeks.Sesuatu seperti ini dapat membantu:
Untuk membuatnya N-ary kita membutuhkan beberapa template magic. Pertama-tama kita harus membuat struktur SFINAE untuk membedakan apakah ini nilai atau containernya. Implementasi default untuk nilai, dan spesialisasi untuk array dan masing-masing tipe kontainer. Bagaimana catatan @Zeta, kita bisa menentukan container standar dengan
iterator
tipe nested (idealnya kita harus mengecek apakah type bisa digunakan dengan range-basefor
atau tidak).Penerapannya
for_each
sangat mudah. Fungsi default akan memanggilfunction
:Dan spesialisasi akan memanggil dirinya sendiri secara rekursif:
Dan voila:
Ini juga tidak akan berfungsi untuk pointer (array yang dialokasikan di heap).
sumber
Container
dan untuk orang lain.is_container : has_iterator<T>::value
dari jawaban saya dan Anda tidak perlu menulis spesialisasi untuk setiap jenis, karena setiap wadah harus memilikiiterator
typedef. Jangan ragu untuk sepenuhnya menggunakan apa pun dari jawaban saya, jawaban Anda sudah lebih baik.Container
konsepnya akan membantu.::iterator
tidak membuat rentang iterable.int x[2][3][4]
iterable sempurna, karenastruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
saya tidak yakinT[]
spesialisasi apa yang seharusnya dilakukan?Sebagian besar jawaban hanya menunjukkan bagaimana C ++ dapat dipelintir menjadi ekstensi sintaksis yang tidak dapat dipahami, IMHO.
Dengan menentukan templat atau makro apa pun, Anda cukup memaksa pemrogram lain untuk memahami bit kode yang dikaburkan yang dirancang untuk menyembunyikan bit lain dari kode yang dikaburkan.
Anda akan memaksa setiap orang yang membaca kode Anda untuk memiliki keahlian template, hanya untuk menghindari tugas Anda dalam mendefinisikan objek dengan semantik yang jelas.
Jika Anda memutuskan untuk menggunakan data mentah seperti array 3 dimensi, gunakan saja itu, atau tentukan kelas yang memberikan beberapa arti yang dapat dimengerti pada data Anda.
hanya konsisten dengan definisi samar dari vektor vektor int tanpa semantik eksplisit.
sumber
UPDATE: Saya tahu, bahwa Anda memintanya, tetapi sebaiknya Anda tidak menggunakannya :)
sumber
TRIPLE_FOR
didefinisikan di beberapa header, apa yang saya pikirkan ketika saya melihat `TRIPLE_FOR di sini.Salah satu idenya adalah menulis kelas pseudo-container iterable yang "berisi" himpunan semua tupel multi-indeks yang akan Anda indeks. Tidak ada implementasi di sini karena akan memakan waktu terlalu lama tetapi idenya adalah Anda harus bisa menulis ...
sumber
Saya melihat banyak jawaban di sini yang bekerja secara rekursif, mendeteksi apakah masukan adalah wadah atau tidak. Sebaliknya, mengapa tidak mendeteksi jika lapisan saat ini adalah jenis yang sama dengan fungsi yang digunakan? Ini jauh lebih sederhana, dan memungkinkan fungsi yang lebih kuat:
Namun, ini (jelas) memberi kita kesalahan ambiguitas. Jadi kami menggunakan SFINAE untuk mendeteksi apakah input saat ini sesuai dengan fungsinya atau tidak
Ini sekarang menangani container dengan benar, tetapi compiler masih menganggap ini ambigu untuk input_types yang bisa diteruskan ke fungsi. Jadi kami menggunakan trik C ++ 03 standar untuk membuatnya lebih menyukai fungsi pertama daripada yang kedua, juga meneruskan nol, dan membuat yang kami lebih suka menerima dan int, dan yang lainnya mengambil ...
Itu dia. Enam, baris kode yang relatif sederhana, dan Anda dapat mengulang nilai, baris, atau sub-unit lainnya, tidak seperti semua jawaban lainnya.
Bukti kompilasi dan eksekusi di sini dan di sini
Jika Anda menginginkan sintaks yang lebih nyaman di C ++ 11, Anda dapat menambahkan makro. (Mengikuti belum teruji)
sumber
Saya mempermasalahkan jawaban ini dengan pernyataan berikut: ini hanya akan berfungsi jika Anda beroperasi pada array yang sebenarnya - ini tidak akan berfungsi untuk contoh yang Anda gunakan
std::vector
.Jika Anda melakukan operasi yang sama pada setiap elemen array multi-dimensi, tanpa mempedulikan posisi setiap item, Anda dapat memanfaatkan fakta bahwa array ditempatkan di lokasi memori yang berdekatan, dan memperlakukan semuanya sebagai satu kesatuan. array satu dimensi yang besar. Misalnya, jika kami ingin mengalikan setiap elemen dengan 2,0 di contoh kedua Anda:
Perhatikan bahwa menggunakan pendekatan di atas juga memungkinkan penggunaan beberapa teknik C ++ yang "tepat":
Saya biasanya tidak menyarankan pendekatan ini (lebih memilih sesuatu seperti jawaban Jefffrey), karena bergantung pada ukuran yang ditentukan untuk array Anda, tetapi dalam beberapa kasus ini dapat berguna.
sumber
B[0][0][i]
untuki >= 3
; ini tidak diperbolehkan karena mengakses di luar larik (dalam).Saya agak terkejut bahwa tidak ada yang mengusulkan beberapa putaran berbasis sihir aritmatika untuk melakukan pekerjaan itu.
Karena C. Wang sedang mencari solusi tanpa loop bersarang, saya akan mengusulkan satu:Nah, pendekatan ini tidak elegan dan fleksibel, jadi kita bisa mengemas semua proses ke dalam fungsi template:
Fungsi template ini juga dapat diekspresikan dalam bentuk loop bersarang:
Dan dapat digunakan untuk menyediakan larik 3D dengan ukuran sembarang ditambah nama fungsi, membiarkan deduksi parameter melakukan kerja keras untuk menghitung ukuran setiap dimensi:
Ke arah yang lebih generik
Tetapi sekali lagi, ini kurang fleksibel karena hanya berfungsi untuk array 3D, tetapi dengan menggunakan SFINAE kita dapat melakukan pekerjaan untuk array dari dimensi arbitrer, pertama-tama kita memerlukan fungsi template yang mengulang array dengan peringkat 1:
Dan satu lagi yang mengulang array dari peringkat apa pun, melakukan rekursi:
Hal ini memungkinkan kita untuk mengulang semua elemen di semua dimensi array berukuran arbitrer dimensi.
Bekerja dengan
std::vector
Untuk beberapa vektor bersarang, solusinya merangkai salah satu dari larik berukuran arbitrer dimensi arbitrer, tetapi tanpa SFINAE: Pertama, kita memerlukan fungsi templat yang mengulangi
std::vector
s dan memanggil fungsi yang diinginkan:Dan fungsi template lain yang mengiterasi segala jenis vektor dan menyebut dirinya sendiri:
Terlepas dari level bersarang,
iterate_all
akan memanggil versi vektor-vektor kecuali versi vektor-nilai-nilai lebih cocok sehingga mengakhiri rekursivitas.Menurut saya fungsi body cukup sederhana dan lurus ke depan ... Saya ingin tahu apakah kompilator dapat membuka gulungan ini (saya hampir yakin bahwa sebagian besar kompiler dapat membuka gulungan contoh pertama).
Lihat demo langsung di sini .
Semoga membantu.
sumber
Gunakan sesuatu di sepanjang baris ini (pseudo-code-nya, tetapi idenya tetap sama). Anda mengekstrak pola untuk mengulang satu kali, dan menerapkan fungsi yang berbeda setiap kali.
sumber
Tetap dengan loop bersarang!
Semua metode yang disarankan di sini memiliki kelemahan dalam hal keterbacaan atau fleksibilitas.
Apa yang terjadi jika Anda perlu menggunakan hasil loop dalam untuk pemrosesan di loop luar? Apa yang terjadi jika Anda membutuhkan nilai dari loop luar dalam loop dalam Anda? Sebagian besar metode "enkapsulasi" gagal di sini.
Percayalah Saya telah melihat beberapa upaya untuk "membersihkan" bersarang untuk loop dan pada akhirnya ternyata loop bersarang sebenarnya adalah solusi paling bersih dan fleksibel.
sumber
Salah satu teknik yang saya gunakan adalah template. Misalnya:
Kemudian Anda cukup memanggil
do_something_on_A(A)
kode utama Anda. Fungsi template dibuat sekali untuk setiap dimensi, pertama kali denganT = std::vector<std::vector<int>>
, kedua kali dengan denganT = std::vector<int>
.Anda bisa membuat ini lebih umum dengan menggunakan
std::function
(atau objek seperti fungsi di C ++ 03) sebagai argumen kedua jika Anda ingin:Kemudian sebut saja seperti:
Ini berfungsi meskipun fungsi memiliki tanda tangan yang sama karena fungsi pertama lebih cocok untuk apa pun yang memiliki
std::vector
tipe.sumber
Anda dapat membuat indeks dalam satu loop seperti ini (A, B, C adalah dimensi):
sumber
Satu hal yang mungkin ingin Anda coba jika Anda hanya memiliki pernyataan di loop paling dalam - dan kekhawatiran Anda lebih banyak tentang sifat kode yang terlalu bertele-tele - adalah menggunakan skema spasi kosong yang berbeda. Ini hanya akan berfungsi jika Anda dapat menyatakan loop for Anda dengan cukup kompak sehingga semuanya pas dalam satu baris.
Untuk contoh pertama Anda, saya akan menulis ulang sebagai:
Ini agak mendorongnya karena Anda memanggil fungsi di loop luar yang setara dengan meletakkan pernyataan di dalamnya. Saya telah menghapus semua ruang kosong yang tidak perlu dan itu mungkin saja.
Contoh kedua jauh lebih baik:
Ini mungkin konvensi spasi putih yang berbeda dari yang ingin Anda gunakan, tetapi ini mencapai hasil yang ringkas yang tetap tidak memerlukan pengetahuan apa pun selain C / C ++ (seperti konvensi makro) dan tidak memerlukan tipu daya seperti makro.
Jika Anda benar-benar menginginkan makro, Anda dapat mengambil langkah lebih jauh dengan sesuatu seperti:
yang akan mengubah contoh kedua menjadi:
dan contoh pertama juga lebih baik:
Mudah-mudahan Anda dapat mengetahui dengan mudah pernyataan mana yang cocok dengan pernyataan mana. Juga, berhati-hatilah dengan koma, sekarang Anda tidak dapat menggunakannya dalam satu klausa
for
s manapun .sumber
for
loop ke satu baris tidak membuatnya lebih mudah dibaca, tetapi membuatnya lebih sedikit .Berikut adalah implementasi C ++ 11 yang menangani semua yang dapat diulang. Solusi lain membatasi diri pada container dengan
::iterator
typedefs atau array: tetapi afor_each
adalah tentang iterasi, bukan container.Saya juga mengisolasi SFINAE ke satu tempat di
is_iterable
sifat tersebut. Pengiriman (antara elemen dan iterable) dilakukan melalui pengiriman tag, yang menurut saya adalah solusi yang lebih jelas.Kontainer dan fungsi yang diterapkan pada elemen semuanya diteruskan dengan sempurna, memungkinkan akses
const
dan non-const
akses ke rentang dan fungsi.Fungsi template yang saya terapkan. Semua yang lain bisa masuk ke ruang nama detail:
Pengiriman tag jauh lebih bersih daripada SFINAE. Keduanya digunakan masing-masing untuk objek iterable dan non iterable. Iterasi terakhir dari yang pertama bisa menggunakan penerusan sempurna, tapi saya malas:
Ini adalah beberapa boilerplate yang diperlukan untuk menulis
is_iterable
. Saya melakukan pencarian bergantung pada argumenbegin
danend
dalam namespace detail. Ini mengemulasi apa yangfor( auto x : y )
dilakukan loop dengan cukup baik:The
TypeSink
berguna untuk menguji apakah kode valid. Anda melakukanTypeSink< decltype(
kode) >
dan jikacode
valid, ekspresinya adalahvoid
. Jika kode tersebut tidak valid, SFINAE akan dijalankan dan spesialisasi diblokir:Saya hanya menguji
begin
. Sebuahadl_end
tes juga bisa dilakukan.Implementasi
for_each_flat
akhir menjadi sangat sederhana:Contoh langsung
Ini jauh di bawah: jangan ragu untuk mencari jawaban atas, yang solid. Saya hanya ingin menggunakan beberapa teknik yang lebih baik!
sumber
Pertama, Anda tidak boleh menggunakan vektor vektor. Setiap vektor dijamin memiliki memori yang berdekatan, tetapi memori "global" dari vektor vektor tidak (dan mungkin tidak akan). Anda harus menggunakan array tipe library standar, bukan array gaya-C juga.
Lebih baik lagi, Anda dapat menentukan kelas matriks 3D sederhana:
Anda dapat melangkah lebih jauh dan membuatnya benar-benar benar, menambahkan perkalian matriks (tepat dan elemen-bijaksana), perkalian dengan vektor, dll. Anda bahkan dapat menggeneralisasikannya ke jenis yang berbeda (Saya akan menjadikannya template jika Anda terutama menggunakan ganda) .
Anda juga dapat menambahkan objek proxy sehingga Anda dapat melakukan B [i] atau B [i] [j]. Mereka dapat mengembalikan vektor (dalam pengertian matematika) dan matriks yang penuh dengan ganda &, berpotensi?
sumber