Pour un tableau à plusieurs dimensions, nous devons généralement écrire une for
boucle 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-for
fréquemment ce genre de boucles dans notre code. Comment utiliser des macros pour définir les for-for-for
boucles afin de ne pas avoir à réécrire ce type de code à chaque fois? Y a-t-il une meilleure manière de faire cela?
O(n) = n^3
code potentiel ...Réponses:
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:
Ou si vous souhaitez indexer en utilisant
[][][]
, vous avez besoin d'unoperator[]
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:
Ensuite, vous écrivez simplement:
(ou juste:
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:
la source
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.Matrix3D
devrait probablement être un modèle, mais c'est un modèle très simple.) Et vous n'avez qu'à déboguerMatrix3D
, 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-cestd::vector<std::vector<std::vector<int>>>
plus clair queMatrix3D
? Sans oublier que celaMatrix3D
renforce 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.Utiliser une macro pour masquer les
for
boucles peut être très déroutant, juste pour enregistrer quelques caractères. J'utiliserais plutôt des boucles range-for :Bien sûr, vous pouvez remplacer
auto&
parconst auto&
si vous ne modifiez pas les données.la source
int
variables.do_something_on_A(*j)
?auto
fork
eti
peut ê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.)k
est un vecteur entier de vecteurs (enfin une référence à celui-ci), pas un index.Quelque chose comme ça peut aider:
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
iterator
type imbriqué (idéalement, nous devrions vérifier si le type peut être utilisé avec range-basefor
ou non).La mise en œuvre de
for_each
est simple. La fonction par défaut appellerafunction
:Et la spécialisation s'appellera récursivement:
Et voila:
De plus, cela ne fonctionnera pas pour les pointeurs (tableaux alloués dans le tas).
la source
Container
et pour les autres.is_container : has_iterator<T>::value
de ma réponse et vous n'avez pas besoin d'écrire une spécialisation pour chaque type, car chaque conteneur doit avoir uniterator
typedef. N'hésitez pas à utiliser complètement n'importe quoi de ma réponse, la vôtre est déjà meilleure.Container
concept aidera.::iterator
ne fait pas une plage itérable.int x[2][3][4]
est parfaitement itérable, carstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
je ne suis pas sûr de ce que laT[]
spécialisation est censée faire?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.
est juste cohérent avec la définition cryptique d'un vecteur de vecteur de vecteur de int sans sémantique explicite.
la source
MISE À JOUR: Je sais que vous l'avez demandé, mais vous feriez mieux de ne pas l'utiliser :)
la source
TRIPLE_FOR
aient été définis dans un en-tête, que dois-je penser quand je vois `TRIPLE_FOR ici.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 ...
la source
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:
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
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 ...
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.
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é)
la source
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:
Notez que l'utilisation de l'approche ci-dessus permet également d'utiliser certaines techniques C ++ «appropriées»:
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.
la source
B[0][0][i]
pouri >= 3
; ceci n'est pas autorisé car il accède à l'extérieur du tableau (interne).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:Eh bien, cette approche n'est ni élégante ni flexible, nous pourrions donc regrouper tout le processus dans une fonction de modèle:
Cette fonction de modèle peut également être exprimée sous la forme de boucles imbriquées:
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:
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:
Et un autre qui itère des tableaux de n'importe quel rang, faisant la récursion:
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::vector
s et appelle la fonction souhaitée:Et une autre fonction de modèle qui itère tout type de vecteur de vecteurs et s'appelle lui-même:
Quel que soit le niveau d'imbrication,
iterate_all
appellera la version de vecteur de vecteurs à moins que la version de vecteur de valeurs soit une meilleure correspondance, mettant ainsi fin à la récursivité.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.
la source
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.
la source
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.
la source
Une technique que j'ai utilisée est celle des modèles. Par exemple:
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 avecT = std::vector<std::vector<int>>
, la deuxième fois avecT = 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:Alors appelez-le comme:
Cela fonctionne même si les fonctions ont la même signature car la première fonction correspond mieux à tout ce qui est
std::vector
dans le type.la source
Vous pouvez générer des indices dans une boucle comme celle-ci (A, B, C sont des dimensions):
la source
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:
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:
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:
ce qui changerait le deuxième exemple en:
et le premier exemple s'en sort mieux aussi:
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
for
s.la source
for
boucle sur une ligne ne la rend pas plus lisible, mais la rend moins .Voici une implémentation C ++ 11 qui gère tout ce qui est itérable. D'autres solutions se limitent aux conteneurs avec des
::iterator
typedefs ou des tableaux: mais afor_each
est 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
const
et le nonconst
aux gammes et foncteurs.La fonction de modèle que j'implémente. Tout le reste pourrait entrer dans un espace de noms de détails:
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:
Ceci est un passe-partout requis pour écrire
is_iterable
. Je fais une recherche dépendante des arguments surbegin
etend
dans un espace de noms de détail. Cela émule ce qu'unefor( auto x : y )
boucle fait raisonnablement bien:Le
TypeSink
est utile pour tester si le code est valide. Vous faites duTypeSink< decltype(
code) >
et si lecode
est valide, l'expression l'estvoid
. Si le code n'est pas valide, SFINAE entre en action et la spécialisation est bloquée:Je teste seulement pour
begin
. Unadl_end
test pourrait également être fait.La mise en œuvre finale de
for_each_flat
finit par être extrêmement simple: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!
la source
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.
Mieux encore, vous pouvez définir une classe de matrice 3D simple:
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?
la source