range based for-loop : wie kommt man an den Index?



  • Hallo,

    in der Numerik wird oft über einen ganzen Vektor iteriert.

    //Mittelwert
    vector<double> X={1,2,3,4,5,6};
    double sum=0;
    for(auto x : X)
        {
        sum += x;
        }
    sum /= X.size();
    

    Will man an die Elemente des Vektors, so:

    //setzen
    vector<double> X={1,2,3,4,5,6};
    double sum=0;
    for(auto& x : X)
        {
        x = 99;
        }
    

    Man sieht schon den Nachteil, denn oft bräuchte man den Index, z. B. wie bei :

    vector<double> X;
    const double k = 3.1415 / 88;
    for(unsigned i = 0; i < X.size(); i++)
        {
        X[i] = sin(k*i);
        }
    

    Man könnte es mit for_each und Iteratoren machen, oder einen extra Index bauen und hochzählen, aber dann ist es nicht mehr so elegant:

    vector<double> X;
    const double k = 3.1415 / 88;
    unsigned i=0;
    for(auto& x : X)
        {
        X[i] = sin(k*i);
        i++;
        }
    

    i ist nun nicht mehr lokal, man kann das Hochzählen falsch machen oder vergessen und es könnte auch mehr Laufzeit bedeuten. X.size() kann ja groß sein, oder der Algorithmus in der Schleife aufwendiger. Und es ist doppelt gemoppelt, denn einen Index (Adressarithmetik) muss die range-based-for-loop intern haben, wie kann sie sonst über alle Elemente ziehen.

    Kann man also bei range-based-for ohne mehr Laufzeit an den Index heran, möglichst elegant?

    ---

    Zusatz 1:
    so wäre es wenigstens lokal
    (ist gefährlich, funktioniert nur genau ein Mal 😮

    vector<double> X;
    const double k = 3.1415 / 88;
    for(auto& x : X)
        {
        static unsigned i=0;
        X[i] = sin(k*i);
        i++;
        }
    

    Zusatz 2:
    Man sieht schon, dass das nicht schön ist, also höchstens noch so:

    vector<double> X;
    const double k = 3.1415 / 88;
    {
    unsigned i=0;
    for(auto& x : X)
        {
        X[i] = sin(k*i);
        i++;
        }
    }
    

    Gruss W.



  • Gibt im Moment keine saubere schöne Lösung.

    wastl schrieb:

    so wäre es wenigstens lokal:

    vector<double> X;
    const double k = 3.1415 / 88;
    for(auto& x : X)
        {
        static unsigned i=0;
        X[i] = sin(k*i);
        i++;
        }
    

    Bitte Hirn einschalten.
    Was machste wenn das Programm die Codestelle zum 2. ausführt?
    Weinen? Weglaufen? Kündigen?


  • Mod

    so wäre es wenigstens lokal:

    Ja, aber extrem hässlich. Wie willst du den Code mehrmals durchlaufen?

    Wenn du die Indize brauchst, mach es doch ganz normal (wie in Variante 3). Wo liegt das Problem? Oder, wenn du richtig geil auf range-based for bist, dann nimm boost::irange (wie kürzlich im Forum demonstriert).

    for( auto i : boost::irange<int>(0, X.size()) )
        X[i] = sin(k*i);
    

    Kann man also bei range-based-for ohne mehr Laufzeit an den Index heran, möglichst elegant?

    Nun, du kannst (bspw. bei einem vector ) die Adresse des aktuellen Elements von der Adresse des ersten Elementes abziehen. Das ist überhaupt das einzig direkte, was mir ohne weitere Laufvariablen einfällt.



  • Hallo wastl,

    wastl schrieb:

    in der Numerik wird oft über einen ganzen Vektor iteriert.

    Ich hatte auch schon oft darüber nachgedacht, doch ich glaube die ranged for loop ist einfach nicht dafür gedacht. Sicher, man möchte das coole neue Feature anwenden, aber wenn es im Rahmen der Numerik um diskretisierte Punkte geht, dann bist du glaube ich mit dem Indexzugriff des Vektors am Besten bedient.

    Gruß,
    -- Klaus.



  • Oder schreib dir einen Adapter für eine Range, der beim Dereferenzieren ein std::pair<double, std::size_t> oder ein eigenes Objekt dafür zurückgibt. Würde ich aber nur machen, wenn ich gute Gründe dafür hätte, denn der Code wird für andere Leute nicht unbedingt verständlicher.



  • Nexus schrieb:

    Oder schreib dir einen Adapter für eine Range, der beim Dereferenzieren ein std::pair<double, std::size_t> oder ein eigenes Objekt dafür zurückgibt. Würde ich aber nur machen, wenn ich gute Gründe dafür hätte, denn der Code wird für andere Leute nicht unbedingt verständlicher.

    und schneller laufen wird es wohl auch nicht...



  • Nun, du kannst (bspw. bei einem vector ) die Adresse des aktuellen Elements von der Adresse des ersten Elementes abziehen. Das ist überhaupt das einzig direkte, was mir ohne weitere Laufvariablen einfällt.

    Ja, aber das wird dann in intern in einen int gecasted, also z. B. durch 4
    geteilt, Adresse ist ja nicht Index.



  • wastl schrieb:

    Adresse ist ja nicht Index.

    Nein, aber Differenz der Adressen ist gleich Index. Die Grösse der Datentypen wird automatisch berücksichtigt, wenn du zwei Iteratoren oder Zeiger subtrahierst; das Resultat ist die Anzahl notwendiger Inkrementierungen, um von einen zum anderen zu gelangen.



  • Ich hatte auch schon oft darüber nachgedacht, doch ich glaube die ranged for loop ist einfach nicht dafür gedacht. Sicher, man möchte das coole neue Feature anwenden, aber wenn es im Rahmen der Numerik um diskretisierte Punkte geht, dann bist du glaube ich mit dem Indexzugriff des Vektors am Besten bedient.

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird, es gibt ja auch Algorithmen, wo nur manche
    Indizes benutzt werden, oder durcheinander usw.



  • wastl schrieb:

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird

    Nein, dass über alle Elemente iteriert wird.



  • Nexus schrieb:

    wastl schrieb:

    Adresse ist ja nicht Index.

    Nein, aber Differenz der Adressen ist gleich Index. Die Grösse der Datentypen wird automatisch berücksichtigt, wenn du zwei Iteratoren oder Zeiger subtrahierst; das Resultat ist die Anzahl notwendiger Inkrementierungen, um von einen zum anderen zu gelangen.

    Inherhalb von C++ ist das richtig. Wenn man aber in den Assembler reinsieht, dann wird die Adresse abgezogen und dann durch sizeof(double) (z. B.) geteilt, oder shift, dieser Wert kommt dann in ein Register und ist der Index. Ist sizeof(double) z. B. 8, so ist die Differenz der effektiven Adressen zweier benachbarter doubles in der Maschine 8 und nicht 1.
    Es ist gut möglich, dass die Variante mit einer zusätzlichen Index-Variablen gar nicht länger läuft, da i++ gleichzeitig mit den Floating-Point-Sachen in der CPU abläuft, vorausgesetzt, der Index befindet sich in einem Register, bzw. in einem Register der Maschine, die den X86-Registersatz simuliert ...



  • Spezielle Anwendungen brauchen spezielle Mittel. Da man in der Numerik häufig mit Vektoren und Matrizen als elementare Bausteine arbeitet, nimmt man eben eine Bibliothek, die das kann und verzichtet dann komplett auf explizite Loops. Das ist sogar viel besser, weil die Bibliothek dann intern große Geschütze auffahren kann, um zum Beispiel sin und cos über SIMD parallel zu berechnen. Das hat ausserdem den Vorteil, dass der Code lesbarer wird.

    blas::vector<double> X = sin(k*blas::irange(0,100));
    


  • wastl schrieb:

    Ich hatte auch schon oft darüber nachgedacht, doch ich glaube die ranged for loop ist einfach nicht dafür gedacht. Sicher, man möchte das coole neue Feature anwenden, aber wenn es im Rahmen der Numerik um diskretisierte Punkte geht, dann bist du glaube ich mit dem Indexzugriff des Vektors am Besten bedient.

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird, es gibt ja auch Algorithmen, wo nur manche
    Indizes benutzt werden, oder durcheinander usw.

    Ich verstehe deinen Punkt nicht so recht.

    Wenn ich z.B. x- und y-Werte jeweils in einem std::vector speichere, dann kann ich auch einfach mittels

    for(unsigned int i = 0; i < vec.size(); ++i)
    // do something for each i
    

    arbeiten. Ich weiß nicht so recht, was daran jetzt weniger intuitiv sei als die Verwendung des ranged for loop? 😕

    Gruß,
    -- Klaus.



  • Ich verstehe deinen Punkt nicht so recht.

    Wenn ich z.B. x- und y-Werte jeweils in einem std::vector speichere, dann kann ich auch einfach mittels

    for(unsigned int i = 0; i < vec.size(); ++i)
    // do something for each i
    

    arbeiten. Ich weiß nicht so recht, was daran jetzt weniger intuitiv sei als die Verwendung des ranged for loop? 😕

    Es geht nicht um richtig oder falsch, bis auf den 😮 oben, sondern um "eindeutig". Die Ausdrücke in der runden Klammer bei for() können ja auch komplizierter oder länger sein. Bei der range-based-for-loop sehe ich ohne Brille aus 2 m Entfernung (das war einmal: also 70 cm Entfernung), dass über das Ganze drübergezogen wird, bei der herkömmlichen for-loop muss man, je nachdem, genauer hinsehen. Es dokumentiert sich sozusagen selbst und völlig eindeutig. Mit dem Komma Separator kann man ja so manches machen und ich war nicht sicher, ob der nicht auch bei der range-based-for-loop zum Einsatz kommen kann, nun offenbar nicht.
    for_each arbeitet mit Iteratoren, das sind im Prinzip Adressen und für die numerische Bearbeitung braucht man aber den Index, der, wie oben dargelegt, in Assembler weitere Operationen benötigt, um aus den Adressen gewonnen zu werden, z. B.

    Pseudo-Assembler:
    index = (address - base_address) / size
    

    Je nach Maschine kann das länger dauern, oder auch gar nicht länger (mehrere Rechenwerke). Ich wollte das einfach einmal wissen. Wirkliche Numerik (z.B. FFT) hat z. T. wüste Index-Berechnungen, der Code dafür ist immer "häßlich" und C-like, Abstraktionen helfen da nichts, das liegt in der Natur der Sache.

    Danke für alle Antworten, es ist nicht in jedem Forum so, dass die Antworter mehr wissen, als der Frager, das Gegenteil ist sehr oft der Fall.



  • wastl schrieb:

    Nexus schrieb:

    Oder schreib dir einen Adapter für eine Range, der beim Dereferenzieren ein std::pair<double, std::size_t> oder ein eigenes Objekt dafür zurückgibt. Würde ich aber nur machen, wenn ich gute Gründe dafür hätte, denn der Code wird für andere Leute nicht unbedingt verständlicher.

    und schneller laufen wird es wohl auch nicht...

    Langsamer aber auch nicht.



  • Arcoth schrieb:

    so wäre es wenigstens lokal:

    Ja, aber extrem hässlich. Wie willst du den Code mehrmals durchlaufen?

    Wenn du die Indize brauchst, mach es doch ganz normal (wie in Variante 3). Wo liegt das Problem? Oder, wenn du richtig geil auf range-based for bist, dann nimm boost::irange (wie kürzlich im Forum demonstriert).

    for( auto i : boost::irange<int>(0, X.size()) )
        X[i] = sin(k*i);
    

    Jo.
    Irgendwie finde ich http://perldoc.perl.org/functions/keys.html hübsch, in C++ wäre das wohl

    for( auto i : keys(X) )
        X[i] = sin(k*i);
    

    Der set::iterator, hat der nicht so ein schönes first und second? Eih, verwirrend, daß Iteratoren mal Durchrenner, mal Ende-Marken und mal Suchergebnisse sind.



  • Jeder Vektor sollte doch auch einen Iterator haben, den man anfordern kann.
    Mit diesem Iterator hat man dann auch den Index.



  • MC78 schrieb:

    Jeder Vektor sollte doch auch einen Iterator haben, den man anfordern kann.
    Mit diesem Iterator hat man dann auch den Index.

    Wie sähe das z. B. aus?


  • Mod

    Das ist schnell geschrieben:

    template <typename C>
    boost::integer_range<typename C::size_type> keys( C const& c )
    {
    	return {0, c.size()};
    }
    
    template <typename C, std::size_t N>
    boost::integer_range<C> keys( C (&arr)[N] )
    {
    	return {0, N};
    }
    

    Übrigens habe ich gerade einen lust'gen Bug bei Clang entdeckt, bei folgendem Code

    #include <boost/range/irange.hpp>
    
    template <typename C>
    auto keys( C const& c ) -> boost::irange<typename C::size_type>
    {
    	return {0, C.size()};
    }
    
    int main()
    {
    	int arr[3];
    	keys(arr);
    }
    

    Der ist so lustig, weil er nur gelegentlich auftritt. Kompiliert das mal zehn mal hintereinander, und seht, wie oft Clang abkrazt. Bei mir ist es ca. 4 mal.



  • Ich finde for-range im Moment nur bedingt einsatzfähig. Ich würde es gern für viel mehr Fälle einsetzen.

    Meine Hoffnung ist, dass spätestens bei C++17 dann auch Hilfsmittel in der Art von Boost-Range-Adaptern dabei sind, über die ich z.B. einen Index oder einen "Reißverschluss" bekomme:

    Beispiel für das Mitzählen

    vector<double> x;
    for (auto t : counted(x)) {
       // t könnte hier ein tuple<decltype(x.size()),double&> sein
       cout << "Der " << (1+get<0>(t)) << ". Eintrag ist "
            << get<1>(t) << endl;
    }
    

    Beispiel für zip

    vector<double> vecx;
    vector<double> vecy;
    for (auto t : zip(vecx,vecy)) {
       // t könnte hier ein tuple<double&,double&> sein
       cout << get<0>(t) << ", " << get<1>(t) << endl;
    }
    

    ...wobei ich mir noch nicht im Klaren darüber bin, ob man hier wirklich "Referenz-Tupel" will oder nicht. Ich favorisiere T = decltype((*iterator)) für tuple<...,T,...> , denke ich.

    Und wer sich hier jetzt hat inspirieren lassen und wegen Langeweile so etwas basteln will, könnte auch noch hier für noch mehr Inspiration schauen.


Log in to reply