İki yönlü / ters harita [yinelenen]


101

Bu santral işini python'da kimin kiminle konuştuğunu takip etmem gereken yerde yapıyorum, yani eğer Alice -> Bob ise, bu Bob -> Alice anlamına gelir.

Evet, iki hash haritayı doldurabilirim, ancak birinin bunu yapmak için bir fikri olup olmadığını merak ediyorum.

Veya başka bir veri yapısı önerin.

Birden fazla konuşma yok. Diyelim ki bu bir müşteri hizmetleri çağrı merkezi için, yani Alice santrali aradığında, sadece Bob ile konuşacak. Cevapları da sadece ona gidiyor.


15
önyargılı bir harita tarif ettiğinizi unutmayın.
Nick Dandoulakis

İşte basit bir bijective Dictionary uygulaması , ancak performans gereksinimlerinizi karşılayıp karşılamayacağını bilmiyorum. ( Konuyla ilgili güzel tartışmalara sahip olan Python için Bimap'i artırma hakkındaki bu blog makalesinin bağlantısı .)
sistem

2
Alice Bob ile konuşuyorsa, Charles ile de konuşamayacağını anlıyorum; ne de Bob başka biriyle konuşuyor olamaz? Ayrıca, herhangi bir zamanda kaç kişi ve kaç konuşma yapabilirsiniz?
sistem

Hayır ... santralimde değil. Alice'in bana gönderdiği her mesaj Bob'a gitmek zorunda kalacak. Ben sadece binlerce eşzamanlı konuşmayı yönlendireceğim. Ancak her kişi aynı anda yalnızca bir başka kişiyle konuşur.
Sudhir Jonathan

1
Hayır ... Sadece müşterinin mesajlarını operatöre yönlendirmem gerekiyor ve bunun tersi de ... görüşmeleri hiçbir şekilde kaydetmiyorum bile.
Sudhir Jonathan

Yanıtlar:


90

Alt sınıflara ayırarak dictve istediğiniz mantığı ekleyerek kendi sözlük türünüzü oluşturabilirsiniz . İşte temel bir örnek:

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

Ve şu şekilde çalışıyor:

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

Eminim tüm vakaları kapsamadım, ama bu başlamanı sağlamalı.


2
@SudhirJonathan: Bu fikirle çok daha ileri gidebilirsiniz - örneğin, gösterdiğim sözdizimini kullanmak yerine .addbenzer şeyler yapabilmeniz için bir yöntem ekleyin d.add('Bob', 'Alice'). Ayrıca bazı hata işlemeyi de eklerdim. Ama temel fikri anladınız. :)
Sasha Chedygov

1
Sanırım bu, bu eklemelerin altına düşüyor, ancak yenilerini ayarlarken eski anahtar çiftlerini silmek faydalı d['foo'] = 'baz'olacaktır ( ayrıca baranahtarı kaldırmanız gerekir ).
sakalc

@TobiasKienzler: Doğru söylüyorsunuz, ancak bu soruda bir varsayım olarak listelenmişti.
Sasha Chedygov

5
Ayrıca bahsetmeye değer: alt sınıflandırma dictburada bazı yanıltıcı davranışlar üretir, çünkü nesneyi bir miktar başlangıç ​​içeriğiyle yaratırsanız, yapı bozulur. __init__gibi bir yapının d = TwoWayDict({'foo' : 'bar'})düzgün çalışmasına izin vermek için geçersiz kılınması gerekir.
Henry Keiter

19
Sadece bunun için bir kütüphane olduğundan söz etmek istiyorum: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719

45

Özel durumunuzda her ikisini de tek bir sözlükte saklayabilirsiniz:

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

Tanımladığınız şey simetrik bir ilişki olduğu için. A -> B => B -> A


3
Hmm ... evet, bunu en çok beğendim. İki giriş yapmaktan kaçınmaya çalışıyordu, ancak bu şimdiye kadarki en iyi fikir.
Sudhir Jonathan

1
Yine de iki yönlü bir haritanın mümkün olması gerektiğini düşünüyorum: - /
Sudhir Jonathan

Etkili olması gerekiyorsa, kapakların altında her iki anahtarın da bazı indeks veri yapısında indekslenmesine ihtiyacınız var - bu ister bir karma, ister sıralı bir liste, bir ikili ağaç, bir trie, yaylarla dolu bir sonek dizisi veya hatta bir şey olsun daha egzotik. Python'da bunu yapmanın basit yolu bir hash kullanmaktır.
Kragen Javier Sitaker

@SudhirJonathan Eğer gerçekten iki yönlü bir haritayı tercih ediyorsanız, örneğin bu soruda bahsedildiği gibi bir teklifçiye bir göz atın - yine de Aya tarafından tartışılan performans konularını benim dupe sorumla ilgili yorumlarda not edin .
Tobias Kienzler

25

Bunun daha eski bir soru olduğunu biliyorum, ancak bu soruna başka bir harika çözümden, yani python paketi teklifinden bahsetmek istedim . Kullanımı son derece basittir:

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

24

Sadece ikinci bir hash doldururdum

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

7
Orada fazladan parantez var:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk

1
Dikteyi daha fazla güncellemeyecekseniz güzel ve kolay bir yol. Kullandımmy_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

Python 3'te bu kodu kullanırken bir uyarı olsun: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). Uyarıdan nasıl kurtulacağınıza dair bir fikriniz var mı?
Kerwin Sneijders

9

Belleği yedekleyebileceğinizi varsayarak, iki karma harita aslında muhtemelen en hızlı performans gösteren çözümdür. Bunları tek bir sınıfta toplardım - programcının yükü, iki karma haritanın doğru şekilde senkronize edilmesini sağlamaktır.


2
+1, Teklif verenin temelde yaptığı şey bu, artı mydict[:value]elde etmek için kullanarak ters eşlemeye erişmek için şeker key(bir miktar performans pahasına)
Tobias Kienzler

6

İki ayrı sorununuz var.

  1. Bir "Konuşma" nesneniz var. İki Kişiye atıfta bulunur. Bir Kişi birden fazla görüşme yapabileceğinden, çoktan çoğa bir ilişkiniz vardır.

  2. Kişiden Konuşmalar listesine giden bir Haritanız var. Bir Dönüşüm bir çift Kişiye sahip olacaktır.

Bunun gibi bir şey yap

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

Hayır, bunu iki sözlük oluşturmadan yapmanın gerçekten bir yolu yok. Karşılaştırılabilir performans sunmaya devam ederken bunu tek bir sözlükle uygulamak nasıl mümkün olabilirdi?

İki sözlüğü içine alan ve istediğiniz işlevselliği ortaya çıkaran özel bir tür oluşturmanız daha iyidir.


3

Daha az ayrıntılı bir yol, hala tersini kullanıyor:

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


2

Başka bir olası çözüm, dictorijinal sözlüğü tutan ve tersine çevrilmiş bir versiyonunun kaydını tutan bir alt sınıf uygulamaktır . Anahtarlar ve değerler örtüşüyorsa, iki ayrı dikti tutmak yararlı olabilir.

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. 

Misal:

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

Pypi'de koleksiyonlar genişletilmiş kitaplık var: https://pypi.python.org/pypi/collections-extended/0.6.0

Birleştirme sınıfını kullanmak şu kadar kolaydır:

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

Yorumlardan birinin teklifini beğendim.

pip install bidict

Kullanım hızı:

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

Bununla ilgili çok fazla belge olmadığı için. Ama ihtiyacım olan tüm özelliklere sahip oldum.

Baskılar:

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

1

Kjbuckets C genişletme modülü, size istediğinizi verdiğine inandığım bir "grafik" veri yapısı sağlar.


Maalesef bundan bahsetmedim, ama uygulama motorunda ... yani C uzantısı yok.
Sudhir Jonathan

1

İşte dictdiğerlerinden hiçbirini beğenmediyseniz, pythons sınıfını genişleterek iki yönlü sözlük uygulaması daha :

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)

Yapım dışında normal bir python sözlüğü olarak kullanın:

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

1

Bu tür şeyleri yapmayı sevdiğim bir yol şuna benzer:

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

1
StackOverflow'a hoş geldiniz! Lütfen cevabınızı düzenleyin ve kodunuz için daha fazla açıklama ekleyin, tercihen diğer 14 cevaptan neden farklı olduğuna dair açıklama ekleyin. Soru on yaşın üzerinde ve şimdiden kabul edilmiş bir cevabı var ve pek çok iyi açıklanmış, iyi oylanmış cevapları var. Gönderinizde daha fazla ayrıntı olmazsa, nispeten daha düşük kalitededir ve muhtemelen reddedilecek veya kaldırılacaktır. Bu ekstra bilgiyi eklemek, cevabınızın varlığını haklı çıkarmaya yardımcı olacaktır.
Das_Geek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.