ganz einfache Undo-Methode 1 Schritt

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.
Antworten
Sebi.Schneider
User
Beiträge: 38
Registriert: Freitag 3. Mai 2013, 15:05

Hallo Freunde,

ich habe leider dieses Thema in den Forum nicht gefunden, bezüglich meines Problems.

Aufgabe: Klasse A von Typ List -> soll sich verhalten wie eine Liste, allerdings noch eine zusätzliche Methode undo
die die letzte Aktion der Methoden appen, extend, insert, sowie mit del rückgängig macht
nur 1 Schritt Rückgängig, ich muss mir irgendwie eine extra Variable merken
aber klappt nicht so ganz, anbei mein code

Code: Alles auswählen

class A(list):
	def __init__(self,l):
		list.__init__(self, l)
		self.l = l
		self.tmp = copy.deepcopy(self.l)
	
	def append(self, ele):
		list.append(self, e)
		return self.l
	
	def undo():
		self.l = copy.deepcopy(self.tmp)
		return self.l
		
if __name__ == '__main__':
	a1 = A( [1, 2, 3, 4] )
	a1.append(10); print(a1) #Ausgabe 1,2,3,4,10
	a1.undo() # print(a1 )Ausgabe 1,2,3,4
	
	

ich erhalte immer beim letzten print 1,2,3,4,10
BlackJack

@Sebi.Schneider: Was hattest Du denn anderes erwartet und warum? Warum zeigst Du Code der nicht läuft? In `append()` wird es einen `NameError` geben weil `e` nirgends definiert wird.

Weder `append()` noch `undo()` sollten etwas zurückgeben. Das sind Methoden die die Liste verändern.

Warum schreibst Du ``list.append(self, e)`` statt ``self.append(e)``?

Man kann das auf zwei Arten angehen. Entweder merkt man sich immer den letzten Zustand, also zum Beispiel das jede Methode die etwas verändert vorher eine Kopie der Liste macht, oder man merkt sich eine Umkehrfunktion für die jeweils letzte Aktion. Die Kopie muss übrigens nicht tief sein, wenn ich hier nichts übersehen habe, man braucht als das `copy`-Modul nicht dazu.

Beim Kopieren ist der Code einfacher weil jede verändernde Methode im Grunde das gleiche macht: Die Kopie erstellen, und dann die originale Änderung durchführen. `undo()` muss dann nur die Elemente die Elemente der beiden Listen tauschen. Es sei denn `undo()` soll man selbst nicht wieder rückgängig machen können, dann könnte man die Kopie auf `None` setzen und da bei `undo()` auch drauf testen. Nachteil: Kopieren kann teuer sein. Wahrscheinlich kann man da auch recht einfach mit ein bisschen Metaprogrammierung und einer Liste der betroffenen Methoden arbeiten und die neuen Methoden programmatisch erstellen lassen.

Wenn man für jede Methode, die etwas verändert, eine Umkehrfunktion + Argumente erstellt und sich merkt, wird der Code aufwändiger, dafür muss dann nicht jedes mal eine volle Kopie erstellt werden. Wenn man beispielsweise einen Wert mit `append()` anhängt, dann besteht die Umkehrfunktion einfach nur aus einem `pop()`. Immer die gleiche Laufzeit, egal wie viele Elemente in der Liste enthalten sind.
Sebi.Schneider
User
Beiträge: 38
Registriert: Freitag 3. Mai 2013, 15:05

@BlackJack Vielen Dank für die schnelle Antwort

der Code geht -> das waren nur Tippfehler sorry :oops:
klar ist append(ele)

ich dachte ich hab mir mit self.tmp die Liste kopiert?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sebi.Schneider: schau doch mal genau nach, was Du Schritt für Schritt machst. Das kann man hier ja noch gut per Bleistift und Papier nachvollziehen. Wo werden welche Attribute mit welchen Werten belegt und wo wird die Liste verändert?
Sebi.Schneider
User
Beiträge: 38
Registriert: Freitag 3. Mai 2013, 15:05

:D vielen Dank für den Tipp,

aber ich sitze bei der Geschichte schon seit 4 std
irgendwie seh ich den Fehler einfach nicht mehr :K :K :K

wenn du ihn sehen solltest, wäre ich dir seeeeeeehr dankbar, mir den Fehler zu nennen
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sebi.Schneider: DEN Fehler gibt es nicht, da ist so einiges falsch. Angefangen beim A. A ist kein guter Name für eine Klasse, die eine Liste mit Undo-Fähigkeiten implementiert. l ist noch ein viel schlechterer Name, da l nichts sagt, leicht mit I und 1 verwechselt werden kann, und self.l überhaupt nicht gebraucht wird. deepcopy ist falsch, weil eine tiefe Kopie sich anders verhält als man von einer normalen Liste gewohnt ist. append ist falsch implementiert, weil der Rückgabewert anders ist, als bei einer Liste und auch keine Undo-Funktionilität enthält. undo ist falsch, weil es den Inhalt der Liste nicht ändert.
Sebi.Schneider
User
Beiträge: 38
Registriert: Freitag 3. Mai 2013, 15:05

Die Auswahl von variablennamen hab ich schnell gewählt, um eine funktionierende Lösung zu erhalten

hast du ein Vorschlag wie ich das implementieren könnte? :K :K :K
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sebi.Schneider: den Vorschlag hat BlackJack schon gemacht. Vor einer Veränderung merkt man sich den Inhalt der Liste und bei undo stellt man ihn wieder her.
Sebi.Schneider
User
Beiträge: 38
Registriert: Freitag 3. Mai 2013, 15:05

:mrgreen: :mrgreen: :mrgreen: oh man

ich weiß es doch was getan werden muss, aber die Umsetzung ist einfach bei mir noch nicht so angekommen
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sebi.Schneider hat geschrieben:ich weiß es doch was getan werden muss, aber die Umsetzung ist einfach bei mir noch nicht so angekommen
Hattest du eine Komplettlösung erwartet oder wie ist das zu verstehen? In welcher Form wünscht du dir die Hilfe genau? Zeig doch wenigstens mal grob deinen Ansatz und beschreibe die Stellen wo du nicht weiter kommst.

EDIT:
Mir hilft es beim Programmieren wenn ich festhänge oft, eine Nacht drüber zu schlafen und den Code am nächsten Tag nochmal anzuschauen. Wenn du schon über 4 Stunden dranhängst, wie du sagst, dann ist die Luft auch irgendwann raus.
BlackJack

@Sebi.Schneider: Die Namen sind *wichtig*, denn wenn wir nicht wissen was Du Dir bei dem Code gedacht hast, dann können wir auch nicht sagen wo Du den Denkfehler gemacht hast. Und wir haben ja auch schon so einiges konkretes gesagt. Setz das doch mal um, inklusive der Namen. Wenn man den Dingen *passende* Namen gibt, die beschreiben was der Wert *bedeutet*, dann fällt einem auch selber eher auf was man falsch macht. Oder man findet keinen passenden Namen, das ist dann ein Zeichen dafür das der Wert dafür irgendwie falsch ist. Du könntest beispielsweise auch mal erklären warum es da *drei* Listen gibt. Denn `A`-Objekte sind *selbst* ja auch eine Liste. Und dann hast Du zusätzlich noch `l` und `tmp`. Was enthält welche der drei Listen zu welchem Zeitpunkt, und warum?
BlackJack

Mal so als Ansatz:

Code: Alles auswählen

def create_undoable(base, method_name):

    def undoable(self, *args, **kwargs):
        self._previous = base(self)
        getattr(base, method_name)(self, *args, **kwargs)

    undoable.__name__ = method_name
    return undoable


def create_undoables(cls):
    for method_name in cls.UNDOABLES:
        setattr(cls, method_name, create_undoable(cls.__base__, method_name))
    return cls


@create_undoables
class List(list):

    UNDOABLES = ['__delitem__', 'append', 'extend', 'insert']

    def __init__(self, items):
        list.__init__(self, items)
        self._previous = None

    def undo(self):
        if self._previous is not None:
            self[:] = self._previous
            self._previous = None


def main():
    items = List([1, 2, 3, 4])
    print(items)

    items.append(10)
    print(items)
    items.undo()
    print(items)
    
    items.extend(x for x in range(10) if x & 1)
    print(items)
    items.undo()
    print(items)

    items.insert(2, 4711)
    print(items)
    items.undo()
    print(items)
    
    del items[1]
    print(items)
    items.undo()
    print(items)


if __name__ == '__main__':
    main()
Was hier nicht berücksichtigt ist, sind Ausnahmen, und wie in diesen Fällen die Semantik von dem `undo()` aussehen soll.
Antworten