Seite 3 von 3
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 13:57
von andi_wand
So, aktueller Stand ist wie folgt:
Code: Alles auswählen
'''
Created on 07.12.2011
@author: Roland-User
'''
# -*- coding: iso-8859-1 -*-
DIRECTIONS = RIGHT, LEFT, UP, DOWN = [(1,0),(-1,0),(0,-1),(0,1)]
DIRECTION_TO_STR = { RIGHT: 'rechts', LEFT: 'links', UP: 'oben', DOWN: 'unten' }
FINISH_CHAR="F"
FREE_CHAR=" "
VISITED_CHAR="."
def suggest_dir(pos,env):
(x,y)=pos
result=list()
for direction in DIRECTIONS:
(x_new,y_new)=direction
field=env[x+x_new][y+y_new]
if(field==FINISH_CHAR):
return [direction]
if (field==FREE_CHAR):
result.append(direction)
return result
class maze:
def __init__(self,Input_Filename):
self.Input_File=open(Input_Filename,'r')
self.maze_File=self.Input_File.readlines()
self.maze_Rows=[]
self.correct=False
for i in xrange(len(self.maze_File)):
if i+1 < len(self.maze_File):
self.storage=self.maze_File[i]
self.storage=self.storage[:len(self.storage)-1]
self.maze_File[i]=self.storage
for i in xrange(len(self.maze_File)):
storage=[]
for j in xrange(len(self.maze_File[i])):
storage.append(self.maze_File[i][j])
self.maze_Rows.append(storage)
self.Input_File.close()
print self.maze_Rows
print len(self.maze_Rows)
'__getitem__ gibt einfach maze_Rows an der Stelle (x,y) aus'
def __getitem__(self,(x,y)):
return self.maze_Rows[y][x]
'Die Funktion __setitem__ erlaubt es, bestimmte Zellen innerhalb des Labyrinths mit eigenen Daten zu fuellen'
def __setitem__(self,(x,y),input_args):
try:
self.maze_Rows[y][x]=input_args
except IndexError:
print "Der angegebene Index ist ausserhalb des Labyrinths"
except:
print "Ein Fehler ist aufgetreten"
'Diese Funktion ist fuer das korrekte funktionieren des Befehls "print <Klasse>" zustaendig'
def __str__(self):
return '\n'.join(' '.join(line) for line in self.maze_Rows)
'getStart gibt den Ort des Startpunktes zurueck'
def getStart(self):
for i in xrange(len(self.maze_Rows)):
for j in xrange(len(self.maze_Rows[i])):
if(self.__getitem__((j, i))=='S'):
return i,j
else:
pass
return False
'getFinish gibt den Ort des Zielpunktes zurueck'
def getFinish(self):
for i in xrange(len(self.maze_Rows)):
for j in xrange(len(self.maze_Rows[i])):
if(self.__getitem__((j,i))=='F'):
return i,j
else:
pass
return False
def solve_maze(self,pos):
print self.__str__()
if (self.getFinish() and self.getStart()) or self.correct:
self.correct=True
(x,y)=pos
suggested_dir=suggest_dir(pos,self.maze_Rows)
#print suggest_dir(pos,self.maze_Rows)
if (x, y) == self.getFinish():
print self.__str__()
print 'Maze solved'
else:
suggested_dir=suggest_dir(pos,self.maze_Rows)
if not suggested_dir:
print "Sackgasse"
print pos
self.maze_Rows[pos[0]][pos[1]]=VISITED_CHAR
return 1
else:
for direction in suggested_dir:
(x_delta,y_delta)=direction
self.maze_Rows[x][y]=VISITED_CHAR
self.solve_maze((x+x_delta,y+y_delta))
else:
print "Das Labyrinth ist nicht loesbar, da wichtige Punkte fehlen. Bitte geben Sie ein gueltiges Labyrinth an und starten Sie"
print "dann das Skript erneut"
Folgendes Problem habe ich noch: Wie kann ich denn jetzt herausfinden, ob ich evtl. schon die letzte Sackgasse besucht habe, d.h. ob das Labyrinth überhaupt lösbar ist?
@BlackJack: Der Style Guide ist grad in der Umsetzung, ebenso werden die Kommentare noch umgesetzt
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 13:59
von andi_wand
Meine Idee dazu war, dass ich mir merke, wie viele Kreuzungen ich bereits besucht habe, und diese nacheinander wieder abklappere. Wenn ich keine Kreuzung mehr habe, aber das Ziel noch nicht erreicht habe->Labyrinth ist nicht lösbar.
Aber das Programm zählt ja auf dem Hinweg alle Kreuzungen, auf dem Rückweg leider nicht...
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 14:31
von /me
andi_wand hat geschrieben:So, aktueller Stand ist wie folgt:
Code: Alles auswählen
[...]
def getStart(self):
for i in xrange(len(self.maze_Rows)):
for j in xrange(len(self.maze_Rows[i])):
if(self.__getitem__((j, i))=='S'):
return i,j
else:
pass
return False
[...]
Ich muss es leider so deutlich sagen: Das liefert zwar wahrscheinlich das gewünschte Ergebnis, ist aber vom Stil her immer noch ein Haufen Müll.
andi_wand hat geschrieben:Folgendes Problem habe ich noch: Wie kann ich denn jetzt herausfinden, ob ich evtl. schon die letzte Sackgasse besucht habe, d.h. ob das Labyrinth überhaupt lösbar ist?
Was dir fehlt ist ein passender Rückgabewert aus der rekursiven Funktion der anzeigt, ob das Zielfeld gefunden wurde. Schau dir doch mal die bereits geposteten Lösungen an. Meine baut ja sogar auf deinem Programm auf.
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 19:01
von andi_wand
So, letzer Stand:
Code: Alles auswählen
import time
DIRECTIONS = RIGHT, LEFT, UP, DOWN = [(1,0),(-1,0),(0,-1),(0,1)]
FINISH_CHAR="F"
FREE_CHAR=" "
VISITED_CHAR="."
BLOCK_CHAR="*"
def suggest_dir(pos,env): #"""Die Funktion suggest_dir prueft, in welche Richtungen ein Laufen moeglich ist"""
(x,y)=pos
result=list()
for direction in DIRECTIONS:
(x_new,y_new)=direction
field=env[x+x_new][y+y_new]
if(field==FINISH_CHAR):
return [direction]
if (field==FREE_CHAR):
result.append(direction)
return result
class maze: #"""Klasse Maze"""
def __init__(self,Input_Filename): #"""Konstruktor der Klasse Maze, liest das Labyrinth ein"""
try:
self.Input_File=open(Input_Filename,'r')
except IOError:
print "File does not exist"
self.maze_Rows=[]
self.correct=False
self.LabSolved=False
self.convert_data(self.Input_File)
self.recursion_deep=0
def convert_data(self,maze_File): #"""convert_data wandelt den eingelesenen String in eine mehrdimensionale Liste um"""
self.maze_Rows=[list(row.strip()) for row in maze_File]
def __getitem__(self,(x,y)): #"""__getitem__ liefert den Wert des Labyrinths an der Stelle x,y zurueck"""
return self.maze_Rows[y][x]
def __setitem__(self,(x,y),input_args): #"""Mit __setitem__ kann man den Wert des Labyrinths an der Stelle (x,y) auf den Wert input_args setzen"""
try:
self.maze_Rows[y][x]=input_args
except IndexError:
print "Der angegebene Index ist ausserhalb des Labyrinths"
except:
print "Ein Fehler ist aufgetreten"
def __str__(self): #"""Diese Funktion ist fuer das korrekte funktionieren des Befehls "print <Klasse>" zustaendig"""
maze_clear=self.maze_Rows
for i,row in enumerate(maze_clear):
for j,position in enumerate(row):
if(position==BLOCK_CHAR):
maze_clear[i][j]=" "
else:
pass
return '\n'.join(' '.join(line) for line in maze_clear)
def getPosition(self, key): #"""getPosition gibt den Ort eines beliebigen Wertes zurueck"""
for i,row in enumerate(self.maze_Rows):
for j,position in enumerate(row):
if(position==key):
return i,j
else:
pass
return False
def getFinish(self): #"""getFinish gibt den Ort des Zielpunktes zurueck"""
return self.getPosition("F")
def getStart(self): #"""getStart gibt den Ort des Startpunktes zurueck"""
return self.getPosition("S")
def solve_maze(self,pos): #"""solve_maze loest das uebergebene Labyrinth"""
print self.recursion_deep
#time.sleep(0.5)
if pos:
(x,y)=pos
else:
pos=self.getStart()
if pos==self.getFinish():
print self.__str__()
print "Maze solved"
self.LabSolved=True
if (self.getFinish() and self.getStart()) or self.correct:
if not self.LabSolved:
self.correct=True
suggested_dir=suggest_dir(pos,self.maze_Rows)
if not suggested_dir:
if(self.recursion_deep==1):
print "Labyrinth kann nicht geloest werden"
return False
else:
self.maze_Rows[pos[0]][pos[1]]=BLOCK_CHAR
#print "Sackgasse"
#print pos
#self.maze_Rows[pos[0]][pos[1]]=FREE_CHAR
else:
for direction in suggested_dir:
(x_delta, y_delta)=direction
self.maze_Rows[x][y]=VISITED_CHAR
print self.__str__()
self.recursion_deep=self.recursion_deep+1
value=self.solve_maze((x+x_delta,y+y_delta))
if value:
return True
self.maze_Rows[x][y]=FREE_CHAR
self.recursion_deep=self.recursion_deep-1
return False
else:
return True
else:
print "Das Labyrinth ist nicht loesbar, da wichtige Punkte fehlen. Bitte geben Sie ein gueltiges Labyrinth an und starten Sie"
print "dann das Skript erneut"
return False
Ich hoffe, ich habe jetzt alle Konventionen eingehalten.
Einziger Fehler: Wenn das Labyrinth nicht lösbar ist, dann bleibt der Läufer irgend wann stehen. Wie kann ich denn den Lauf abbrechen?
Vielen Dank für eure Hilfe
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 21:41
von andi_wand
Hat sich erledigt, ich denke, die restlichen Optimierungen kann ich mir schenken.
Vielen Dank an alle, die mir hier geholfen haben
Re: Rekursionen abbrechen
Verfasst: Freitag 9. Dezember 2011, 23:19
von BlackJack
@andi_wand: Die Kommentare zu den Funktionen/Methoden sind immer noch falsch. Die gehören als Zeichenkette *unter* die Definitionen, damit es DocStrings sind.
Code: Alles auswählen
def suggest_dir(pos,env):
"""Die Funktion suggest_dir prueft, in welche Richtungen ein Laufen
moeglich ist
"""
(x,y)=pos
# ...
Nur *so* wird das vom Compiler erkannt und kann zur Laufzeit oder von diversen Werkzeugen ausgewertet werden.
Deine Fehlerbehandlung ist keine, da sie selber fehlerhaft ist. Wenn es zum Beispiel beim öffnen der Datei zu einer Ausnahme kommst, gibst Du den Text aus, dass die Datei nicht existiert — was nebenbei gesagt gar nicht der Fall sein muss — und machst dann einfach weiter. Was zwangsläufig zu einem `AttributeError` vier Zeilen später führt. Das ist Murks.
Genau so das ignorieren von Ausnahmen in `__setitem__()`. Einfach einen Text ausgeben und so weiter machen als wäre nichts passiert, ist keine vernünftige Fehlerbehandlung. Wenn man an einer Stelle nicht sinnvoll auf eine Ausnahme reagieren kann, dann sollte man es auch gar nicht erst versuchen. Es ist doch vollkommen in Ordnung wenn die `__setitem__()`-Methode einen `IndexError` auslöst, wenn die Koordinaten nicht im Labyrinth liegen. Der Aufrufer kann dann entscheiden was er in dem Fall tun möchte.
Und mit einem nackten ``except:`` *alle* möglichen Ausnahmen mit einer "Ein Fehler ist aufgetreten"-Ausgabe zu verschlucken ist so gar keine gute Idee.
``else: pass``!?
`recursion_deep` sollte wohl eher `recurcion_depth` heissen. Und Du solltest Dich bei der Namensgebung auf `Maze` oder `Labyrinth` festlegen. Laut Aufgabenstellung soll es `Maze` sein. Dann ist `LabSolved` aber vielleicht auch besser ein `maze_solved`. Oder nur `solved`, denn das `maze` steckt schon im Datentyp. Den Sinn dieses Attributs habe ich nicht so ganz verstanden!? `self.correct` und dass in jedem rekursiven Aufruf `getStart()` und `getFinish()` ausgeführt werden, letzteres sogar *zweimal*, ist auch etwas schräg.
Du rufst immer noch `__str__()` direkt auf.
Re: Rekursionen abbrechen
Verfasst: Sonntag 11. Dezember 2011, 16:34
von andi_wand
Wie soll ich denn das sonst mit dem "except:" machen? Ich dachte mir, dass ich dann einfach alle Fehler, die sonst noch auftreten, abfange, oder mache ich das anders?
Re: Rekursionen abbrechen
Verfasst: Sonntag 11. Dezember 2011, 16:51
von EyDu
Wenn du einen Fehler nicht sinnvoll behandeln kannst, dann muss dieser eben zum Absturz des gesamten Programms führen. Wenn du mit einem except alle Fehler abfängst, dan behauptest du, dass sich das Programm anschließend in einem konsistenten Zustand befindet, was aber gar nicht der Fall ist. Zu einem späteren Zeitpunkt wird das unweigerlich an einer anderen Stelle zu einer Exception springen und das Spiel geht von vorne los. Zwischendurch kann natürlich alles mögliche passieren, wie das Fehlerhafte schreiben von Datein oder sonst etwas. Hinzu kommt, dass Fehler bei einem leeren Except nicht mehr nachvollziehbar sind. Es ist zwar schön, dass du anzeigst, dass ein Fehler aufgetreten ist, aber viel wichtiger ist WELCHER Fehler aufgetreten ist, damit du das korrigieren kannst.
Re: Rekursionen abbrechen
Verfasst: Montag 12. Dezember 2011, 10:25
von andi_wand
Vorschlag:
Würde das dann gehen? Das Programm wird ja zuverlässig gestoppt.
Re: Rekursionen abbrechen
Verfasst: Montag 12. Dezember 2011, 10:49
von EyDu
Nein, das ist total sinnlos. Du willst wissen um welchen Fehler an welcher Stelle es sich handelt. Wie willst du nach einem Fehler suchen, wenn du nicht einmal weißt um was für einen Fehler es sich handelt? Entweder lässt du also die Exception durchlaufen oder schreibst den Fehler inklusive Traceback in ein Log.
Re: Rekursionen abbrechen
Verfasst: Montag 12. Dezember 2011, 10:50
von Hyperion
andi_wand hat geschrieben:Vorschlag:
Würde das dann gehen? Das Programm wird ja zuverlässig gestoppt.
Jein. Das ist letztlich immer noch schlecht, da der Fehler bzw. der Grund nicht ausgegeben werden. Bei diesem Code kannst Du Dir die "Behandlung" doch sparen, da Du nichts behandelst. Hättest Du diesen Code nicht, so würde das Programm eh beendet, jedoch dann mit einer helfenden Fehlermeldung:
Code: Alles auswählen
>>> try:
... 4 / 0
... except:
... print("Fehler")
...
Fehler
Was bringt Dir das jetzt? Im Normalfall siehst Du den Code dazu ja nicht und bekommst jetzt irgend wann diese Meldung auf der Konsole. Und nu? Du kannst nicht zurückverfolgen, wo der Fehler auftrat, um entweder einen Bug zu fixen oder aber festzustellen, dass man an dieser Stelle den Fehler wirklich behandeln kann!
Code: Alles auswählen
>>> 4 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Ohne Deine Behandlung liest sich das doch gleich besser, oder? Hier siehst Du, was genau passiert ist und wo im Code das Problem auftrat.
Nehmen wir an, dass die "0" im Nenner durch eine Benutzereingabe zustande kommt. Nun kannst Du überlegen, ob Du den Fehler behandeln magst. Im einfachsten Falle formulierst Du eine Fehlermeldung, die für einen Nicht-Entwickler verständlich ist:
Code: Alles auswählen
>>> try:
... 4 / 0
... except ZeroDivisionError:
... print("Ihre Eingabe hat dazu geführt, dass durch '0' geteilt werden soll
. Dies ist mathematisch nicht möglich. Bitte wählen Sie nur Zahlen ungleich '0'.")
Damit endet das Programm zwar, aber ein Benutzer mag nur erkennen, was er bei der Bedienung falsch gemacht hast. Zudem schluckst Du hier nur exakt den Fehler, für den Deine Meldung eine "Lösung" anbietet.
Oder aber Du versuchst, den Fehler auszuschließen:
Code: Alles auswählen
>>> def divide(divident, divisor):
... if divisor:
... return divident / divisor
...
Somit kann der ``DivisionZeroError`` nicht mehr auftreten.
Mit Deiner Art der "Behandlung" hättest Du nie erfahren, dass das Dividieren durch 0 das Problem war und auch nicht, an welcher Stelle im Code das Problem auftritt. Also mache das nie so, wie Du Dir es ausgedacht hast. Sofern Du beim Testen / Betrieb eine Excpetion um die Ohren gehauen bekommst, schau Dir den Code an und überlege Dir, ob und wie Du das behandeln willst. Dabei musst Du Dich nur auf einen Fehler(typen) beziehen und nicht pauschal alle Fehler abfangen!
Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 13:43
von andi_wand
Okay, dann werde ich das zukünftig so machen.
In cpp hab ich halt gelernt, dass ich nach jedem try-Block auch einen Fänger für alle anderen Fehler, die ich davor nicht behandelt habe, einführen muss.
Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 13:47
von Hyperion
andi_wand hat geschrieben:
In cpp hab ich halt gelernt, dass ich nach jedem try-Block auch einen Fänger für alle anderen Fehler, die ich davor nicht behandelt habe, einführen muss.
Meine C++ Zeit ist zwar schon lange vorbei und ich war nie so wirklich weit drin in der Sprache, aber das klingt auch bei C++ falsch in meinen Augen

Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 13:58
von snafu
Hyperion hat geschrieben:Oder aber Du versuchst, den Fehler auszuschließen:
Code: Alles auswählen
>>> def divide(divident, divisor):
... if divisor:
... return divident / divisor
...
Somit kann der ``DivisionZeroError`` nicht mehr auftreten.
Wobei eine Funktion, die gar nichts macht, wenn ihr ein Wert nicht gefällt, imo auch nicht das allerbeste Beispiel für guten Code ist.

Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 17:34
von andi_wand
@Hyperion: Stand zumindest in dem Buch, mit dem ich angefangen habe. Seitdem hab ich das immer beibehalten, denn was will denn ein Nutzer mit einer kryptischen Fehlermeldung anfangen? Ist es da nicht besser, wenn er nur die Mitteilung bekommt, dass ein Fehler aufgetreten ist, und sich das Programm selbst schließt?
Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 18:05
von webspider
Nö. Eine nichtssagende Fehlermeldung bringt nichts (man bemerke die Wiederholung in diesem Teilsatz), insbesondere falls du detaillierte Absturzinfos vom Nutzer brauchst um den Fehler auszumerzen.
Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 18:30
von andi_wand
ok, überzeugt, dann werd ich das in Zukunft rauslassen
Re: Rekursionen abbrechen
Verfasst: Donnerstag 15. Dezember 2011, 19:46
von lunar
@andi_wand: Was für ein Buch war das denn?
Re: Rekursionen abbrechen
Verfasst: Samstag 17. Dezember 2011, 07:48
von andi_wand
Kann ich dir am Montag sagen