Seite 1 von 1

Eigene Datenstruktur (verbundene Objekte) komplett kopieren

Verfasst: Mittwoch 16. März 2011, 14:58
von AllesMeins
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][:]

Re: Eigene Datenstruktur (verbundene Objekte) komplett kopie

Verfasst: Mittwoch 16. März 2011, 15:49
von Hyperion
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.

Re: Eigene Datenstruktur (verbundene Objekte) komplett kopie

Verfasst: Mittwoch 16. März 2011, 15:59
von 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.

Re: Eigene Datenstruktur (verbundene Objekte) komplett kopie

Verfasst: Mittwoch 16. März 2011, 16:16
von AllesMeins
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

Re: Eigene Datenstruktur (verbundene Objekte) komplett kopie

Verfasst: Mittwoch 16. März 2011, 16:19
von /me
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.

Re: Eigene Datenstruktur (verbundene Objekte) komplett kopie

Verfasst: Mittwoch 16. März 2011, 16:38
von AllesMeins
@/me: Nein, hilft leider nichts. Exakt die selber Fehlermeldung...