2010-03-12 10 views
10

Bei dieser Frage geht es darum, die vollständige Perl-Autovivifikation in Python zu implementieren. Ich weiß, dass ähnliche Fragen vorher gestellt wurden und die beste Antwort ist bisher "What is the best way to implement nested dictionaries in Python?". Aber ich bin auf der Suche, dies zu tun:Wie mache ich erweiterte Python Hash Autovivification?

a['x']['y'].append('z') 

ohne a['x']['y'] = [] zuerst zu erklären, oder besser gesagt, nicht a['x'] = {} entweder deklarieren. (Beachten Sie in Perl können Sie push @{$a->{x}{y}}, 'z'; tun.)

Ich weiß dict und list Klassen sorta nicht mischen, so ist dies schwer, aber ich bin daran interessiert, wenn jemand hat eine geniale Lösung wahrscheinlich Beteiligung eine vererbte Klasse erstellen von dict aber definiert eine neue append Methode darauf?

Ich weiß auch, dies könnte einige Python-Puristen, die mich fragen abzuwerfen mit Perl zu bleiben. Aber selbst für eine Herausforderung würde ich gerne etwas sehen.

Antwort

16
a = collections.defaultdict(lambda: collections.defaultdict(list)) 
+0

Schöne Lösung. Vielen Dank. – Zhang18

+2

Ein Beispiel von Python mit einer einzigen, offensichtlichen Möglichkeit, etwas zu tun :) – Brian

+0

Tatsächlich erkannte ich, dass diese Antwort nur für 2 Ebenen des Diktats und eine dritte Ebene der Liste funktioniert. Gibt es eine Möglichkeit, levelunabhängig zu sein? dh nachdem ich a deklariert habe, kann ich entweder 'a' (x '), append (' z ')' oder 'a' 'x' '[' y '] [' w '] append (' z ') '? Danke. – Zhang18

2

Vielleicht löst Ihr Bedürfnis nach einer beliebigen Anzahl von „Dimensionen“ in Ihrem Wörterbuch:

a= collections.defaultdict(list) 

die einzige Änderung im Code Wesen:

a['x', 'y'].append('z') 

Natürlich ist diese Lösung ist diejenige, die Sie wollen, hängt von zwei Bedingungen:

  1. ob Sie einfach auf alle Listen mit z. ‚X‘ in der „ersten Dimension“
  2. , ob Sie auf dem Weg stecken geblieben sind Perl erfreut Sie auf magische Weise mehr :)

Wenn eine dieser beiden Bedingungen erfüllt ist, meine Lösung wird dir nicht helfen.

+0

Ja für 1. und Ja für 2, leider. Also muss ich mit der Tatsache leben, dass es in Python keine Analogie für Perl gibt - irgendwie enttäuschend. – Zhang18

+2

Stellen Sie sich vor, Sie sind Dating, weiß nicht, Nicole Kidman. Sie sagen ihr, dass es irgendwie enttäuschend ist, dass sie nicht die Kurven von, sagen wir, Monica Bellucci hat. Kannst du anfangen, den Mangel an Nützlichkeit (und Anmut!) Einer solchen Aussage zu ergründen? Wie auch immer, Parabeln beiseite, ISTM, auf den du deinen Standpunkt bezogen hast, und die Welt kann endlich ausatmen. – tzot

2

Da wir wissen nicht im Voraus, wenn wir ein Wörterbuch oder eine Liste benötigen, dann kombinieren Sie können nicht autovivification mit Listen. Wenn Sie Nosklos Antwort von der verknüpften Frage nicht abarbeiten, fügen Sie dem zugrunde liegenden Wörterbuch "Features" hinzu. Im Grunde nehmen wir eine "Sortierreihenfolge" für Schlüssel an und verwenden sie immer mit den Listenmethoden. Ich habe dies als ein Beispiel getan:

class AutoVivification(dict): 
    """Implementation of perl's autovivification feature. Has features from both dicts and lists, 
    dynamically generates new subitems as needed, and allows for working (somewhat) as a basic type. 
    """ 
    def __getitem__(self, item): 
     if isinstance(item, slice): 
      d = AutoVivification() 
      items = sorted(self.iteritems(), reverse=True) 
      k,v = items.pop(0) 
      while 1: 
       if (item.start < k < item.stop): 
        d[k] = v 
       elif k > item.stop: 
        break 
       if item.step: 
        for x in range(item.step): 
         k,v = items.pop(0) 
       else: 
        k,v = items.pop(0) 
      return d 
     try: 
      return dict.__getitem__(self, item) 
     except KeyError: 
      value = self[item] = type(self)() 
      return value 

    def __add__(self, other): 
     """If attempting addition, use our length as the 'value'.""" 
     return len(self) + other 

    def __radd__(self, other): 
     """If the other type does not support addition with us, this addition method will be tried.""" 
     return len(self) + other 

    def append(self, item): 
     """Add the item to the dict, giving it a higher integer key than any currently in use.""" 
     largestKey = sorted(self.keys())[-1] 
     if isinstance(largestKey, str): 
      self.__setitem__(0, item) 
     elif isinstance(largestKey, int): 
      self.__setitem__(largestKey+1, item) 

    def count(self, item): 
     """Count the number of keys with the specified item.""" 
     return sum([1 for x in self.items() if x == item]) 

    def __eq__(self, other): 
     """od.__eq__(y) <==> od==y. Comparison to another AV is order-sensitive 
     while comparison to a regular mapping is order-insensitive. """ 
     if isinstance(other, AutoVivification): 
      return len(self)==len(other) and self.items() == other.items() 
     return dict.__eq__(self, other) 

    def __ne__(self, other): 
     """od.__ne__(y) <==> od!=y""" 
     return not self == other 

Dies folgt die grundlegende Autovivifikations-Funktion der dynamischen Generierung selbst für Blindtasten. Es implementiert jedoch auch einige der Methoden listed here. Dies ermöglicht es, wie eine Quasi-Liste/Diktat zu handeln.

Für den Rest der Liste Funktionen, in den aufgeführten Methoden hinzufügen. Ich behandle es als ein Wörterbuch mit den Listenmethoden. Wenn eine Listenmethode aufgerufen wird, nimmt sie eine Annahme über die Reihenfolge der gehaltenen Elemente vor, nämlich, dass Zeichenfolgen niedriger als ganze Zahlen sortiert werden und dass Schlüssel immer in "sortierter" Reihenfolge sind.

Es unterstützt auch das Hinzufügen, als Beispiel für these methods. Das kommt von meinem eigenen Anwendungsfall. Ich musste Elemente aus einem AutoVivified-Wörterbuch hinzufügen, aber wenn es nicht existiert, wird ein neues AutoVivification-Objekt erstellt und zurückgegeben.Sie haben keine ganze Zahl „Wert“ und so können Sie dies nicht tun:

rp = AutoVivification() 
rp['a']['b'] = 3 
rp['a']['b'] + rp['q'] 

, dass der Zweck vereitelt, da ich weiß nicht, ob etwas, was es sein wird, aber ich möchte einen Standard sowieso. Also habe ich die Methoden __add__ und __radd__ hinzugefügt. Sie verwenden das length des zugrunde liegenden Wörterbuchs als integer Wert, so ein neu erstelltes AV-Objekt hat einen Wert von Null für die Addition. Wenn ein Schlüssel etwas anderes als ein AV-Objekt enthält, dann erhalten wir die Additionsmethode, wenn diese implementiert ist.

1

Erweitern Sie einfach Ignacios Antwort, um einige zusätzliche Optionen zu präsentieren, mit denen Sie explizit mehr magisches Verhalten von Python-Wörterbüchern anfordern können. Die Wartbarkeit des auf diese Weise geschriebenen Codes bleibt zweifelhaft, aber ich wollte klarstellen, dass es eine Frage von "Ist diese Art von Datenstruktur haltbar?" (Ich habe meine Zweifel) nicht "Kann Python so verhalten werden?" (Es kann sicherlich).

Um beliebigen Ebenen der Verschachtelung für den Namespace Aspekt zu unterstützen, alles, was Sie tun müssen, ist der Name der Funktion (statt Lambda zu verwenden) und machen es selbstbezüglicher:

>>> from collections import defaultdict 
>>> def autodict(): return defaultdict(autodict) 
... 
>>> a = autodict() 
>>> a[1][2][3] = [] 
>>> type(a[1]) 
<class 'collections.defaultdict'> 
>>> type(a[1][2]) 
<class 'collections.defaultdict'> 
>>> type(a[1][2][3]) 
<class 'list'> 
>>> a[1][2][3] 
[] 

Doch dies stellt die " Problem ", dass Sie eine Liste explizit festlegen müssen, bevor Sie an sie anhängen können. Pythons Antwort auf diese Frage liegt in der setdefault Methode, die eigentlich schon hat länger als collections.defaultdict:

>>> a.setdefault(3, []).append(10) 
>>> a.setdefault(3, []).append(11) 
>>> a[3] 
[10, 11] 
>>> a[2].setdefault(3, []).append(12) 
>>> a[2].setdefault(3, []).append(13) 
>>> a[2][3] 
[12, 13] 
>>> a[1][2].setdefault(3, []).append(14) 
>>> a[1][2].setdefault(3, []).append(15) 
>>> a[1][2][3] 
[14, 15] 

Alle collections.defaultdict wirklich tut, ist den gemeinsamen Fall, wo Sie immer den gleichen zweiten Parameter dict.setdefault passieren viel einfacher zu bedienen. Für komplexere Fälle wie diesen können Sie immer noch dict.setdefault direkt für die Aspekte verwenden, die collections.defaultdict nicht verarbeiten kann.