Mappa bidirezionale / inversa [duplicato]


101

Sto facendo questa cosa del centralino in Python in cui ho bisogno di tenere traccia di chi sta parlando con chi, quindi se Alice -> Bob, allora questo implica che Bob -> Alice.

Sì, potrei popolare due mappe hash, ma mi chiedo se qualcuno ha un'idea per farlo con una.

Oppure suggerisci un'altra struttura dati.

Non ci sono più conversazioni. Diciamo che questo è per un call center del servizio clienti, quindi quando Alice chiama il centralino, parlerà solo con Bob. Anche le sue risposte vanno solo a lei.


15
nota che stai descrivendo una mappa biiettiva.
Nick Dandoulakis

Ecco una semplice implementazione del dizionario biiettiva , anche se non so se soddisferà i tuoi requisiti di prestazioni. (Link da questo articolo del blog su boost Bimap per Python , che ha una bella discussione sull'argomento.)
sistema PAUSA

2
Se Alice sta parlando con Bob, presumo che non possa parlare anche con Charles; né Bob può parlare con qualcun altro? Inoltre, quante persone e quante conversazioni puoi avere in un dato momento?
sistema PAUSA

Nah ... non sul mio centralino. Qualsiasi messaggio che mi manda Alice dovrà andare a Bob. È solo che instraderò migliaia di conversazioni simultanee. Ma ogni persona parla solo con un'altra persona alla volta.
Sudhir Jonathan,

1
No ... devo solo instradare i messaggi del cliente all'operatore e viceversa ... senza nemmeno memorizzare le conversazioni in alcun modo.
Sudhir Jonathan,

Risposte:


90

È possibile creare il proprio tipo di dizionario creando sottoclassi dicte aggiungendo la logica desiderata. Ecco un esempio di base:

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

E funziona così:

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

Sono sicuro di non aver coperto tutti i casi, ma questo dovrebbe farti iniziare.


2
@SudhirJonathan: Puoi andare molto oltre con questa idea - per esempio, aggiungi un .addmetodo in modo da poter fare cose come d.add('Bob', 'Alice')invece di usare la sintassi che ho mostrato. Includerei anche un po 'di gestione degli errori. Ma hai l'idea di base. :)
Sasha Chedygov

1
Immagino che questo rientri in quelle aggiunte, ma sarebbe utile eliminare le vecchie coppie di chiavi quando si impostano quelle nuove ( d['foo'] = 'baz'sarebbe necessario rimuovere ulteriormente la barchiave).
barba

@TobiasKienzler: Hai ragione, ma questo era elencato come presupposto nella domanda.
Sasha Chedygov

5
Vale anche la pena menzionare: la sottoclasse dictproduce un comportamento fuorviante qui, perché se crei l'oggetto con un contenuto iniziale, la struttura verrà interrotta. __init__deve essere sovrascritto per consentire a una costruzione come d = TwoWayDict({'foo' : 'bar'})di funzionare correttamente.
Henry Keiter

19
Voglio solo dire che v'è una libreria per questo: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719

45

Nel tuo caso speciale puoi memorizzare entrambi in un dizionario:

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

Poiché quello che stai descrivendo è una relazione simmetrica. A -> B => B -> A


3
Hmm ... sì, questo mi piace di più. Stavo cercando di evitare di fare due voci, ma questa è l'idea migliore finora.
Sudhir Jonathan,

1
Penso ancora che dovrebbe essere possibile una mappa bidirezionale: - /
Sudhir Jonathan,

Se deve essere efficiente, allora sotto le coperte hai bisogno di entrambe le chiavi per essere indicizzate in una struttura di dati di indice - che si tratti di un hash, un elenco ordinato, un albero binario, un trie, un array di suffissi pieno di sistrings o qualcosa di simile più esotico. Il modo più semplice per farlo in Python è usare un hash.
Kragen Javier Sitaker,

@SudhirJonathan Se preferisci una mappa veramente bidirezionale, dai un'occhiata al bidict come menzionato ad esempio in questa domanda - nota i problemi di prestazioni discussi da Aya nei commenti sulla mia domanda dupe .
Tobias Kienzler

25

So che è una vecchia domanda, ma volevo menzionare un'altra ottima soluzione a questo problema, vale a dire il pacchetto Python bidict . È estremamente semplice da usare:

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

24

Vorrei solo popolare un secondo hash, con

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

7
Ci sono alcune parentesi extra:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk

1
Bel modo semplice se non aggiorni ulteriormente il dict. Ho usatomy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

Quando si utilizza questo codice in Python 3 Ho ricevuto un avviso: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Qualche idea su come sbarazzarsi dell'avvertimento?
Kerwin Sneijders l'

9

Due mappe hash sono in realtà probabilmente la soluzione più veloce supponendo che tu possa risparmiare la memoria. Li avvolgerei in una singola classe: l'onere per il programmatore è garantire che due mappe hash si sincronizzino correttamente.


2
+1, Questo è ciò che fondamentalmente fa bidict , più lo zucchero per accedere alla mappatura inversa utilizzando mydict[:value]per ottenere key(a costo di alcune prestazioni)
Tobias Kienzler

6

Hai due problemi separati.

  1. Hai un oggetto "Conversazione". Si riferisce a due persone. Poiché una persona può avere più conversazioni, hai una relazione molti-a-molti.

  2. Hai una mappa da persona a un elenco di conversazioni. Una conversione avrà una coppia di persone.

Fai qualcosa del genere

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

No, non c'è davvero modo di farlo senza creare due dizionari. Come sarebbe possibile implementarlo con un solo dizionario pur continuando a offrire prestazioni comparabili?

È meglio creare un tipo personalizzato che incapsuli due dizionari ed esponga le funzionalità desiderate.


3

Un modo meno prolisso, usando ancora invertito:

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


2

Un'altra possibile soluzione è implementare una sottoclasse di dict, che contiene il dizionario originale e tiene traccia di una sua versione invertita. Mantenere due dict separati può essere utile se chiavi e valori si sovrappongono.

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. 

Esempio:

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

C'è la libreria estesa alle collezioni su pypi: https://pypi.python.org/pypi/collections-extended/0.6.0

Usare la classe di biiezione è facile come:

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

Mi piace il suggerimento di bidict in uno dei commenti.

pip install bidict

Utilizzo:

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

Dal momento che non ci sono molti documenti a riguardo. Ma ho tutte le funzionalità di cui ho bisogno per funzionare correttamente.

Stampe:

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

1

Il modulo di estensione C di kjbuckets fornisce una struttura dati "grafo" che credo ti dia quello che vuoi.


Scusa se non l'ho menzionato, ma è sul motore dell'app ... quindi niente estensioni C.
Sudhir Jonathan,

1

Ecco un'altra implementazione del dizionario a due vie estendendo la dictclasse dei pitoni nel caso in cui non ti piacesse nessuna di quelle altre:

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)

Usalo come un normale dizionario Python tranne nella costruzione:

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

1

Un modo in cui mi piace fare questo genere di cose è qualcosa del tipo:

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

1
Benvenuto in StackOverflow! Si prega di modificare la vostra risposta e aggiungere più spiegazione per il codice, preferibilmente anche l'aggiunta di descrizione del perché è diverso dagli altri 14 risposte. La domanda ha più di dieci anni e ha già una risposta accettata e anche molte ben spiegate e ben votate. Senza ulteriori dettagli nel tuo post, è di qualità relativamente inferiore e probabilmente verrà sottovalutato o rimosso. L'aggiunta di queste informazioni extra aiuterà a giustificare l'esistenza della tua risposta.
Das_Geek
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.