एक कुशल द्विदिश हैश तालिका कैसे लागू करें?


86

पायथन dictएक बहुत ही उपयोगी डेटा-संरचना है:

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

d['a'] # get 1

कभी-कभी आप मूल्यों द्वारा अनुक्रमण करना भी चाहेंगे।

d[1] # get 'a'

इस डेटा-संरचना को लागू करने का सबसे कारगर तरीका कौन सा है? कोई भी अधिकारी इसे करने का तरीका सुझाता है?


यदि आप पसंद करते हैं, तो हम मान सकते हैं कि मूल्य अपरिवर्तनीय हैं और साथ ही चाबियाँ हैं।
जुआनजो कोंटी

4
आप इस तानाशाह के लिए क्या लौटाएंगे: {'a': 1, 'b': 2, 'A': 1}
PaulMcG

2
@PaMMcGuire: मैं लौटूंगा {1: ['a', 'A'], 2: 'b'}। ऐसा करने के लिए मेरे जवाब को देखें।
बसज

4
मॉडरेटर पर ध्यान दें: यह stackoverflow.com/questions/1456373/two-way-reverse-map का डुप्लिकेट नहीं है । उत्तरार्द्ध में 1) बहुत अस्पष्ट शब्द है 2) कोई MCVE 3) केवल विशेषण मानचित्र के मामले से संबंधित है (इस प्रश्न में पहली टिप्पणी देखें), जो कि इस वास्तविक प्रश्न की तुलना में बहुत अधिक प्रतिबंधात्मक है, जो अधिक सामान्य है। इसलिए मुझे लगता है कि इसे डुप्लिकेट के रूप में चिह्नित करना यहाँ है, इस विशेष मामले में, भ्रामक है। यदि वास्तव में किसी को दूसरे का डुप्लिकेट होना चाहिए, तो यह विपरीत होना चाहिए क्योंकि यह यहां सामान्य मामले को कवर करता है जबकि अन्य (उत्तर देखें) गैर-विशेषण मामले को कवर नहीं करता है।
बसज

जवाबों:


67

यहाँ एक द्विदिश के लिए एक वर्ग है dict, पायथन डिक्शनरी में मूल्य की कुंजी से प्रेरित है और निम्नलिखित 2) और 3) की अनुमति देने के लिए संशोधित किया गया है।

ध्यान दें कि :

  • 1) उलटा निर्देशिका bd.inverse स्वतः अद्यतन करता है जब मानक ताना bdसंशोधित होता है।
  • 2) उलटा निर्देशिका bd.inverse[value] हमेशा एक है सूची की keyऐसी है कि bd[key] == value
  • 3) https://pypi.python.org/pypi/bidict के bidictमॉड्यूल के विपरीत , यहाँ हमारे पास 2 मान समान हो सकते हैं, यह बहुत महत्वपूर्ण है

कोड:

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)

उपयोग उदाहरण:

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
अस्पष्ट मामले का बहुत साफ समाधान!
टोबियास किंजलर

2
मुझे लगता है कि यह डेटा संरचना कई व्यावहारिक समस्याओं में बहुत उपयोगी है।
0xc0de

6
यह अभूतपूर्व है। यह सक्सेसफुल है; यह स्व-दस्तावेजीकरण है; यह उचित रूप से कुशल है; यह सिर्फ काम करता है। मेरे ही वक्रोक्ति के बार-बार लुकअप अनुकूलन करने के लिए किया जाएगा self[key]में __delitem__()एक भी साथ value = self[key]इस तरह के लुकअप के लिए पुन: उपयोग किया काम। लेकिन ... हाँ। वह नगण्य है। शुद्ध भयानक के लिए धन्यवाद, बसज !
सेसिल करी

1
पायथन 3 संस्करण के बारे में कैसे?
zelusp

1
मुझे उदाहरण के लिए यह उत्तर पसंद है। स्वीकृत उत्तर अभी भी सही है और मुझे लगता है कि स्वीकार किए गए उत्तर को स्वीकार किए गए उत्तर के रूप में रहना चाहिए, लेकिन यह अपने आप को परिभाषित करने के लिए थोड़ा अधिक स्पष्ट है, केवल इसलिए कि यह स्पष्ट रूप से यह कहता है कि शब्दकोश को उलटने के लिए आपको उल्टा होना चाहिए एक सूची में मान चूंकि एक-से-एक मैपिंग नहीं हो सकते हैं क्योंकि एक शब्दकोश में कुंजी-से-मूल्यों के साथ एक-से-कई संबंध हैं।
searchengine27

41

आप रिवर्स ऑर्डर में की, वैल्यू पेयर को जोड़कर खुद भी उसी तरह के हुक्म का इस्तेमाल कर सकते हैं।

घ = { 'एक': 1, 'बी': 2}
Revd = तानाशाही ([उलट (i) i के लिए d.items () में))
d.update (Revd)

5
+1 एक अच्छा, व्यावहारिक समाधान। इसे लिखने का एक और तरीका d.update( dict((d[k], k) for k in d) ):।
FMc

4
+1 उलट () के स्वच्छ उपयोग के लिए। यदि यह स्पष्ट से अधिक पठनीय है तो मैं अनिर्दिष्ट हूं dict((v, k) for (k, v) in d.items())। किसी भी मामले में, आप सीधे .update में जोड़े पास कर सकते हैं d.update(reversed(i) for i in d.items()):।
बेनी चेर्नियाव्स्की-पास्किन

22
ध्यान दें कि यह विफल रहता हैd={'a':1, 'b':2, 1: 'b'}
टोबियास किंजलर

3
थोड़ा संशोधन dict(map(reversed, a_dict.items())):।
0xc0de

13
मूल शब्दकोश में रिवर्स मैपिंग जोड़ना एक भयानक विचार है। जैसा कि उपरोक्त टिप्पणियां प्रदर्शित करती हैं, ऐसा करना सामान्य मामले में सुरक्षित नहीं है । बस दो अलग-अलग शब्दकोश बनाए रखें। इस उत्तर की पहली दो पंक्तियों की अनदेखी के बाद d.update(revd)से महान हैं, हालांकि, मैं अभी भी एक upvote पर विचार कर रहा हूं। चलो यह कुछ सोचा।
सेसिल करी

36

एक गरीब आदमी की द्विदिश हैश तालिका सिर्फ दो शब्दकोशों का उपयोग करने के लिए होगी (ये पहले से ही उच्च स्तर के डेटास्ट्रक्चर हैं)।

सूचकांक पर एक बोली पैकेज भी है :

बोली लगाने का स्रोत गितुब पर पाया जा सकता है:


1
2 dicts को डबल आवेषण और हटाने की आवश्यकता होती है।
जुआनजो कोंटी

12
@ जुआनजो: लगभग किसी भी द्विदिश / प्रतिवर्ती हैश तालिका में "डबल आवेषण और हटाएं" शामिल होंगे, या तो संरचना को लागू करने के हिस्से के रूप में, या इसके उपयोग के भाग के रूप में। दो इंडेक्स रखना वास्तव में एकमात्र ऐसा तेज़ तरीका है, AFAIK।
वाल्टर मुंड

7
बेशक; मेरा मतलब था कि हाथ से 2 सूचकांक का ध्यान रखना समस्या है।
जुआनजो कोंटी

1
@ बस्ज मुझे लगता है कि यह सही है कि इसे स्वीकार नहीं किया गया है क्योंकि एक से अधिक मूल्य होने का मतलब है कि यह अब कोई आक्षेप नहीं है और रिवर्स लुकअप के लिए अस्पष्ट है।
user193130

1
@ बस्स खैर, मैं समझ सकता हूं कि ऐसे उपयोग के मामले होंगे जो प्रति कुंजी एक से अधिक मूल्य के लिए उपयोगी होंगे, इसलिए शायद इस प्रकार की डेटा संरचना बोली के उपवर्ग के रूप में मौजूद होनी चाहिए। हालांकि, एक सामान्य वस्तु के बाद से एक ही वस्तु के लिए, मुझे लगता है कि यह रिवर्स के लिए बहुत अधिक समझ में आता है। (बस स्पष्ट करने के लिए, हालांकि मूल्य एक संग्रह भी हो सकता है, मेरा मतलब था कि पहले
तानाशाह की कुंजी

4

कोड का नीचे का स्निपेट एक उलटा (विशेषण) मानचित्र लागू करता है:

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)

इस कार्यान्वयन का लाभ यह है कि एक की inverseविशेषता BijectiveMapफिर से एक है BijectiveMap। इसलिए आप निम्न चीजें कर सकते हैं:

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

2

दुर्भाग्य से, उच्चतम श्रेणी का उत्तर, bidictकाम नहीं करता है।

तीन विकल्प हैं:

  1. उपवर्ग तानाशाही : आप एक उपवर्ग बना सकते हैं dict, लेकिन सावधान रहें। आप के कस्टम कार्यान्वयन लिखने की ज़रूरत update, pop, initializer, setdefaultdictकार्यान्वयन कॉल नहीं करते __setitem__। यही कारण है कि उच्चतम मूल्यांकन किए गए उत्तर में समस्याएं हैं।

  2. UserDict से इनहेरिट : यह केवल एक तानाशाह की तरह है, सिवाय इसके कि सभी रूटीन को सही ढंग से कॉल करने के लिए बनाया गया है। यह हुड के नीचे एक आइटम नामक एक आइटम का उपयोग करता है data। आप पायथन डॉक्यूमेंटेशन को पढ़ सकते हैं , या पायथन 3 में काम करने वाली दिशात्मक सूची द्वारा एक सरल कार्यान्वयन का उपयोग कर सकते हैं । इसे शब्दशः शामिल न करने के लिए क्षमा करें: मैं इसके कॉपीराइट के बारे में अनिश्चित हूं।

  3. सार आधार वर्ग से विरासत : से विरासत collections.abc आप सभी सही प्रोटोकॉल और एक नया वर्ग के लिए कार्यान्वयन पाने में मदद मिलेगी। यह एक द्विदिश शब्दकोश के लिए ओवरकिल है, जब तक कि यह डेटाबेस में एन्क्रिप्ट और कैश भी नहीं कर सकता।

टीएल; डीआर - अपने कोड के लिए इसका उपयोग करें । विवरण के लिए ट्रे हुनर का लेख पढ़ें ।


1

कुछ इस तरह, शायद:

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

आपको यह तय करना होगा कि यदि आप एक से अधिक कुंजी दिए गए मान के साथ क्या करना चाहते हैं; किसी दिए गए जोड़े की द्विदिशता को आपके द्वारा बाद में डाली गई कुछ जोड़ी द्वारा आसानी से बंद किया जा सकता है। मैंने एक संभव विकल्प लागू किया।


उदाहरण :

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

1
मुझे यकीन नहीं है कि यह एक समस्या है, लेकिन उपरोक्त कार्यान्वयन का उपयोग करते हुए, अगर चाबियाँ और मानों को ओवरलैप किया गया है तो क्या कोई समस्या नहीं होगी? तो dict([('a', 'b'), ('b', 'c')]); dict['b']-> 'c'कुंजी के बजाय 'a'
tgray

1
यह ओपी के उदाहरण के लिए एक मुद्दा नहीं है, लेकिन इसमें शामिल होने के लिए एक अच्छा अस्वीकरण हो सकता है।
त्रिगुट

हम कैसे कर सकते हैं कि print bd['myvalue2']उत्तर b, c( [b, c]या (b, c), या कुछ और)?
बसज

0

सबसे पहले, आपको यह सुनिश्चित करना होगा कि मूल्य मैपिंग की कुंजी एक से एक है, अन्यथा, यह एक द्विदिश नक्शा बनाने के लिए संभव नहीं है।

दूसरा, डेटासेट कितना बड़ा है? यदि बहुत अधिक डेटा नहीं है, तो बस 2 अलग-अलग मानचित्रों का उपयोग करें, और अपडेट करते समय दोनों को अपडेट करें। या बेहतर, मौजूदा समाधान का उपयोग करें जैसे कि बिडिक्क , जो केवल 2 डिट्स का आवरण है, जिसमें अपडेट / डिलीट किया गया है।

लेकिन अगर डेटासेट बड़ा है, और 2 डाइक बनाए रखना वांछनीय नहीं है:

  • यदि कुंजी और मान दोनों संख्यात्मक हैं, तो मैपिंग को अनुमानित करने के लिए इंटरपोलेशन का उपयोग करने की संभावना पर विचार करें। यदि कुंजी-मूल्य वाले जोड़े के विशाल बहुमत को मैपिंग फ़ंक्शन (और इसके
    रिवर्स फ़ंक्शन) द्वारा कवर किया जा सकता है , तो आपको केवल आउटलेर्स को मैप्स में रिकॉर्ड करने की आवश्यकता है।

  • यदि अधिकांश पहुंच यूनि-दिशात्मक (कुंजी-> मान) है, तो
    अंतरिक्ष के लिए समय का व्यापार करने के लिए, रिवर्स मैप को आकस्मिक रूप से बनाना पूरी तरह से ठीक है।

कोड:

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]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.