Saubere Möglichkeiten, mehrere 'for'-Schleifen zu schreiben


98

Für ein Array mit mehreren Dimensionen müssen wir normalerweise forfü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-forSchleifen häufig in unserem Code. Wie verwende ich Makros, um die for-for-forSchleifen zu definieren, damit ich diese Art von Code nicht jedes Mal neu schreiben muss? Gibt es einen besseren Weg, dies zu tun?


62
Die offensichtliche Antwort ist, dass Sie nicht. Sie erstellen keine neue Sprache mit Makros (oder einer anderen Technik). Die Person, die nach Ihnen kommt, kann den Code nicht lesen.
James Kanze

17
Wenn Sie einen Vektor eines Vektors eines Vektors haben, ist das ein Zeichen für schlechtes Design.
Maroun

5
@Nim: Du kannst es mit 1 Flat Array machen (nicht sicher, ob es besser ist).
Jarod42

16
Ich würde denken, Sie würden potenziellen O(n) = n^3Code nicht verstecken wollen ...
poy

36
@ TC1: Und dann fällt es mir schwerer zu lesen. Es ist alles eine Frage der persönlichen Vorlieben und es hilft eigentlich nicht bei der hier vorliegenden Frage.
am

Antworten:


281

Das erste ist, dass Sie eine solche Datenstruktur nicht verwenden. Wenn Sie eine dreidimensionale Matrix benötigen, definieren Sie eine:

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

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:

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

Dann schreibst du einfach:

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

(oder nur:

for ( auto& elem: m ) {
}

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:

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
Diese Antwort sollte viel besser bewertet werden, da sie die einzige ist, die sich mit der tatsächlichen Ursache des Problems befasst.
am

5
Es mag eine gute Antwort sein, aber ich stimme nicht zu, dass es eine gute ist. Viele kryptische Vorlagencodes mit wahrscheinlich x10-mal langsamer Kompilierungszeit und wahrscheinlich x10-langsamem Debug-Code (möglicherweise mehr). Für mich ist der definitiv ursprüngliche Code viel klarer für mich ...
Gorkem

10
@beehorf ... und auch viel, viel langsamer. Weil mehrdimensionale Arrays in C und C ++ tatsächlich verschachtelte Arrays in dem Sinne sind, dass die äußeren Dimensionen Zeiger auf die verschachtelten Arrays speichern. Diese verschachtelten Arrays werden dann willkürlich im Speicher verteilt, wodurch das Vorabrufen und Zwischenspeichern effektiv verhindert wird. Ich kenne Beispiele, bei denen jemand einen Code geschrieben hat 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.
Michael Wild

5
@beehorf Wo sehen Sie einen Vorlagencode? (In der Praxis Matrix3Dsollte es wahrscheinlich eine Vorlage sein, aber es ist eine sehr einfache Vorlage.) Und Sie müssen nur debuggen Matrix3D, nicht jedes Mal, wenn Sie eine 3D-Matrix benötigen, sodass Sie beim Debuggen enorm viel Zeit sparen. Zur Klarheit: Wie ist std::vector<std::vector<std::vector<int>>>klarer als Matrix3D? Ganz zu schweigen davon, dass dies Matrix3Ddie 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.
James Kanze

10
@MichaelWild Aber der wahre Vorteil meines Ansatzes ist natürlich, dass Sie die Darstellung ändern können, je nachdem, was in Ihrer Umgebung schneller ist, ohne den Client-Code ändern zu müssen. Der Schlüssel zu einer guten Leistung ist die ordnungsgemäße Kapselung, sodass Sie die vom Profiler angegebenen Änderungen vornehmen können, ohne die gesamte Anwendung neu schreiben zu müssen.
James Kanze

44

Die Verwendung eines Makros zum Ausblenden der forSchleifen kann sehr verwirrend sein, nur um wenige Zeichen zu speichern. Ich würde stattdessen Range-for- Schleifen verwenden:

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

Natürlich können Sie ersetzen auto&mit , const auto&wenn Sie sind in der Tat, die Daten nicht zu ändern.


3
Angenommen, OP kann C ++ 11 verwenden.
Jarod42

1
@herohuyongtao Im Fall von Iteratoren. Was hier vielleicht idiomatischer ist, aber es gibt Fälle, in denen Sie die drei intVariablen möchten .
James Kanze

1
Und sollte das nicht sein do_something_on_A(*j)?
James Kanze

1
@ Jefffrey Ah, ja. Ein weiterer Grund für die Angabe des Typs. (Ich denke, die Verwendung von autofür kund ikann 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.)
James Kanze

2
@Dhara kist ein ganzer Vektor von Vektoren (also ein Verweis darauf), kein Index.
Yakk - Adam Nevraumont

21

So etwas kann helfen:

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

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 iteratorTyps bestimmen (idealerweise sollten wir prüfen, ob der Typ mit Range-Base verwendet werden kann foroder nicht).

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

Die Implementierung von for_eachist unkompliziert. Die Standardfunktion ruft auf 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);
 }

Und die Spezialisierung wird sich rekursiv nennen:

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

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

Dies funktioniert auch nicht für Zeiger (im Heap zugewiesene Arrays).


@herohuyongtao mit Einschränkungen können wir zwei Spezialisierungen für Containerund für andere implementieren .
Fasked

1
@herohuyongtao Ich habe ein Beispiel für K-ary foreach gemacht.
Fasked

1
@fasked: Verwenden Sie is_container : has_iterator<T>::valueaus meiner Antwort und Sie müssen nicht für jeden Typ eine Spezialisierung schreiben, da jeder Container ein iteratortypedef haben sollte. Fühlen Sie sich frei, alles aus meiner Antwort vollständig zu verwenden, Ihre ist bereits besser.
Zeta

@ Zeta +1 dafür. Auch wie gesagt ContainerKonzept hilft.
Fasked

::iteratormacht keinen iterierbaren Bereich. int x[2][3][4]ist perfekt iterierbar, da struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; ich nicht sicher bin, was T[]Spezialisierung tun soll?
Yakk - Adam Nevraumont

17

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.

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

stimmt nur mit der kryptischen Definition eines Vektors des Vektors des Vektors von int ohne explizite Semantik überein.


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

UPDATE: Ich weiß, dass du danach gefragt hast, aber das solltest du besser nicht benutzen :)


5
Ich weiß, dass das OP darum gebeten hat, aber im Ernst ... Das sieht nach einem wunderbaren Beispiel für Verschleierung aus. Angenommen TRIPLE_FOR, in einem Header wurde definiert, was soll ich denken, wenn ich hier TRIPLE_FOR sehe.
James Kanze

2
Ja, ich denke, Sie haben Recht :) Ich denke, ich werde es hier nur als Beispiel belassen, dass dies mit einem Makro möglich ist, aber fügen Sie einen Hinweis hinzu, dass es besser ist, dies nicht zu tun :) Ich bin gerade aufgewacht und beschloss, diese Frage als kleine Aufwärmübung für den Geist zu verwenden.
FreeNickname

5

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

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

beste antwort hier imo.
Davidhigh

4

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:

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

Dies führt jedoch (offensichtlich) zu Mehrdeutigkeitsfehlern. Wir verwenden also SFINAE, um festzustellen, ob der Stromeingang in die Funktion passt oder nicht

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

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

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

Das ist es. Sechs relativ einfache Codezeilen, und Sie können im Gegensatz zu allen anderen Antworten Werte, Zeilen oder andere Untereinheiten durchlaufen.

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

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)

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

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:

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

Beachten Sie, dass die Verwendung des obigen Ansatzes auch die Verwendung einiger "richtiger" C ++ - Techniken ermöglicht:

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

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.



@ecatmur: Interessant - Ich bin gerade erst zur Arbeit gekommen, also werde ich das überprüfen und die Antwort entsprechend aktualisieren / löschen. Vielen Dank.
icabod

@ecatmur: Ich habe mir den C ++ 11-Standard angesehen (Abschnitt 8.3.4), und was ich geschrieben habe, sollte funktionieren und sieht (für mich) nicht illegal aus. Der von Ihnen angegebene Link bezieht sich auf den Zugriff auf Mitglieder außerhalb der definierten Arraygröße. Es stimmt zwar, dass ich die Adresse direkt hinter dem Array erhalte, aber es wird nicht auf die Daten zugegriffen. Dies dient dazu, ein "Ende" bereitzustellen, genauso wie Sie Zeiger als Iteratoren verwenden können, wobei "Ende" eine Vergangenheit ist das letzte Element.
icabod

Sie greifen effektiv auf B[0][0][i]zu i >= 3; Dies ist nicht zulässig, da der Zugriff außerhalb des (inneren) Arrays erfolgt.
Ecatmur

1
Eine klarere Möglichkeit, IMO das Ende zuzuweisen, wenn Sie dies tun würden, ist end = start + (xSize * ySize * zSize)
noggin182

2

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:

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

Nun, dieser Ansatz ist nicht elegant und flexibel, daher könnten wir den gesamten Prozess in eine Vorlagenfunktion packen:

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

Diese Vorlagenfunktion kann auch in Form von verschachtelten Schleifen ausgedrückt werden:

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

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:

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

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 :

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

Und noch eine, die Arrays beliebigen Ranges iteriert und die Rekursion durchführt:

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

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::vectors iteriert und die gewünschte Funktion aufruft:

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

Und eine andere Vorlagenfunktion, die jede Art von Vektorvektor iteriert und sich selbst aufruft:

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

Unabhängig von der Verschachtelungsebene iterate_allwird die Vektor-Vektor-Version aufgerufen, es sei denn, die Vektor-Wert-Version passt besser zusammen, wodurch die Rekursivität beendet wird.

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

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.


1

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.

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

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.


0

Eine Technik, die ich verwendet habe, sind Vorlagen. Z.B:

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
}

Dann rufen Sie einfach do_something_on_A(A)Ihren Hauptcode auf. Die Vorlagenfunktion wird einmal für jede Dimension erstellt, das erste Mal mit T = std::vector<std::vector<int>>, das zweite Mal mit mit T = 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:

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

Dann nenne es wie:

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

Dies funktioniert, obwohl die Funktionen dieselbe Signatur haben, da die erste Funktion besser zu allem passt, was std::vectorim Typ enthalten ist.


0

Sie können Indizes in einer Schleife wie folgt generieren (A, B, C sind Dimensionen):

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

Ich stimme dir zu, es ist speziell für 3 Dimensionen konzipiert;)
Janek

1
Ganz zu schweigen davon, dass es unglaublich langsam ist!
Noggin182

@ noggin182: Die Frage war nicht nach Geschwindigkeit, sondern nach Vermeidung verschachtelter for-Schleifen.
Außerdem

Ok, dies ist ein alternativer Weg, wahrscheinlich effizienter (Javascript): für (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

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:

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

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:

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

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:

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

was das zweite Beispiel ändern würde zu:

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

und das erste Beispiel schneidet auch besser ab:

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

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 fors verwenden.


1
Die Lesbarkeit dieser ist schrecklich. Wenn Sie mehr als eine forSchleife auf eine Zeile klemmen, wird sie nicht besser lesbar, sondern weniger .

0

Hier ist eine C ++ 11-Implementierung, die alles iterable handhabt. Andere Lösungen beschränken sich auf Container mit ::iteratorTypedefs oder Arrays: Bei a for_eachgeht es jedoch um Iteration, nicht um einen Container.

Ich isoliere die SFINAE auch auf einen einzelnen Punkt im is_iterableMerkmal. 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 constnicht constzugänglich sind.

#include <utility>
#include <iterator>

Die Vorlagenfunktion, die ich implementiere. Alles andere könnte in einen Detail-Namespace gehen:

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

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:

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

Dies ist ein Boilerplate, das zum Schreiben benötigt wird is_iterable. Ich mache argumentabhängige Suche auf beginund endin einem Detail-Namespace. Dies emuliert, was eine for( auto x : y )Schleife ziemlich gut macht:

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;

Dies TypeSinkist nützlich, um zu testen, ob der Code gültig ist. Sie TypeSink< decltype(codieren ) >und wenn das codegültig ist, ist der Ausdruck void. Wenn der Code nicht gültig ist, wird SFINAE aktiviert und die Spezialisierung blockiert:

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

Ich teste nur für begin. Ein adl_endTest könnte auch durchgeführt werden.

Die endgültige Implementierung von for_each_flatist äußerst einfach:

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

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!


-2

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.

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

Besser noch, Sie könnten eine einfache 3D-Matrixklasse definieren:

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

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?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.