Mapa dwukierunkowa / odwrotna [duplikat]


101

Robię tę funkcję przełącznika w Pythonie, gdzie muszę śledzić, kto z kim rozmawia, więc jeśli Alice -> Bob, to oznacza, że ​​Bob -> Alice.

Tak, mógłbym wypełnić dwie mapy skrótów, ale zastanawiam się, czy ktoś ma pomysł, aby to zrobić z jedną.

Lub zasugeruj inną strukturę danych.

Nie ma wielu rozmów. Powiedzmy, że dotyczy to centrum obsługi klienta, więc kiedy Alicja wybierze numer z centrali, będzie rozmawiać tylko z Bobem. Jego odpowiedzi również trafiają tylko do niej.


15
zwróć uwagę, że opisujesz mapę bijektywną.
Nick Dandoulakis

Oto prosta implementacja słownika bijektywnego , chociaż nie wiem, czy spełni Twoje wymagania dotyczące wydajności. (Link do tego artykułu na blogu na temat boost Bimap for Python , który zawiera miłą dyskusję na ten temat.)
system PAUSE

2
Jeśli Alice rozmawia z Bobem, to rozumiem, że nie może też rozmawiać z Charlesem; ani Bob nie może rozmawiać z kimkolwiek innym? Ile osób i ile rozmów możesz prowadzić w danym momencie?
system PAUSE

Nie ... nie na mojej centrali. Każda wiadomość, którą wyśle ​​mi Alice, będzie musiała trafić do Boba. Po prostu przekierowuję tysiące jednoczesnych rozmów. Ale każda osoba rozmawia tylko z jedną osobą na raz.
Sudhir Jonathan

1
Nie ... Po prostu muszę przekierować wiadomości klienta do operatora i odwrotnie ... nawet nie zapisując w żaden sposób rozmów.
Sudhir Jonathan

Odpowiedzi:


90

Możesz utworzyć własny typ słownika, tworząc podklasy dicti dodając odpowiednią logikę. Oto podstawowy przykład:

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

I to działa tak:

>>> 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'

Jestem pewien, że nie omówiłem wszystkich przypadków, ale to powinno zacząć.


2
@SudhirJonathan: Możesz pójść znacznie dalej z tym pomysłem - na przykład dodać .addmetodę, abyś mógł robić takie rzeczy, jak d.add('Bob', 'Alice')zamiast używać składni, którą pokazałem. Chciałbym również dołączyć obsługę błędów. Ale masz podstawowy pomysł. :)
Sasha Chedygov

1
Wydaje mi się, że mieści się to w ramach tych dodatków, ale byłoby pomocne, aby usunąć stare pary kluczy podczas ustawiania nowych ( d['foo'] = 'baz'trzeba by dodatkowo usunąć barklucz).
beardc

@TobiasKienzler: Masz rację, ale zostało to wymienione jako założenie w pytaniu.
Sasha Chedygov

5
Warto również wspomnieć: tworzenie podklas dictpowoduje tutaj pewne mylące zachowanie, ponieważ jeśli utworzysz obiekt z pewną zawartością początkową, struktura zostanie zerwana. __init__musi zostać zastąpiony, aby konstrukcja taka d = TwoWayDict({'foo' : 'bar'})działała poprawnie.
Henry Keiter

19
Po prostu chcę wspomnieć, że istnieje biblioteka do tego: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719

45

W twoim szczególnym przypadku możesz przechowywać oba w jednym słowniku:

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

Ponieważ to, co opisujesz, jest relacją symetryczną. A -> B => B -> A


3
Hmm ... tak, ten podoba mi się najbardziej. Próbowałem uniknąć dwóch wpisów, ale jak dotąd jest to najlepszy pomysł.
Sudhir Jonathan

1
Nadal myślę, że dwukierunkowa mapa powinna być możliwa: - /
Sudhir Jonathan

Jeśli ma być wydajne, to pod okładkami potrzebujesz obu kluczy do indeksowania w jakiejś strukturze danych indeksu - czy to hash, posortowana lista, drzewo binarne, trie, tablica sufiksów pełna sistrings, czy coś nawet bardziej egzotyczny. Prostym sposobem na to w Pythonie jest użycie skrótu.
Kragen Javier Sitaker

@SudhirJonathan Jeśli wolisz prawdziwie dwukierunkową mapę, spójrz na bidict, jak wspomniano np. W tym pytaniu - zwróć jednak uwagę na kwestie wydajności omówione przez Aya w komentarzach do mojego dupe pytania .
Tobias Kienzler

25

Wiem, że to starsze pytanie, ale chciałem wspomnieć o innym świetnym rozwiązaniu tego problemu, a mianowicie o pakiecie python bidict . Jest niezwykle prosty w użyciu:

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

24

Po prostu zapełniłbym drugi hash za pomocą

reverse_map = dict((reversed(item) for item in forward_map.items()))

7
Mam tam dodatkowe nawiasy:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk,

1
Niezły prosty sposób, jeśli nie będziesz dalej aktualizować dyktowania. Użyłemmy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

Przy użyciu tego kodu w Pythonie 3 otrzymuję ostrzeżenie: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Jakieś pomysły, jak pozbyć się ostrzeżenia?
Kerwin Sneijders

9

Dwie mapy skrótów są prawdopodobnie najszybszym rozwiązaniem przy założeniu, że można zaoszczędzić pamięć. Umieściłbym je w jednej klasie - obciążenie programisty polega na zapewnieniu prawidłowej synchronizacji dwóch map skrótów.


2
+1, to co bidict zasadzie robi, plus cukier dostępu do odwrotnego mapowania za pomocą mydict[:value]uzyskanie key(kosztem pewnej wydajności)
Tobias Kienzler

6

Masz dwa oddzielne problemy.

  1. Masz obiekt „Rozmowa”. Odnosi się do dwóch osób. Ponieważ osoba może prowadzić wiele rozmów, istnieje relacja „wiele do wielu”.

  2. Masz mapę od osoby do listy konwersacji. Nawrócenie będzie miało parę Osób.

Zrób coś takiego

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 )

5

Nie, naprawdę nie da się tego zrobić bez utworzenia dwóch słowników. Jak można by to zaimplementować za pomocą tylko jednego słownika, jednocześnie oferując porównywalną wydajność?

Lepiej jest utworzyć niestandardowy typ, który zawiera dwa słowniki i udostępnia żądaną funkcjonalność.


3

Mniej rozwlekły sposób, nadal używający odwrócenia:

dict(map(reversed, my_dict.items()))


2

Innym możliwym rozwiązaniem jest zaimplementowanie podklasy dict, która przechowuje oryginalny słownik i śledzi jego odwróconą wersję. Przechowywanie dwóch oddzielnych poleceń może być przydatne, jeśli klucze i wartości nakładają się.

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. 

Przykład:

>>> 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'}

2

Na pypi znajduje się rozszerzona biblioteka kolekcji: https://pypi.python.org/pypi/collections-extended/0.6.0

Korzystanie z klasy bijection jest tak proste, jak:

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

2

Podoba mi się sugestia licytanta w jednym z komentarzy.

pip install bidict

Zastosowanie:

# 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))    

Ponieważ nie ma na ten temat wielu dokumentów. Ale wszystkie funkcje, których potrzebuję, działają poprawnie.

Wydruki:

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

1

Moduł rozszerzenia kjbuckets C zapewnia strukturę danych "grafową", która moim zdaniem daje ci to, czego chcesz.


Przepraszam, że o tym nie wspomniałem, ale jest na silniku aplikacji ... więc nie ma rozszerzeń C.
Sudhir Jonathan

1

Oto jeszcze jedna dwukierunkowa implementacja słownika poprzez rozszerzenie dictklasy Pythona na wypadek, gdybyś nie lubił żadnego z tych innych:

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)

Użyj go jako normalnego słownika Pythona, z wyjątkiem konstrukcji:

dd = DoubleD()
dd['foo'] = 'bar'

1

Sposób, w jaki lubię robić tego typu rzeczy, jest taki:

{my_dict[key]: key for key in my_dict.keys()}

1
Witamy w StackOverflow! Proszę edytować swoje odpowiedzi i dodać więcej wyjaśnień na kodzie, korzystnie również dodanie opisu, dlaczego to różni się od 14 innych odpowiedzi. Pytanie ma ponad dziesięć lat i ma już zaakceptowaną odpowiedź, a także wiele dobrze wyjaśnionych, dobrze ocenianych. Bez bardziej szczegółowych informacji w Twoim poście jest on stosunkowo niższej jakości i prawdopodobnie zostanie odrzucony lub usunięty. Dodanie tych dodatkowych informacji pomoże uzasadnić istnienie Twojej odpowiedzi.
Das_Geek
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.