Konsolen-Simulation des Algorithmus "Türme von Hanoi" unter Verwendung einer Klasse
-
Danke für den Code. C++11 ist ja kurz, wie praktisch.
Aber was bedeutet das:
= [&]
-
Das ist eine Zuweisung,
[&]{ /*... */ }
ist ein Lambda-Ausdruck, mit dem die Variabledump_all
initialisiert wird.
-
Funktionen mit function<void()> zu übergeben macht niemand freiwillig, weil das enorm langsam ist. Ich bin mir sicher, Arcoth hat das nur gemacht, damit der Code verständlicher ist, aber sobald du Templates kennst, würde ich diese verwenden.
-
Danke,
der Thread hier schnappt mir das Wort aus dem Mund:
-
Ich bin mir sicher, Arcoth hat das nur gemacht, damit der Code verständlicher ist
Natürlich wäre der normale Fall so:template<typename Func> void solve( ..., Func const& dump_all ) // Const-Referenz, um Kopie des closure-Objektes zu vermeiden
-
Ich habe gerade angefangen, die Visualisierung objektorientiert zu programmieren. Mir fehlt leider noch die Vorstelllungskraft, wie ich den Verlauf richtig gestalten soll.
Ich habe mir überlegt, dass ich folgende Klassen erstelle:
* Slice - kann ihre Länge selbst bestimmen u. zurückgeben - kann ihre Mitte selbst bestimmen u. zurückgeben - kann sich selbst zurückgeben * Tower - kann ihre Größe selbst bestimmen u. zurückgeben - kann sich selbst zurückgeben - kann eine Scheibe aufnehmen - kann eine Scheibe von sich nehmen u. an einen anderen Turm geben - kann auf ein Mal komplett eine Ladung Scheiben auf sich nehmen (beim Start) * Simulation - kann rekursiv die Bewegungen der Scheiben errechnen * Display - kann den Konsolenbildschirm leeren - kann eine Position auf der Konsole ansteuern - kann auf einen Tastendruck warten - kann String-Werte auf der Konsole ausgeben - kann die Scheiben und ihre Türme auf die Konsole einzeichen
Ich kann im Moment nicht absehen, wie ich "die Scheibe an den Turm geben" klassen-technisch lösen soll.
In der strukturierten Programmierung hatte ich bisher einen Container deque<string> als Übergabeparameter.
Nun mit dem Klassendesign hätte ich also den Container deque<Slice>, der an die Klasse Tower zu übergeben wäre, oder? Wie geht das? Übergebe ich das ganze Objekt (Slice)?
-
tower.add(slice)
?
-
SeppJ schrieb:
tower.add(slice)
?ist slice dann die Klasse selbst? Kann ich sie so einfach übergeben, oder muss ich da was bauen für?
-
Cpp_Anfaeger schrieb:
SeppJ schrieb:
tower.add(slice)
?ist slice dann die Klasse selbst? Kann ich sie so einfach übergeben, oder muss ich da was bauen für?
du kannst natürlich schon nicht einen typen als funktionsparameter (ich hab extra funktion davorgehängt, nicht dass mir nun die leute erklären kommen was templates sind...) übergeben. das würde dann vielleicht so aussehen:
void Transfer(Tower& Lhs, Tower& Rhs) { Lhs.Add(Rhs.Top()); Rhs.Pop(); }
oder wenn du es exceptionsafe machen willst:
void Transfer(Tower& Lhs, Tower& Rhs) { Tower LhsCopy(Lhs); LhsCopy.Add(Rhs.Top()); Rhs.Pop(); Lhs.Swap(LhsCopy); }
wobei die ensprechenden funktionen (add, top etc) alle mit instanzen vom typen slice arbeiten.
-
oder wenn du es exceptionsafe machen willst:
Das braucht man nicht. (bzw. es ist bereits exception-safe).
Destruktoren sollten nie etwas werfen. Daher solltepop()
auch nie etwas werfen.
Edit: Eine assertion, welches die Groesse vonrhs
prueft, waere auch nicht verkehrt.
-
Arcoth schrieb:
oder wenn du es exceptionsafe machen willst:
Das braucht man nicht. (bzw. es ist bereits exception-safe).
Destruktoren sollten nie etwas werfen. Daher solltepop()
auch nie etwas werfen.da hast du recht. stell dir aber den (unwahrscheinlichen) fall vor, dass bei jedem pop ein neuer speicherbereich alloziiert wird und alle elemente umkopiert werden (bzw. gemoved). dann kann ein pop schonmal werfen. (mir ist schon bewusst dass das bei std::stack<T, std::vector<T> > nicht passiert aber man kann ja nie wissen, womit und wie etwas implementiert werden wird.
-
asdfsa schrieb:
void Transfer(Tower& Lhs, Tower& Rhs) { Lhs.Add(Rhs.Top()); Rhs.Pop(); }
Hmmm... wo hätte ich dann das Objekt Slice?
-
Cpp_Anfaeger schrieb:
asdfsa schrieb:
void Transfer(Tower& Lhs, Tower& Rhs) { Lhs.Add(Rhs.Top()); Rhs.Pop(); }
Hmmm... wo hätte ich dann das Objekt Slice?
asdfsa schrieb:
wobei die ensprechenden funktionen (add, top etc) alle mit instanzen vom typen slice arbeiten.
-
asdfsa schrieb:
Cpp_Anfaeger schrieb:
asdfsa schrieb:
void Transfer(Tower& Lhs, Tower& Rhs) { Lhs.Add(Rhs.Top()); Rhs.Pop(); }
Hmmm... wo hätte ich dann das Objekt Slice?
asdfsa schrieb:
wobei die ensprechenden funktionen (add, top etc) alle mit instanzen vom typen slice arbeiten.
achso, das muss ich noch sacken lassen ...
-
aber man kann ja nie wissen, womit und wie etwas implementiert werden wird.
Der Fall ist so unwahrscheinlich (und andererseits so verheerend*), dass man ihn vernachlaessigen kann. Es macht die Funktion nur komplizierter und langsamer.
Und falls eine Exception fliegt, dann kommt diese wahrscheinlich sowieso bis zur
main()
durch und wird den Programmfluss mindestens unterbrechen, sodass der Status eines der Stacks keinen Unterschied mehr macht.
Prinzipiell hast du aber Recht.* ~Ueberleg dir mal, wie lange das Programm noch weiterlaeuft, sobald der Speicher ausgegangen ist.~
-
Mir war langweilig
. Das Programm erzählt die Geschichte der Türme von Hanoi. Ich habe natürlich peinlich genau darauf geachtet, dass die Scheiben wirklich verschoben werden, keine Tricksereien mit Kopien von Scheiben. Kopien von realen Gegenständen gibt es in einem wohl geformten Universum nicht. Über allem wacht der mächtige Gott Brahma, dass alle Regeln eingehalten werden:
#include <iostream> #include <vector> #include <stdexcept> #include <utility> class EndOfTheWorldAsWeKnowIt { }; class Disk { unsigned diameter; void brahma_check() const { if (diameter == 0) throw std::logic_error("Brahma ist zornig! " "Du hast die heiligen Regeln verletzt, " "als du eine nicht existierende Scheibe nahmst." ); } public: Disk(unsigned diameter = 0) : diameter(diameter) { } Disk(Disk &&other): diameter(other.diameter) { other.brahma_check(); other.diameter = 0; } Disk(const Disk&) = delete; void operator=(const Disk&) = delete; unsigned get_diameter() const { brahma_check(); return diameter; } friend std::ostream& operator<<(std::ostream &out, Disk const& disk) { return out << "|\t" << disk.diameter << "\t|"; } }; class Tower { std::vector<Disk> disks; public: void add_disk(Disk &&disk) { if (!disks.empty() && disks.back().get_diameter() < disk.get_diameter() ) throw std::logic_error("Brahma ist zornig! " "Du hast die heiligen Regeln verletzt, " "als du eine große Scheibe auf eine kleine legtest." ); disks.push_back(std::move(disk)); } Disk remove_top() { if (disks.empty()) throw std::logic_error("Brahma ist zornig! " "Du hast die heiligen Regeln verletzt, " "als du eine Scheibe von einem leeren Turm nahmst." ); Disk disk = std::move(disks.back()); disks.pop_back(); return std::move(disk); } bool empty() const { return disks.empty(); } Tower() = default; Tower(const Tower&) = delete; void operator=(const Tower&) = delete; friend std::ostream& operator<<(std::ostream &out, Tower const& tower) { for(auto const& disk: tower.disks) out << disk; return out; } }; template <unsigned num_disks> class Temple { Tower towers[3]; public: Temple() { for(unsigned i = num_disks; i != 0; --i) towers[0].add_disk(Disk(i)); } void move(unsigned from, unsigned to) { towers[to].add_disk(towers[from].remove_top()); } void show_to_brahma() const { if (towers[0].empty() && (towers[1].empty() || towers[2].empty())) throw EndOfTheWorldAsWeKnowIt(); else throw std::logic_error("Brahma ist zornig! " "Du hast die heiligen Regeln verletzt, " "als du ihm eine unvollstandige Lösung zeigtest." ); } friend std::ostream& operator<<(std::ostream &out, Temple<num_disks> const& temple) { return out << "----------------------------------------------------------\n" << temple.towers[0] << '\n' << temple.towers[1] << '\n' << temple.towers[2] << '\n' << "----------------------------------------------------------\n"; } }; class Monk { unsigned rank; static unsigned other_tower(unsigned t1, unsigned t2) { if (t1 == 0 && t2 == 1) return 2; if (t1 == 1 && t2 == 0) return 2; if (t1 == 2 && t2 == 1) return 0; if (t1 == 1 && t2 == 2) return 0; if (t1 == 2 && t2 == 0) return 1; if (t1 == 0 && t2 == 2) return 1; throw std::logic_error("Brahma ist zornig! " "Du hast die heiligen Regeln verletzt, " "als du totalen Unsinn programmiert hast." ); } public: Monk(unsigned rank): rank(rank) { } template <typename Temple> void solve_puzzle(Temple &temple, unsigned from, unsigned to) { if (rank > 1) { // Ask subordinate to move all the top disks: Monk minor_monk(rank - 1); unsigned other = other_tower(from, to); minor_monk.solve_puzzle(temple, from, other); // Move the last piece temple.move(from, to); // Proudly draw the result: std::cout << temple; // Ask subordinate to move all the rest: minor_monk.solve_puzzle(temple, other, to); } else { // This task is simple, even for the lowliest monk: temple.move(from, to); // Proudly draw the result: std::cout << temple; } } }; int main() { const unsigned num_disks = 5; Temple<num_disks> temple; try { std::cout << temple; Monk abbot(num_disks); abbot.solve_puzzle(temple, 0, 1); temple.show_to_brahma(); } catch (EndOfTheWorldAsWeKnowIt) { std::cout << "Brahma ist zufrieden. Das Universum hat seinen Zweck " "erfüllt und kann nun neu erschaffen werden. Einen schönen Tag noch!\n"; } }
P.S.: Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZl
-
SeppJ schrieb:
Mir war langweilig
. Das Programm erzählt die Geschichte der Türme von Hanoi. Ich habe natürlich peinlich genau darauf geachtet, dass die Scheiben wirklich verschoben werden, keine Tricksereien mit Kopien von Scheiben. Kopien von realen Gegenständen gibt es in einem wohl geformten Universum nicht. Über allem wacht der mächtige Gott Brahma, dass alle Regeln eingehalten werden:
...
nicht schlecht SeppJ .. nur bei der Ausgabe der Türme sehe ich noch Verbesserungspotential.
Dazu habe ich mir auch was einfallen lassen. Vom Algorithmus her ist das so eine Art einfacher-2D-Raytracer. Der Kern besteht aus dem Interface
Shape
und der KlasseDisplay
.Display
hat Zugriff auf alleShape
-Objekte, die gezeichnet werden sollen. Wird einDisplay
-Objekt ausgegeben, so berechnet Display für jeden Punkt (Zeichen) in der Ausgabe die jeweilige Position und fordert alle Shapes auf, ggf ihren 'Stempel' dort zu hinterlassen.Jedes Objekt, was 'gezeichnet' werden soll, muss von
Shape
abgeleitet sein, und die Methodevoid draw( const Pos& drawPos, char& out ) const
überladen. Dort muss dann eine Implementierung dafür sorgen, dass das Zeichenout
genau dann überschrieben wird, wenn die PositiondrawPos
auf eine Punkt des Objekts zeigt. Natürlich sollte das Objekt dazu wissen, wo es sich befindet.
Die Klasse Pos enthält schlicht x- und y-Wert einer Position. Man bräuchtePos
in dieser Programm-Skizze nicht zwingend, ich finde das aber durchgängiger und verständlicher. Für Erweiterungen, die Addition- und/oder Subtraktion von Positionen nötig machen istPos
sicher von Vorteil.Die Methode ermöglicht es auch, Objekte übereinander zu zeichnen. Es gewinnt das Objekt, welches 'oben' liegt.
Anbei die Demo:
#include <iostream> #include <string> #include <vector> class Pos { public: Pos() : x_(0), y_(0) {} Pos( int x, int y ) : x_(x), y_(y) {} friend int X( const Pos& pos ) { return pos.x_; } friend int Y( const Pos& pos ) { return pos.y_; } private: int x_, y_; }; class Shape { public: virtual ~Shape() =0; virtual void draw( const Pos& drawPos, char& out ) const =0; }; Shape::~Shape() {} // -- die Scheibe ist ein schlichter int // es folgen 5 überflüssige Zeilen; sie dienen nur als 'syntaktischer Zucker' (s. Tower::draw) typedef int Slice; int get_diameter( const Slice& slice ) { return slice; } // -- der Turm aus 'die Türme von Hanoi' class Tower : public Shape { public: Tower( const Pos& turm_pos ) : slices_() , pos_(turm_pos) {} void push( const Slice& slice ) { slices_.push_back( slice ); } // -- die Methode 'draw' schaut nach, ob der Punkt 'out' in der // Position 'drawPos' auf einer der Scheiben des Turms // liegt. // Falls ja, wird 'out' mit der 'FARBE' der Scheibe belegt virtual void draw( const Pos& drawPos, char& out ) const { const char FARBE = 'X'; const int idx = Y(drawPos) - Y(pos_); if( idx >= 0 && idx < int(slices_.size()) ) { const int xBegin = X(pos_) - get_diameter( slices_[idx] )/2; if( xBegin <= X(drawPos) && X(drawPos) < xBegin + get_diameter( slices_[idx] ) ) out = FARBE; // Punkt 'drawPos' liegt in einer Scheibe 'slices_[idx]' -> 'anmalen' } } private: std::vector< Slice > slices_; // die Scheiben Pos pos_; // Position des Turms (Mitte unten) }; class Display { public: Display() : shapes_() , lower_left_() , upper_right_( 60, 12 ) {} void add( const Shape& shape ) { shapes_.push_back( &shape ); } // -- Bildausschnitt setzen Display& operator()( const Pos& lower_left, const Pos& upper_right ) { lower_left_ = lower_left; upper_right_ = upper_right; return *this; } // -- 'zeichnen' des Display-Inhalts friend std::ostream& operator<<( std::ostream& out, const Display& d ) { for( int y = Y(d.upper_right_); y >= Y(d.lower_left_); --y ) { out.width(3); out << y << ": "; for( int x = X(d.lower_left_); x <= X(d.upper_right_); ++x ) { char c = out.fill(); // background const Pos pos(x, y); for( auto s = begin(d.shapes_); s != end(d.shapes_); ++s ) (*s)->draw( pos, c ); out << c; } out << '\n'; // EOL } return out << "--x->" << std::string("|-10 |-5 | |5 |10 | |20 | |30 | |40 | |50 | |60 | |80") .substr(X(d.lower_left_) + 10, X(d.upper_right_) - X(d.lower_left_) + 1) << "\n"; } private: std::vector< const Shape* > shapes_; Pos lower_left_, upper_right_; }; // -- Demo-main-Programm für obige Konstruktion int main() { using namespace std; Tower t1( Pos(10,0) ); for( int slice = 9; slice > 0; slice -= 2 ) t1.push( slice ); Tower t2( Pos(25,2) ); // der zweite Turm liegt um 2 höher t2.push( 7 ); t2.push( 3 ); t2.push( 1 ); Display display; display.add( t1 ); display.add( t2 ); cout << "zwei Tuerme:\n" << display( Pos(0,0), Pos(60,8) ) << endl; // die Koordinaten bestimmen den Bildausschnitt // -- noch ein wenig Demo-Schischi t2.push( 0 ); for( int i=1; i<36; i+=4 ) t2.push( i > 13? 26 - i: i ); cout << "zweiter Bildausschnitt:\n" << display( Pos(9,2), Pos(35,12) ) << endl; return 0; }
Noch ein Hinweis: die Ausgabe der Skaliereung für x und y ist für die Demo hier. Wer das nicht haben will, lässt die Zeilen 94 und 95 weg und ersetzt die Zeilen 106 und 107 durch ein schlichtes
return out;
.Es ist übrigens ein Leichtes, SeppJs Programm und dieses zu verbinden. Man nehme die Klassen
Display
undShape
und die MethodeTower::draw
, füge Letztere zu SeppJsTower
hinzu und ersetze die Ausgabe desTemple
durch die Ausgabe vonDisplay
.SeppJ schrieb:
Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZl.. manchmal schlägt eben der normative Zwang des Faktischen erbarmungslos zu
Gruß
Werner
-
SeppJ schrieb:
Mir war langweilig
. Das Programm erzählt die Geschichte der Türme von Hanoi. Ich habe natürlich peinlich genau darauf geachtet, dass die Scheiben wirklich verschoben werden, keine Tricksereien mit Kopien von Scheiben. Kopien von realen Gegenständen gibt es in einem wohl geformten Universum nicht. Über allem wacht der mächtige Gott Brahma, dass alle Regeln eingehalten werden:
#include <iostream> #include <vector> #include <stdexcept> #include <utility> class EndOfTheWorldAsWeKnowIt { };
P.S.: Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZlDas liest sich wie ein spannendes Adventure ... mit mystischen Unwirklichkeiten
-
Werner Salomon schrieb:
nicht schlecht SeppJ .. nur bei der Ausgabe der Türme sehe ich noch Verbesserungspotential.
Dazu habe ich mir auch was einfallen lassen. Vom Algorithmus her ist das so eine Art einfacher-2D-Raytracer. Der Kern besteht aus dem Interface
Shape
und der KlasseDisplay
.Display
hat Zugriff auf alleShape
-Objekte, die gezeichnet werden sollen. Wird einDisplay
-Objekt ausgegeben, so berechnet Display für jeden Punkt (Zeichen) in der Ausgabe die jeweilige Position und fordert alle Shapes auf, ggf ihren 'Stempel' dort zu hinterlassen.Jedes Objekt, was 'gezeichnet' werden soll, muss von
Shape
abgeleitet sein, und die Methodevoid draw( const Pos& drawPos, char& out ) const
überladen. Dort muss dann eine Implementierung dafür sorgen, dass das Zeichenout
genau dann überschrieben wird, wenn die PositiondrawPos
auf eine Punkt des Objekts zeigt. Natürlich sollte das Objekt dazu wissen, wo es sich befindet.
Die Klasse Pos enthält schlicht x- und y-Wert einer Position. Man bräuchtePos
in dieser Programm-Skizze nicht zwingend, ich finde das aber durchgängiger und verständlicher. Für Erweiterungen, die Addition- und/oder Subtraktion von Positionen nötig machen istPos
sicher von Vorteil.Die Methode ermöglicht es auch, Objekte übereinander zu zeichnen. Es gewinnt das Objekt, welches 'oben' liegt.
Noch ein Hinweis: die Ausgabe der Skaliereung für x und y ist für die Demo hier. Wer das nicht haben will, lässt die Zeilen 94 und 95 weg und ersetzt die Zeilen 106 und 107 durch ein schlichtesreturn out;
.Es ist übrigens ein Leichtes, SeppJs Programm und dieses zu verbinden. Man nehme die Klassen
Display
undShape
und die MethodeTower::draw
, füge Letztere zu SeppJsTower
hinzu und ersetze die Ausgabe desTemple
durch die Ausgabe vonDisplay
.SeppJ schrieb:
Da manche Leute immer noch kein C++11 haben:
https://ideone.com/bgcPZl.. manchmal schlägt eben der normative Zwang des Faktischen erbarmungslos zu
Gruß
WernerCode lesen ...
Ich sehe's schon ... es wird bei mir noch lange dauern ...
-
Werner Salomon schrieb:
.. nur bei der Ausgabe der Türme sehe ich noch Verbesserungspotential.
Ja, das war auch meine Sorge beim Programmieren. Erst alles schön fertig gemacht, aber dann wusste ich nicht mehr weiter, wie ich das elegant dargestellt bekomme. Derzeit ist es eine Krücke, die Objekte zeichnen sich selbst, das sollte nicht so sein. Logik und Darstellung sollten sauber getrennt sein, aber ich kenne die üblichen Designs dafür nicht und so wirklich zufrieden war ich auch nicht mit dem, was ich mir auf die Schnelle ausgedacht habe. Dein Design ist da ähnlich, wenn auch eleganter. Ist das schon eine der üblichen Vorgehensweisen? Da müsste man jetzt wohl wirklich mal einen Spieleprogrammierer fragen, wie das gut gemacht wird.