Für ein Array mit mehreren Dimensionen müssen wir normalerweise for
für jede seiner Dimensionen eine Schleife schreiben . Beispielsweise:
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]);
}
}
}
Sie sehen diese Art von for-for-for
Schleifen häufig in unserem Code. Wie verwende ich Makros, um die for-for-for
Schleifen zu definieren, damit ich diese Art von Code nicht jedes Mal neu schreiben muss? Gibt es einen besseren Weg, dies zu tun?
O(n) = n^3
Code nicht verstecken wollen ...Antworten:
Das erste ist, dass Sie eine solche Datenstruktur nicht verwenden. Wenn Sie eine dreidimensionale Matrix benötigen, definieren Sie eine:
Oder wenn Sie mit indizieren möchten
[][][]
, benötigen Sie einen,operator[]
der einen Proxy zurückgibt.Sobald Sie dies getan haben und feststellen, dass Sie ständig iterieren müssen, wie Sie es präsentiert haben, stellen Sie einen Iterator bereit, der dies unterstützt:
Dann schreibst du einfach:
(oder nur:
wenn Sie C ++ 11 haben.)
Wenn Sie die drei Indizes während solcher Iterationen benötigen, können Sie einen Iterator erstellen, der sie verfügbar macht:
quelle
vector<vector<vector<double> > >
, der ein dreidimensionales Feld darstellt. Das Umschreiben des Codes entsprechend der obigen Lösung führte zu einer Beschleunigung von 10.Matrix3D
sollte es wahrscheinlich eine Vorlage sein, aber es ist eine sehr einfache Vorlage.) Und Sie müssen nur debuggenMatrix3D
, nicht jedes Mal, wenn Sie eine 3D-Matrix benötigen, sodass Sie beim Debuggen enorm viel Zeit sparen. Zur Klarheit: Wie iststd::vector<std::vector<std::vector<int>>>
klarer alsMatrix3D
? Ganz zu schweigen davon, dass diesMatrix3D
die Tatsache erzwingt, dass Sie eine Matrix haben, während die verschachtelten Vektoren zerlumpt sein könnten, und dass die oben genannten wahrscheinlich erheblich schneller sind.Die Verwendung eines Makros zum Ausblenden der
for
Schleifen kann sehr verwirrend sein, nur um wenige Zeichen zu speichern. Ich würde stattdessen Range-for- Schleifen verwenden:Natürlich können Sie ersetzen
auto&
mit ,const auto&
wenn Sie sind in der Tat, die Daten nicht zu ändern.quelle
int
Variablen möchten .do_something_on_A(*j)
?auto
fürk
undi
kann gerechtfertigt sein. Außer, dass es das Problem immer noch auf der falschen Ebene löst; das eigentliche Problem ist, dass er die verschachtelten Vektoren verwendet.)k
ist ein ganzer Vektor von Vektoren (also ein Verweis darauf), kein Index.So etwas kann helfen:
Um es N-ary zu machen, brauchen wir etwas Template-Magie. Zunächst sollten wir eine SFINAE-Struktur erstellen, um zu unterscheiden, ob dieser Wert oder Container. Die Standardimplementierung für Werte und Spezialisierungen für Arrays und jeden Containertyp. Wie @Zeta feststellt, können wir die Standardcontainer anhand des verschachtelten
iterator
Typs bestimmen (idealerweise sollten wir prüfen, ob der Typ mit Range-Base verwendet werden kannfor
oder nicht).Die Implementierung von
for_each
ist unkompliziert. Die Standardfunktion ruft auffunction
:Und die Spezialisierung wird sich rekursiv nennen:
Und voila:
Dies funktioniert auch nicht für Zeiger (im Heap zugewiesene Arrays).
quelle
Container
und für andere implementieren .is_container : has_iterator<T>::value
aus meiner Antwort und Sie müssen nicht für jeden Typ eine Spezialisierung schreiben, da jeder Container einiterator
typedef haben sollte. Fühlen Sie sich frei, alles aus meiner Antwort vollständig zu verwenden, Ihre ist bereits besser.Container
Konzept hilft.::iterator
macht keinen iterierbaren Bereich.int x[2][3][4]
ist perfekt iterierbar, dastruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
ich nicht sicher bin, wasT[]
Spezialisierung tun soll?Die meisten Antworten zeigen lediglich, wie C ++ zu unverständlichen syntaktischen Erweiterungen verdreht werden kann, IMHO.
Indem Sie beliebige Vorlagen oder Makros definieren, zwingen Sie andere Programmierer lediglich, Teile des verschleierten Codes zu verstehen, um andere Teile des verschleierten Codes zu verbergen.
Sie werden jeden, der Ihren Code liest, dazu zwingen, über Vorlagenkenntnisse zu verfügen, nur um zu vermeiden, dass Sie Objekte mit klarer Semantik definieren.
Wenn Sie Rohdaten wie dreidimensionale Arrays verwenden möchten, leben Sie einfach damit oder definieren Sie eine Klasse, die Ihren Daten eine verständliche Bedeutung verleiht.
stimmt nur mit der kryptischen Definition eines Vektors des Vektors des Vektors von int ohne explizite Semantik überein.
quelle
UPDATE: Ich weiß, dass du danach gefragt hast, aber das solltest du besser nicht benutzen :)
quelle
TRIPLE_FOR
, in einem Header wurde definiert, was soll ich denken, wenn ich hier TRIPLE_FOR sehe.Eine Idee besteht darin, eine iterierbare Pseudocontainerklasse zu schreiben, die die Menge aller Multi-Index-Tupel "enthält", über die Sie indizieren. Keine Implementierung hier, da es zu lange dauern wird, aber die Idee ist, dass Sie schreiben können sollten ...
quelle
Ich sehe hier viele Antworten, die rekursiv funktionieren und erkennen, ob die Eingabe ein Container ist oder nicht. Ermitteln Sie stattdessen, ob die aktuelle Ebene vom Typ der Funktion ist. Es ist viel einfacher und ermöglicht leistungsfähigere Funktionen:
Dies führt jedoch (offensichtlich) zu Mehrdeutigkeitsfehlern. Wir verwenden also SFINAE, um festzustellen, ob der Stromeingang in die Funktion passt oder nicht
Dies behandelt die Container jetzt korrekt, aber der Compiler betrachtet dies immer noch als mehrdeutig für input_types, die an die Funktion übergeben werden können. Wir verwenden also einen Standard-C ++ 03-Trick, um die erste Funktion der zweiten vorzuziehen, indem wir auch eine Null übergeben und die eine, die wir bevorzugen, akzeptieren und int, und die andere nimmt ...
Das ist es. Sechs relativ einfache Codezeilen, und Sie können im Gegensatz zu allen anderen Antworten Werte, Zeilen oder andere Untereinheiten durchlaufen.
Nachweis der Zusammenstellung und Ausführung hier und hier
Wenn Sie eine bequemere Syntax in C ++ 11 wünschen, können Sie ein Makro hinzufügen. (Folgendes ist ungetestet)
quelle
Ich beschränke diese Antwort mit der folgenden Aussage: Dies würde nur funktionieren, wenn Sie mit einem tatsächlichen Array arbeiten würden - es würde für Ihr Beispiel mit nicht funktionieren
std::vector
.Wenn Sie für jedes Element eines mehrdimensionalen Arrays dieselbe Operation ausführen, ohne sich um die Position jedes Elements zu kümmern, können Sie die Tatsache nutzen, dass Arrays an zusammenhängenden Speicherorten platziert sind, und das Ganze als eins behandeln großes eindimensionales Array. Wenn wir beispielsweise in Ihrem zweiten Beispiel jedes Element mit 2,0 multiplizieren möchten:
Beachten Sie, dass die Verwendung des obigen Ansatzes auch die Verwendung einiger "richtiger" C ++ - Techniken ermöglicht:
Ich empfehle diesen Ansatz im Allgemeinen nicht ( ich bevorzuge so etwas wie Jefffreys Antwort), da er von definierten Größen für Ihre Arrays abhängt, aber in einigen Fällen kann er nützlich sein.
quelle
B[0][0][i]
zui >= 3
; Dies ist nicht zulässig, da der Zugriff außerhalb des (inneren) Arrays erfolgt.Ich war ein bisschen schockiert, dass niemand eine auf Arithmetik-Magie basierende Schleife vorgeschlagen hat, um die Arbeit zu erledigen.
Da C. Wang nach einer Lösung ohne verschachtelte Schleifen sucht, schlage ich eine vor:Nun, dieser Ansatz ist nicht elegant und flexibel, daher könnten wir den gesamten Prozess in eine Vorlagenfunktion packen:
Diese Vorlagenfunktion kann auch in Form von verschachtelten Schleifen ausgedrückt werden:
Und kann verwendet werden, um ein 3D-Array mit beliebiger Größe und dem Funktionsnamen bereitzustellen, sodass der Parameterabzug die harte Arbeit des Zählens der Größe jeder Dimension erledigt:
Auf dem Weg zu mehr Generika
Aber auch hier mangelt es an Flexibilität, da es nur für 3D-Arrays funktioniert. Mit SFINAE können wir jedoch für Arrays beliebiger Dimension arbeiten. Zuerst benötigen wir eine Vorlagenfunktion, die Arrays mit Rang 1 iteriert :
Und noch eine, die Arrays beliebigen Ranges iteriert und die Rekursion durchführt:
Dies ermöglicht es uns, alle Elemente in allen Dimensionen eines Arrays beliebiger Dimensionen und beliebiger Größe zu iterieren.
Arbeiten mit
std::vector
Für den mehrfach verschachtelten Vektor ähnelt die Lösung der eines Arrays mit beliebiger Dimension und beliebiger Größe, jedoch ohne SFINAE: Zuerst benötigen wir eine Vorlagenfunktion, die
std::vector
s iteriert und die gewünschte Funktion aufruft:Und eine andere Vorlagenfunktion, die jede Art von Vektorvektor iteriert und sich selbst aufruft:
Unabhängig von der Verschachtelungsebene
iterate_all
wird die Vektor-Vektor-Version aufgerufen, es sei denn, die Vektor-Wert-Version passt besser zusammen, wodurch die Rekursivität beendet wird.Ich denke, dass der Funktionskörper ziemlich einfach und unkompliziert ist ... Ich frage mich, ob der Compiler diese Schleifen abrollen könnte (ich bin fast sicher, dass die meisten Compiler das erste Beispiel abrollen könnten).
Sehen Sie hier die Live-Demo .
Ich hoffe es hilft.
quelle
Verwenden Sie etwas in diese Richtung (sein Pseudocode, aber die Idee bleibt gleich). Sie extrahieren das Muster, um es einmal zu wiederholen, und wenden jedes Mal eine andere Funktion an.
quelle
Bleib bei den verschachtelten for-Schleifen!
Alle hier vorgeschlagenen Methoden haben Nachteile in Bezug auf Lesbarkeit oder Flexibilität.
Was passiert, wenn Sie die Ergebnisse einer inneren Schleife für die Verarbeitung in der äußeren Schleife verwenden müssen? Was passiert, wenn Sie einen Wert aus der äußeren Schleife in Ihrer inneren Schleife benötigen? Die meisten "Kapselungs" -Methoden schlagen hier fehl.
Vertrauen Sie mir, ich habe mehrere Versuche gesehen, verschachtelte Schleifen zu "bereinigen", und am Ende stellt sich heraus, dass die verschachtelte Schleife tatsächlich die sauberste und flexibelste Lösung ist.
quelle
Eine Technik, die ich verwendet habe, sind Vorlagen. Z.B:
Dann rufen Sie einfach
do_something_on_A(A)
Ihren Hauptcode auf. Die Vorlagenfunktion wird einmal für jede Dimension erstellt, das erste Mal mitT = std::vector<std::vector<int>>
, das zweite Mal mit mitT = std::vector<int>
.Sie können dies allgemeiner machen, indem Sie
std::function
(oder funktionsähnliche Objekte in C ++ 03) als zweites Argument verwenden, wenn Sie möchten:Dann nenne es wie:
Dies funktioniert, obwohl die Funktionen dieselbe Signatur haben, da die erste Funktion besser zu allem passt, was
std::vector
im Typ enthalten ist.quelle
Sie können Indizes in einer Schleife wie folgt generieren (A, B, C sind Dimensionen):
quelle
Eine Sache, die Sie versuchen möchten, wenn Sie nur Anweisungen in der innersten Schleife haben - und Ihre Sorge ist mehr über die übermäßig ausführliche Natur des Codes -, ist die Verwendung eines anderen Leerzeichenschemas. Dies funktioniert nur, wenn Sie Ihre for-Schleifen so kompakt angeben können, dass sie alle in eine Zeile passen.
Für Ihr erstes Beispiel würde ich es wie folgt umschreiben:
Dies drückt es irgendwie aus, weil Sie Funktionen in den äußeren Schleifen aufrufen, was dem Einfügen von Anweisungen in diese entspricht. Ich habe alle unnötigen Leerzeichen entfernt und es kann passabel sein.
Das zweite Beispiel ist viel besser:
Dies kann eine andere Whitespace-Konvention sein, als Sie gerne verwenden, aber es wird ein kompaktes Ergebnis erzielt, das dennoch keine Kenntnisse über C / C ++ hinaus erfordert (z. B. Makrokonventionen) und keine Tricks wie Makros erfordert.
Wenn Sie wirklich ein Makro wollen, können Sie dies mit etwas wie einem Schritt weiter gehen:
was das zweite Beispiel ändern würde zu:
und das erste Beispiel schneidet auch besser ab:
Hoffentlich können Sie ziemlich leicht sagen, welche Aussagen zu welchen Aussagen gehören. Achten Sie auch auf die Kommas, jetzt können Sie sie nicht mehr in einer einzelnen Klausel eines der
for
s verwenden.quelle
for
Schleife auf eine Zeile klemmen, wird sie nicht besser lesbar, sondern weniger .Hier ist eine C ++ 11-Implementierung, die alles iterable handhabt. Andere Lösungen beschränken sich auf Container mit
::iterator
Typedefs oder Arrays: Bei afor_each
geht es jedoch um Iteration, nicht um einen Container.Ich isoliere die SFINAE auch auf einen einzelnen Punkt im
is_iterable
Merkmal. Das Versenden (zwischen Elementen und iterablen Elementen) erfolgt über das Versenden von Tags, was meiner Meinung nach eine klarere Lösung darstellt.Die Container und die Funktionen, die auf Elemente angewendet werden, werden alle perfekt weitergeleitet, sodass sowohl die Bereiche als auch die Funktoren
const
nichtconst
zugänglich sind.Die Vorlagenfunktion, die ich implementiere. Alles andere könnte in einen Detail-Namespace gehen:
Das Versenden von Tags ist viel sauberer als bei SFINAE. Diese beiden werden für iterierbare Objekte bzw. nicht iterierbare Objekte verwendet. Die letzte Iteration der ersten könnte eine perfekte Weiterleitung gebrauchen, aber ich bin faul:
Dies ist ein Boilerplate, das zum Schreiben benötigt wird
is_iterable
. Ich mache argumentabhängige Suche aufbegin
undend
in einem Detail-Namespace. Dies emuliert, was einefor( auto x : y )
Schleife ziemlich gut macht:Dies
TypeSink
ist nützlich, um zu testen, ob der Code gültig ist. SieTypeSink< decltype(
codieren) >
und wenn dascode
gültig ist, ist der Ausdruckvoid
. Wenn der Code nicht gültig ist, wird SFINAE aktiviert und die Spezialisierung blockiert:Ich teste nur für
begin
. Einadl_end
Test könnte auch durchgeführt werden.Die endgültige Implementierung von
for_each_flat
ist äußerst einfach:Live-Beispiel
Dies ist ganz unten: Sie können gerne nach den besten Antworten suchen, die solide sind. Ich wollte nur ein paar bessere Techniken verwenden!
quelle
Erstens sollten Sie keinen Vektor von Vektoren von Vektoren verwenden. Jeder Vektor hat garantiert einen zusammenhängenden Speicher, aber der "globale" Speicher eines Vektorvektors ist nicht (und wird es wahrscheinlich auch nicht sein). Sie sollten auch das Standardarray vom Typ Bibliothek anstelle von Arrays im C-Stil verwenden.
Besser noch, Sie könnten eine einfache 3D-Matrixklasse definieren:
Sie könnten noch weiter gehen und es vollständig const-korrekt machen, Matrixmultiplikation (richtig und elementweise), Multiplikation mit Vektoren usw. hinzufügen. Sie könnten es sogar auf verschiedene Typen verallgemeinern (ich würde es als Vorlage verwenden, wenn Sie hauptsächlich Doppel verwenden). .
Sie können auch Proxy-Objekte hinzufügen, um B [i] oder B [i] [j] auszuführen. Sie könnten Vektoren (im mathematischen Sinne) und Matrizen voller Doppel & zurückgeben, möglicherweise?
quelle