Ich würde ja gerne Iteratoren verwenden, aber...
-
Kennt ihr das?
Man sagt immer: "schreib deinen Code so, dass du Iteratoren verwenden kannst. Verallgemeinere den Code dann so, dass er ein eigener Algorithmus werden kann".Aber es klappt niemals. Immer steht einem irgendwas im Weg. Gerade brüte ich über folgende (leicht vereinfachte) Schleife:
Container label; Container2 prediction; double error = 0; for(std::size_t i = 0; i != label.size(); ++i) error += distanceSqr(prediction[i] , label[i]);das dürfte eigentlich gar nicht so schwer sein. In einer funktionalen Sprache könnte man das im Pseudocode so schreiben:
accumulate(transform(zip(label,prediction),distanceSqr(_1,_2)),0);Wahrscheinlich noch mit so viel Syntaxzucker, dass der Code tausendmal lesbarer wird.
Aber in C++ steh ich auf dem Schlauch. Die Standardalgorithmen decken das nicht ab, da ich keine Zwischenergebnisse haben möchte. Ich brauche also so etwas wie boost::range. Die haben transform() aber kein zip. Es gibt zwar die zip-iteratoren, aber das ist unglaublich hässlich - von dem Lambda-ausdruck habe ich zu dem Zeitpunkt noch nicht geredet, und der support von transform für zipped-iteratoren ist...naja.
Aber ich weiß, dass einige hier bei so was echte wunder vollbringen können.
Meine Anforderungen:
1. es soll schöner aussehen
2. es sollte nicht länger sein als der Startcode
2.1. Allerdings dürfen Algorithmen geschrieben werden, die dazu da sind das Muster von oben einfach zu modellieren(für die Lösung reicht hier das Interface
)
3. Erlaubte Bibliotheken: Boost.Einen Preis für die schönste Implementation gibt es nicht. Aber die Ehre von Iteratoren zu verteidigen ist es doch Wert, oder?

-
Genaugenommen verwendest du da einen Index-Iterator :p
label.size() == prediction.size()?
Container::iterator it = label.begin(),end = label.end(); Container2::iterator pit = prediction.begin(), pend = prediction.end(); double error = 0.0; for(; it != end && pit != pend; ++it,++pit) error += distanceSqr(*pit , *it);^wäre jetzt wohl die Dummylösung.
-
Man arbeitet nicht mit Iteratoren der Schönheit wegen oder für eine bessere Lesbarkeit. Iteratoren bieten einen abstrahierten Zugriff auf Elemente in Containern. Und das ist was gutes. Deshalb gibt es auch Standardalgortihmen die alle auf Iteratoren arbeiten. Verschiedene Datenquellen -> gleicher Algorithmus. Dein Beispiel hätte man genausogut auch mit Iteratoren schreiben können, was die Lösung schonnmal stark verbessert hätte.
Wenn es aber keinen passenden Standardalgorithmus gibt, bringt es überhaupt nichts mit Gewalt irgendwas in eine vorhandene Standardfunktion zu pressen. Es hindert einen ja aber niemand daran, eine eigene Funktion zu schreiben, die auch ganz genau sagt was gemacht wird (AccumulatError_KeineAhnungWas(...). So würde ich dann auch selbst bei verwendetem Standardalgorithmus den Vorgang kapseln.
So, nunmal zu Deiner Aufgabe. Vielleicht passt das:double distanceSqr(double a, double b) { return a*b; } template<typename Container> double AccumulateError(Container label, Container prediction) { return std::inner_product(label.begin(), label.end(), prediction.begin(), 0.0, std::plus<double>(), distanceSqr); } int main() { std::array<double, 3> label = {1, 2}; std::array<double, 3> prediction = {1, 2}; std::cout << AccumulateError(label, prediction); }
-
phlox81 schrieb:
^wäre jetzt wohl die Dummylösung.
Ja, ich finde sie aber gruselig. Aus verschiedenen Gründen:
1.Ich muss den Typen der Iteratoren leicht konstruieren können(das ist in diesem Fall leider nur über gruselige Traitsklassen möglich, da das Objekt von sich aus keinen iterator anbietet). Wenn ich das nicht kann, sollte ich schon einen eigenen Funktionsaufruf verwenden. Spätestens in dem Moment sollte ich mir überlegen, ob es da nicht a) was fertiges gibt oder b) man da was verallgemeinern kann.2. ich schätze die Fehleranfälligkeit höher und Verständlichkeit geringer an als die initiale Version. Selbst ohne die doppelte Abbruchbedinging.
@Brotbernd ahh inner_prod hatte ich in dem Fall vergessen. Ich widerspreche dir allerdings in einem Punkt: es sollte um Eleganz gehen, denn elegante Lösungen sind häufig gut wartbar und einfach verständlich. Es ist in C++ möglich dieses Ziel zu erreichen und deswegen sollten wir das auch anstreben.
-
Kann es sein, dass du versuchst C++ zu einer funktionalen Programmiersprache zu machen, C++ aber keine ist?
-
otze schrieb:
@Brotbernd ahh inner_prod hatte ich in dem Fall vergessen. Ich widerspreche dir allerdings in einem Punkt: es sollte um Eleganz gehen, denn elegante Lösungen sind häufig gut wartbar und einfach verständlich. Es ist in C++ möglich dieses Ziel zu erreichen und deswegen sollten wir das auch anstreben.
inner_product funktioniert vielleicht in Deinem Problem und ist auch elegant, aber in Deinem Fall vollkommen nichtssagend und damit weder verständlich noch wartbar. Du siehst es an Dir selbst: Du hast nicht damit gerechnet, dass es mit inner_product geht, wie soll also der Durchschnittsprogrammierer Deinen Code verstehen, wenn er voller "eleganter" Tricks ist.
Die Verwendung von inner_product wird erst dadurch verständlich, dass Du den Aufruf in eine beschreibende Funktion versteckst. Ob Du dann wiederum innerhalb dieser Funktion inner_prodcut aufrufst, oder mit Iteratoren selber eine Schleife schreibst ist relativ egal, solange Du so eine sequenzielle Operation eben mit Iteratoren implementierst und nicht mit Random Access Operator, der die Wahl der verwendeten Datenquelle stark einschränkt.
-
brotbernd schrieb:
Die Verwendung von inner_product wird erst dadurch verständlich, dass Du den Aufruf in eine beschreibende Funktion versteckst.
Ich weiß. Womit habe ich den Anschein erweckt, ich hätte das nicht getan?

@whitepunksondope
Ich fürchte, dass ich mit der Idee nicht der erste bin, nachdem nun so gut wie jedes Feature funktionaler Sprachen durch die ein oder andere C++-Bibliothek implementiert wird(lambda-kalkyl, lazy-evaluation, closures...)