Ein Iterator für mehrere Vektoren?



  • Hallo,

    ich frage mich gerade wie man optimalerweise über mehrere gleichgroße Vektoren in einer Schleife iteriert. Ich hab schon öfter gelesen dass wenn man auf die Elemente des Vektors zugreifen will, sollte man Iteratoren verwenden (warum eigentlich?).

    Auf jeden Fall hab ich jetzt folgendes Problem:

    // x und y sind gleichgroße double Vektoren
    double temp = 0.0;
    for (std::vector<double>::iterator ix = x.begin(), e = x.end(); ix != e; ++ix) {
        temp += *ix**iy; // <- Das *iy geht natürlich nicht, da es nicht existiert
    }
    

    Auf meinen zweiten Vektor kann ich jetzt hier ja nicht zugreifen, weil mein Iterator ja nur für den ersten gilt. Nur, wie mache ich das jetzt am besten?


  • Mod

    happystudent schrieb:

    wie mache ich das jetzt am besten?

    Indem du auch noch einen zweiten iterator hinzufügst.

    // x und y sind gleichgroße double Vektoren
    double temp = 0.0;
    for (std::vector<double>::iterator ix = x.begin(), e = x.end(), iy = y.begin(); ix != e; ++ix, ++iy) {
        assert( iy != y.end() );
        temp += *ix**iy;
    }
    

    oder den entsprechenden Standardalgorithmus (aus <numeric>)verwendest:

    double temp = std::inner_product( x.begin(), x.end(), y.begin(), 0. );
    


  • camper schrieb:

    Indem du auch noch einen zweiten iterator hinzufügst.

    Heh, so einfach? Manchmal bin ich sogar richtig begeistert von C++ 😃

    Besten Dank!

    EDIT:

    Nur mal interessenhalber, was spricht eigentlich gegen:

    double temp = 0.0;
    for (unsigned int i = 0; i < x.size(); i++) {
        temp += x[i]*y[i];
    }
    

    ?



  • Iteratoren abstrahieren und kapseln somit einen Algorithmus von eienr Datenstruktur ab.

    Sagen wir du willst ein Array sortieren und schreibst dir dein (Quick-/Bubble-/Radix-/etc...) Sort. Den Zugriff managest du mit Indizes.
    Also alles auf der Ebene 7. Klasse Informatik Unterricht.

    Jetzt merkst du aber, dass dir das Array an anderer Stelle für gewisse Dinge zu langsam oder unflexibel ist. Ok, kein Problem: Du änderst die Datenstruktur in eine Liste (z.B. Verkettete Liste).
    Oh Mist, jetzt klappt dein Indexzugriff in dem Sotieralgorithmus nicht mehr. Du denkst dir "Kein Problem, schreibe ich grad mal neu".

    Und das ist der Knackpunkt, du schreibst etwas neu (oder änderst etwas), was neue Fehler erzeugen kann. Weiter merkst du, für jede Datenstruktur musst du deinen Algorithmus anpassen. (Ok, beim Sotieren ist es ein schelchtes Beispiel, weil z.B. Bäume nicht sortiert werden brauchen und Hashing-Strukturen nicht sortiert werden können)

    D.h. für n Container(mit n Implementierungen) und m Algorithmen(mit m Algorithmen Ideen) brauchst du insgesamt n*m Algorithmusimplementierungen.

    Zurück zu Iteratoren:
    Erwartet dein Algorithmus jedoch einen Iterator und fordert ein paar Eigenschaften von ihm (z.B. Forward oder Bidirektional oder Random-Access usw.) und nutzt ihn für den Zugriff auf die Daten, dann hast du schön eine Unabhängigkeit zwischen Container und Algorithmen. Du brauchst nur noch n+m Implementierungen der Algorithmen.

    Stell dir mal die C++-Standardbibliothek ohne Iteratoren vor. Bei den 50 Algorithmen und 10 Containern (beides grob ungenau geschätzt :D) wärend das 500 verschiedene, aber doch sehr ähnliche Implementierungen. So sind es 50 und halt die 10 für die Container selbst.



  • Ok, alles klar das macht Sinn. Danke für die ausführliche Erklärung 👍



  • happystudent schrieb:

    Nur mal interessenhalber, was spricht eigentlich gegen:

    double temp = 0.0;
    for (unsigned int i = 0; i < x.size(); i++) {
        temp += x[i]*y[i];
    }
    

    ?

    Nicht besonders viel. Das x.size() da würde mich noch am meisten stören. Müsste man mal benchmarken.

    Ich habe auch viel mit kontinuierlichen Feldern zu tun und bin inzwischen bei 'ner anderen Lösung angekommen: array_ref, ein selbst gebasteltes Klassentemplate, was sich wie eine Referenz auf ein Array verhält und dementsprechend Konvertierungen

    vector<T>  -> array_ref<U>
    array<T,N> -> array_ref<U>
    T[N]       -> array_ref<U>
    

    anbietet, sofern T* nach U* konvertierbar ist. Das finde ich persönlich praktischer in vielen Fällen. Der Code könnte damit dann so aussehen:

    void dotproduct(array_ref<const double> x, array_ref<const double> y)
    {
        auto siz = x.size();
        assert(siz == y.size());
        double temp = 0;
        for (decltype(siz) i=0; i<siz; ++i)
            temp += x[i]*y[i];
        return temp;
    }
    

    oder auch per

    return std::inner_product(&x[0],&x[0]+siz,&y[0],0.0);
    

    in diesem Fall. Mit den Konvertierungen könnte man direkt Vektoren in dotproduct reinstecken.

    In array_ref<T>::operator[](ptrdiff_t index) habe ich ein assert drin, was mir in der Debug-Version noch testet, ob der Index auch gültig ist.

    Das kann man auch auf mehrere Dimensionen erweitern (multi_array_ref), was dann so ähnlich wie boost::multi_array arbeitet -- nur eben eine "Referenz" ist.

    just my 2 cents,
    kk


Log in to reply