Eigene Datenstruktur (verbundene Objekte) komplett kopieren

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
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Ich bin mal wieder am Ende meines Lateins angelangt. Folgende Situation: Für einen Algorithmus habe ich mir eine passende Binärbaum-Datenstruktur gebaut, die wiefolgt aussieht:
Ich habe eine Klasse für Baumknoten gebaut. Diese speichert jeweils einen eigenen Wert und referenzen auf die Kinderknoten und den Elternknoten (die natürlich auch wieder Objekte der Baumknoten-Klasse sind). Das ganze habe ich noch mit einigen Funktionen angereichert, die mir helfen meinen Algorithmus ablaufen zu lassen, hier aber eher nicht relevant sind. Ich speichere alle inneren Knoten-Objekte in einer mehrdimensionalen Dict "knoten" und alle Blätter-Objekte in einem Dict "blaetter".
Mein Problem ist nun, dass ich diesen ganzen Baum mit allem drum und dran gerne kopieren würde. Sprich ich müsste irgendwie das Dict "knoten" und "blaetter" so kopieren das alle Objekte dupliziert werden und die inneren Verknüpfungen entsprechend umgeschrieben werden. Habt ihr Vorschläge, wie das gehen könnte? Es ist auch noch möglich die Datenstruktur umzubauen, allerdings müsste sie schon ein Baum aus Objekten sein, damit die Knoten untereinander mit entsprechenden Methoden komunizieren können und außerdem brauche ich das blaetter-dict um schnell an ein bestimmtes Blatt zu kommen.

Anbei mal das wichtigste aus meinem Code:

Die Klasse TreeNode aus der die einzelnen Knotenobjekte generiert werden. Für den Baumaufbau sind nur die ersten Methoden wichtig, die meisten dienen der Werteverwaltung und haben mit dem Baumaufbau nichts zu tun.

Code: Alles auswählen

import weakref

class TreeNode:
  
    def __init__(self, value = 0,  itemId = None):
        self.parent = None                                      #Speichert Referenz auf den Elternknoten
        self.parentPos = None                                #Speichert ob dieser Knoten das linke oder rechte Kind seiner Eltern ist
        self.children = [None, None]                      #Speichert Referenzen auf die beiden Kinder
        self.value = {"self":value, 0:0, 1:0}           #Speichert den Eigen-Wert des Knotens und die Werte der Kinder
        self.itemId = itemId
        return None
        
    def __del__(self):
        r = self.parent()
        if r is not None:
            r.removeChildByPos(self.parentPos) 
            
    def addChild(self, pos, child):
        """Speichert Referenz auf Kind und aktualisiert Wert"""
        print "Füge Kind hinzu (self, pos, kin)",  self,  pos, child
        self.children[pos] = weakref.ref(child)
        self.value[pos] += child.getValue()
        child._setParent(self, pos)
        self.updateValueToParent()
        
    def _setParent(self, parent, pos):
        """Empfange und speichere Referenz auf Elternknoten"""
        print "Setze Parent (self, parent, pos)",  self,  parent,  pos
        self.parent = weakref.ref(parent)
        self.parentPos = pos

##########################################################
# Nachfolgende Methoden dienen der Werte-Verwaltung und haben nichts direkt mit dem Aufbau 
# des Baumes zu tun!!!        
##########################################################            
            
        
    def setItem(self, value, itemId):
        """Verändert Eigenwert und ItemId des Knotens"""
        self.value["self"] = value
        self.itemId = itemId
        self.updateValueToParent()
        return True
        
    def updateValueFromChild(self, pos):
        """Wird aufgerufen, wenn sich Wert eines Kindes ändert, empfängt geänderte Werte von Kindern und gibt diese zusammen mit dem Eigenwert nach oben weiter"""
        r = self.children[pos]()
        self.value[pos] = r.getValue()
        self.updateValueToParent()
        
    def updateValueToParent(self):
        """Eigene Gesamtwert hat sich geändert, gibt ihn nach oben weiter"""
        if self.parent is not None:
            r = self.parent ()
            #print "Gebe neuen Wert an Parent weiter",  self
            r.updateValueFromChild(self.parentPos)
        
    def addChild(self, pos, child):
        """Speichert Referenz auf Kind und aktualisiert Wert"""
        print "Füge Kind hinzu (self, pos, kin)",  self,  pos, child
        self.children[pos] = weakref.ref(child)
        self.value[pos] += child.getValue()
        child._setParent(self, pos)
        self.updateValueToParent()
        
    def _setParent(self, parent, pos):
        """Empfange und speichere Referenz auf Elternknoten"""
        print "Setze Parent (self, parent, pos)",  self,  parent,  pos
        self.parent = weakref.ref(parent)
        self.parentPos = pos
        
    def getValue(self):
        return sum(self.value.values())
        
    def getItemId(self):
        return self.itemId
        
    def removeChildByPos(self, pos):
        self.value[pos] = 0
        self.children[pos] = None
        self.updateValueToParent()
        
    def printRec(self, level = 0):
        print "%d: %s [%s-%s-%s] (%s)" % (level, str(sum(self.value.values())), str(self.value["self"]), str(self.value[0]), str(self.value[1]),   str(self.itemId))
       
        if self.children[0] is not None:
            r = self.children[0]()
            r.printRec((level+1))
        if self.children[1] is not None:
            r = self.children[1]()
            r.printRec((level+1))
Und hier noch mein Test-Code mit dem aus einer Liste von Werten der Baum erzeugt wird. Dabei wird immer für zwei Werte ein Knoten erzeugt, der diese beiden verbindet. Die erzeugten Knoten werden dann wieder zu zweit verbunden usw. Der Baum wird also von unten, von den Blättern aus aufgebaut

Code: Alles auswählen

liste = sorted([1, 2, 3, 4, 5, 6, 7, 8, 9])
blaetter = {}
knoten = {}

for i, v in enumerate(liste):
    print i,  v
    blaetter[i] = TreeNode(v, i)    #Erzeuge Knoten für Blätter
    
tmp = blaetter.values()

level = 0
dir = 0
while len(tmp) > 1:                     #Verbinde jeweils 2 Blätter zu einem Knoten,
                                                    #bzw. zwei Knoten zu einem Knoten höherer 
                                                    #Ordnung, bis nur noch ein Wurzelknoten übrig ist

    level += 1
    knoten[level] = []
    while 1:

        if len(tmp) > 1:
            knoten[level].append(TreeNode())
            knoten[level][-1].addChild(0, tmp.pop(0))
            knoten[level][-1].addChild(1, tmp.pop(0))
        elif len(tmp) == 1:
            knoten[level].append(TreeNode())
            knoten[level][-1].addChild(0, tmp.pop(0))
            dir = 0
            break
        if len(tmp) < 1:
            break
    tmp = knoten[level][:]
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Stichwort copy-Modul!

Bei so langem Code solltest Du den imho auch in ein Paste-Bin auslagern, wie etwa das im Board oder paste.pooo.org.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@AllesMeins: Die Verwendung von `weakref` verstehe ich nicht wirklich!? Das benutzt man normalerweise um keine Zyklen zu haben, damit die Objekte schneller freigegeben werden werden können. Und dann sollte man auch nicht den gesamten Kreis aus `weakref`\s erstellen. Bei Bäumen macht man üblicherweise die Referenz auf den Elternknoten "schwach". Man braucht nur eine "Sollbruchstelle".

Die `__del__`-Methode solltest Du in Python-Quelltext meiden wie die Pest -- es gibt keine Zusage *wann* die Aufgerufen wird -- ja nicht einmal eine Garantie, dass die *überhaupt* jemals aufgerufen wird. Du kannst Dich also nicht darauf verlassen dass der Code in der Methode auch tatsächlich ausgeführt wird.

Zum eigentlichen Problem: Ein `copy.deepcopy()` macht zu viel? Falls ja, kannst Du doch einfach eine `copy()`-Metode auf den Knoten packen, die eine Kopie vom aktuellen Knoten erstellt und da Kopien von den Kindern einfügt -- rekursiv mit der `copy()`-Methode.
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

BlackJack hat geschrieben:Die Verwendung von `weakref` verstehe ich nicht wirklich!?
War eine Lösung, da __del__ sonst nicht aufgerufen wurde. Aber wenn es so unzuverlässig ist, dann werde ich sehen, wie ich da drumherum komme
BlackJack hat geschrieben:Zum eigentlichen Problem: Ein `copy.deepcopy()` macht zu viel?
deepcopy kannte ich bisher nicht. Funktioniert aber auch mit einer wenig aussagekräftigen Fehlermeldung nicht. Möglicherweise (wahrscheinlich) wende ich es aber auch einfach falsch an. Habe ein blaetter2 = copy.deepcopy(blaetter) gemacht.

Code: Alles auswählen

  File "treetest.py", line 123, in <module>
    blaetter2 = copy.deepcopy(blaetter)
  File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python2.6/copy.py", line 292, in _deepcopy_inst
    state = deepcopy(state, memo)
  File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  File "/usr/lib/python2.6/copy.py", line 323, in _reconstruct
    y = callable(*args)
  File "/usr/lib/python2.6/copy_reg.py", line 93, in __newobj__
    return cls.__new__(cls, *args)
TypeError: __new__ expected at least 1 arguments, got 0
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

AllesMeins hat geschrieben:deepcopy kannte ich bisher nicht. Funktioniert aber auch mit einer wenig aussagekräftigen Fehlermeldung nicht. Möglicherweise (wahrscheinlich) wende ich es aber auch einfach falsch an. Habe ein blaetter2 = copy.deepcopy(blaetter) gemacht.
Du verwendest Python 2 mit Old-Style-Klassen. Lass TreeNode mal von object erben.

Ich weiß allerdings nicht, ob das wirklich die Ursache für das Problem ist.
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

@/me: Nein, hilft leider nichts. Exakt die selber Fehlermeldung...
Antworten