Peta dua arah / mundur [duplikat]

101

Saya melakukan hal switchboard ini dengan python di mana saya perlu melacak siapa yang berbicara dengan siapa, jadi jika Alice -> Bob, maka itu berarti Bob -> Alice.

Ya, saya dapat mengisi dua peta hash, tetapi saya ingin tahu apakah ada yang punya ide untuk melakukannya dengan satu peta.

Atau sarankan struktur data lain.

Tidak ada banyak percakapan. Katakanlah ini untuk pusat panggilan layanan pelanggan, jadi ketika Alice menelepon ke telepon, dia hanya akan berbicara dengan Bob. Jawabannya juga hanya ditujukan padanya.

Sudhir Jonathan
sumber
15
perhatikan bahwa Anda mendeskripsikan peta bijective.
Nick Dandoulakis
Berikut adalah implementasi Kamus bijektiva sederhana , meskipun saya tidak tahu apakah itu akan memenuhi persyaratan kinerja Anda. (Tautan dari artikel blog ini tentang boost Bimap untuk Python , yang memiliki beberapa diskusi bagus tentang topik ini.)
sistem JEDA
2
Jika Alice berbicara dengan Bob, saya rasa dia juga tidak bisa berbicara dengan Charles; juga tidak bisakah Bob berbicara dengan orang lain? Juga, berapa banyak orang dan berapa banyak percakapan yang dapat Anda lakukan pada waktu tertentu?
sistem JEDA
Nah ... tidak di switchboard saya. Setiap pesan yang alice kirimkan padaku harus pergi ke Bob. Hanya saja, saya akan mengarahkan ribuan percakapan simultan. Tetapi setiap orang hanya berbicara dengan satu orang pada satu waktu.
Sudhir Jonathan
1
Tidak ... Saya hanya perlu merutekan pesan pelanggan ke operator dan sebaliknya ... bahkan tidak menyimpan percakapan dengan cara apa pun.
Sudhir Jonathan

Jawaban:

90

Anda dapat membuat jenis kamus Anda sendiri dengan membuat subclass dictdan menambahkan logika yang Anda inginkan. Berikut contoh dasarnya:

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

Dan cara kerjanya seperti ini:

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

Saya yakin saya tidak membahas semua kasus, tetapi itu akan membantu Anda memulai.

Sasha Chedygov
sumber
2
@SudhirJonathan: Anda dapat melangkah lebih jauh dengan ide ini - misalnya, menambahkan .addmetode sehingga Anda dapat melakukan hal-hal seperti d.add('Bob', 'Alice')daripada menggunakan sintaks yang saya tunjukkan. Saya juga akan menyertakan beberapa penanganan kesalahan. Tapi Anda mendapatkan ide dasarnya. :)
Sasha Chedygov
1
Saya kira ini termasuk dalam penambahan tersebut, tetapi akan sangat membantu untuk menghapus pasangan kunci lama saat mengatur yang baru ( d['foo'] = 'baz'perlu juga menghapus barkunci).
beardc
@TobiasKienzler: Anda benar, tetapi ini terdaftar sebagai asumsi dalam pertanyaan.
Sasha Chedygov
5
Juga patut disebutkan: subclassing dictmenghasilkan beberapa perilaku menyesatkan di sini, karena jika Anda membuat objek dengan beberapa konten awal, strukturnya akan rusak. __init__perlu diganti agar konstruksi seperti d = TwoWayDict({'foo' : 'bar'})bekerja dengan baik.
Henry Keiter
19
Hanya ingin menyebutkan bahwa ada perpustakaan untuk ini: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719
45

Dalam kasus khusus Anda, Anda dapat menyimpan keduanya dalam satu kamus:

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

Karena yang Anda gambarkan adalah hubungan simetris. A -> B => B -> A

Nadia Alramli
sumber
3
Hmm ... ya, aku paling suka yang ini. Berusaha menghindari membuat dua entri, tapi sejauh ini ide terbaik.
Sudhir Jonathan
1
Masih berpikir peta dua arah harus dimungkinkan: - /
Sudhir Jonathan
Jika itu harus efisien, maka di bawah sampul Anda perlu kedua kunci untuk diindeks dalam beberapa struktur data indeks - apakah itu hash, daftar yang diurutkan, pohon biner, trie, array sufiks penuh sistring, atau bahkan sesuatu lebih eksotis. Cara sederhana untuk melakukannya dengan Python adalah dengan menggunakan hash.
Kragen Javier Sitaker
@SudhirJonathan Jika Anda lebih suka peta dua arah yang sebenarnya, lihat bidik seperti yang disebutkan misalnya dalam pertanyaan ini - perhatikan masalah kinerja yang dibahas oleh Aya dalam komentar pada pertanyaan penipuan saya .
Tobias Kienzler
25

Saya tahu ini pertanyaan lama, tetapi saya ingin menyebutkan solusi hebat lain untuk masalah ini, yaitu bidik paket python . Ini sangat mudah digunakan:

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

Saya hanya akan mengisi hash kedua, dengan

reverse_map = dict((reversed(item) for item in forward_map.items()))
Ian Clelland
sumber
7
Punya beberapa tanda kurung tambahan di sana:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk
1
Cara mudah yang bagus jika Anda tidak akan memperbarui dict lebih lanjut. Saya menggunakanmy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly
Bila menggunakan kode ini di Python 3 saya mendapatkan peringatan: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Ada ide bagaimana cara menghilangkan peringatan?
Kerwin Sneijders
9

Dua peta hash sebenarnya mungkin solusi berkinerja tercepat dengan asumsi Anda dapat menyisihkan memori. Saya akan membungkusnya dalam satu kelas - beban pada programmer adalah memastikan bahwa dua peta hash disinkronkan dengan benar.

Triptych
sumber
2
+1, Itulah yang pada dasarnya dilakukan bidik , ditambah gula untuk mengakses pemetaan terbalik dengan menggunakan mydict[:value]untuk memperoleh key(dengan mengorbankan beberapa performa)
Tobias Kienzler
6

Anda memiliki dua masalah terpisah.

  1. Anda memiliki objek "Percakapan". Ini mengacu pada dua Pribadi. Karena Seseorang dapat melakukan banyak percakapan, Anda memiliki hubungan banyak-ke-banyak.

  2. Anda memiliki Peta dari Orang ke daftar Percakapan. Sebuah Konversi akan memiliki sepasang Orang.

Lakukan sesuatu seperti ini

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

Tidak, tidak ada cara untuk melakukan ini tanpa membuat dua kamus. Bagaimana mungkin menerapkan ini hanya dengan satu kamus sambil terus menawarkan kinerja yang sebanding?

Anda lebih baik membuat tipe kustom yang merangkum dua kamus dan memperlihatkan fungsionalitas yang Anda inginkan.

Andrew Hare
sumber
3

Cara yang tidak terlalu bertele-tele, masih menggunakan terbalik:

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

Solusi lain yang mungkin adalah mengimplementasikan subkelas dari dict, yang menyimpan kamus asli dan melacak versi kebalikannya. Menjaga dua dicts terpisah dapat berguna jika kunci dan nilai tumpang tindih.

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. 

Contoh:

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

Ada perpustakaan tambahan koleksi di pypi: https://pypi.python.org/pypi/collections-extended/0.6.0

Menggunakan kelas bijection semudah:

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

Saya suka saran bidikt di salah satu komentar.

pip install bidict

Penggunaan:

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

Karena tidak banyak dokumen tentang itu. Tapi saya punya semua fitur yang saya butuhkan dari itu bekerja dengan benar.

Cetakan:

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

Modul ekstensi kjbuckets C menyediakan struktur data "grafik" yang menurut saya memberikan apa yang Anda inginkan.

Kragen Javier Sitaker
sumber
Maaf saya tidak menyebutkannya, tetapi di mesin aplikasi ... jadi tidak ada ekstensi C.
Sudhir Jonathan
1

Berikut satu lagi implementasi kamus dua arah dengan memperluas dictkelas ular sanca jika Anda tidak menyukai yang lain:

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)

Gunakan sebagai kamus python normal kecuali dalam konstruksi:

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

Cara yang saya suka untuk melakukan hal semacam ini adalah seperti:

{my_dict[key]: key for key in my_dict.keys()}
Tobi Abiodun
sumber
1
Selamat datang di StackOverflow! Silakan edit jawaban Anda dan tambahkan lebih banyak penjelasan untuk kode Anda, sebaiknya tambahkan juga penjelasan mengapa berbeda dari 14 jawaban lainnya. Pertanyaannya sudah lebih dari sepuluh tahun , dan sudah memiliki jawaban yang diterima dan banyak jawaban yang dijelaskan dengan baik juga. Tanpa detail lebih lanjut dalam posting Anda, kualitasnya relatif lebih rendah dan mungkin akan diturunkan atau dihapus. Menambahkan info tambahan itu akan membantu membenarkan keberadaan jawaban Anda.
Das_Geek