Seite 1 von 1

Türme von Hanoi

Verfasst: Donnerstag 17. Dezember 2020, 16:48
von Tyroun
Gute Tag zusammen,

Ich habe eine Frage und zwar soll ich die Türme von Hanoi programmieren und mit tkinter dann Animieren.
Nun bin ich schon recht weit gekommen (siehe Code unten). Jetzt häng ich nur bei dem Animieren, dass die Scheiben sich von einem Fleck zum anderen bewegen hin und her,wie man es bei den Türmen von Hanoi kennt.

Code: Alles auswählen

import tkinter as tk
import time

# Fenster anlegen
window = tk.Tk()
window.title("Türme von Hanoi")
# Frame anlegen
frame = tk.Frame(window)
frame.pack()
# Canvas anlegen, Hintergrundfarbe und Größe wählen
canvas = tk.Canvas(frame, bg="#f0e0b0", width=600, height=400)
canvas.pack()


# Animation starten
class Hanoi:
    def __init__(self, anzahl, ursprung, hilf, ziel):
        self.staebe = {
            ursprung: Stack(),
            hilf: Stack(),
            ziel: Stack()
        }
   
        self.staebe[ursprung].push(Scheibe.create_ini(self))
        i = 1
        m = 1
        self.n = anzahl
        while (self.n > i):
            self.staebe[ursprung].push(Scheibe.create(self,m))
            m += 1
            self.n -= 1

    def stabUmstapeln(self, n, ursprung, hilf, ziel):
        if n >= 1:
            self.stabUmstapeln(n - 1, ursprung, ziel, hilf)
            self.scheibenUmstapeln(ursprung, ziel)
            self.stabUmstapeln(n - 1, hilf, ursprung, ziel)

    def scheibenUmstapeln(self, ursprung, ziel):
        self.staebe[ziel].push(self.staebe[ursprung].pop())
        # alle stangen neu schreiben

class Scheibe:
    def create_ini(self):
        item = canvas.create_rectangle(10, 400, 150, 380, fill='blue')
        return item

    def create(self,i):
        item = canvas.create_rectangle(10+(10*i), 400-(20*i), 150-(10*i), 380-(20*i), fill='blue')
        return item

class Stack:
    def __init__(self):
        self.liste = []

    def it(self):
        return self.liste

    def push(self, element):
        self.liste.append(element)

    def push1(self, element):
        self.liste.append(element)

    def pop(self):
        return self.liste.pop()

n = 8
stab = Hanoi(n, "A", "B", "C")
stab.stabUmstapeln(n, "A", "B", "C")
# Fenster offenhalten
window.mainloop()
Kann mir jemand bei der Problematik mit dem Animieren helfen, weil ich habe null Ahnung wie ich es machen soll :(
LG Grüße

Re: Türme von Hanoi

Verfasst: Donnerstag 17. Dezember 2020, 17:02
von Sirius3
Was kann denn der Stack anderes als eine Liste? Warum gibt es ein push und ein push1? Was soll die it-Methode denn?
Kurz, Stack kann ersatzlos gestrichen werden und durch list ersetzt werden.
Die Methoden von `Scheibe` benutzen ein globales canvas, das darf nicht sein, alles was eine Methode braucht muß sie auch über ihre Argumente bekommen. Die Klasse an sich ist überflüssig, weil sie keinen Zustand hat, das sind einfach nur ganz normale Funktionen. Dass das keine Klasse ist, siehst Du schon alleine daran, dass Du gar keine Instanz der Klasse erzeugst. Was ist der Sinn von create_ini?

Die Argumente von Hanoi.__init__ sind komisch. Was ist der Sinn "A", "B" und "C" variabel zu haben?
Wenn eine Laufvariable ein Attribut ist, dann ist das mit Sicherheit falsch, wie hier bei n. Statt der while-Schleife benutzt man eine for-Schleife. Was ist der Unterschied zwischen n und m? Und was ist der Sinn von i?
Methodennamen werden generell, wie Variablenamen auch, komplett klein geschrieben.

Alles auf oberster Ebene muß auch in eine Funktion gepackt werden, die üblicherweise main genannt wird.
Der letzte Kommentar "# Fenster offenhalten" ist falsch, denn das ist die Hauptschleife, ohne die bei einem GUI-Programm im wahrsten Sinne des Wortes nichts läuft.

Der Code mal aufgeräumt:

Code: Alles auswählen

import tkinter as tk

class Hanoi:
    def __init__(self, canvas, anzahl):
        self.canvas = canvas
        self.staebe = {
            "A": [self.create(i) for i in range(anzahl)],
            "B": [],
            "C": [],
        }

    def umstapeln(self, n, ursprung, hilf, ziel):
        if n >= 1:
            self.umstapeln(n - 1, ursprung, ziel, hilf)
            self.staebe[ziel].append(self.staebe[ursprung].pop())
            self.umstapeln(n - 1, hilf, ursprung, ziel)

    def create(self, i):
        return self.canvas.create_rectangle(10+(10*i), 400-(20*i), 150-(10*i), 380-(20*i), fill='blue')

def main():
    window = tk.Tk()
    window.title("Türme von Hanoi")
    canvas = tk.Canvas(window, bg="#f0e0b0", width=600, height=400)
    canvas.pack()

    n = 8
    stab = Hanoi(canvas, n)
    stab.umstapeln(n, "A", "B", "C")
    window.mainloop()

if __name__ == '__main__':
    main()
So kann das aber mit der Animation nicht laufen. Für eine Animation mußt Du für jeden Schritt die Rechtecke im Canvas verschieben und dann wieder in den mainloop zurückkehren. Es darf also nicht einen rekursiven Aufruf von umstapeln geben, sondern Du mußt jeden Schritt einzeln durchführen und den nächsten Schritt dann per after anstoßen.

Re: Türme von Hanoi

Verfasst: Donnerstag 17. Dezember 2020, 17:21
von Tyroun
Danke für die schnelle Antwort.

Tatsächlich wurde uns vorgegeben wir sollen ein Klasse Stack, und eine Klasse Scheibe die die Größe , Farbe regelt haben und benutzen. Das es ein push1 gibt war noch ein Aus versehen, da ich davor verschiedene Sachen ausprobiert habe. Die It-Methode ist auch noch vom rumprobieren,da hab ich ausversehen eine alte Version reingeschrieben. Und der Sinn von create_ini ist die unterste Scheibe zu erstellen, anders hab ich es nicht geschafft.

Re: Türme von Hanoi

Verfasst: Donnerstag 17. Dezember 2020, 17:30
von __blackjack__
@Tyroun: `time` wird importiert aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

`stab` ist ein schlechter Name für ein `Hanoi`-Objekt, denn das beschreibt ja nicht *einen* Stab, sondern *drei* Stäbe.

Grunddatentypen haben in Namen nichts zu suchen. Man ändert so etwas zu oft, so dass man die Namen dann entweder überall anpassen muss, oder irreführende Namen im Code hat.

Bei der `Stack`-Klasse machen die `it()`-Methode (was soll das bedeuten?) und die `push1()`-Methode keinen Sinn. Die werden auch nirgends verwendet.

Die Scheibe-Klasse macht keinen Sinn. Da sind einfach nur zwei Funktionen in eine Klasse gesteckt worden die überhaupt gar keinen Zustand verwaltet. Es wird ja auch nie irgendwo im Programm ein Objekt vom Typ `Scheibe` erstellt. `self` wird nicht verwendet und beim Aufrufen übergibst Du aus irgendwelchen Gründen ein `Hanoi`-Objekt. Was dagegen fehlt ist `canvas`. Das kommt einfach aus dem Nichts, muss aber eigentlich als Argument übergeben werden.

Die beiden Funktionen sind auch redundant, denn `create_ini()` ist eigentlich `create()` mit i=0.

In der `Hanoi.__init__` wird `i` an 1 gebunden und nie verändert. Den Namen kann man sich also sparen und die 1 an der Stelle einsetzen wo `i` verwendet wird.

Dann gibt es da `self.n` und `m` wobei das eine rauf und das andere runtergezählt wird. Manuell, in einer ``while``-Schleife. `self.n` wird ausserhalb der `__init__()` nicht mehr verwendet. Das ist alles ziemlich unsinnig. `m` fängt effektiv bei 1 an. Aber vor dieser verqueren Schleife wird der gleiche Code der für jedes `m` aufgerufen einmal mit 0 aufgerufen. Was soll diese extra Zeile wenn man doch einfach `m` bei 0 anfangen lassen können?

Das hier:

Code: Alles auswählen

        self.staebe[ursprung].push(create_scheibe(canvas, 0))
        i = 1
        m = 1
        self.n = anzahl
        while self.n > i:
            self.staebe[ursprung].push(create_scheibe(canvas, m))
            m += 1
            self.n -= 1
ist effektiv einfach nur das hier:

Code: Alles auswählen

        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))
`scheiben_umstapeln()` ist ein unpassender Name, denn da wird *eine* Scheibe bewegt, nicht mehrere.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk


class Stack:
    def __init__(self):
        self.elemente = []

    def push(self, element):
        self.elemente.append(element)

    def pop(self):
        return self.elemente.pop()


def create_scheibe(canvas, i):  # TODO Besserer Name für `i`.
    return canvas.create_rectangle(
        10 + (10 * i),
        400 - (20 * i),
        150 - (10 * i),
        380 - (20 * i),
        fill="blue",
    )


class Hanoi:
    def __init__(self, canvas, anzahl, ursprung, hilf, ziel):
        self.staebe = {ursprung: Stack(), hilf: Stack(), ziel: Stack()}
        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))

    def stab_umstapeln(self, anzahl, ursprung, hilf, ziel):
        if anzahl >= 1:
            self.stab_umstapeln(anzahl - 1, ursprung, ziel, hilf)
            self.scheibe_umstapeln(ursprung, ziel)
            self.stab_umstapeln(anzahl - 1, hilf, ursprung, ziel)

    def scheibe_umstapeln(self, ursprung, ziel):
        self.staebe[ziel].push(self.staebe[ursprung].pop())


def main():
    window = tk.Tk()
    window.title("Türme von Hanoi")

    canvas = tk.Canvas(window, bg="#f0e0b0", width=600, height=400)
    canvas.pack()

    scheibenanzahl = 8
    staebe = Hanoi(canvas, scheibenanzahl, "A", "B", "C")
    staebe.stab_umstapeln(scheibenanzahl, "A", "B", "C")

    window.mainloop()


if __name__ == "__main__":
    main()
Letztlich ist das ganze mit einem ereignisbasierten GUI-Rahmenwerk nicht so einfach wie Du Dir das vielleicht vorstellst. Auf der einen Seite hast Du einen rekursiven Algorithmus der in einem Aufruf komplett durchlaufen möchte. Auf der anderen Seite ein GUI-Rahmenwerk das nach jedem Schritt die Kontrolle zurück haben muss um den Schritt darstellen zu können.

Die beiden naheliegendsten Möglichkeiten die mir da einfallen würden, wären die Informationen zu den Bewegungen vor der Animation alle als Daten erzeugen. Oder die rekursive Lösung zu einer rekursiven Generatorfunktion machen, welche die Informationen zu den Schritten generiert.

Oder Du verwendest etwas anderes zur Visualisierung. `pygame` beispielsweise.

Re: Türme von Hanoi

Verfasst: Donnerstag 17. Dezember 2020, 17:46
von Tyroun
__blackjack__ hat geschrieben: Donnerstag 17. Dezember 2020, 17:30 @Tyroun: `time` wird importiert aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

`stab` ist ein schlechter Name für ein `Hanoi`-Objekt, denn das beschreibt ja nicht *einen* Stab, sondern *drei* Stäbe.

Grunddatentypen haben in Namen nichts zu suchen. Man ändert so etwas zu oft, so dass man die Namen dann entweder überall anpassen muss, oder irreführende Namen im Code hat.

Bei der `Stack`-Klasse machen die `it()`-Methode (was soll das bedeuten?) und die `push1()`-Methode keinen Sinn. Die werden auch nirgends verwendet.

Die Scheibe-Klasse macht keinen Sinn. Da sind einfach nur zwei Funktionen in eine Klasse gesteckt worden die überhaupt gar keinen Zustand verwaltet. Es wird ja auch nie irgendwo im Programm ein Objekt vom Typ `Scheibe` erstellt. `self` wird nicht verwendet und beim Aufrufen übergibst Du aus irgendwelchen Gründen ein `Hanoi`-Objekt. Was dagegen fehlt ist `canvas`. Das kommt einfach aus dem Nichts, muss aber eigentlich als Argument übergeben werden.

Die beiden Funktionen sind auch redundant, denn `create_ini()` ist eigentlich `create()` mit i=0.

In der `Hanoi.__init__` wird `i` an 1 gebunden und nie verändert. Den Namen kann man sich also sparen und die 1 an der Stelle einsetzen wo `i` verwendet wird.

Dann gibt es da `self.n` und `m` wobei das eine rauf und das andere runtergezählt wird. Manuell, in einer ``while``-Schleife. `self.n` wird ausserhalb der `__init__()` nicht mehr verwendet. Das ist alles ziemlich unsinnig. `m` fängt effektiv bei 1 an. Aber vor dieser verqueren Schleife wird der gleiche Code der für jedes `m` aufgerufen einmal mit 0 aufgerufen. Was soll diese extra Zeile wenn man doch einfach `m` bei 0 anfangen lassen können?

Das hier:

Code: Alles auswählen

        self.staebe[ursprung].push(create_scheibe(canvas, 0))
        i = 1
        m = 1
        self.n = anzahl
        while self.n > i:
            self.staebe[ursprung].push(create_scheibe(canvas, m))
            m += 1
            self.n -= 1
ist effektiv einfach nur das hier:

Code: Alles auswählen

        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))
`scheiben_umstapeln()` ist ein unpassender Name, denn da wird *eine* Scheibe bewegt, nicht mehrere.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk


class Stack:
    def __init__(self):
        self.elemente = []

    def push(self, element):
        self.elemente.append(element)

    def pop(self):
        return self.elemente.pop()


def create_scheibe(canvas, i):  # TODO Besserer Name für `i`.
    return canvas.create_rectangle(
        10 + (10 * i),
        400 - (20 * i),
        150 - (10 * i),
        380 - (20 * i),
        fill="blue",
    )


class Hanoi:
    def __init__(self, canvas, anzahl, ursprung, hilf, ziel):
        self.staebe = {ursprung: Stack(), hilf: Stack(), ziel: Stack()}
        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))

    def stab_umstapeln(self, anzahl, ursprung, hilf, ziel):
        if anzahl >= 1:
            self.stab_umstapeln(anzahl - 1, ursprung, ziel, hilf)
            self.scheibe_umstapeln(ursprung, ziel)
            self.stab_umstapeln(anzahl - 1, hilf, ursprung, ziel)

    def scheibe_umstapeln(self, ursprung, ziel):
        self.staebe[ziel].push(self.staebe[ursprung].pop())


def main():
    window = tk.Tk()
    window.title("Türme von Hanoi")

    canvas = tk.Canvas(window, bg="#f0e0b0", width=600, height=400)
    canvas.pack()

    scheibenanzahl = 8
    staebe = Hanoi(canvas, scheibenanzahl, "A", "B", "C")
    staebe.stab_umstapeln(scheibenanzahl, "A", "B", "C")

    window.mainloop()


if __name__ == "__main__":
    main()
Letztlich ist das ganze mit einem ereignisbasierten GUI-Rahmenwerk nicht so einfach wie Du Dir das vielleicht vorstellst. Auf der einen Seite hast Du einen rekursiven Algorithmus der in einem Aufruf komplett durchlaufen möchte. Auf der anderen Seite ein GUI-Rahmenwerk das nach jedem Schritt die Kontrolle zurück haben muss um den Schritt darstellen zu können.

Die beiden naheliegendsten Möglichkeiten die mir da einfallen würden, wären die Informationen zu den Bewegungen vor der Animation alle als Daten erzeugen. Oder die rekursive Lösung zu einer rekursiven Generatorfunktion machen, welche die Informationen zu den Schritten generiert.

Oder Du verwendest etwas anderes zur Visualisierung. `pygame` beispielsweise.

Vielen Dank für die Tipps.

Ja push1 und it sollten weg sein, da hab ich eine Falsche Version vom Code geschickt.
Ich versuch es nochmal mit den Tipps das Problem zu lösen. Mal schauen ob ich es umsetzen kann, weil ich bin echt nicht gut im Programmieren und bin grad im Stress wegen Prüfungsvorbereitung .

LG

Re: Türme von Hanoi

Verfasst: Freitag 18. Dezember 2020, 16:56
von Tyroun
__blackjack__ hat geschrieben: Donnerstag 17. Dezember 2020, 17:30 @Tyroun: `time` wird importiert aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

`stab` ist ein schlechter Name für ein `Hanoi`-Objekt, denn das beschreibt ja nicht *einen* Stab, sondern *drei* Stäbe.

Grunddatentypen haben in Namen nichts zu suchen. Man ändert so etwas zu oft, so dass man die Namen dann entweder überall anpassen muss, oder irreführende Namen im Code hat.

Bei der `Stack`-Klasse machen die `it()`-Methode (was soll das bedeuten?) und die `push1()`-Methode keinen Sinn. Die werden auch nirgends verwendet.

Die Scheibe-Klasse macht keinen Sinn. Da sind einfach nur zwei Funktionen in eine Klasse gesteckt worden die überhaupt gar keinen Zustand verwaltet. Es wird ja auch nie irgendwo im Programm ein Objekt vom Typ `Scheibe` erstellt. `self` wird nicht verwendet und beim Aufrufen übergibst Du aus irgendwelchen Gründen ein `Hanoi`-Objekt. Was dagegen fehlt ist `canvas`. Das kommt einfach aus dem Nichts, muss aber eigentlich als Argument übergeben werden.

Die beiden Funktionen sind auch redundant, denn `create_ini()` ist eigentlich `create()` mit i=0.

In der `Hanoi.__init__` wird `i` an 1 gebunden und nie verändert. Den Namen kann man sich also sparen und die 1 an der Stelle einsetzen wo `i` verwendet wird.

Dann gibt es da `self.n` und `m` wobei das eine rauf und das andere runtergezählt wird. Manuell, in einer ``while``-Schleife. `self.n` wird ausserhalb der `__init__()` nicht mehr verwendet. Das ist alles ziemlich unsinnig. `m` fängt effektiv bei 1 an. Aber vor dieser verqueren Schleife wird der gleiche Code der für jedes `m` aufgerufen einmal mit 0 aufgerufen. Was soll diese extra Zeile wenn man doch einfach `m` bei 0 anfangen lassen können?

Das hier:

Code: Alles auswählen

        self.staebe[ursprung].push(create_scheibe(canvas, 0))
        i = 1
        m = 1
        self.n = anzahl
        while self.n > i:
            self.staebe[ursprung].push(create_scheibe(canvas, m))
            m += 1
            self.n -= 1
ist effektiv einfach nur das hier:

Code: Alles auswählen

        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))
`scheiben_umstapeln()` ist ein unpassender Name, denn da wird *eine* Scheibe bewegt, nicht mehrere.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk


class Stack:
    def __init__(self):
        self.elemente = []

    def push(self, element):
        self.elemente.append(element)

    def pop(self):
        return self.elemente.pop()


def create_scheibe(canvas, i):  # TODO Besserer Name für `i`.
    return canvas.create_rectangle(
        10 + (10 * i),
        400 - (20 * i),
        150 - (10 * i),
        380 - (20 * i),
        fill="blue",
    )


class Hanoi:
    def __init__(self, canvas, anzahl, ursprung, hilf, ziel):
        self.staebe = {ursprung: Stack(), hilf: Stack(), ziel: Stack()}
        for m in range(anzahl):  # TODO `m` ist ein besch…dener Name!
            self.staebe[ursprung].push(create_scheibe(canvas, m))

    def stab_umstapeln(self, anzahl, ursprung, hilf, ziel):
        if anzahl >= 1:
            self.stab_umstapeln(anzahl - 1, ursprung, ziel, hilf)
            self.scheibe_umstapeln(ursprung, ziel)
            self.stab_umstapeln(anzahl - 1, hilf, ursprung, ziel)

    def scheibe_umstapeln(self, ursprung, ziel):
        self.staebe[ziel].push(self.staebe[ursprung].pop())


def main():
    window = tk.Tk()
    window.title("Türme von Hanoi")

    canvas = tk.Canvas(window, bg="#f0e0b0", width=600, height=400)
    canvas.pack()

    scheibenanzahl = 8
    staebe = Hanoi(canvas, scheibenanzahl, "A", "B", "C")
    staebe.stab_umstapeln(scheibenanzahl, "A", "B", "C")

    window.mainloop()


if __name__ == "__main__":
    main()
Letztlich ist das ganze mit einem ereignisbasierten GUI-Rahmenwerk nicht so einfach wie Du Dir das vielleicht vorstellst. Auf der einen Seite hast Du einen rekursiven Algorithmus der in einem Aufruf komplett durchlaufen möchte. Auf der anderen Seite ein GUI-Rahmenwerk das nach jedem Schritt die Kontrolle zurück haben muss um den Schritt darstellen zu können.

Die beiden naheliegendsten Möglichkeiten die mir da einfallen würden, wären die Informationen zu den Bewegungen vor der Animation alle als Daten erzeugen. Oder die rekursive Lösung zu einer rekursiven Generatorfunktion machen, welche die Informationen zu den Schritten generiert.

Oder Du verwendest etwas anderes zur Visualisierung. `pygame` beispielsweise.
Hallo ich nochmal,

ich habe jetzt nochmal auf krampf versucht es zu machen aber ich bekomme es leider nicht :(
hast du vllt nochmal ein Tipp wie ich es angehen kann ?

LG