Modalități curate de a scrie mai multe bucle ‘for’ (Programare, C++, Pentru Buclă)

C. Wang a intrebat.

Pentru o matrice cu mai multe dimensiuni, de obicei trebuie să scriem o buclă for buclă pentru fiecare dintre dimensiunile sale. De exemplu:

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

Vedeți acest tip de for-for-for bucle în codul nostru în mod frecvent. Cum se utilizează macrourile pentru a defini for-for-for buclele astfel încât să nu fie nevoie să rescriu acest tip de cod de fiecare dată? Există o modalitate mai bună de a face acest lucru?

Comentarii

    62

  • Răspunsul evident este că nu. Nu creați un nou limbaj folosind macro-uri (sau orice altă tehnică); persoana care vine după dumneavoastră nu va putea citi codul. –  > Por James Kanze.
  • 17

  • Când ai un vector al unui vector al unui vector al unui vector, acesta este un semn de proiectare proastă. –  > Por Maroun.
  • @Nim: Puteți face acest lucru cu 1 matrice plată (nu sunt sigur că este mai bine). –  > Por Jarod42.
  • 16

  • M-aș gândi că nu ați vrea să ascundeți potențialul O(n) = n^3 cod… –  > Por poy.
  • 36

  • @TC1: Și atunci o să mi se pară mai greu de citit. Totul este o chestiune de preferințe personale și de fapt nu ajută cu nimic la întrebarea de față. –  > Por ereOn.
16 răspunsuri
James Kanze

Primul lucru este că nu folosiți o astfel de structură de date. Dacă aveți nevoie de o matrice tridimensională, definiți una:

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

Sau dacă doriți să indexați folosind [][][], , aveți nevoie de o operator[]care returnează un proxy.

După ce ați făcut acest lucru, dacă vă dați seama că trebuie să iterați în mod constant așa cum ați prezentat, expuneți un iterator care va suporta acest lucru:

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

Apoi, nu trebuie decât să scrieți:

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

(sau doar:

for ( auto& elem: m ) {
}

dacă aveți C++11.)

Iar dacă aveți nevoie de cei trei indici în timpul acestor iterații, este posibil să creați un iterator care să îi expună:

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

Comentarii

    21

  • Acest răspuns ar trebui să fie mult mai mult upvoted, deoarece este singurul care se ocupă de sursa reală a problemei. –  > Por ereOn.
  • poate fi un răspuns corect, dar nu sunt de acord că este unul bun. o mulțime de coduri de șabloane criptice cu un timp de compilare probabil de x10 ori mai lent și un cod de depanare probabil de x10 ori mai lent (poate mai mult). Pentru mine, codul original este cu siguranță mult mai clar… –  > Por Gorkem.
  • @beehorf …și, de asemenea, mult, mult mai lent. Pentru că array-urile multidimensionale din C și C++ sunt de fapt array-uri imbricate, în sensul că dimensiunile exterioare stochează pointeri către array-urile imbricate. Aceste matrici imbricate sunt apoi împrăștiate în mod arbitrar în memorie, înfrângând efectiv orice prefetching și caching. Cunosc exemple în care cineva a scris un cod folosind vector<vector<vector<double> > > pentru a reprezenta un câmp tridimensional. Rescrierea codului echivalent cu soluția de mai sus a dus la o creștere a vitezei de 10. –  > Por Michael Wild.
  • @beehorf Unde vezi vreun cod de șablon? (În practică, Matrix3D probabil că ar trebui să fie un șablon, dar este un șablon foarte simplu). Și trebuie doar să depanați Matrix3D, , nu de fiecare dată când ai nevoie de o matrice 3D, așa că economisești o cantitate enormă de timp în depanare. În ceea ce privește claritatea: cum este std::vector<std::vector<std::vector<int>>> mai clar decât Matrix3D? Ca să nu mai spun că Matrix3D impune faptul că aveți o matrice, în timp ce vectorii imbricați ar putea fi zdrențuiți, și că probabil că varianta de mai sus este semnificativ mai rapidă. –  > Por James Kanze.
  • @MichaelWild Dar, bineînțeles, adevăratul avantaj al abordării mele este că puteți schimba reprezentarea, în funcție de ceea ce este mai rapid în mediul dumneavoastră, fără a fi nevoie să modificați nimic din codul clientului. Cheia unei bune performanțe este încapsularea adecvată, astfel încât să puteți face modificările de care profilerul spune că aveți nevoie fără a fi nevoie să rescrieți întreaga aplicație. –  > Por James Kanze.
Pantofii

Utilizarea unui macro pentru a ascunde for bucle poate fi foarte derutant, doar pentru a economisi câteva caractere. Eu aș folosi range-for bucle în schimb:

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

Desigur, puteți înlocui auto& cu const auto& dacă, de fapt, nu modificați datele.

Comentarii

  • Presupunând că OP poate folosi C++11. –  > Por Jarod42.
  • @herohuyongtao În cazul iteratorilor. Care poate fi mai idiomatic aici, dar există cazuri în care ați dori cele trei int variabile. –  > Por James Kanze.
  • Și nu ar trebui să fie do_something_on_A(*j)? –  > Por James Kanze.
  • @Jefffrey Ah, da. Un alt motiv pentru a scrie tipul. (Cred că utilizarea auto pentru k și i poate fi justificată. Doar că tot rezolvă problema la un nivel greșit; adevărata problemă este că folosește vectori imbricați). –  > Por James Kanze.
  • @Dhara k este un întreg vector de vectori (bine, o referință la acesta), nu un index. –  > Por Yakk – Adam Nevraumont.
fasked

Ceva de genul acesta poate ajuta:

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

Pentru a-l face N-ary avem nevoie de o magie de șablon. Mai întâi de toate ar trebui să creăm structura SFINAE pentru a distinge dacă această valoare sau container. Implementarea implicită pentru valori și specializări pentru array-uri și pentru fiecare dintre tipurile de containere. După cum notează @Zeta, putem determina containerele standard prin intermediul elementelor imbricate iterator (în mod ideal ar trebui să verificăm dacă tipul poate fi utilizat cu range-base for sau nu).

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

Implementarea lui for_each este simplă. Funcția implicită va apela 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);
 }

Iar specializarea se va apela pe sine în mod recursiv:

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

Și iată:

 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,
 }

De asemenea, acest lucru nu va funcționa pentru pointeri (array-uri alocate în heap).

Comentarii

  • @herohuyongtao cu constrângeri putem implementa două specializări pentru Container și pentru altele. –  > Por fasked.
  • @herohuyongtao Am făcut un exemplu de foreach K-ary. –  > Por fasked.
  • @fasked: Utilizați is_container : has_iterator<T>::value din răspunsul meu și nu este nevoie să scrieți o specializare pentru fiecare tip, deoarece fiecare container ar trebui să aibă un iterator typedef. Simțiți-vă liber să folosiți complet orice din răspunsul meu, al tău este deja mai bun. –  > Por Zeta.
  • @Zeta +1 pentru asta. De asemenea, așa cum am menționat Container conceptul va ajuta. –  > Por fasked.
  • ::iterator nu face un interval iterabil. int x[2][3][4] este perfect iterabil, la fel ca și struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; Nu sunt sigur ce T[] ce ar trebui să facă specializarea? –  > Por Yakk – Adam Nevraumont.
kuroi neko

Majoritatea răspunsurilor demonstrează pur și simplu modul în care C++ poate fi răsucit în extensii sintactice de neînțeles, IMHO.

Definind nu știu ce șabloane sau macro-uri, nu faci decât să forțezi alți programatori să înțeleagă bucăți de cod ofuscat concepute pentru a ascunde alte bucăți de cod ofuscat.
Îi veți forța pe toți cei care vă citesc codul să aibă experiență în șabloane, doar pentru a evita să vă faceți treaba de a defini obiecte cu o semantică clară.

Dacă ați decis să folosiți date brute, cum ar fi array-uri tridimensionale, trăiți cu ele, sau definiți o clasă care să dea un sens inteligibil datelor dumneavoastră.

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

este pur și simplu coerentă cu definiția criptică a unui vector de vectori de vectori de vectori de int fără o semantică explicită.

FreeNickname
#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
", i, j, k);
    }
    return 0;
}

UPDATE: Știu, că ai cerut-o, dar ar fi bine să nu o folosești 🙂

Comentarii

  • Știu că asta a cerut OP, dar serios… Acesta pare un exemplu minunat de ofuscare. Presupunând că TRIPLE_FOR au fost definite într-un antet, ce să cred când văd `TRIPLE_FOR aici. –  > Por James Kanze.
  • Da, cred că aveți dreptate 🙂 Cred că o voi lăsa aici doar ca exemplu că acest lucru se poate face folosind o macro, dar voi adăuga o notă că este mai bine să nu se facă așa 🙂 Tocmai m-am trezit și am decis să folosesc această întrebare ca o mică încălzire a minții. –  > Por FreeNickname.
Steve314

O idee este de a scrie o clasă pseudo-container iterabilă care „conține” setul tuturor tuplurilor multi-index pe care le veți indexa. Nu există o implementare aici, deoarece va dura prea mult timp, dar ideea este că ar trebui să puteți scrie…

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

Comentarii

  • Cel mai bun răspuns aici, imo. –  > Por davidhigh.
Rață care face mișto

Văd multe răspunsuri aici care funcționează recursiv, detectând dacă intrarea este un container sau nu. În schimb, de ce să nu detectezi dacă stratul curent este de același tip cu cel pe care îl ia funcția? Este mult mai simplu și permite funcții mai puternice:

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

Cu toate acestea, acest lucru ne oferă (evident) erori de ambiguitate. Așadar, folosim SFINAE pentru a detecta dacă intrarea curentă se potrivește sau nu în funcție

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

Acest lucru gestionează acum corect containerele, dar compilatorul consideră în continuare că acest lucru este ambiguu pentru input_types care pot fi transmise funcției. Astfel, folosim un truc standard C++03 pentru a face ca acesta să prefere prima funcție în locul celei de-a doua, care constă în a trece și un zero și în a face ca cea pe care o preferăm să accepte int, iar cealaltă să accepte …

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

Asta este tot. Șase linii de cod relativ simple și puteți itera peste valori, rânduri sau orice altă subunitate, spre deosebire de toate celelalte răspunsuri.

#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 << "}
";
         };
     rfor_each(B, write );
 };

Dovada compilării și a execuției aici și aici

Dacă ați dori o sintaxă mai convenabilă în C++11, ați putea adăuga o macro. (Următoarea este netestată)

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 << ", ";
     }
}

icabod

Pun la îndoială acest răspuns cu următoarea afirmație: acest lucru ar funcționa numai dacă ați opera pe o matrice reală. – nu ar funcționa pentru exemplul dvs. care utilizează std::vector.

Dacă efectuați aceeași operație pe fiecare element al unui array multidimensional, fără a vă interesa poziția fiecărui element, atunci puteți profita de faptul că array-urile sunt plasate în locații de memorie contiguă și puteți trata totul ca pe un array unidimensional mare. De exemplu, dacă am dori să înmulțim fiecare element cu 2,0 în cel de-al doilea exemplu al dvs:

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

Rețineți că utilizarea abordării de mai sus permite, de asemenea, utilizarea unor tehnici C++ „adecvate”:

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

I nu recomand în general această abordare (preferând ceva de genul răspunsului lui Jefffrey), deoarece se bazează pe existența unor dimensiuni definite pentru array-urile dumneavoastră, dar în unele cazuri poate fi utilă.

Comentarii

  • Acest lucru este ilegal: stackoverflow.com/questions/6015080/… –  > Por ecatmur.
  • @ecatmur: Interesant – abia am ajuns la serviciu, așa că voi verifica acest lucru și voi actualiza/șterge răspunsul în consecință. Mulțumesc. –  > Por icabod.
  • @ecatmur: M-am uitat la standardul C++11 (secțiunea 8.3.4), iar ceea ce am scris ar trebui să funcționeze și nu pare ilegal (pentru mine). Linkul pe care l-ai furnizat se referă la accesarea membrilor în afara dimensiunii definite a array-ului. Deși este adevărat că obțin adresa de imediat după array, nu accesez datele – acest lucru are scopul de a oferi un „capăt”, în același mod în care puteți utiliza pointeri ca iteratori, „sfârșitul” fiind după ultimul element. –  > Por icabod.
  • În realitate, accesați B[0][0][i] pentru i >= 3; acest lucru nu este permis deoarece se accesează în afara tabloului (interior). –  > Por ecatmur.
  • Un mod mai clar IMO de atribuire a sfârșitului DACĂ ar trebui să faceți acest lucru este end = start + (xSize * ySize * zSize) – –  > Por noggin182.
PaperBirdMaster

Am fost oarecum șocat că nimeni nu a propus vreo buclă bazată pe aritmetică-magie pentru a face treaba. Din moment ce C. Wang caută o soluție fără bucle imbricate, , voi propune eu una:

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

Ei bine, această abordare nu este elegantă și flexibilă, așa că am putea împacheta tot procesul într-o funcție șablon:

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

Această funcție șablon poate fi exprimată și sub forma unor bucle imbricate:

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

Și poate fi utilizată furnizând o matrice 3D de dimensiune arbitrară plus numele funcției, lăsând deducția parametrilor să facă munca grea de a număra dimensiunea fiecărei dimensiuni:

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

Către o utilizare mai generică

Dar, încă o dată, este lipsit de flexibilitate, deoarece funcționează doar pentru array-uri 3D, dar folosind SFINAE putem face treaba pentru array-uri de o dimensiune arbitrară, mai întâi avem nevoie de o funcție șablon care să itereze array-uri de dimensiuni rang 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);
    }
}

Și o alta care să itereze array-uri de orice rang, efectuând recursivitatea:

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

Acest lucru ne permite să iterăm toate elementele din toate dimensiunile unui array de dimensiuni arbitrare.


Lucrul cu std::vector

Pentru vectorul multiplu imbricate, soluția seamănă cu cea a tabloului de dimensiuni arbitrare cu dimensiuni arbitrare, dar fără SFINAE: În primul rând, vom avea nevoie de o funcție șablon care să itereze std::vectors și apelează funcția dorită:

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

Și o altă funcție șablon care să itereze orice fel de vector de vectori și să se apeleze pe sine:

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

Indiferent de nivelul de anvelopare, iterate_all va apela versiunea vectorului de vectori, cu excepția cazului în care versiunea vectorului de valori se potrivește mai bine, punând astfel capăt recursivității.

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

Cred că corpul funcției este destul de simplu și direct… Mă întreb dacă compilatorul ar putea derula această buclă (sunt aproape sigur că majoritatea compilatoarelor ar putea derula primul exemplu).

A se vedea demonstrația live aici.

Sper că vă ajută.

RobAu

Folosiți ceva de genul acesta (este un pseudocod, dar ideea rămâne aceeași). Extragi modelul pentru a face o buclă o dată și aplici o funcție diferită de fiecare dată.

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)

James Anderson

Rămâi cu buclele for imbricate!

Toate metodele sugerate aici au dezavantaje fie în ceea ce privește lizibilitatea, fie în ceea ce privește flexibilitatea.

Ce se întâmplă dacă trebuie să folosiți rezultatele unei bucle interioare pentru procesarea în bucla exterioară? Ce se întâmplă dacă aveți nevoie de o valoare din bucla exterioară în cadrul buclei interioare? Majoritatea metodelor de „încapsulare” eșuează aici.

Credeți-mă, am văzut mai multe încercări de „curățare” a buclelor for imbricate și, în cele din urmă, se dovedește că bucla imbricata este de fapt cea mai curată și mai flexibilă soluție.

JoshG79

O tehnică pe care am folosit-o sunt șabloanele. De ex:

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
}

Apoi, pur și simplu apelați do_something_on_A(A) în codul tău principal. Funcția șablon este creată o dată pentru fiecare dimensiune, prima dată cu T = std::vector<std::vector<int>>, , a doua oară cu cu T = std::vector<int>.

Ați putea face acest lucru mai generic folosind std::function (sau obiecte de tip funcție în C++03) ca al doilea argument, dacă doriți:

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

Apoi apelați-o ca:

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

Acest lucru funcționează chiar dacă funcțiile au aceeași semnătură, deoarece prima funcție se potrivește mai bine pentru orice cu std::vector în tip.

janek

Ați putea genera indici într-o singură buclă, astfel (A, B, C sunt dimensiuni):

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

Comentarii

  • Sunt de acord cu tine, este conceput special pentru 3 dimensiuni 😉 –  > Por janek.
  • Ca să nu mai spun că este incredibil de lent! –  > Por noggin182.
  • @noggin182: întrebarea nu se referea la viteză, ci la evitarea buclelor for imbricate; în plus, există diviziuni inutile acolo, i/(B*C) poate fi înlocuit cu a – -.  > Por janek.
  • Ok, aceasta este o modalitate alternativă, probabil mai eficientă (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); } –  > Por janek.
Michael

Un lucru pe care ai putea să îl încerci dacă ai doar declarații în cea mai interioară buclă – iar preocuparea ta este mai mult legată de natura prea verbuloasă a codului – este să folosești o schemă diferită de spații albe. Acest lucru va funcționa numai dacă puteți să vă declarați buclele for suficient de compact pentru ca toate să încapă pe o singură linie.

Pentru primul dvs. exemplu, l-aș rescrie astfel:

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

Acest lucru este cam forțat, deoarece apelați funcții în buclele exterioare, ceea ce este echivalent cu introducerea de declarații în ele. Am eliminat toate spațiile albe inutile și este posibil să fie acceptabil.

Al doilea exemplu este mult mai bun:

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

Poate că este o convenție de spații albe diferită de cea pe care vă place să o folosiți, dar se obține un rezultat compact care, cu toate acestea, nu necesită cunoștințe în afara C/C++ (cum ar fi convențiile macro) și nu necesită nici o șmecherie precum macrourile.

Dacă doriți cu adevărat o macro, ați putea face un pas mai departe cu ceva de genul:

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

care ar schimba al doilea exemplu în:

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

iar primul exemplu se descurcă și el mai bine:

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

Să sperăm că vă puteți da seama destul de ușor ce declarații se potrivesc cu ce declarații for. De asemenea, aveți grijă la virgule, acum nu le puteți folosi într-o singură clauză a nici uneia dintre instrucțiunile fors.

Comentarii

  • Lizibilitatea acestora este oribilă. Încrucișarea mai multor for buclă pe o linie nu o face mai lizibilă, ci o face mai ușor de citit. mai puțin. – utilizator764357
Yakk – Adam Nevraumont

Iată o implementare C++11 care se ocupă de tot ce este iterabil. Alte soluții se limitează la containere cu ::iterator tipedefs sau array-uri: dar o for_each este despre iterație, nu despre a fi un container.

De asemenea, izolez SFINAE într-un singur punct din is_iterable trăsătură. Dispecerizarea (între elemente și iterabile) se face prin intermediul dispecerizării prin etichete, ceea ce mi se pare o soluție mai clară.

Containerele și funcțiile aplicate elementelor sunt toate perfect transmise, permițând atât const și non-const accesul la intervale și functori.

#include <utility>
#include <iterator>

Funcția șablon pe care o implementez. Restul ar putea merge într-un namespace de detalii:

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

Dispecerizarea etichetelor este mult mai curată decât SFINAE. Acestea două sunt utilizate pentru obiecte iterabile și, respectiv, pentru obiecte neiterabile. Ultima iterație a primului ar putea folosi o expediere perfectă, dar sunt leneș:

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

Aceasta este o parte din boilerplate-ul necesar pentru a scrie is_iterable. Fac căutarea în funcție de argument pe begin și end într-un spațiu de nume detaliat. Acest lucru emulează ceea ce face un for( auto x : y ) o buclă face destul de bine:

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;

TypeSink este utilă pentru a testa dacă codul este valid. Se face TypeSink< decltype( cod ) > și dacă code este valid, expresia este void. Dacă codul nu este valid, intervine SFINAE și specializarea este blocată:

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

Eu testez doar pentru begin. Un adl_end test ar putea fi de asemenea realizat.

Implementarea finală a for_each_flat se dovedește a fi extrem de simplă:

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

Exemplu viu

Aceasta este mult mai jos, în partea de jos: nu ezitați să braconați pentru răspunsurile de sus, care sunt solide. Am vrut doar câteva tehnici mai bune care să fie folosite!

Miles Rout

În primul rând, nu ar trebui să folosiți un vector de vectori de vectori de vectori. Fiecare vector este garantat să aibă memorie contiguă, dar memoria „globală” a unui vector de vectori nu este (și probabil că nu va fi). De asemenea, ar trebui să folosiți tipul de matrice din biblioteca standard în loc de array-uri în stil C.

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

Totuși, mai bine, ați putea defini o clasă simplă de matrice 3D:

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

Ați putea merge mai departe și să o faceți complet const-correctă, să adăugați înmulțirea matricelor (corectă și în funcție de elemente), înmulțirea cu vectori etc. Ați putea chiar să o generalizați la diferite tipuri (eu aș face un șablon dacă folosiți în principal dubluri).

De asemenea, ați putea adăuga obiecte proxy, astfel încât să puteți face B[i] sau B[i][j]. Acestea ar putea returna vectori (în sens matematic) și matrici pline de double&, potențial?