Rekursionen abbrechen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

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
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

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...
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

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.
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

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
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

Hat sich erledigt, ich denke, die restlichen Optimierungen kann ich mir schenken.
Vielen Dank an alle, die mir hier geholfen haben
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.
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

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?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
Das Leben ist wie ein Tennisball.
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

Vorschlag:

Code: Alles auswählen

except:
   print "Fehler"
   sys.exit()
Würde das dann gehen? Das Programm wird ja zuverlässig gestoppt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

andi_wand hat geschrieben:Vorschlag:

Code: Alles auswählen

except:
   print "Fehler"
   sys.exit()
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!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

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.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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 ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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. ;)
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

@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?
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

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.
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

ok, überzeugt, dann werd ich das in Zukunft rauslassen
lunar

@andi_wand: Was für ein Buch war das denn?
andi_wand
User
Beiträge: 37
Registriert: Samstag 3. Dezember 2011, 17:04

Kann ich dir am Montag sagen
Antworten