Von dem Versuch, eine Tabelle zu iterieren



  • Ja eher MultiArray, aber dort kann ich keine Zeilen oder Spalten hinzufügen oder löschen 😞



  • Moah, ich sitze jetzt hier schon stundenlang und versuche das mit dem echten const und nicht const und dem iterator und const_iterator richtig hinzubekommen, mit einem zeilen-adapter dazwischen...

    Code, der iteratoren verwendet ist schwerer zu lesen, als solcher, der sie nicht verwendet (wenn man vom einfachen drüberiterieren mal absieht).
    Code, der iteratoren implementiert ist kompliziert zu schreiben.

    Da müssen mir noch viele Containerwechsel über den Weg laufen, bei denen der Containertyp zwar ändert, die Iteratoren aber immernoch derselben Tag-Klasse angehören, dass ich vollends davon überzeugt bin...



  • Rein aus Interesse, wie tust du "Zeilen oder Spalten hinzufügen oder löschen"? Im schlimmsten Fall brauchst das ja O(n·m) Komplexität, dann kannst du gleich das Multi-Array nehmen und kopieren.



  • Ja klar läuft das aufs Kopieren hinaus. Aber es macht doch einen Unterschied, ob der Benutzer da wild herumkopieren muss, oder ob die Klasse das schon für ihn erledigt Oo



  • Und überhaupt, spätestens wenn man Undo-Information speichern will, hangelt man sich die ganze Zeit mit std::distance und std::advance rum, eben weil die iteratoren ständig invalidiert werden. Wenn man das Datenmodell dann aber für integer-Indizes kapselt, kann man sich bald gleich den ganzen Iterator-Kram sparen...
    Das ist jetzt etwas aus dem Kontext, aber ich frage mich hier gerade, ob es sinnvoll ist, auf biegen und brechen iteratoren einzusetzen zu wollen, weil es hip ist...



  • Wie repräsentierst du die Tabelle intern? vector<vector<T>>?

    Wenn Indizes so viel einfacher handzuhaben sind und du sowieso Random Access hast, warum nicht die Iteratoren mittels Indizes implementieren? Ist möglicherweise leicht langsamer, aber das verkraftest du wahrscheinlich.

    <:xmas2:>



  • Ich bin der Einfachheit halber dazu übergegangen, die in einem vector (1D) zu speichern, weil sich so das Iterieren einfacher (gleichförmiger) gestaltet.



  • Also multi_array scheint leider auch nicht mehr so sehr gepflegt zu werden. Wenn ich das richtig sehe, unterstützt es noch keine move-semantics und die Compiler-Liste sieht mir auch von der alten Sorte aus. Zudem müsste ich da noch einiges an Funktionalität drumbasteln. (aber ich habe hier schonmal etwas damit rumgespielt...)
    Auf der anderen Seite ist es eben auch kein schnelles unterfangen, mal eben einen 2D-Table zu implementieren mit den ganzen views und iteratoren, die da praktisch wären. Und dann ist die Versuchung ja nun auch schon wieder groß, das alles zu generalisieren.
    So stehe ich jetzt dazwischen und schaue unbegeistert auf beide Seiten. -.-



  • Iteratoren sind hauptsächlich dann sinnvoll, wenn es Algorithmen und verschiedene Implementationen gibt.

    Was sicher sinnvoll ist, ist ein eindimensionaler Iterator. Dann kann transform und accumulate und so verwendet werden.

    Zweidimensionale Iteratoren, naja. Vielleicht, wenn du einen Algorithmus hast, bei dem die nötig werden. Aber dann müssten die zwei Member zur Verfügung stellen, next_horizontal(bool up_or_down) und next_vertical(bool up_or_down). Ausserdem wird das mit dem End-Iterator schwierig.

    Insgesamt denke ich, dass für zweidimensionale Strukturen Concepts besser passen als Iteratoren. Stell einen Iterator zur Verfügung für eindimensionales iterieren.
    Und verpasse Views und Tabelle ein einheitliches Interface (Concept). IMHO genügt das.



  • Joar, also ich probiere gerade parallel multi_array und meinen Ansatz aus.
    Meiner besteht jetzt aus Spalten und Zeilen-Iteratoren und aus Spaltenelement- und Zeilenelement-Iterator.
    Wirkliche 2D-Iteratoren wollte ich ja gar nicht machen, mir wuchs nur etwas der ganze administrative Aufwand über den Kopf, obwohl das ja eigentlich auch nur simple Gebilde sein sollten.
    Nachdem ich jetzt aber die einzelnen Klassen aus der Tabellen-Klasse rausgenommen habe und separat implementiere, sieht das ganze wieder etwas übersichtlicher aus. Und boost hilft einem ja glücklicherweise auch Zeit zu sparen bei den Iteratoren...
    Danke für euren Rat, ich weiß das zu schätzen. Hatte eine kleine down-phase weil ich hier so lange das Gefühl hatte nur rumzuspielen und zu keinem vernünftigen Ergebnis zu kommen.



  • So, ich komme hier allmählich weiter, aber bin halt nicht so der Template-Guru. Hat jemand von euch ein gutes Auge für Template-Container und Spaß an der Sache und würde mal über
    https://github.com/decimad/na_containers/blob/master/include/na_containers/array2d.h
    drüberschauen? Der begleitende Beispielcode ist unter
    https://github.com/decimad/na_containers/blob/master/projects/test/test.cpp
    zu finden. Es sind noch keine wirklichen Operationen vorhanden. Im Moment gefällt mir erstmal noch nicht, dass man gerade den voll spezialisierten Container in array2d reinstecken muss, vielleicht finde ich da noch einen Weg, der vector als default nimmt. Eigentlich wäre es auch cool, wenn man da ein normales array reinstecken könnte, aber dann müsste ich ja irgendwie alle Operationen einschränken könnnen, weil resize nicht geht. Hrmmm. Würde mich über Tipps und Vorschläge sehr freuen :xmas2: :xmas1:

    Viele Grüße,
    Deci



  • Mir fällt nur gerade auf, dass du recht viel Codeduplizierung hast. col_type , row_type , cols_sequence und rows_sequence sehen recht ähnlich aus, ebenso die einzelnen Iteratoren.

    Allerdings wirklich merkwürdig, dass es keine solche Implementierung bereits zu geben scheint. Dürfte doch ab und zu benutzt werden. Naja, kopierbare Smart-Pointer braucht scheinbar auch niemand, Leute schreiben lieber jedes Mal wieder alles neu 😉



  • Also bei den Iteratoren muss ich zwei Typen implementieren, weil je nach Ordering ein Iterator einen Stride hat und einer nicht (und wenn man bei den Iterationen major_element_iterator benutzt, bekommt man automatisch sequenziellen Zugriff ohne stride). Bei den anderen Typen bin ich mir gerade nicht sicher, ob ich da Zeilen sparen kann, hast natürlich Recht, dass da Duplizierung ist. Schaue ich gerade mal drüber.
    Danke für's Anschauen! :xmas2:



  • Also bei cols_sequence und rows_sequence, welche Adapter sind um mit for( a : b ) kompatibel zu sein, weiß ich jetzt nicht, wie ich das anders machen sollte, da kommt es ja auf die Namen der Symbole an, die dort weitergeleitet werden.

    Edit: bei row_type und col_type ist es dasselbe... die verlassen sich auch darauf, dass array2d_base ihnen die entsprechenden Iteratoren liefert, weil sie sonst ja selbst darüber entscheiden müssten, ob der row_ oder col_iterator einer mit oder ohne Stride ist.



  • Eine Möglichkeit wäre Funktionsüberladung. Damit kannst du ein sehr nettes Idiom benutzen.

    Statt

    row_element_iterator get_row_begin(size_type rowIndex);
    col_element_iterator get_col_begin(size_type colIndex);
    

    hättest du

    struct row_tag {};
    struct col_tag {};
    
    // Generischer Code
    template <typename Tag>
    element_iterator<Tag> get_begin(size_type index, Tag);
    
    // Spezifischer Code
    element_iterator<row_tag> get_begin(size_type index, row_tag);
    element_iterator<col_tag> get_begin(size_type index, col_tag);
    

    Vielleicht kannst du sogar mehr als Row/Column damit abdecken. Unter Umständen könntest du so einiges zusammenfassen.



  • Hrmmm, das könnte klappen! Uiuiui, das wird jetzt Arbeit 😃



  • Decimad schrieb:

    Also bei den Iteratoren muss ich zwei Typen implementieren, weil je nach Ordering ein Iterator einen Stride hat und einer nicht

    oder der eine hat stride > 1 und der andere stride = 1



  • Otze: Ich implementiere hier keine 2D-Iteratoren. Nur 2 1D-Iteratoren über Spalten bzw. Zeilen. Stride = 1 mag ich nicht, weil dann immernoch nicht-optimale Addition durchgeführt würde, auch wenn es auf inkrementieren hinausläuft, deshalb 2 Klassen.



  • Decimad schrieb:

    Stride = 1 mag ich nicht, weil dann immernoch nicht-optimale Addition durchgeführt würde, auch wenn es auf inkrementieren hinausläuft, deshalb 2 Klassen.

    Willst du das Ganze nicht zuerst sauber implementieren, und dich danach um Mikrooptimierungen kümmern? Der Code wird auch so schon kompliziert genug sein...



  • ich hole meinen edit nochmal vor:

    das könnte dir eine Menge Code sparen. Dann baust du statt 2 Spezialisierungen nur eine helfer klasse, die dir aus deinem 2d array die richtigen strides je nach row-major und colum-major zurück gibt:

    struct row_major{
        std::size_t stride1(std::size_t rows, std::size_t cols){
            return cols;
        }
        std::size_t stride2(std::size_t rows, std::size_t cols){
            return 1;
        }
    };
    struct column_major{
        std::size_t stride1(std::size_t rows, std::size_t cols){
            return 1;
        }
        std::size_t stride2(std::size_t rows, std::size_t cols){
            return rows;
        }
    };
    

    und bist fast fertig.

    zu deinm Argument: ich bin mir sicher, dass der Unterschied nicht messbar ist.


Anmelden zum Antworten