Wie implementiert man eine effiziente bidirektionale Hash-Tabelle?

Lesezeit: 10 Minuten

Wie implementiert man eine effiziente bidirektionale Hash Tabelle
Juanjo Conti

Python dict ist eine sehr nützliche Datenstruktur:

d = {'a': 1, 'b': 2}

d['a'] # get 1

Manchmal möchten Sie auch nach Werten indizieren.

d[1] # get 'a'

Welches ist der effizienteste Weg, um diese Datenstruktur zu implementieren? Gibt es eine offizielle Empfehlung dafür?

  • Wenn Sie es vorziehen, können wir davon ausgehen, dass Werte ebenso unveränderlich sind wie Schlüssel.

    – Juanjo Conti

    23. Juli 10 um 13:39 Uhr

  • Was würden Sie für dieses Diktat zurückgeben: {‘a’ : 1, ‘b’: 2, ‘A’ : 1 }

    – PaulMcG

    23. Juli 10 um 16:49 Uhr

  • @PaulMcGuire: Ich würde zurückkehren {1: ['a', 'A'], 2: 'b'}. Siehe meine Antwort für einen solchen Weg.

    – Basj

    19. Februar 14 um 22:41 Uhr


  • Hinweis für den Moderator: Dies ist nicht ein Duplikat von stackoverflow.com/questions/1456373/two-way-reverse-map. Letzteres hat 1) einen sehr vagen Wortlaut 2) kein MCVE 3) befasst sich nur mit dem Fall der bijektiven Karte (siehe erster Kommentar in dieser Frage), der viel restriktiver ist als diese eigentliche Frage, die allgemeiner ist. Daher denke ich, dass es in diesem speziellen Fall irreführend ist, es als Duplikat zu markieren. Wenn eines wirklich ein Duplikat eines anderen sein sollte, sollte es das Gegenteil sein, da dieses hier den allgemeinen Fall abdeckt, während das andere (siehe Antworten) den nicht-bijektiven Fall nicht abdeckt.

    – Basj

    19. Juni 18 um 9:13 Uhr


  • Diese Frage ist mehr als zehn Jahre alt, aber ich lese sie jetzt zum ersten Mal. Vielleicht finden Sie Inspiration in der Java-Bibliothek Google Guava. Sie haben eine Klasse HashBiMap das ist lesenswert. (Ich gehe davon aus, dass eine ähnliche Struktur in Python implementiert werden könnte.) Die Dokumentation umreißt klar Grenzfälle und wie sie gehandhabt werden. Ref: github.com/google/guava/blob/master/guava/src/com/google/common/…

    – Kevinarpe

    27. April 21 um 5:59 Uhr

Wie implementiert man eine effiziente bidirektionale Hash Tabelle
Basj

Hier ist eine Klasse für eine bidirektionale dictinspiriert von Finding key from value in Python dictionary und modifiziert, um die folgenden 2) und 3) zu ermöglichen.

Beachten Sie, dass :

  • 1) Die umgekehrtes Verzeichnis bd.inverse aktualisiert sich automatisch, wenn das Standard-dict bd wird modifiziert.
  • 2) Die umgekehrtes Verzeichnis bd.inverse[value] ist immer ein aufführen von key so dass bd[key] == value.
  • 3) Anders als die bidict Modul ab https://pypi.python.org/pypi/bidicthier können wir 2 Schlüssel mit demselben Wert haben, das ist sehr wichtig.

Code:

class bidict(dict):
    def __init__(self, *args, **kwargs):
        super(bidict, self).__init__(*args, **kwargs)
        self.inverse = {}
        for key, value in self.items():
            self.inverse.setdefault(value,[]).append(key) 

    def __setitem__(self, key, value):
        if key in self:
            self.inverse[self[key]].remove(key) 
        super(bidict, self).__setitem__(key, value)
        self.inverse.setdefault(value,[]).append(key)        

    def __delitem__(self, key):
        self.inverse.setdefault(self[key],[]).remove(key)
        if self[key] in self.inverse and not self.inverse[self[key]]: 
            del self.inverse[self[key]]
        super(bidict, self).__delitem__(key)

Anwendungsbeispiel:

bd = bidict({'a': 1, 'b': 2})  
print(bd)                     # {'a': 1, 'b': 2}                 
print(bd.inverse)             # {1: ['a'], 2: ['b']}
bd['c'] = 1                   # Now two keys have the same value (= 1)
print(bd)                     # {'a': 1, 'c': 1, 'b': 2}
print(bd.inverse)             # {1: ['a', 'c'], 2: ['b']}
del bd['c']
print(bd)                     # {'a': 1, 'b': 2}
print(bd.inverse)             # {1: ['a'], 2: ['b']}
del bd['a']
print(bd)                     # {'b': 2}
print(bd.inverse)             # {2: ['b']}
bd['b'] = 3
print(bd)                     # {'b': 3}
print(bd.inverse)             # {2: [], 3: ['b']}

  • Sehr saubere Lösung des mehrdeutigen Falls!

    – Tobias Kienzler

    20. Februar 14 um 11:47 Uhr

  • Ich denke, dass diese Datenstruktur bei vielen praktischen Problemen sehr nützlich ist.

    – 0xc0de

    23. Juli 15 um 16:08 Uhr

  • Das ist phänomenal. Es ist prägnant; es ist selbstdokumentierend; es ist ziemlich effizient; es funktioniert einfach. Meine einzige Spitzfindigkeit wäre, die wiederholten Suchen von zu optimieren self[key] in __delitem__() mit einer einzigen value = self[key] Zuweisung für solche Suchen wiederverwendet. Aber… ja. Das ist vernachlässigbar. Danke für die pure Wahnsinnsleistung, Basj!

    – Cecil Curry

    28. Juni 16 um 2:18 Uhr


  • Wie wäre es mit einer Python 3-Version?

    – zelusp

    14. September 16 um 20:01 Uhr


  • Ah. Richtig. Versuchen Sie es ohne “iter” und es sollte funktionieren.

    – Der Nate

    20. Oktober 16 um 1:40 Uhr

Sie können dasselbe Diktat selbst verwenden, indem Sie ein Schlüssel-Wert-Paar in umgekehrter Reihenfolge hinzufügen.

d={'a':1,'b':2}
revd=dict([reversed(i) for i in d.items()])
d.update(revd)

  • +1 Eine schöne, praktische Lösung. Eine andere Schreibweise: d.update( dict((d[k], k) for k in d) ).

    – FMc

    23. Juli 10 um 20:04 Uhr

  • +1 Für die saubere Verwendung von reversed(). Ich bin unentschlossen, ob es besser lesbar ist als das Explizite dict((v, k) for (k, v) in d.items()). In jedem Fall können Sie Paare direkt an .update übergeben: d.update(reversed(i) for i in d.items()).

    – Beni Cherniavsky-Paskin

    14. August 2012 um 10:50 Uhr

  • Beachten Sie, dass dies zB für fehlschlägt d={'a':1, 'b':2, 1: 'b'}

    – Tobias Kienzler

    29. Mai ’13 um 7:38 Uhr

  • Geringe Änderung: dict(map(reversed, a_dict.items())).

    – 0xc0de

    23. Juli 15 um 15:55 Uhr

  • Das Hinzufügen von umgekehrten Zuordnungen zum ursprünglichen Wörterbuch ist eine schreckliche Idee. Wie die obigen Kommentare zeigen, ist dies der Fall nicht sicher im allgemeinen Fall. Pflegen Sie einfach zwei separate Wörterbücher. Da die ersten beiden Zeilen dieser Antwort das Nachstellen ignorieren d.update(revd) sind großartig, aber ich denke immer noch über eine positive Bewertung nach. Lassen Sie uns darüber nachdenken.

    – Cecil Curry

    28. Juni 16 um 2:10 Uhr


1643909770 308 Wie implementiert man eine effiziente bidirektionale Hash Tabelle
miku

Die bidirektionale Hash-Tabelle eines armen Mannes würde darin bestehen, nur zwei Wörterbücher zu verwenden (dies sind bereits hochgradig abgestimmte Datenstrukturen).

Da ist auch ein Gebot Paket auf dem Index:

Die Quelle für bidict finden Sie auf github:

  • 2 Diktate erfordern doppelte Einfügungen und Löschungen.

    – Juanjo Conti

    23. Juli 10 um 13:46 Uhr

  • @Juanjo: Fast jede bidirektionale / umkehrbare Hash-Tabelle beinhaltet “doppelte Einfügungen und Löschungen”, entweder als Teil der Implementierung der Struktur oder als Teil ihrer Verwendung. Das Beibehalten von zwei Indizes ist wirklich der einzige schnelle Weg, AFAIK.

    – Walter Mundt

    23. Juli 10 um 13:53 Uhr

  • Na sicher; Ich meinte, dass es das Problem ist, sich um den 2-Index von Hand zu kümmern.

    – Juanjo Conti

    23. Juli 10 um 14:21 Uhr

  • @Basj Ich denke, es ist richtig, dass es nicht akzeptiert wird, da mehr als ein Wert bedeutet, dass es keine Bijektion mehr ist und für die Rückwärtssuche mehrdeutig ist.

    – Benutzer193130

    11. Dezember 14 um 5:26 Uhr

  • @Basj Nun, ich kann verstehen, dass es Anwendungsfälle geben würde, die nützlich wären, mehr als einen Wert pro Schlüssel zu haben, also sollte diese Art von Datenstruktur vielleicht als Unterklasse von bidict existieren. Da jedoch ein normales Diktat einem einzelnen Objekt zugeordnet ist, halte ich es für viel sinnvoller, wenn auch die Umkehrung gleich ist. (Nur zur Verdeutlichung, obwohl der Wert auch eine Sammlung sein kann, meinte ich, dass der Schlüssel des ersten Diktats vom gleichen Typ sein sollte wie der Wert des umgekehrten Diktats.)

    – Benutzer193130

    11. Dezember 14 um 16:48 Uhr


Das folgende Code-Snippet implementiert eine invertierbare (bijektive) Karte:

class BijectionError(Exception):
    """Must set a unique value in a BijectiveMap."""

    def __init__(self, value):
        self.value = value
        msg = 'The value "{}" is already in the mapping.'
        super().__init__(msg.format(value))


class BijectiveMap(dict):
    """Invertible map."""

    def __init__(self, inverse=None):
        if inverse is None:
            inverse = self.__class__(inverse=self)
        self.inverse = inverse

    def __setitem__(self, key, value):
        if value in self.inverse:
            raise BijectionError(value)

        self.inverse._set_item(value, key)
        self._set_item(key, value)

    def __delitem__(self, key):
        self.inverse._del_item(self[key])
        self._del_item(key)

    def _del_item(self, key):
        super().__delitem__(key)

    def _set_item(self, key, value):
        super().__setitem__(key, value)

Der Vorteil dieser Implementierung besteht darin, dass die inverse Attribut von a BijectiveMap ist wieder ein BijectiveMap. Daher können Sie Dinge tun wie:

>>> foo = BijectiveMap()
>>> foo['steve'] = 42
>>> foo.inverse
{42: 'steve'}
>>> foo.inverse.inverse
{'steve': 42}
>>> foo.inverse.inverse is foo
True

Leider ist die am besten bewertete Antwort, bidict funktioniert nicht.

Es gibt drei Möglichkeiten:

  1. Unterklasse Diktat: Sie können eine Unterklasse von erstellen dict, aber Vorsicht. Sie müssen benutzerdefinierte Implementierungen von schreibenupdate, pop, initializer, setdefault. Der dict Implementierungen rufen nicht auf __setitem__. Aus diesem Grund hat die am besten bewertete Antwort Probleme.

  2. Von UserDict erben: Dies ist genau wie ein Diktat, außer dass alle Routinen korrekt aufgerufen werden. Es verwendet ein Diktat unter der Haube, in einem Element namens data. Sie können die lesen Python-Dokumentationoder Verwenden Sie eine einfache Implementierung einer Richtungsliste, die in Python 3 funktioniert. Entschuldigung, dass ich es nicht wörtlich aufgenommen habe: Ich bin mir nicht sicher, ob es sich um ein Urheberrecht handelt.

  3. Von abstrakten Basisklassen erben: Erben von Sammlungen.abc hilft Ihnen, alle richtigen Protokolle und Implementierungen für eine neue Klasse zu erhalten. Dies ist für ein bidirektionales Wörterbuch übertrieben, es sei denn, es kann auch verschlüsseln und in einer Datenbank zwischenspeichern.

TL;DR – Verwenden Dies für deinen Code. Lesen Sie Trey Hunners Artikel für Details.

  • Schöner Artikel. Trotzdem, was genau funktioniert nicht mit bidict?

    – tejasvi88

    11. Juli 21 um 12:54 Uhr

  • Was vor zwei Jahren nicht funktioniert hat, kann jetzt funktionieren oder auch nicht.

    – Karl Merriam

    13. Juli 21 um 17:45 Uhr

Wie implementiert man eine effiziente bidirektionale Hash Tabelle
Basj

So etwas vielleicht:

import itertools

class BidirDict(dict):
    def __init__(self, iterable=(), **kwargs):
        self.update(iterable, **kwargs)
    def update(self, iterable=(), **kwargs):
        if hasattr(iterable, 'iteritems'):
            iterable = iterable.iteritems()
        for (key, value) in itertools.chain(iterable, kwargs.iteritems()):
            self[key] = value
    def __setitem__(self, key, value):
        if key in self:
            del self[key]
        if value in self:
            del self[value]
        dict.__setitem__(self, key, value)
        dict.__setitem__(self, value, key)
    def __delitem__(self, key):
        value = self[key]
        dict.__delitem__(self, key)
        dict.__delitem__(self, value)
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, dict.__repr__(self))

Sie müssen entscheiden, was passieren soll, wenn mehr als ein Schlüssel einen bestimmten Wert hat; Die Bidirektionalität eines bestimmten Paares könnte leicht durch ein später eingefügtes Paar beeinträchtigt werden. Ich habe eine mögliche Auswahl implementiert.


Beispiel :

bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'})
print bd['myvalue1']   # a
print bd['myvalue2']   # b        

  • Schöner Artikel. Trotzdem, was genau funktioniert nicht mit bidict?

    – tejasvi88

    11. Juli 21 um 12:54 Uhr

  • Was vor zwei Jahren nicht funktioniert hat, kann jetzt funktionieren oder auch nicht.

    – Karl Merriam

    13. Juli 21 um 17:45 Uhr

Wie implementiert man eine effiziente bidirektionale Hash Tabelle
NeoWang

Zunächst müssen Sie sicherstellen, dass die Schlüssel-zu-Wert-Zuordnung eins zu eins ist, andernfalls ist es nicht möglich, eine bidirektionale Zuordnung zu erstellen.

Zweitens, wie groß ist der Datensatz? Wenn nicht viele Daten vorhanden sind, verwenden Sie einfach 2 separate Karten und aktualisieren Sie beide beim Aktualisieren. Oder besser, verwenden Sie eine vorhandene Lösung wie Gebotdas nur ein Wrapper von 2 Diktaten ist, mit eingebauter Aktualisierung/Löschung.

Aber wenn der Datensatz groß ist und die Pflege von 2 Diktaten nicht wünschenswert ist:

  • Wenn sowohl der Schlüssel als auch der Wert numerisch sind, ziehen Sie die Möglichkeit in Betracht, Interpolation zu verwenden, um die Zuordnung zu approximieren. Wenn die überwiegende Mehrheit der Schlüssel-Wert-Paare von der Zuordnungsfunktion (und ihrer
    Reverse-Funktion), dann brauchen Sie nur noch die Ausreißer in Karten aufzeichnen.

  • Wenn der größte Teil des Zugriffs unidirektional ist (Schlüssel-> Wert), ist es völlig in Ordnung, die umgekehrte Karte inkrementell zu erstellen, um Zeit dafür einzutauschen
    Platz.

Code:

d = {1: "one", 2: "two" }
reverse = {}

def get_key_by_value(v):
    if v not in reverse:
        for _k, _v in d.items():
           if _v == v:
               reverse[_v] = _k
               break
    return reverse[v]

.

758150cookie-checkWie implementiert man eine effiziente bidirektionale Hash-Tabelle?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy