Zweiwege- / Rückwärtskarte [Duplikat]

101

Ich mache diese Telefonzentrale in Python, wo ich nachverfolgen muss, wer mit wem spricht. Wenn also Alice -> Bob, dann bedeutet das, dass Bob -> Alice.

Ja, ich könnte zwei Hash-Karten füllen, aber ich frage mich, ob jemand eine Idee hat, dies mit einer zu tun.

Oder schlagen Sie eine andere Datenstruktur vor.

Es gibt keine Mehrfachgespräche. Nehmen wir an, dies ist für ein Kundendienst-Callcenter. Wenn Alice sich in die Telefonzentrale einwählt, wird sie nur mit Bob sprechen. Seine Antworten gehen auch nur an sie.

Sudhir Jonathan
quelle
15
Beachten Sie, dass Sie eine bijektive Karte beschreiben.
Nick Dandoulakis
Hier ist eine einfache bijektive Dictionary-Implementierung , obwohl ich nicht weiß, ob sie Ihren Leistungsanforderungen entspricht. (Link von diesem Blog-Artikel über Boost Bimap für Python , der einige nette Diskussionen über das Thema enthält.)
System PAUSE
2
Wenn Alice mit Bob spricht, kann sie nicht auch mit Charles sprechen. noch kann Bob mit jemand anderem sprechen? Wie viele Personen und wie viele Gespräche können Sie zu einem bestimmten Zeitpunkt führen?
System PAUSE
Nein ... nicht auf meiner Telefonzentrale. Jede Nachricht, die Alice mir sendet, muss an Bob gehen. Ich werde nur Tausende von gleichzeitigen Gesprächen leiten. Aber jede Person spricht immer nur mit einer anderen Person.
Sudhir Jonathan
1
Nein ... Ich muss nur die Nachrichten des Kunden an den Operator weiterleiten und umgekehrt ... und nicht einmal die Gespräche in irgendeiner Weise speichern.
Sudhir Jonathan

Antworten:

90

Sie können Ihren eigenen Wörterbuchtyp erstellen, dictindem Sie die gewünschte Logik unterordnen und hinzufügen. Hier ist ein einfaches Beispiel:

class TwoWayDict(dict):
    def __setitem__(self, key, value):
        # Remove any previous connections with these values
        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):
        dict.__delitem__(self, self[key])
        dict.__delitem__(self, key)

    def __len__(self):
        """Returns the number of connections"""
        return dict.__len__(self) // 2

Und es funktioniert so:

>>> d = TwoWayDict()
>>> d['foo'] = 'bar'
>>> d['foo']
'bar'
>>> d['bar']
'foo'
>>> len(d)
1
>>> del d['foo']
>>> d['bar']
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
KeyError: 'bar'

Ich bin sicher, ich habe nicht alle Fälle abgedeckt, aber das sollte Ihnen den Einstieg erleichtern.

Sasha Chedygov
quelle
2
@SudhirJonathan: Sie können mit dieser Idee viel weiter gehen - fügen Sie beispielsweise eine .addMethode hinzu, mit der Sie beispielsweise die von mir gezeigte d.add('Bob', 'Alice')Syntax verwenden können. Ich würde auch eine Fehlerbehandlung einschließen. Aber Sie bekommen die Grundidee. :)
Sasha Chedygov
1
Ich denke, dies fällt unter diese Ergänzungen, aber es wäre hilfreich, alte Schlüsselpaare zu löschen, wenn neue festgelegt werden ( d['foo'] = 'baz'der barSchlüssel müsste zusätzlich entfernt werden ).
Bart
@TobiasKienzler: Sie haben Recht, aber dies wurde als Annahme in der Frage aufgeführt.
Sasha Chedygov
5
Ebenfalls erwähnenswert: Unterklassen dictführen hier zu irreführendem Verhalten, denn wenn Sie das Objekt mit anfänglichem Inhalt erstellen, wird die Struktur beschädigt. __init__muss überschrieben werden, damit eine Konstruktion d = TwoWayDict({'foo' : 'bar'})ordnungsgemäß funktioniert.
Henry Keiter
19
Ich möchte nur erwähnen, dass es dafür eine Bibliothek gibt : pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719
45

In Ihrem speziellen Fall können Sie beide in einem Wörterbuch speichern:

relation = {}
relation['Alice'] = 'Bob'
relation['Bob'] = 'Alice'

Denn was Sie beschreiben, ist eine symmetrische Beziehung. A -> B => B -> A

Nadia Alramli
quelle
3
Hmm ... ja, ich mag dieses am besten. Ich habe versucht, zwei Einträge zu vermeiden, aber dies ist die bisher beste Idee.
Sudhir Jonathan
1
Denken Sie immer noch, dass eine Zwei-Wege-Karte möglich sein sollte: - /
Sudhir Jonathan
Wenn es effizient sein muss, müssen beide Schlüssel unter dem Deckmantel in einer Indexdatenstruktur indiziert werden - ob es sich um einen Hash, eine sortierte Liste, einen Binärbaum, einen Trie, ein Suffix-Array voller Sistrings oder etwas anderes handelt exotischer. Die einfache Möglichkeit, dies in Python zu tun, besteht darin, einen Hash zu verwenden.
Kragen Javier Sitaker
@SudhirJonathan Wenn Sie eine echte Zwei-Wege-Karte bevorzugen, schauen Sie sich Bidict an, wie z. B. in dieser Frage erwähnt. Beachten Sie jedoch die Leistungsprobleme, die Aya in den Kommentaren zu meiner Dupe-Frage besprochen hat .
Tobias Kienzler
25

Ich weiß, dass es eine ältere Frage ist, aber ich wollte eine andere großartige Lösung für dieses Problem erwähnen, nämlich das Bidict des Python-Pakets . Es ist sehr einfach zu bedienen:

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])
Nearoo
quelle
24

Ich würde nur einen zweiten Hash mit füllen

reverse_map = dict((reversed(item) for item in forward_map.items()))
Ian Clelland
quelle
7
reverse_map = dict(reversed(item) for item in forward_map.items())
Habe ein
1
Schöne einfache Möglichkeit, wenn Sie das Diktat nicht weiter aktualisieren. Ich benutztemy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly
Bei Verwendung dieses Codes in Python 3 wird eine Warnung angezeigt : Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Irgendwelche Ideen, wie man die Warnung loswird?
Kerwin Sneijders
9

Zwei Hash-Maps sind wahrscheinlich die Lösung mit der schnellsten Leistung, vorausgesetzt, Sie können Speicherplatz sparen. Ich würde diese in eine einzige Klasse einschließen - die Belastung für den Programmierer besteht darin, sicherzustellen, dass zwei der Hash-Maps korrekt synchronisiert werden.

Triptychon
quelle
2
+1, das ist es, was Bidict im Grunde tut, plus Zucker für den Zugriff auf das inverse Mapping, indem es verwendet wird mydict[:value], um key(auf Kosten einer gewissen Leistung) zu erhalten
Tobias Kienzler
6

Sie haben zwei separate Probleme.

  1. Sie haben ein "Conversation" -Objekt. Es bezieht sich auf zwei Personen. Da eine Person mehrere Gespräche führen kann, haben Sie eine Viele-zu-Viele-Beziehung.

  2. Sie haben eine Karte von Person zu einer Liste von Gesprächen. Eine Konvertierung hat zwei Personen.

Mach so etwas

from collections import defaultdict
switchboard= defaultdict( list )

x = Conversation( "Alice", "Bob" )
y = Conversation( "Alice", "Charlie" )

for c in ( x, y ):
    switchboard[c.p1].append( c )
    switchboard[c.p2].append( c )
S.Lott
quelle
5

Nein, es gibt wirklich keine Möglichkeit, dies zu tun, ohne zwei Wörterbücher zu erstellen. Wie wäre es möglich, dies mit nur einem Wörterbuch zu implementieren und gleichzeitig eine vergleichbare Leistung zu bieten?

Sie sollten einen benutzerdefinierten Typ erstellen, der zwei Wörterbücher enthält und die gewünschte Funktionalität bereitstellt.

Andrew Hare
quelle
3

Ein weniger ausführlicher Weg, immer noch umgekehrt:

dict(map(reversed, my_dict.items()))
Eti JS
quelle
2

Eine andere mögliche Lösung besteht darin, eine Unterklasse von zu implementieren dict, die das ursprüngliche Wörterbuch enthält und eine umgekehrte Version davon verfolgt. Das Halten von zwei getrennten Diktaten kann nützlich sein, wenn sich Schlüssel und Werte überschneiden.

class TwoWayDict(dict):
    def __init__(self, my_dict):
        dict.__init__(self, my_dict)
        self.rev_dict = {v : k for k,v in my_dict.iteritems()}

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        self.rev_dict.__setitem__(value, key)

    def pop(self, key):
        self.rev_dict.pop(self[key])
        dict.pop(self, key)

    # The above is just an idea other methods
    # should also be overridden. 

Beispiel:

>>> d = {'a' : 1, 'b' : 2} # suppose we need to use d and its reversed version
>>> twd = TwoWayDict(d)    # create a two-way dict
>>> twd
{'a': 1, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b'}
>>> twd['a']
1
>>> twd.rev_dict[2]
'b'
>>> twd['c'] = 3    # we add to twd and reversed version also changes
>>> twd
{'a': 1, 'c': 3, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b', 3: 'c'}
>>> twd.pop('a')   # we pop elements from twd and reversed  version changes
>>> twd
{'c': 3, 'b': 2}
>>> twd.rev_dict
{2: 'b', 3: 'c'}
Akavall
quelle
2

Auf pypi befindet sich die Bibliothek mit erweiterten Sammlungen: https://pypi.python.org/pypi/collections-extended/0.6.0

Die Verwendung der Bijektionsklasse ist so einfach wie:

RESPONSE_TYPES = bijection({
    0x03 : 'module_info',
    0x09 : 'network_status_response',
    0x10 : 'trust_center_device_update'
})
>>> RESPONSE_TYPES[0x03]
'module_info'
>>> RESPONSE_TYPES.inverse['network_status_response']
0x09
Schwolop
quelle
2

Ich mag den Vorschlag von Bidict in einem der Kommentare.

pip install bidict

Verwendung:

# This normalization method should save hugely as aDaD ~ yXyX have the same form of smallest grammar.
# To get back to your grammar's alphabet use trans

def normalize_string(s, nv=None):
    if nv is None:
        nv = ord('a')
    trans = bidict()
    r = ''
    for c in s:
        if c not in trans.inverse:
            a = chr(nv)
            nv += 1
            trans[a] = c
        else:
            a = trans.inverse[c]
        r += a
    return r, trans


def translate_string(s, trans):
    res = ''
    for c in s:
        res += trans[c]
    return res


if __name__ == "__main__":
    s = "bnhnbiodfjos"

    n, tr = normalize_string(s)
    print(n)
    print(tr)
    print(translate_string(n, tr))    

Da gibt es nicht viele Dokumente darüber. Aber ich habe alle Funktionen, die ich brauche, damit es richtig funktioniert.

Drucke:

abcbadefghei
bidict({'a': 'b', 'b': 'n', 'c': 'h', 'd': 'i', 'e': 'o', 'f': 'd', 'g': 'f', 'h': 'j', 'i': 's'})
bnhnbiodfjos
AbstractAlgebraLearner
quelle
1

Das kjbuckets C-Erweiterungsmodul bietet eine "Graph" -Datenstruktur, von der ich glaube, dass sie Ihnen das bietet, was Sie wollen.

Kragen Javier Sitaker
quelle
Entschuldigung, ich habe es nicht erwähnt, aber es ist eine App-Engine ... also keine C-Erweiterungen.
Sudhir Jonathan
1

Hier ist eine weitere Zwei-Wege-Wörterbuchimplementierung durch Erweitern der Python- dictKlasse, falls Ihnen eine dieser anderen nicht gefallen hat:

class DoubleD(dict):
    """ Access and delete dictionary elements by key or value. """ 

    def __getitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            return inv_dict[key]
        return dict.__getitem__(self, key)

    def __delitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            dict.__delitem__(self, inv_dict[key])
        else:
            dict.__delitem__(self, key)

Verwenden Sie es als normales Python-Wörterbuch, außer in der Konstruktion:

dd = DoubleD()
dd['foo'] = 'bar'
browlm13
quelle
1

Eine Art und Weise, wie ich so etwas mache, ist so etwas wie:

{my_dict[key]: key for key in my_dict.keys()}
Tobi Abiodun
quelle
1
Willkommen bei StackOverflow! Bitte bearbeiten Sie Ihre Antwort und fügen Sie weitere Erklärungen für Ihren Code hinzu, vorzugsweise auch eine Beschreibung, warum er sich von den 14 anderen Antworten unterscheidet. Die Frage ist über zehn Jahre alt und hat bereits eine akzeptierte Antwort und viele gut erklärte, gut bewertete. Ohne weitere Details in Ihrem Beitrag ist er von vergleichsweise geringerer Qualität und wird wahrscheinlich herabgestuft oder entfernt. Wenn Sie diese zusätzlichen Informationen hinzufügen, können Sie die Existenz Ihrer Antwort rechtfertigen.
Das_Geek