Modi puliti per scrivere più cicli "for"


98

Per un array con più dimensioni, di solito è necessario scrivere un forciclo per ciascuna delle sue dimensioni. Per esempio:

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

Vedi for-for-forspesso questo tipo di loop nel nostro codice. Come posso utilizzare le macro per definire i for-for-forcicli in modo da non dover riscrivere questo tipo di codice ogni volta? C'è un modo migliore per farlo?


62
La risposta ovvia è che non lo fai. Non crei una nuova lingua usando le macro (o qualsiasi altra tecnica); la persona che verrà dopo di te non sarà in grado di leggere il codice.
James Kanze

17
Quando hai un vettore di un vettore di un vettore, questo è un segno di cattivo design.
Maroun

5
@Nim: Puoi farlo con 1 array piatto (non sono sicuro che sia migliore).
Jarod42

16
Penso che non vorresti nascondere il O(n) = n^3codice potenziale ...
poy

36
@ TC1: E poi lo troverò più difficile da leggere. È tutta una questione di preferenze personali e in realtà non aiuta con la domanda qui a portata di mano.
ereOn

Risposte:


281

La prima cosa è che non usi una tale struttura dati. Se hai bisogno di una matrice tridimensionale, ne definisci 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 ];
    }
};

O se vuoi indicizzare usando [][][], hai bisogno di un operator[] che restituisce un proxy.

Dopo averlo fatto, se scopri che devi costantemente iterare come hai presentato, esponi un iteratore che lo supporterà:

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

Quindi scrivi semplicemente:

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

(o semplicemente:

for ( auto& elem: m ) {
}

se hai C ++ 11.)

E se hai bisogno dei tre indici durante tali iterazioni, è possibile creare un iteratore che li esponga:

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

21
Questa risposta dovrebbe essere molto più votata in quanto è l'unica che si occupa dell'effettiva fonte del problema.
ere L'

5
potrebbe essere una risposta corretta ma non sono d'accordo che sia buona. un sacco di codici modello criptici con un tempo di compilazione probabilmente x10 volte lento e probabilmente un debug lento x10 (potrebbe essere più). Per me sicuramente il codice originale è molto più chiaro per me ...
Gorkem

10
@beehorf ... e anche molto, molto più lento. Perché gli array multidimensionali in C e C ++ sono in realtà array annidati nel senso che le dimensioni esterne memorizzano i puntatori agli array annidati. Questi array annidati vengono quindi sparsi arbitrariamente nella memoria, eliminando efficacemente qualsiasi precaricamento e memorizzazione nella cache. Conosco esempi in cui qualcuno ha scritto un codice usando vector<vector<vector<double> > >per rappresentare un campo tridimensionale. La riscrittura del codice equivalente alla soluzione sopra ha portato a un aumento di velocità di 10.
Michael Wild

5
@beehorf Dove vedi il codice del modello? (In pratica, Matrix3Dprobabilmente dovrebbe essere un modello, ma è un modello molto semplice.) E devi solo eseguire il debug Matrix3D, non ogni volta che hai bisogno di una matrice 3D, quindi risparmi un'enorme quantità di tempo nel debug. Per quanto riguarda la chiarezza: come è std::vector<std::vector<std::vector<int>>>più chiaro di Matrix3D? Senza contare che Matrix3Drafforza il fatto che hai una matrice, mentre i vettori annidati potrebbero essere irregolari e che quanto sopra è probabilmente significativamente più veloce.
James Kanze

10
@MichaelWild Ma, naturalmente, il vero vantaggio del mio approccio è che puoi cambiare la rappresentazione, a seconda di ciò che è più veloce nel tuo ambiente, senza dover modificare alcun codice client. La chiave per una buona prestazione è un corretto incapsulamento, in modo che tu possa apportare le modifiche che il profiler dice che ti servono senza dover riscrivere l'intera applicazione.
James Kanze

44

Usare una macro per nascondere i forloop può creare confusione, solo per salvare pochi caratteri. Userei gamma-for loop invece:

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

Ovviamente puoi sostituire auto&con const auto&se non stai, infatti, modificando i dati.


3
Supponendo che OP possa usare C ++ 11.
Jarod42

1
@herohuyongtao Nel caso degli iteratori. Il che potrebbe essere più idiomatico qui, ma ci sono casi in cui vorresti le tre intvariabili.
James Kanze

1
E non dovrebbe essere così do_something_on_A(*j)?
James Kanze

1
@ Jefffrey Ah, sì. Un altro motivo per precisare il tipo. (Immagino che l'uso di autofor ke ipossa essere giustificato. Tranne che risolve ancora il problema al livello sbagliato; il vero problema è che sta usando i vettori annidati.)
James Kanze

2
@Dhara kè un intero vettore di vettori (beh, un riferimento ad esso), non un indice.
Yakk - Adam Nevraumont

21

Qualcosa di simile può aiutare:

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

Per renderlo N-ary abbiamo bisogno di qualche template magic. Prima di tutto dovremmo creare la struttura SFINAE per distinguere se questo valore o contenitore. L'implementazione predefinita per i valori e le specializzazioni per gli array e ciascuno dei tipi di contenitore. Come nota @Zeta, possiamo determinare i contenitori standard in base al iteratortipo annidato (idealmente dovremmo verificare se il tipo può essere utilizzato con range-base foro meno).

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

L'implementazione di for_eachè semplice. La funzione predefinita chiamerà 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);
 }

E la specializzazione si chiamerà ricorsivamente:

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

E voilà:

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

Anche questo non funzionerà per i puntatori (array allocati nell'heap).


@herohuyongtao con vincoli possiamo implementare due specializzazioni per Containere per gli altri.
fasked l'

1
@herohuyongtao Ho fatto un esempio di K-ary foreach.
fasked l'

1
@fasked: usa is_container : has_iterator<T>::valuedalla mia risposta e non è necessario scrivere una specializzazione per ogni tipo, poiché ogni contenitore dovrebbe avere un iteratortypedef. Sentiti libero di usare completamente qualsiasi cosa dalla mia risposta, la tua è già migliore.
Zeta

@Zeta +1 per questo. Inoltre, come ho detto, il Containerconcetto aiuterà.
fasked l'

::iteratornon fa una gamma iterabile. int x[2][3][4]è perfettamente iterabile, in quanto struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; non sono sicuro di cosa T[]dovrebbe fare la specializzazione?
Yakk - Adam Nevraumont

17

La maggior parte delle risposte dimostra semplicemente come C ++ possa essere trasformato in estensioni sintattiche incomprensibili, IMHO.

Definendo qualsiasi modello o macro, costringi gli altri programmatori a comprendere i bit di codice offuscato progettati per nascondere altri bit di codice offuscato.
Costringerai ogni persona che legge il tuo codice ad avere esperienza con i modelli, solo per evitare di fare il tuo lavoro di definizione di oggetti con una semantica chiara.

Se hai deciso di utilizzare dati grezzi come array tridimensionali, vivici semplicemente oppure definisci una classe che dia un significato comprensibile ai tuoi dati.

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

è semplicemente coerente con la definizione criptica di un vettore di vettore di vettore di int senza semantica esplicita.


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

AGGIORNAMENTO: Lo so, che l'hai chiesto, ma faresti meglio a non usarlo :)


5
So che è quello che chiedeva l'OP, ma seriamente ... Sembra un meraviglioso esempio di offuscamento. Supponendo che siano TRIPLE_FORstati definiti in qualche header, cosa devo pensare quando vedo `TRIPLE_FOR qui.
James Kanze

2
Sì, immagino, hai ragione :) Penso, lo lascerò qui solo come esempio che questo può essere fatto usando una macro, ma aggiungi una nota che è meglio non farlo :) Mi sono appena svegliato e ha deciso di usare questa domanda come un piccolo riscaldamento per la mente.
FreeNickname

5

Un'idea è scrivere una classe pseudo-contenitore iterabile che "contenga" l'insieme di tutte le tuple multi-indice su cui indicizzare. Nessuna implementazione qui perché ci vorrà troppo tempo ma l'idea è che dovresti essere in grado di scrivere ...

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

migliore risposta qui imo.
davidhigh

4

Vedo molte risposte qui che funzionano in modo ricorsivo, rilevando se l'input è un contenitore o meno. Invece, perché non rilevare se il livello corrente è dello stesso tipo della funzione? È molto più semplice e consente funzioni più potenti:

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

Tuttavia, questo (ovviamente) ci dà errori di ambiguità. Quindi usiamo SFINAE per rilevare se l'ingresso corrente si adatta o meno alla funzione

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

Questo ora gestisce correttamente i contenitori, ma il compilatore lo considera ancora ambiguo per input_types che possono essere passati alla funzione. Quindi usiamo un trucco standard C ++ 03 per fare in modo che preferisca la prima funzione sulla seconda, passando anche uno zero e facendo in modo che quella che preferiamo accetti e int, e l'altra prenda ...

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

Questo è tutto. Sei righe di codice relativamente semplici e puoi iterare su valori, righe o qualsiasi altra sottounità, a differenza di tutte le altre risposte.

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

Prova di compilazione ed esecuzione qui e qui

Se desideri una sintassi più conveniente in C ++ 11, puoi aggiungere una macro. (Il seguito non è stato testato)

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

3

Avvertisco questa risposta con la seguente dichiarazione: funzionerebbe solo se operassi su un array reale - non funzionerebbe per il tuo esempio usandostd::vector .

Se stai eseguendo la stessa operazione su ogni elemento di un array multidimensionale, senza preoccuparti della posizione di ogni elemento, puoi sfruttare il fatto che gli array sono collocati in posizioni di memoria contigue e trattare il tutto come uno grande matrice unidimensionale. Ad esempio, se volessimo moltiplicare ogni elemento per 2,0 nel secondo esempio:

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

Si noti che l'utilizzo dell'approccio di cui sopra consente anche l'uso di alcune tecniche C ++ "appropriate":

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

In genere non consiglio questo approccio (preferendo qualcosa come la risposta di Jefffrey), poiché si basa sull'avere dimensioni definite per i tuoi array, ma in alcuni casi può essere utile.



@ecatmur: Interessante - Sono appena entrato al lavoro, quindi controllerò e aggiornerò / cancellerò la risposta di conseguenza. Grazie.
icabod

@ecatmur: ho esaminato lo standard C ++ 11 (sezione 8.3.4) e quello che ho scritto dovrebbe funzionare e non sembra illegale (a me). Il collegamento fornito si riferisce all'accesso a membri al di fuori della dimensione dell'array definita. Anche se è vero che ottengo l'indirizzo appena dopo l'array, non sta accedendo ai dati - questo è per fornire una "fine", nello stesso modo in cui puoi usare i puntatori come iteratori, con "fine" che è uno dopo l'ultimo elemento.
icabod

Stai effettivamente accedendo B[0][0][i]a i >= 3; questo non è consentito poiché accede all'esterno dell'array (interno).
ecatmur

1
Un modo più chiaro IMO di assegnare la fine SE dovessi farlo è end = start + (xSize * ySize * zSize)
noggin182

2

Ero un po 'scioccato dal fatto che nessuno avesse proposto un ciclo basato sulla magia aritmetica per fare il lavoro. Dato che C. Wang sta cercando una soluzione senza loop annidati , ne proporrò 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;
}

Bene, questo approccio non è elegante e flessibile, quindi potremmo raggruppare tutto il processo in una funzione modello:

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

Questa funzione modello può essere espressa anche sotto forma di cicli annidati:

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

E può essere utilizzato fornendo un array 3D di dimensioni arbitrarie più il nome della funzione, lasciando che la deduzione del parametro faccia il duro lavoro di contare la dimensione di ciascuna dimensione:

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

Verso più generico

Ma ancora una volta, manca di flessibilità perché funziona solo per array 3D, ma usando SFINAE possiamo fare il lavoro per array di una dimensione arbitraria, prima abbiamo bisogno di una funzione template che itera array di rango 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);
    }
}

E un altro che itera array di qualsiasi rango, eseguendo la ricorsione:

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

Questo ci permette di iterare tutti gli elementi in tutte le dimensioni di un array di dimensioni arbitrarie.


Lavorando con std::vector

Per il vettore annidato multiplo, la soluzione riassembla quella di un array di dimensioni arbitrarie, ma senza SFINAE: per prima cosa avremo bisogno di una funzione modello che itera se std::vectorchiama la funzione desiderata:

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

E un'altra funzione modello che itera qualsiasi tipo di vettore di vettori e si chiama:

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

Indipendentemente dal livello di annidamento, iterate_allchiamerà la versione del vettore di vettori a meno che la versione del vettore di valori non sia una corrispondenza migliore, ponendo così fine alla ricorsività.

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

Penso che il corpo della funzione sia piuttosto semplice e diretto ... Mi chiedo se il compilatore possa srotolare questi cicli (sono quasi sicuro che la maggior parte dei compilatori potrebbe srotolare il primo esempio).

Guarda la demo live qui .

Spero che sia d'aiuto.


1

Usa qualcosa del genere (il suo pseudo-codice, ma l'idea rimane la stessa). Si estrae il modello per eseguire il ciclo una volta e si applica una funzione diversa ogni volta.

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)

1

Attenersi ai cicli for annidati!

Tutti i metodi qui suggeriti presentano degli svantaggi in termini di leggibilità o flessibilità.

Cosa succede se è necessario utilizzare i risultati di un ciclo interno per l'elaborazione nel ciclo esterno? Cosa succede se hai bisogno di un valore dal ciclo esterno all'interno del tuo ciclo interno? La maggior parte dei metodi di "incapsulamento" falliscono qui.

Credetemi, ho visto diversi tentativi di "ripulire" i cicli for nidificati e alla fine risulta che il ciclo annidato è in realtà la soluzione più pulita e flessibile.


0

Una tecnica che ho usato sono i modelli. Per esempio:

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
}

Quindi chiami semplicemente il do_something_on_A(A)tuo codice principale. La funzione template viene creata una volta per ogni dimensione, la prima volta con T = std::vector<std::vector<int>>, la seconda volta conT = std::vector<int> .

Puoi renderlo più generico usando std::function(o oggetti simili a funzioni in C ++ 03) come secondo argomento se vuoi:

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

Quindi chiamalo come:

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

Funziona anche se le funzioni hanno la stessa firma perché la prima funzione è una corrispondenza migliore per qualsiasi cosa con std::vectornel tipo.


0

È possibile generare indici in un ciclo come questo (A, B, C sono dimensioni):

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

Sono d'accordo con te, è progettato specificamente per 3 dimensioni;)
janek

1
Per non parlare del fatto che è incredibilmente lento!
noggin182

@ noggin182: la domanda non era sulla velocità ma sull'evitare i cicli for annidati; inoltre, ci sono divisioni non necessarie lì, i / (B * C) può essere sostituito da a
janek

Ok, questo è un modo alternativo, probabilmente più efficiente (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

Una cosa che potresti voler provare se hai solo istruzioni nel ciclo più interno - e la tua preoccupazione è più sulla natura eccessivamente prolissa del codice - è usare uno schema di spazi bianchi diverso. Questo funzionerà solo se puoi dichiarare i tuoi cicli for in modo sufficientemente compatto in modo che si adattino tutti su una riga.

Per il tuo primo esempio, lo riscriverei come:

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

Questo è un po 'spinto perché stai chiamando funzioni nei cicli esterni che equivale a inserire istruzioni in essi. Ho rimosso tutti gli spazi bianchi non necessari e potrebbe essere passabile.

Il secondo esempio è molto migliore:

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

Questa potrebbe essere una convenzione di spazi bianchi diversa da quella che si desidera utilizzare, ma raggiunge un risultato compatto che tuttavia non richiede alcuna conoscenza oltre C / C ++ (come le convenzioni macro) e non richiede alcun trucco come le macro.

Se vuoi davvero una macro, puoi fare un ulteriore passo avanti con qualcosa come:

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

che cambierebbe il secondo esempio in:

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

e anche il primo esempio se la cava meglio:

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

Si spera che tu possa dire abbastanza facilmente quali dichiarazioni vanno con quali dichiarazioni. Inoltre, fai attenzione alle virgole, ora non puoi usarle in una singola clausola di nessuna delle fors.


1
La leggibilità di questi è orribile. Jamming più di un forloop su una linea non lo rende più leggibile, lo rende meno .

0

Ecco un'implementazione C ++ 11 che gestisce tutto l'iterabile. Altre soluzioni si limitano a contenitori con ::iteratortypedef o array: ma afor_each riguarda l'iterazione, non l'essere un contenitore.

Isolo anche lo SFINAE in un unico punto nel file is_iterable tratto. L'invio (tra elementi e iterabili) avviene tramite l'invio di tag, che trovo sia una soluzione più chiara.

I contenitori e le funzioni applicate agli elementi sono tutti perfettamente inoltrati, consentendo sia l' accesso che il constnon constaccesso alle gamme e ai funtori.

#include <utility>
#include <iterator>

La funzione template che sto implementando. Tutto il resto potrebbe andare in uno spazio dei nomi dei dettagli:

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

L'invio di tag è molto più pulito di SFINAE. Questi due sono usati rispettivamente per oggetti iterabili e non iterabili. L'ultima iterazione del primo potrebbe utilizzare l'inoltro perfetto, ma sono pigro:

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

Questo è un boilerplate richiesto per scrivere is_iterable. Eseguo una ricerca dipendente dall'argomento in begine endin uno spazio dei nomi di dettaglio. Questo emula ciò che un for( auto x : y )ciclo fa ragionevolmente bene:

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;

Il TypeSinkè utile per verificare se il codice è valido. Fai TypeSink< decltype(codice ) >e se codeè valido, l'espressione è void. Se il codice non è valido, SFINAE interviene e la specializzazione viene bloccata:

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

Provo solo per begin. Si adl_endpotrebbe anche fare un test.

L'implementazione finale di for_each_flatfinisce per essere estremamente semplice:

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

Esempio dal vivo

Questo è in fondo in fondo: sentiti libero di braccare per le risposte migliori, che sono solide. Volevo solo utilizzare alcune tecniche migliori!


-2

In primo luogo, non dovresti usare un vettore di vettori di vettori. Ogni vettore è garantito per avere memoria contigua, ma la memoria "globale" di un vettore di vettori non lo è (e probabilmente non lo sarà). Dovresti usare l'array del tipo di libreria standard invece degli array in stile 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]);
        }
    }
}

Meglio ancora, però, potresti definire una semplice classe di 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;
}

Potresti andare oltre e renderlo completamente const-corretto, aggiungere la moltiplicazione di matrici (corretta e per elemento), moltiplicazione per vettori, ecc. .

Puoi anche aggiungere oggetti proxy in modo da poter eseguire B [i] o B [i] [j]. Potrebbero restituire vettori (in senso matematico) e matrici piene di doppie &, potenzialmente?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.