Cara bersih untuk menulis beberapa loop 'untuk'

98

Untuk array dengan banyak dimensi, biasanya kita perlu menulis forloop 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-forloop semacam ini di kode kami. Bagaimana cara menggunakan makro untuk menentukan for-for-forloop sehingga saya tidak perlu menulis ulang kode semacam ini setiap saat? Apakah ada cara yang lebih baik untuk melakukan ini?

C. Wang
sumber
62
Jawaban yang jelas adalah Anda tidak melakukannya. Anda tidak membuat bahasa baru menggunakan makro (atau teknik lainnya); orang yang datang setelah Anda tidak akan dapat membaca kode.
James Kanze
17
Ketika Anda memiliki sebuah vektor dari sebuah vektor, itu pertanda desain yang buruk.
Maroun
5
@Nim: Anda dapat melakukannya dengan 1 array datar (tidak yakin lebih baik).
Jarod42
16
Saya akan berpikir Anda tidak ingin menyembunyikan O(n) = n^3kode potensial ...
poy
36
@ TC1: Lalu saya akan merasa lebih sulit untuk membaca. Ini semua adalah pertanyaan preferensi pribadi dan sebenarnya tidak membantu dengan pertanyaan yang ada di sini.
sebelum

Jawaban:

281

Hal pertama adalah Anda tidak menggunakan struktur data seperti itu. Jika Anda membutuhkan matriks tiga dimensi, tentukan salah satunya:

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

Atau jika Anda ingin mengindeks menggunakan [][][], Anda memerlukan operator[] 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:

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

Kemudian Anda tinggal menulis:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(atau hanya:

for ( auto& elem: m ) {
}

jika Anda memiliki C ++ 11.)

Dan jika Anda memerlukan tiga indeks selama iterasi seperti itu, Anda dapat membuat iterator yang memaparkannya:

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};
James Kanze
sumber
21
Jawaban ini harus jauh lebih disukai karena ini adalah satu-satunya yang berhubungan dengan sumber masalah yang sebenarnya.
sebelum
5
ini mungkin jawaban yang benar tapi saya tidak setuju itu bagus. banyak kode template samar dengan waktu kompilasi yang mungkin x10 kali lambat dan mungkin x10 kode debug lambat (mungkin lebih). Bagi saya jelas kode asli jauh lebih jelas bagi saya ...
Gorkem
10
@beehorf ... dan juga jauh lebih lambat. Karena larik multi-dimensi dalam C dan C ++ sebenarnya adalah larik bersarang dalam arti bahwa dimensi luar menyimpan penunjuk ke larik bersarang. Array bersarang ini kemudian tersebar secara acak di dalam memori, secara efektif mengalahkan prefetching dan caching apa pun. Saya tahu contoh di mana seseorang menulis kode yang digunakan vector<vector<vector<double> > >untuk mewakili bidang 3 dimensi. Menulis ulang kode yang setara dengan solusi di atas menghasilkan percepatan 10.
Michael Wild
5
@beehorf Di mana Anda melihat kode template? (Dalam praktiknya, Matrix3Dmungkin harus berupa template, tetapi ini adalah template yang sangat mudah.) Dan Anda hanya perlu melakukan debug Matrix3D, tidak setiap kali Anda membutuhkan matriks 3D, sehingga Anda menghemat banyak waktu dalam debugging. Adapun kejelasan: bagaimana std::vector<std::vector<std::vector<int>>>lebih jelas dari Matrix3D? Belum lagi yang Matrix3Dmemberlakukan fakta bahwa Anda memiliki matriks, sedangkan vektor bersarang bisa compang-camping, dan bahwa di atas mungkin jauh lebih cepat.
James Kanze
10
@MichaelWild Tapi tentu saja, keuntungan nyata dari pendekatan saya adalah Anda dapat mengubah representasi, tergantung pada apa yang lebih cepat di lingkungan Anda, tanpa harus memodifikasi kode klien apa pun. Kunci untuk performa yang baik adalah enkapsulasi yang tepat, sehingga Anda dapat membuat perubahan yang menurut profiler Anda perlukan tanpa harus menulis ulang seluruh aplikasi.
James Kanze
44

Menggunakan makro untuk menyembunyikan forloop bisa sangat membingungkan, hanya untuk menyimpan beberapa karakter. Saya akan menggunakan loop range-for sebagai gantinya:

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

Tentu saja Anda dapat mengganti auto&dengan const auto&jika Anda sebenarnya tidak memodifikasi data.

Sepatu
sumber
3
Dengan asumsi OP bisa menggunakan C ++ 11.
Jarod42
1
@herohuyongtao Dalam kasus iterator. Yang mungkin lebih idiomatis di sini, tetapi ada kasus di mana Anda menginginkan tiga intvariabel.
James Kanze
1
Dan bukankah seharusnya begitu do_something_on_A(*j)?
James Kanze
1
@Jeffrey Ah, ya. Alasan lain untuk mengeja jenisnya. (Saya kira penggunaan autountuk kdan idapat dibenarkan. Kecuali bahwa itu masih memecahkan masalah pada tingkat yang salah; masalah sebenarnya adalah dia menggunakan vektor bersarang.)
James Kanze
2
@Dhara kadalah seluruh vektor vektor (baik referensi untuk itu), bukan indeks.
Yakk - Adam Nevraumont
21

Sesuatu seperti ini dapat membantu:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

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 iteratortipe nested (idealnya kita harus mengecek apakah type bisa digunakan dengan range-base foratau tidak).

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

Penerapannya for_eachsangat mudah. Fungsi default akan memanggil function:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

Dan spesialisasi akan memanggil dirinya sendiri secara rekursif:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

Dan voila:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

Ini juga tidak akan berfungsi untuk pointer (array yang dialokasikan di heap).

fasked
sumber
@herohuyongtao dengan batasan kita dapat menerapkan dua spesialisasi untuk Containerdan untuk orang lain.
fasked
1
@herohuyongtao Saya membuat contoh K-ary foreach.
fasked
1
@fasked: Gunakan is_container : has_iterator<T>::valuedari jawaban saya dan Anda tidak perlu menulis spesialisasi untuk setiap jenis, karena setiap wadah harus memiliki iteratortypedef. Jangan ragu untuk sepenuhnya menggunakan apa pun dari jawaban saya, jawaban Anda sudah lebih baik.
Zeta
@Zeta +1 untuk ini. Juga seperti yang saya sebutkan Containerkonsepnya akan membantu.
fasked
::iteratortidak membuat rentang iterable. int x[2][3][4]iterable sempurna, karena struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; saya tidak yakin T[]spesialisasi apa yang seharusnya dilakukan?
Yakk - Adam Nevraumont
17

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.

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

hanya konsisten dengan definisi samar dari vektor vektor int tanpa semantik eksplisit.

kuroi neko
sumber
10
#include "stdio.h"

#define FOR(i, from, to)    for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to)   FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)

int main()
{
    TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
    {
        printf("i: %d, j: %d, k: %d\n", i, j, k);
    }
    return 0;
}

UPDATE: Saya tahu, bahwa Anda memintanya, tetapi sebaiknya Anda tidak menggunakannya :)

FreeNickname
sumber
5
Aku tahu itulah yang diminta OP, tapi serius ... Ini terlihat seperti contoh kebingungan yang luar biasa. Misalkan TRIPLE_FORdidefinisikan di beberapa header, apa yang saya pikirkan ketika saya melihat `TRIPLE_FOR di sini.
James Kanze
2
Ya, saya rasa, Anda benar :) Saya pikir, saya akan meninggalkannya di sini hanya sebagai contoh bahwa ini dapat dilakukan dengan menggunakan makro, tetapi tambahkan catatan bahwa lebih baik tidak melakukannya :) Saya baru saja bangun up, dan memutuskan untuk menggunakan pertanyaan ini sebagai pemanasan kecil untuk pikiran.
FreeNickname
5

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 ...

multi_index mi (10, 8, 5);
  //  The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...

for (auto i : mi)
{
  //  In here, use i[0], i[1] and i[2] to access the three index values.
}
Steve314
sumber
jawaban terbaik di sini imo.
davidhigh
4

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:

//This is roughly what we want for values
template<class input_type, class func_type> 
void rfor_each(input_type&& input, func_type&& func) 
{ func(input);}

//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

Namun, ini (jelas) memberi kita kesalahan ambiguitas. Jadi kami menggunakan SFINAE untuk mendeteksi apakah input saat ini sesuai dengan fungsinya atau tidak

//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input)) 
{ return func(input);}

//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

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 ...

template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input)) 
{ return func(input);}

//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to 
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...) 
{ for(auto&& i : input) rfor_each(i, func, 0);}

Itu dia. Enam, baris kode yang relatif sederhana, dan Anda dapat mengulang nilai, baris, atau sub-unit lainnya, tidak seperti semua jawaban lainnya.

#include <iostream>
int main()
 {

     std::cout << std::endl;
     double B[3][3] = { { 1.2 } };
     rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
     auto write = [](double (&i)[3]) //iterate over rows
         {
             std::cout << "{";
             for(double d : i) 
                 std::cout << d << ", ";
             std::cout << "}\n";
         };
     rfor_each(B, write );
 };

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)

template<class container>
struct container_unroller {
    container& c;
    container_unroller(container& c_) :c(c_) {}
    template<class lambda>
    void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index) 
//note that this can't handle functions, function pointers, raw arrays, or other complex bits

int main() {
     double B[3][3] = { { 1.2 } };
     FOR_NESTED(double, v, B) {
         std::cout << v << ", ";
     }
}
Mooing Duck
sumber
3

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:

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];     // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
    (*begin) *= 2.0;
}

Perhatikan bahwa menggunakan pendekatan di atas juga memungkinkan penggunaan beberapa teknik C ++ yang "tepat":

double do_something(double d) {
    return d * 2.0;
}

...

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];  // get a pointer to the first element
double* end = &B[3][0][0];    // get a pointer past the last element

std::transform(begin, end, begin, do_something);

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.

kapal es
sumber
@ Catmur: Menarik - Saya baru saja mulai bekerja, jadi saya akan memeriksa ini dan memperbarui / menghapus jawabannya sesuai. Terima kasih.
icabod
@ Catmur: Saya telah melihat standar C ++ 11 (bagian 8.3.4), dan apa yang saya tulis seharusnya berfungsi, dan tidak terlihat ilegal (bagi saya). Tautan yang Anda berikan berkaitan dengan mengakses anggota di luar ukuran larik yang ditentukan. Meskipun benar bahwa saya mendapatkan alamat yang baru saja melewati array, itu tidak mengakses data - ini untuk memberikan "akhir", dengan cara yang sama Anda dapat menggunakan pointer sebagai iterator, dengan "akhir" menjadi satu masa lalu elemen terakhir.
icabod
Anda mengakses secara efektif B[0][0][i]untuk i >= 3; ini tidak diperbolehkan karena mengakses di luar larik (dalam).
ecatmur
1
Cara yang lebih jelas IMO untuk menetapkan akhir JIKA Anda melakukan ini adalah end = start + (xSize * ySize * zSize)
noggin182
2

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:

double B[10][8][5];
int index = 0;

while (index < (10 * 8 * 5))
{
    const int x = index % 10,
              y = (index / 10) % 10,
              z = index / 100;

    do_something_on_B(B[x][y][z]);
    ++index;
}

Nah, pendekatan ini tidak elegan dan fleksibel, jadi kita bisa mengemas semua proses ke dalam fungsi template:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    const int limit = X * Y * Z;
    int index = 0;

    while (index < limit)
    {
        const int x = index % X,
                  y = (index / X) % Y,
                  z = index / (X * Y);

        func(xyz[x][y][z]);
        ++index;
    }
}

Fungsi template ini juga dapat diekspresikan dalam bentuk loop bersarang:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    for (auto &yz : xyz)
    {
        for (auto &z : yz)
        {
            for (auto &v : z)
            {
                func(v);
            }
        }
    }
}

Dan dapat digunakan untuk menyediakan larik 3D dengan ukuran sembarang ditambah nama fungsi, membiarkan deduksi parameter melakukan kerja keras untuk menghitung ukuran setiap dimensi:

int main()
{
    int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};

    iterate_all(A, do_something_on_A);
    iterate_all(B, do_something_on_B);

    return 0;
}

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:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

Dan satu lagi yang mengulang array dari peringkat apa pun, melakukan rekursi:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

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::vectors dan memanggil fungsi yang diinginkan:

template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

Dan fungsi template lain yang mengiterasi segala jenis vektor dan menyebut dirinya sendiri:

template <typename F, typename T, template<typename, typename> class V> 
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

Terlepas dari level bersarang, iterate_allakan memanggil versi vektor-vektor kecuali versi vektor-nilai-nilai lebih cocok sehingga mengakhiri rekursivitas.

int main()
{
    using V0 = std::vector< std::vector< std::vector<int> > >;
    using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;

    V0 A0 =   {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};

    iterate_all(A0, do_something_on_A);
    iterate_all(A1, do_something_on_A);

    return 0;
}

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.

PaperBirdMaster
sumber
1

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.

doOn( structure A, operator o)
{
    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++)
                {
                        o.actOn(A[k][i][j]);
                }
            }
    }
}

doOn(a, function12)
doOn(a, function13)
RobAu
sumber
1

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.

James Anderson
sumber
0

Salah satu teknik yang saya gunakan adalah template. Misalnya:

template<typename T> void do_something_on_A(std::vector<T> &vec) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_A(i);
    }
}

void do_something_on_A(int &val) {
    // this is where your `do_something_on_A` method goes
}

Kemudian Anda cukup memanggil do_something_on_A(A)kode utama Anda. Fungsi template dibuat sekali untuk setiap dimensi, pertama kali dengan T = std::vector<std::vector<int>>, kedua kali dengan dengan T = 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:

template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_vec(i, func);
    }
}

template<typename T> void do_something_on_vec(T &val, std::function &func) {
    func(val);
}

Kemudian sebut saja seperti:

do_something_on_vec(A, std::function(do_something_on_A));

Ini berfungsi meskipun fungsi memiliki tanda tangan yang sama karena fungsi pertama lebih cocok untuk apa pun yang memiliki std::vectortipe.

JoshG79
sumber
0

Anda dapat membuat indeks dalam satu loop seperti ini (A, B, C adalah dimensi):

int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
    int a = i/(B*C);
    int b = (i-((B*C)*(i/(B*C))))/C;
    int c = i%C;
}
janek
sumber
Saya setuju dengan Anda, ini dirancang khusus untuk 3 dimensi;)
janek
1
Belum lagi ini sangat lambat!
noggin182
@ noggin182: pertanyaannya bukan tentang kecepatan tetapi tentang menghindari loop-for bersarang; selain itu, ada divisi yang tidak perlu di sana, i / (B * C) bisa diganti dengan a
janek
Ok, ini adalah cara alternatif, mungkin lebih efisien (javascript): for (var i = 0, j = 0, k = 0; i <A; i + = (j == B-1 && k == C - 1)? 1: 0, j = (k == C - 1)? ((J == B-1)? 0: j + 1): j, k = (k == C - 1)? 0: k + 1) {console.log (i + "" + j + "" + k); }
Janek
0

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:

vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
    do_something_on_A(A[k][i][j]);
}

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:

double B[10][8][5];
int i,j,k;

for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
    do_something_on_B(B[k][i][j]);
}

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:

#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)

yang akan mengubah contoh kedua menjadi:

double B[10][8][5];
int i,j,k;

FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
    do_something_on_B(B[k][i][j]);
}

dan contoh pertama juga lebih baik:

vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
    do_something_on_A(A[k][i][j]);
}

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 fors manapun .

Michael
sumber
1
Keterbacaan ini mengerikan. Memasukkan lebih dari satu forloop ke satu baris tidak membuatnya lebih mudah dibaca, tetapi membuatnya lebih sedikit .
0

Berikut adalah implementasi C ++ 11 yang menangani semua yang dapat diulang. Solusi lain membatasi diri pada container dengan ::iteratortypedefs atau array: tetapi a for_eachadalah tentang iterasi, bukan container.

Saya juga mengisolasi SFINAE ke satu tempat di is_iterablesifat 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 constdan non- constakses ke rentang dan fungsi.

#include <utility>
#include <iterator>

Fungsi template yang saya terapkan. Semua yang lain bisa masuk ke ruang nama detail:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f );

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:

template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
  for( auto&& x : std::forward<C>(c) )
    for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
  std::forward<F>(f)(std::forward<D>(data));
}

Ini adalah beberapa boilerplate yang diperlukan untuk menulis is_iterable. Saya melakukan pencarian bergantung pada argumen begindan enddalam namespace detail. Ini mengemulasi apa yang for( auto x : y )dilakukan loop dengan cukup baik:

namespace adl_aux {
  using std::begin; using std::end;
  template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
  template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;

The TypeSinkberguna untuk menguji apakah kode valid. Anda melakukan TypeSink< decltype(kode ) >dan jika codevalid, ekspresinya adalah void. Jika kode tersebut tidak valid, SFINAE akan dijalankan dan spesialisasi diblokir:

template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;

template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};

Saya hanya menguji begin. Sebuah adl_endtes juga bisa dilakukan.

Implementasi for_each_flatakhir menjadi sangat sederhana:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
  for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}        

Contoh langsung

Ini jauh di bawah: jangan ragu untuk mencari jawaban atas, yang solid. Saya hanya ingin menggunakan beberapa teknik yang lebih baik!

Yakk - Adam Nevraumont
sumber
-2

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.

using std::array;

array<array<array<double, 5>, 8>, 10> B;
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]);

// or, if you really don't like that, at least do this:

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

Lebih baik lagi, Anda dapat menentukan kelas matriks 3D sederhana:

#include <stdexcept>
#include <array>

using std::size_t;

template <size_t M, size_t N, size_t P>
class matrix3d {
    static_assert(M > 0 && N > 0 && P > 0,
                  "Dimensions must be greater than 0.");
    std::array<std::array<std::array<double, P>, N>, M> contents;
public:
    double& at(size_t i, size_t j, size_t k)
    { 
        if (i >= M || j >= N || k >= P)
            throw out_of_range("Index out of range.");
        return contents[i][j][k];
    }
    double& operator(size_t i, size_t j, size_t k)
    {
        return contents[i][j][k];
    }
};

int main()
{
    matrix3d<10, 8, 5> B;
        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(i,j,k));
    return 0;
}

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?

Rute Miles
sumber