Des moyens propres d'écrire plusieurs boucles 'for'

98

Pour un tableau à plusieurs dimensions, nous devons généralement écrire une forboucle pour chacune de ses dimensions. Par exemple:

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

Vous voyez for-for-forfréquemment ce genre de boucles dans notre code. Comment utiliser des macros pour définir les for-for-forboucles afin de ne pas avoir à réécrire ce type de code à chaque fois? Y a-t-il une meilleure manière de faire cela?

C. Wang
la source
62
La réponse évidente est que non. Vous ne créez pas un nouveau langage en utilisant des macros (ou toute autre technique); la personne qui vient après vous ne pourra pas lire le code.
James Kanze
17
Lorsque vous avez un vecteur d'un vecteur d'un vecteur, c'est le signe d'une mauvaise conception.
Maroun
5
@Nim: Vous pouvez le faire avec 1 tableau plat (pas sûr que ce soit mieux).
Jarod42
16
Je pense que vous ne voudriez pas cacher un O(n) = n^3code potentiel ...
poy
36
@ TC1: Et puis je trouverai plus difficile à lire. Tout est une question de préférences personnelles et cela n'aide en fait pas à la question qui se pose ici.
ereOn

Réponses:

281

La première chose est que vous n'utilisez pas une telle structure de données. Si vous avez besoin d'une matrice tridimensionnelle, vous en définissez une:

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

Ou si vous souhaitez indexer en utilisant [][][], vous avez besoin d'un operator[] qui renvoie un proxy.

Une fois que vous avez fait cela, si vous constatez que vous devez constamment itérer comme vous l'avez présenté, vous exposez un itérateur qui le supportera:

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

Ensuite, vous écrivez simplement:

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

(ou juste:

for ( auto& elem: m ) {
}

si vous avez C ++ 11.)

Et si vous avez besoin des trois index lors de telles itérations, il est possible de créer un itérateur qui les expose:

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
la source
21
Cette réponse devrait être beaucoup plus favorisée car c'est la seule qui traite de la source réelle du problème.
ereOn
5
c'est peut-être une bonne réponse, mais je ne suis pas d'accord pour dire que c'est une bonne réponse. beaucoup de codes de modèles cryptiques avec probablement un temps de compilation x10 fois lent et probablement un code de débogage lent x10 (peut-être plus). Pour moi, le code original est
définitivement
10
@beehorf ... et aussi beaucoup, beaucoup plus lentement. Parce que les tableaux multidimensionnels en C et C ++ sont en fait des tableaux imbriqués dans le sens où les dimensions externes stockent des pointeurs vers les tableaux imbriqués. Ces tableaux imbriqués sont ensuite arbitrairement dispersés dans la mémoire, annulant efficacement toute prélecture et mise en cache. Je connais des exemples où quelqu'un a écrit un code en utilisant vector<vector<vector<double> > >pour représenter un champ en 3 dimensions. La réécriture du code l'équivalent de la solution ci-dessus a entraîné une accélération de 10.
Michael Wild
5
@beehorf Où voyez-vous un code de modèle? (En pratique, cela Matrix3Ddevrait probablement être un modèle, mais c'est un modèle très simple.) Et vous n'avez qu'à déboguer Matrix3D, pas à chaque fois que vous avez besoin d'une matrice 3D, vous économisez donc énormément de temps dans le débogage. Quant à la clarté: comment est-ce std::vector<std::vector<std::vector<int>>>plus clair que Matrix3D? Sans oublier que cela Matrix3Drenforce le fait que vous avez une matrice, alors que les vecteurs imbriqués pourraient être irréguliers, et que ce qui précède est probablement beaucoup plus rapide.
James Kanze
10
@MichaelWild Mais bien sûr, le véritable avantage de mon approche est que vous pouvez changer la représentation, en fonction de ce qui est le plus rapide dans votre environnement, sans avoir à modifier le code client. La clé d'une bonne performance est une encapsulation appropriée, afin que vous puissiez apporter les modifications dont le profileur dit avoir besoin sans avoir à réécrire l'ensemble de l'application.
James Kanze
44

Utiliser une macro pour masquer les forboucles peut être très déroutant, juste pour enregistrer quelques caractères. J'utiliserais plutôt des boucles range-for :

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

Bien sûr, vous pouvez remplacer auto&par const auto&si vous ne modifiez pas les données.

Chaussure
la source
3
En supposant qu'OP peut utiliser C ++ 11.
Jarod42
1
@herohuyongtao Dans le cas des itérateurs. Ce qui peut être plus idiomatique ici, mais il y a des cas où vous voudriez les trois intvariables.
James Kanze
1
Et ça ne devrait pas être do_something_on_A(*j)?
James Kanze
1
@Jefffrey Ah, oui. Une autre raison pour épeler le type. (Je suppose que l'utilisation de autofor ket ipeut être justifiée. Sauf que cela résout toujours le problème au mauvais niveau; le vrai problème est qu'il utilise les vecteurs imbriqués.)
James Kanze
2
@Dhara kest un vecteur entier de vecteurs (enfin une référence à celui-ci), pas un index.
Yakk - Adam Nevraumont
21

Quelque chose comme ça peut aider:

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

Afin de le rendre N-aire, nous avons besoin d'un peu de magie de modèle. Tout d'abord, nous devons créer une structure SFINAE pour distinguer si cette valeur ou ce conteneur. Implémentation par défaut pour les valeurs et spécialisations pour les tableaux et chacun des types de conteneurs. Comme le note @Zeta, nous pouvons déterminer les conteneurs standard par le iteratortype imbriqué (idéalement, nous devrions vérifier si le type peut être utilisé avec range-base forou non).

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

La mise en œuvre de for_eachest simple. La fonction par défaut appellera 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);
 }

Et la spécialisation s'appellera récursivement:

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

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

De plus, cela ne fonctionnera pas pour les pointeurs (tableaux alloués dans le tas).

fasked
la source
@herohuyongtao avec des contraintes nous pouvons implémenter deux spécialisations pour Containeret pour les autres.
fasked le
1
@herohuyongtao J'ai fait un exemple de K-ary foreach.
fasked le
1
@fasked: Utilisez is_container : has_iterator<T>::valuede ma réponse et vous n'avez pas besoin d'écrire une spécialisation pour chaque type, car chaque conteneur doit avoir un iteratortypedef. N'hésitez pas à utiliser complètement n'importe quoi de ma réponse, la vôtre est déjà meilleure.
Zeta
@Zeta +1 pour cela. Aussi, comme je l'ai mentionné, le Containerconcept aidera.
fasked le
::iteratorne fait pas une plage itérable. int x[2][3][4]est parfaitement itérable, car struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; je ne suis pas sûr de ce que la T[]spécialisation est censée faire?
Yakk - Adam Nevraumont
17

La plupart des réponses démontrent simplement comment C ++ peut être tordu en extensions syntaxiques incompréhensibles, à mon humble avis.

En définissant des modèles ou des macros, vous forcez simplement les autres programmeurs à comprendre des bits de code obscurci conçus pour cacher d'autres bits de code obscurci.
Vous obligerez chaque personne qui lit votre code à avoir une expertise en matière de modèle, juste pour éviter de faire votre travail de définition d'objets avec une sémantique claire.

Si vous avez décidé d'utiliser des données brutes comme des tableaux en 3 dimensions, vivez avec, ou définissez une classe qui donne une signification compréhensible à vos données.

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

est juste cohérent avec la définition cryptique d'un vecteur de vecteur de vecteur de int sans sémantique explicite.

kuroi neko
la source
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;
}

MISE À JOUR: Je sais que vous l'avez demandé, mais vous feriez mieux de ne pas l'utiliser :)

FreeNickname
la source
5
Je sais que c'est ce que l'OP a demandé, mais sérieusement ... Cela ressemble à un merveilleux exemple d'obscurcissement. Supposons qu'ils TRIPLE_FORaient été définis dans un en-tête, que dois-je penser quand je vois `TRIPLE_FOR ici.
James Kanze
2
Oui, je suppose que vous avez raison :) Je pense, je vais laisser ici juste un exemple que cela peut être fait en utilisant des macros, mais ajoutez une note qu'il vaut mieux ne pas faire comme ça :) Je viens de me réveiller et a décidé d'utiliser cette question comme un petit échauffement pour l'esprit.
FreeNickname
5

Une idée est d'écrire une classe pseudo-conteneur itérable qui "contient" l'ensemble de tous les tuples multi-index sur lesquels vous indexerez. Aucune implémentation ici car cela prendra trop de temps mais l'idée est que vous devriez être capable d'écrire ...

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
la source
meilleure réponse ici imo.
davidhigh
4

Je vois de nombreuses réponses ici qui fonctionnent de manière récursive, détectant si l'entrée est un conteneur ou non. Au lieu de cela, pourquoi ne pas détecter si la couche actuelle est du même type que la fonction? C'est beaucoup plus simple et permet des fonctions plus puissantes:

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

Cependant, cela nous donne (évidemment) des erreurs d'ambiguïté. Nous utilisons donc SFINAE pour détecter si l'entrée actuelle s'inscrit dans la fonction ou non

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

Cela gère maintenant correctement les conteneurs, mais le compilateur considère toujours cela comme ambigu pour les types d'entrée qui peuvent être passés à la fonction. Nous utilisons donc une astuce C ++ 03 standard pour lui faire préférer la première fonction à la seconde, en passant également un zéro, et en faisant celle que nous préférons accepter et int, et l'autre prend ...

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

C'est tout. Six lignes de code relativement simples et vous pouvez parcourir des valeurs, des lignes ou toute autre sous-unité, contrairement à toutes les autres réponses.

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

Preuve de compilation et d'exécution ici et ici

Si vous souhaitez une syntaxe plus pratique en C ++ 11, vous pouvez ajouter une macro. (Ce qui suit n'est pas testé)

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 << ", ";
     }
}
Canard meunier
la source
3

Je préviens cette réponse avec la déclaration suivante: cela ne fonctionnerait que si vous utilisiez un tableau réel - cela ne fonctionnerait pas pour votre exemple en utilisant std::vector.

Si vous effectuez la même opération sur chaque élément d'un tableau multidimensionnel, sans vous soucier de la position de chaque élément, vous pouvez profiter du fait que les tableaux sont placés dans des emplacements de mémoire contigus et traiter le tout comme un seul. grand tableau unidimensionnel. Par exemple, si nous voulions multiplier chaque élément par 2,0 dans votre deuxième exemple:

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

Notez que l'utilisation de l'approche ci-dessus permet également d'utiliser certaines techniques C ++ «appropriées»:

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

Je ne conseille généralement pas cette approche (préférant quelque chose comme la réponse de Jefffrey), car elle repose sur des tailles définies pour vos tableaux, mais dans certains cas, cela peut être utile.

icabod
la source
@ecatmur: Intéressant - Je viens juste de me mettre au travail, alors je vais vérifier cela et mettre à jour / supprimer la réponse en conséquence. Merci.
icabod
@ecatmur: J'ai regardé le standard C ++ 11 (section 8.3.4), et ce que j'ai écrit devrait fonctionner et ne semble pas illégal (pour moi). Le lien que vous avez fourni concerne l'accès aux membres en dehors de la taille de tableau définie. S'il est vrai que j'obtiens l'adresse juste après le tableau, cela n'accède pas aux données - c'est pour fournir une "fin", de la même manière que vous pouvez utiliser des pointeurs comme itérateurs, avec "fin" étant un passé le dernier élément.
icabod
Vous accédez efficacement B[0][0][i]pour i >= 3; ceci n'est pas autorisé car il accède à l'extérieur du tableau (interne).
ecatmur
1
Une manière plus claire pour l'OMI d'attribuer la fin SI vous deviez le faire est end = start + (xSize * ySize * zSize)
noggin182
2

J'étais un peu choqué que personne n'ait proposé une boucle basée sur la magie arithmétique pour faire le travail. Puisque C. Wang recherche une solution sans boucles imbriquées , je vais en proposer une:

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

Eh bien, cette approche n'est ni élégante ni flexible, nous pourrions donc regrouper tout le processus dans une fonction de modèle:

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

Cette fonction de modèle peut également être exprimée sous la forme de boucles imbriquées:

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

Et peut être utilisé en fournissant un tableau 3D de taille arbitraire plus le nom de la fonction, laissant la déduction de paramètre faire le dur travail de compter la taille de chaque dimension:

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

Vers plus générique

Mais encore une fois, cela manque de flexibilité car cela ne fonctionne que pour les tableaux 3D, mais en utilisant SFINAE nous pouvons faire le travail pour des tableaux d'une dimension arbitraire, nous avons d'abord besoin d'une fonction de modèle qui itère des tableaux de 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);
    }
}

Et un autre qui itère des tableaux de n'importe quel rang, faisant la récursion:

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

Cela nous permet d'itérer tous les éléments dans toutes les dimensions d'un tableau de dimensions arbitraires de taille arbitraire.


Travailler avec std::vector

Pour le vecteur imbriqué multiple, la solution ressemble à celle d'un tableau de taille arbitraire de dimensions arbitraires, mais sans SFINAE: nous aurons d'abord besoin d'une fonction modèle qui itère std::vectors et appelle la fonction souhaitée:

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

Et une autre fonction de modèle qui itère tout type de vecteur de vecteurs et s'appelle lui-même:

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

Quel que soit le niveau d'imbrication, iterate_allappellera la version de vecteur de vecteurs à moins que la version de vecteur de valeurs soit une meilleure correspondance, mettant ainsi fin à la récursivité.

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

Je pense que le corps de la fonction est assez simple et direct ... Je me demande si le compilateur pourrait dérouler ces boucles (je suis presque sûr que la plupart des compilateurs pourraient dérouler le premier exemple).

Voir la démo en direct ici .

J'espère que ça aide.

PaperBirdMaster
la source
1

Utilisez quelque chose de ce genre (son pseudo-code, mais l'idée reste la même). Vous extrayez le motif en boucle une fois et appliquez une fonction différente à chaque fois.

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
la source
1

Tenez-vous en aux boucles for imbriquées!

Toutes les méthodes proposées ici présentent des inconvénients en termes de lisibilité ou de flexibilité.

Que se passe-t-il si vous devez utiliser les résultats d'une boucle interne pour le traitement dans la boucle externe? Que se passe-t-il si vous avez besoin d'une valeur de la boucle externe dans votre boucle interne? La plupart des méthodes "d'encapsulation" échouent ici.

Croyez-moi, j'ai vu plusieurs tentatives de «nettoyage» des boucles for imbriquées et à la fin il s'avère que la boucle imbriquée est en fait la solution la plus propre et la plus flexible.

James Anderson
la source
0

Une technique que j'ai utilisée est celle des modèles. Par exemple:

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
}

Ensuite, vous appelez simplement do_something_on_A(A)votre code principal. La fonction de modèle est créée une fois pour chaque dimension, la première fois avec T = std::vector<std::vector<int>>, la deuxième fois avec T = std::vector<int>.

Vous pouvez rendre cela plus générique en utilisant std::function(ou des objets de type fonction en C ++ 03) comme deuxième argument si vous le souhaitez:

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

Alors appelez-le comme:

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

Cela fonctionne même si les fonctions ont la même signature car la première fonction correspond mieux à tout ce qui est std::vectordans le type.

JoshG79
la source
0

Vous pouvez générer des indices dans une boucle comme celle-ci (A, B, C sont des dimensions):

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
la source
Je suis d'accord avec vous, il est spécialement conçu pour 3 dimensions;)
janek
1
Sans oublier que c'est incroyablement lent!
noggin182
@ noggin182: la question n'était pas de vitesse mais d'éviter les boucles for imbriquées; de plus, il y a des divisions inutiles là-bas, i / (B * C) peut être remplacé par a
janek
Ok, c'est une autre façon, probablement plus efficace (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

Une chose que vous voudrez peut-être essayer si vous n'avez que des instructions dans la boucle la plus interne - et que vous vous inquiétez davantage de la nature trop verbeuse du code - est d'utiliser un schéma d'espaces différent. Cela ne fonctionnera que si vous pouvez indiquer vos boucles for de manière suffisamment compacte pour qu'elles tiennent toutes sur une seule ligne.

Pour votre premier exemple, je le réécrirais comme suit:

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

C'est un peu le pousser parce que vous appelez des fonctions dans les boucles externes, ce qui équivaut à y placer des instructions. J'ai supprimé tous les espaces blancs inutiles et cela peut être passible.

Le deuxième exemple est bien meilleur:

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

Cela peut être une convention d'espaces différente de celle que vous aimeriez utiliser, mais cela permet d'obtenir un résultat compact qui ne nécessite néanmoins aucune connaissance au-delà de C / C ++ (comme les conventions de macro) et ne nécessite aucune supercherie comme les macros.

Si vous voulez vraiment une macro, vous pouvez alors aller plus loin avec quelque chose comme:

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

ce qui changerait le deuxième exemple en:

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

et le premier exemple s'en sort mieux aussi:

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

J'espère que vous pourrez dire assez facilement quelles déclarations vont avec quelles déclarations. De plus, méfiez-vous des virgules, vous ne pouvez plus les utiliser dans une seule clause de l'un des fors.

Michael
la source
1
La lisibilité de ceux-ci est horrible. Le brouillage de plus d'une forboucle sur une ligne ne la rend pas plus lisible, mais la rend moins .
0

Voici une implémentation C ++ 11 qui gère tout ce qui est itérable. D'autres solutions se limitent aux conteneurs avec des ::iteratortypedefs ou des tableaux: mais a for_eachest une itération, pas un conteneur.

J'isole également le SFINAE à un seul endroit dans le is_iterable trait. La répartition (entre les éléments et les itérables) se fait via la répartition des balises, ce que je trouve être une solution plus claire.

Les conteneurs et les fonctions appliqués aux éléments sont tous parfaitement acheminés, permettant à la fois l' accès constet le non constaux gammes et foncteurs.

#include <utility>
#include <iterator>

La fonction de modèle que j'implémente. Tout le reste pourrait entrer dans un espace de noms de détails:

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

La distribution des tags est beaucoup plus propre que SFINAE. Ces deux sont utilisés respectivement pour les objets itérables et les objets non itérables. La dernière itération de la première pourrait utiliser un transfert parfait, mais je suis paresseux:

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

Ceci est un passe-partout requis pour écrire is_iterable. Je fais une recherche dépendante des arguments sur beginet enddans un espace de noms de détail. Cela émule ce qu'une for( auto x : y )boucle fait raisonnablement bien:

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;

Le TypeSinkest utile pour tester si le code est valide. Vous faites du TypeSink< decltype(code ) >et si le codeest valide, l'expression l'est void. Si le code n'est pas valide, SFINAE entre en action et la spécialisation est bloquée:

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

Je teste seulement pour begin. Un adl_endtest pourrait également être fait.

La mise en œuvre finale de for_each_flatfinit par être extrêmement simple:

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

Exemple en direct

C'est tout en bas: n'hésitez pas à braconner pour les premières réponses, qui sont solides. Je voulais juste que quelques meilleures techniques soient utilisées!

Yakk - Adam Nevraumont
la source
-2

Premièrement, vous ne devez pas utiliser un vecteur de vecteurs de vecteurs. Chaque vecteur est garanti d'avoir une mémoire contiguë, mais la mémoire "globale" d'un vecteur de vecteurs ne l'est pas (et ne le sera probablement pas). Vous devez également utiliser le tableau de type bibliothèque standard au lieu des tableaux de style 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]);
        }
    }
}

Mieux encore, vous pouvez définir une classe de matrice 3D simple:

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

Vous pouvez aller plus loin et le rendre totalement correct, ajouter une multiplication matricielle (propre et élémentaire), une multiplication par vecteurs, etc. Vous pouvez même le généraliser à différents types (je le ferais modèle si vous utilisez principalement des doubles) .

Vous pouvez également ajouter des objets proxy pour pouvoir faire B [i] ou B [i] [j]. Ils pourraient renvoyer des vecteurs (au sens mathématique) et des matrices pleines de double &, potentiellement?

Miles Rout
la source