Etkili bir çift yönlü hash tablosu nasıl uygulanır?


86

Python dictçok kullanışlı bir veri yapısıdır:

d = {'a': 1, 'b': 2}

d['a'] # get 1

Bazen değerlere göre de indekslemek istersiniz.

d[1] # get 'a'

Bu veri yapısını uygulamanın en verimli yolu hangisidir? Bunu yapmanın resmi bir yolu var mı?


Tercih ederseniz, anahtarların olduğu kadar değerlerin de değişmez olduğunu varsayabiliriz.
Juanjo Conti

4
Bu söz için ne döndürürdünüz: {'a': 1, 'b': 2, 'A': 1}
PaulMcG

2
@PaulMcGuire: Dönecektim {1: ['a', 'A'], 2: 'b'}. Bunu yapmanın böyle bir yolu için cevabıma bakın.
Basj

4
Moderatöre not: Bu, stackoverflow.com/questions/1456373/two-way-reverse-map dosyasının bir kopyası değildir . İkincisi 1) çok belirsiz ifadelere sahiptir 2) MCVE yok 3) sadece önyargılı haritanın durumunu ele almaktadır (bu sorudaki ilk yoruma bakınız), ki bu daha genel olan bu gerçek sorudan çok daha kısıtlayıcıdır. Bu yüzden onu kopya olarak işaretlemenin burada, bu özel durumda yanıltıcı olduğunu düşünüyorum. Eğer gerçekten biri diğerinin kopyası olacaksa, bu genel durumu kapsadığından, diğeri (cevaplara bakınız) önyargılı olmayan durumu kapsamadığı için bunun tersi olmalıdır.
Basj

Yanıtlar:


68

Burada, Python sözlüğündeki değerden anahtar bulma yönteminden dictesinlenilmiş ve aşağıdaki 2) ve 3) 'e izin verecek şekilde değiştirilmiş çift ​​yönlü bir sınıf bulunmaktadır .

Bunu not et :

  • 1) Ters dizin bd.inverse , standart dikte bddeğiştirildiğinde kendini otomatik olarak günceller .
  • 2) ters dizin bd.inverse[value] bir daima liste ait keyöyle ki bd[key] == value.
  • 3) https://pypi.python.org/pypi/bidictbidict modülünden farklı olarak burada aynı değere sahip 2 anahtarımız olabilir, bu çok önemlidir .

Kod:

class bidict(dict):
    def __init__(self, *args, **kwargs):
        super(bidict, self).__init__(*args, **kwargs)
        self.inverse = {}
        for key, value in self.items():
            self.inverse.setdefault(value,[]).append(key) 

    def __setitem__(self, key, value):
        if key in self:
            self.inverse[self[key]].remove(key) 
        super(bidict, self).__setitem__(key, value)
        self.inverse.setdefault(value,[]).append(key)        

    def __delitem__(self, key):
        self.inverse.setdefault(self[key],[]).remove(key)
        if self[key] in self.inverse and not self.inverse[self[key]]: 
            del self.inverse[self[key]]
        super(bidict, self).__delitem__(key)

Kullanım örneği:

bd = bidict({'a': 1, 'b': 2})  
print(bd)                     # {'a': 1, 'b': 2}                 
print(bd.inverse)             # {1: ['a'], 2: ['b']}
bd['c'] = 1                   # Now two keys have the same value (= 1)
print(bd)                     # {'a': 1, 'c': 1, 'b': 2}
print(bd.inverse)             # {1: ['a', 'c'], 2: ['b']}
del bd['c']
print(bd)                     # {'a': 1, 'b': 2}
print(bd.inverse)             # {1: ['a'], 2: ['b']}
del bd['a']
print(bd)                     # {'b': 2}
print(bd.inverse)             # {2: ['b']}
bd['b'] = 3
print(bd)                     # {'b': 3}
print(bd.inverse)             # {2: [], 3: ['b']}

2
Belirsiz durumun çok temiz çözümü!
Tobias Kienzler

2
Bu veri yapısının birçok pratik problemde çok faydalı olduğunu düşünüyorum.
0xc0de

6
Bu olağanüstü. Kısa ve öz; kendi kendini belgeliyor; oldukça etkilidir; sadece çalışıyor. Benim tek kelime oyunu tekrarlanan aramaları optimize etmek olacaktır self[key]içinde __delitem__()tek olan value = self[key]bu tür aramalar için yeniden atama. Ama ... evet. Bu önemsiz. Saf harika için teşekkürler, Basj !
Cecil Curry

1
Python 3 sürümüne ne dersiniz?
zelusp

1
Örnek için bu cevabı beğendim. Kabul edilen cevap yine de doğrudur ve kabul edilen cevabın kabul edilen cevap olarak kalması gerektiğini düşünüyorum, ancak bu, onu kendiniz tanımlamak için biraz daha açık, çünkü sözlüğü tersine çevirmek için tersini koymanız gerektiğini açıkça ortaya koyuyor. bir sözlüğün anahtar-değerlerle bire çok ilişkisi olduğu için bire bir eşleme olamayacağından, değerleri listeye dahil edin.
searchengine27

41

Aynı dikteyi anahtar, değer çiftini ters sırada ekleyerek kullanabilirsiniz.

d = {'a': 1, 'b': 2}
revd = dikte ([d.items () i içindeki i için (i) tersine çevrildi])
d. güncelleme (revd)

5
+1 Güzel, pratik bir çözüm. Başka yolu yazmak için: d.update( dict((d[k], k) for k in d) ).
FMc

4
+1 Ters () 'in düzgün kullanımı için. Açıktan daha okunaklı olup olmadığına kararsızım dict((v, k) for (k, v) in d.items()). Her durumda, sen .update doğrudan çiftlerini de geçirebilir: d.update(reversed(i) for i in d.items()).
Beni Cherniavsky-Paskin

22
Bunun başarısız d={'a':1, 'b':2, 1: 'b'}
olduğuna dikkat edin,

3
Hafif modifikasyon: dict(map(reversed, a_dict.items())).
0xc0de

13
Orijinal sözlüğe ters eşlemeler eklemek berbat bir fikir. Yukarıdaki yorumların gösterdiği gibi, bunu yapmak genel durumda güvenli değildir . Sadece iki ayrı sözlük bulundurun. d.update(revd)Sonu görmezden gelen bu cevabın ilk iki satırı harika olduğu için, yine de bir olumlu oy düşünüyorum. Bunu biraz düşünelim.
Cecil Curry

36

Fakir bir adamın çift yönlü hash tablosu sadece iki sözlük kullanmak olacaktır (bunlar zaten yüksek düzeyde ayarlanmış veri yapılarıdır).

Ayrıca endekste bir de ihale paketi var:

Teklif sahibinin kaynağı github'da bulunabilir:


1
2 dicts, çift ekleme ve silme gerektirir.
Juanjo Conti

12
@Juanjo: Neredeyse tüm çift yönlü / tersine çevrilebilir karma tablolar, yapının uygulanmasının bir parçası olarak veya onu kullanmanın bir parçası olarak "çift eklemeler ve silmeler" içerecektir. İki dizini tutmak gerçekten bunu yapmanın tek hızlı yolu, AFAIK.
Walter Mundt

7
Elbette; Problem 2 endeksine elle bakmanın sorun olduğunu söyledim.
Juanjo Conti

1
@Basj Bence birden fazla değere sahip olmak artık bir bijeksiyon olmadığı ve ters arama için belirsiz olduğu anlamına geldiğinden kabul edilmediğinin doğru olduğunu düşünüyorum.
user193130

1
@Basj Pekala, anahtar başına birden fazla değere sahip olmanın yararlı olacağı kullanım durumlarının olacağını anlayabiliyorum, bu yüzden belki bu tür bir veri yapısı teklifin bir alt sınıfı olarak var olmalıdır. Bununla birlikte, normal bir dikte tek bir nesneye eşlendiği için, tersinin de aynı olmasının çok daha mantıklı olduğunu düşünüyorum. (Sadece açıklığa kavuşturmak için, değer de bir koleksiyon olsa da, ilk
diktenin anahtarının

4

Aşağıdaki kod parçası, ters çevrilebilir (önyargılı) bir harita uygular:

class BijectionError(Exception):
    """Must set a unique value in a BijectiveMap."""

    def __init__(self, value):
        self.value = value
        msg = 'The value "{}" is already in the mapping.'
        super().__init__(msg.format(value))


class BijectiveMap(dict):
    """Invertible map."""

    def __init__(self, inverse=None):
        if inverse is None:
            inverse = self.__class__(inverse=self)
        self.inverse = inverse

    def __setitem__(self, key, value):
        if value in self.inverse:
            raise BijectionError(value)

        self.inverse._set_item(value, key)
        self._set_item(key, value)

    def __delitem__(self, key):
        self.inverse._del_item(self[key])
        self._del_item(key)

    def _del_item(self, key):
        super().__delitem__(key)

    def _set_item(self, key, value):
        super().__setitem__(key, value)

Bu uygulamanın avantajı, inversea'nın özniteliğinin BijectiveMapyine a olmasıdır BijectiveMap. Bu nedenle, aşağıdaki gibi şeyler yapabilirsiniz:

>>> foo = BijectiveMap()
>>> foo['steve'] = 42
>>> foo.inverse
{42: 'steve'}
>>> foo.inverse.inverse
{'steve': 42}
>>> foo.inverse.inverse is foo
True

2

Ne yazık ki, en yüksek puan alan cevap bidictçalışmıyor.

Üç seçenek vardır:

  1. Alt sınıf diktesi : Bir alt sınıf oluşturabilirsiniz dict, ancak dikkatli olun. Sen özel uygulamaları yazmak gerekir update, pop, initializer, setdefault. dictUygulamalar demiyorlar __setitem__. Bu nedenle en yüksek puan alan cevapta sorunlar vardır.

  2. UserDict'ten Devral : Bu, tüm rutinlerin doğru şekilde çağrılması için yapılması dışında, tıpkı bir dikte gibidir. Kaputun altında, adı verilen bir öğede bir dikte kullanır data. Python Belgelerini okuyabilir veya Python 3'te çalışan yönlü bir listenin basit bir uygulamasını kullanabilirsiniz . Aynen dahil etmediğim için özür dilerim: Telif hakkından emin değilim.

  3. Soyut Temel Sınıflardan Devralma : collections.abc'den devralmak, yeni bir sınıf için tüm doğru protokolleri ve uygulamaları edinmenize yardımcı olacaktır. Bu, aynı zamanda bir veritabanını şifreleyip önbelleğe alamadığı sürece çift yönlü bir sözlük için gereğinden fazla bir şeydir.

TL; DR - Kodunuz için bunu kullanın . Okuma Trey Hunner 'ın makale detayları için.


1

Bunun gibi bir şey, belki:

import itertools

class BidirDict(dict):
    def __init__(self, iterable=(), **kwargs):
        self.update(iterable, **kwargs)
    def update(self, iterable=(), **kwargs):
        if hasattr(iterable, 'iteritems'):
            iterable = iterable.iteritems()
        for (key, value) in itertools.chain(iterable, kwargs.iteritems()):
            self[key] = value
    def __setitem__(self, key, value):
        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):
        value = self[key]
        dict.__delitem__(self, key)
        dict.__delitem__(self, value)
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, dict.__repr__(self))

Birden fazla anahtarın belirli bir değeri varsa, ne olmasını istediğinize karar vermelisiniz; Belirli bir çiftin çift yönlü olması, daha sonra yerleştirdiğiniz bir çift tarafından kolayca bozulabilir. Olası bir seçeneği uyguladım.


Misal :

bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'})
print bd['myvalue1']   # a
print bd['myvalue2']   # b        

1
Bunun bir sorun olup olmadığından emin değilim, ancak yukarıdaki uygulamayı kullanmak, anahtarlar ve değerler çakışırsa sorun olmaz mıydı? Yani dict([('a', 'b'), ('b', 'c')]); dict['b']-> 'c'anahtar yerine 'a'.
tgray

1
Bu, OP'nin örneği için bir sorun değil, ancak dahil edilmesi iyi bir sorumluluk reddi olabilir.
tgray

Bu print bd['myvalue2']cevapları b, c(veya [b, c]veya (b, c)veya veya başka herhangi bir şeyi) nasıl yapabiliriz ?
Basj

0

Öncelikle, değer eşlemesinin anahtarının bire bir olduğundan emin olmalısınız, aksi takdirde çift yönlü bir harita oluşturmak mümkün değildir.

İkincisi, veri kümesi ne kadar büyük? Fazla veri yoksa 2 ayrı harita kullanın ve güncelleme sırasında ikisini de güncelleyin. Ya da daha iyisi, yerleşik güncelleme / silme özelliğine sahip, yalnızca 2 diktlik bir sarmalayıcı olan Bidict gibi mevcut bir çözümü kullanın .

Ancak veri kümesi büyükse ve 2 dikt tutmak istenmiyorsa:

  • Hem anahtar hem de değer sayısal ise, eşlemeye yaklaşmak için Enterpolasyon kullanma olasılığını göz önünde bulundurun. Anahtar-değer çiftlerinin büyük çoğunluğu eşleme işlevi (ve
    ters işlevi) tarafından ele alınabiliyorsa , o zaman yalnızca aykırı değerleri haritalara kaydetmeniz gerekir.

  • Erişimin çoğu tek yönlü ise (anahtar-> değer),
    uzay için zaman ticareti yapmak için ters haritayı aşamalı olarak oluşturmak tamamen uygundur .

Kod:

d = {1: "one", 2: "two" }
reverse = {}

def get_key_by_value(v):
    if v not in reverse:
        for _k, _v in d.items():
           if _v == v:
               reverse[_v] = _k
               break
    return reverse[v]
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.