Dictionary mit Zurück-Funktion

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
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Hallo Leute

Ich habe für eine GUI ein Dict um Daten zu speichern:

Code: Alles auswählen

{"0": "irgendwas", "1": "irgendwas"}
Dieses wird fortlaufend durch den Benutzerinteraktionen geändert.
Ich möchte nun gerne eine Zurück-Funktion, welche den letzten Schritt rückgängig macht.
Natürlich könnte ich das Dict einfach nach jeder Änderung in eine Liste reinstecken, aber da das Dict gross werden kann ist das nicht die beste Option.
Eher würde ich versuchen die einzelnen Schritte zu speichern, so dass sie Rückgängig gemacht werden können.
Was eignet sich da als Lösung?
Benutzeravatar
ThomasL
User
Beiträge: 1367
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Ein LiFO Stack deiner Aktionen?
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Eher nicht die Aktionen. Mehr so etwas wie Diff.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Sieht interessant aus. Schaue ich mir mal an :)
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

ChainMap ais der Standard Lib könnte man verwenden, aber dann ändert sich die Lookup-Zeit von O(1) auf O(n).

Ich würdre vermutlich sowas machen:

Code: Alles auswählen

class UndoDict(dict):

    _sentinel = object()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._undostack = []

    def __setitem__(self, key, value):
        self._undostack.append((key, self.get(key, self._sentinel)))
        super()[key] = value

    def undo(self):
        if self._undostack:
            key, value = self._undostack.pop()
            if value is self._sentinel:
                del self[key]
            else:
                super()[key] = value
Ungetestet.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fire Spike: Eine Allgemeine Lösung baut üblicherweise doch auf den Aktionen auf. Wobei man beide Richtungen speichert, wenn nicht nur Undo, sondern auch Redo funktionieren soll.

Manche GUI-Rahmenwerke haben da auch schon ein Gerüst für. Qt beispielsweise hat QUndoStack & Co.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Oder vielleicht auch eine persistente Datenstruktur wie `PMap` aus `pyrsistent`.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Fire Spike hat geschrieben: Samstag 22. Juli 2023, 10:31 Sieht interessant aus. Schaue ich mir mal an :)
Und, schon weiter gekommen?

Anscheinend kann eine Benutzer-Aktion auch aus der Änderung von mehreren Einträgen im Dict bestehen, verstehe ich das richtig? Nur aus Interesse: Wie wird so eine Aktion denn abgeschlossen? Gibt es einen speziellen Button dafür? Oder einfach, sobald der Benutzer eine Eingabe mit Return abgeschlossen hat? Lässt sich das wirklich auf einzelne Einträge wie bei einer Konfiguration herunterbrechen oder muss man sich diese Aktionen etwas abstrakter innerhalb der GUI vorstellen?

Handelt es sich dabei um mehrere Wörterbücher oder um ein globales Dict? Sind die Schlüssel immer gleichbleibend bzw. fest vorgegeben, sodass es sich also auf die Änderungen der zugehörigen Werte beschränkt? Oder können auch neue Schlüssel vom Benutzer im Dict angelegt werden?

Das sind viele Fragen, aber sie würden unser Verständnis für dein Vorhaben erhöhen. Man kann dir dann wahrscheinlich eine bessere Lösung vorschlagen...
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Grundsätzlich ist dieses Dict dafür da, um Daten einer QGraphicsScene zu speichern. Ich habe ein kleines Zeichnungsprogramm geschrieben. Jedes Objekt (Linien, Bilder usw.) wird mit einer ID als Key in das Dictionary eingetragen. Diese ID bleibt dann auch. Die Value ist dann nochmals ein Dictionary, in dem dann die Eigenschaften stehen. Grundsätzlich können solche Objekte bearbeitet, hinzugefügt oder alle zusammen gelöscht werden. Die Aktionen (Linien zeichnen usw.) bearbeiten immer direkt das Dictionary. Dieses Dictionary wird dann als JSON gespeichert. Das Dictionary ist unabhängig von der GUI. Die Zeichnung kann also von dem Dict geladen aber nicht als Dict gespeichert werden. Die Aktionen ändern also immer das Dict und die GUI.
Hilft dir das?
Testen konnte ich noch nichts, da ich gerade in den Ferien bin.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das klingt gut und schlecht gleichzeitig ;) Die Idee, auf die du da gekommen bist, ist an sich erstmal gut. Das wuerde man als Modellbezeichnen, also eine reine Datenrepraesentation von dem, was dein Programm eigentlich darstellt. Und dann durch einen View wird das Modell dargestellt. Soweit, so gut. Das kann dann auch gespeichert und geladen werden, will man ja auch.

Die Sache mit den IDs, die dann die Schluessel dict sind, da wird's sketchy. Da du deine Operationen immer in einer definierten Reihenfolge abarbeiten musst, ist die richtige Datenstruktur eine Liste. Von der kannst du fuer UNDO dann ein Element abknapsen, und dann das ganze neu rendern. Oder aber, und das wird im Zweifel effizienter: du speicherst die Informationen, die du brauchst, wie zB Zeiger/Referenze auf die eigentlichen QGraphicsScene-Objekte, die erzeugt wurden, und wenn UNDO kommt, entfernst du die, statt alles neu zu malen. Das waere dann das eher klassiche Command-Pattern fuer Undo, weil es zu jedem Kommando (Linie malen) ein UNDO-Kommando (Linie loeschen) gibt.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hatte ich schon erwähnt das Qt für Undo/Redo schon Klassen bereitstellt!?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Ja hast du. Hatte es mir aber noch nicht angeschaut.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Fire Spike hat geschrieben: Mittwoch 26. Juli 2023, 16:54 Hatte es mir aber noch nicht angeschaut.
Mach das mal ruhig. Hier ist ein Beispiel von Qt's Undo Framework, was ähnlich wie in deinem Fall mit gezeichneten Formen umgehen kann:

https://doc.qt.io/qt-6/qtwidgets-tools- ... ample.html

Möglichweise kannst du damit jede Menge Code einsparen, weil du die Objekte nicht mehr in Python-Dicts abbilden müsstest. Zudem lässt sich damit leicht eine Liste der letzten Schritte erstellen. Somit kann der Anwender auch mehrere Schritte in einem Rutsch zurück gehen. Ist natürlich auch alles mit Python-Boardmitteln umsetzbar, aber eben aufwändiger und potenziell fehleranfälliger. Nur das Undo-Framework dürfte eine gewisse Einarbeitung erfordern...
Antworten