पाइथन में विलोम शब्दकोश लुकअप


102

क्या किसी शब्दकोश के भीतर के मूल्य को जानकर कुंजी खोजने का कोई सीधा तरीका है?

मैं बस यही सोच सकता हूँ:

key = [key for key, value in dict_obj.items() if value == 'value'][0]



Google ने मुझे यहां निर्देशित किया ... और मुझे कहना चाहिए .. iteritemsमेरे लिए कोई भी इसका उपयोग क्यों नहीं कर रहा है, इससे 40 गुना तेज अंतर होता है ... the () .next विधि
गुस्सा 84

4
यदि आपके पास करने के लिए बहुत सारे रिवर्स लुकअप हैं:reverse_dictionary = {v:k for k,v in dictionary.items()}
ऑस्टिन

जवाबों:


5

वहां कोई नहीं है। यह मत भूलो कि मान 0 या 1 से अधिक सहित किसी भी संख्या में पाया जा सकता है।


2
अजगर के पास एक .index तरीका है, जिसमें पाया गया पहला रिटर्न इंडेक्स को निर्दिष्ट मान या अपवाद के साथ सूचीबद्ध करता है यदि नहीं मिला है ... कोई भी कारण क्यों इस तरह के शब्दार्थ को शब्दकोशों में लागू नहीं किया जा सकता है?
ब्रायन जैक

@BriJack: सेट की तरह, डिक्शनरी का आदेश नहीं दिया गया है। जो आदेश दिया गया है, उसके लिए संग्रह देखें ।
मार्टिन पीटर्स

3
.index को केवल यह गारंटी देने की आवश्यकता है कि यह एक एकल मान लौटाता है और इसे केवल पहली बार lexically होने की आवश्यकता नहीं है कि यह पहला मैच है और यह व्यवहार स्थिर है (समय के साथ एक ही समय पर कई कॉल एक ही मिलान तत्व का उत्पादन करना चाहिए)। जब तक समय के साथ अन्य तत्वों के जुड़ने, हटाए जाने या संशोधित किए जाने के समय तक शब्दकोशों उनके अनमॉडिफाइड हैश को पुनर्व्यवस्थित नहीं करते, तब भी यह उपयुक्त रूप से काम करेगा। एक भोली कार्यान्वयन: तानाशाही विषय। ()। सूचकांक (कुंजी)
ब्रायन जैक

मुख्य रूप से .index () की बात यह है कि परिभाषा के अनुसार हम केवल डुप्लिकेट के बारे में परवाह नहीं करते हैं कि हम एक ही तत्व को लगातार देख सकते हैं
ब्रायन जैक

130
मैं इस तरह के गैर-जवाबों को घृणा करता हूं। "ऐसा करने की कोशिश करना बंद करो जो आप उचित रूप से करना चाहते हैं!" है एक स्वीकार्य जवाब। इसे क्यों स्वीकार किया गया? इस प्रश्न के उच्चतर रेटेड उत्तर के रूप में, रिवर्स डिक्शनरी लुकअप शुद्ध-पायथन के 80 से कम वर्णों में तुच्छ रूप से लागू है। यह उससे अधिक "सीधे आगे" नहीं मिलता है। पॉल McGuire के समाधान शायद सबसे कारगर है, लेकिन वे सब काम करते हैं। </sigh>
सेसिल करी

95

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

key = next(key for key, value in dd.items() if value == 'value')

कहाँ ddहै बढ़ा देंगे StopIterationअगर कोई मुकाबला नहीं पाया जाता है, ताकि आप उस पकड़ने और की तरह एक और अधिक उपयुक्त अपवाद वापस जाने के लिए चाहते हो सकता है ValueErrorया KeyError


1
हाँ यह सूची में नहीं होने पर संभवत: अपवाद को छोड़ देना चाहिए।
ब्रायन जैक

7
keys = { key for key,value in dd.items() if value=='value' }कई मैचों के लिए सभी कुंजी का सेट पाने के लिए भी ।
पूछवाचन

6
@askewchan - इसे सेट के रूप में वापस करने की कोई वास्तविक आवश्यकता नहीं है, तानाशाह चाबियों को पहले से ही अद्वितीय होना चाहिए, बस एक सूची लौटाएं - या बेहतर, एक जनरेटर अभिव्यक्ति वापस लौटाएं, और कॉलर को इसे उस कंटेनर में डाल दें जो वे चाहते हैं।
पॉलकॉम

55

ऐसे मामले हैं जहां एक शब्दकोश एक है: एक मानचित्रण

उदाहरण के लिए,

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

यदि आप केवल एक ही खोज कर रहे हैं तो आपका दृष्टिकोण ठीक है। हालाँकि, यदि आपको एक से अधिक लुकअप करने की आवश्यकता है, तो उलटा शब्दकोश बनाने के लिए यह अधिक कुशल होगा

ivd = {v: k for k, v in d.items()}

यदि एक ही मूल्य के साथ कई कुंजियों की संभावना है, तो आपको इस मामले में वांछित व्यवहार को निर्दिष्ट करने की आवश्यकता होगी।

यदि आपका पायथन 2.6 या अधिक पुराना है, तो आप उपयोग कर सकते हैं

ivd = dict((v, k) for k, v in d.items())

6
अच्छा अनुकूलन। लेकिन, मुझे लगता है कि आपने तानाशाही का इस्तेमाल करते हुए 2-ट्यूपल्स की अपनी सूची को डिक्शनरी में बदल दिया ():ivd=dict([(v,k) for (k,v) in d.items()])
hobs

4
@ हब्स सिर्फ सूची invd = { v:k for k,v in d.items() }
बोध के

@gnibbler तानाशाह की समझ वापस पाइथन 2.6 में माइग्रेट नहीं की गई है, इसलिए यदि आप पोर्टेबल रहना चाहते हैं, तो आपको 2-टुपल्स के जनरेटर के आसपास या (2 की सूची समझ) के लिए 6 अतिरिक्त वर्णों के साथ रखना होगा। -tuples
hobs

@ हब्स, मैंने कहा कि मेरे जवाब के लिए।
जॉन ला रूय

32

यह संस्करण आपकी तुलना में 26% छोटा है , लेकिन अनावश्यक रूप से / अस्पष्ट मानों के लिए भी कार्य करता है (पहला मैच, जैसा कि आपका है) लौटाता है। हालांकि, यह संभवतः आपकी तुलना में दोगुना धीमा है, क्योंकि यह दो बार तानाशाह से एक सूची बनाता है।

key = dict_obj.keys()[dict_obj.values().index(value)]

या यदि आप पठनीयता पर संक्षिप्तता पसंद करते हैं तो आप एक और चरित्र को बचा सकते हैं

key = list(dict_obj)[dict_obj.values().index(value)]

और अगर आप कार्यकुशलता पसंद करते हैं, तो @ PaulMcGuire का दृष्टिकोण बेहतर है। यदि बहुत सारी कुंजियाँ हैं जो समान मूल्य को साझा करती हैं तो यह अधिक कुशल है कि सूची समझ के साथ कुंजियों की सूची को तत्काल न करें और इसके बजाय एक जनरेटर का उपयोग करें:

key = (key for key, value in dict_obj.items() if value == 'value').next()

2
एक परमाणु ऑपरेशन की मानें, तो क्या कुंजियाँ और मूल्य समान क्रम में होने की गारंटी है?
नॉक्टिस स्काईटॉवर

1
@NoctisSkytower हाँ, dict.keys()और कॉल के बीच उत्परिवर्तित नहीं होने dict.values()तक के रूप में लंबे समय के अनुरूप करने के लिए गारंटी दी dictजाती है।
hobs

7

चूंकि यह अभी भी बहुत प्रासंगिक है, पहला Google हिट और मैं अभी कुछ समय लगा रहा हूं, मैं इसे पोस्ट करूंगा (पायथन 3 में काम कर रहा हूं) समाधान:

testdict = {'one'   : '1',
            'two'   : '2',
            'three' : '3',
            'four'  : '4'
            }

value = '2'

[key for key in testdict.items() if key[1] == value][0][0]

Out[1]: 'two'

यह आपको पहला मूल्य देगा जो मेल खाता है।


6

हो सकता है DoubleDictनीचे जैसा शब्दकोष वर्ग हो, जैसा आप चाहते हैं, वह नीचे है? आप किसी भी उपलब्ध मेटाक्लस में से किसी एक के साथ संयोजन में DoubleDictउपयोग कर सकते हैं या किसी भी मेटाक्लस का उपयोग करने से बच सकते हैं।

import functools
import threading

################################################################################

class _DDChecker(type):

    def __new__(cls, name, bases, classdict):
        for key, value in classdict.items():
            if key not in {'__new__', '__slots__', '_DoubleDict__dict_view'}:
                classdict[key] = cls._wrap(value)
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def check(self, *args, **kwargs):
            value = function(self, *args, **kwargs)
            if self._DoubleDict__forward != \
               dict(map(reversed, self._DoubleDict__reverse.items())):
                raise RuntimeError('Forward & Reverse are not equivalent!')
            return value
        return check

################################################################################

class _DDAtomic(_DDChecker):

    def __new__(cls, name, bases, classdict):
        if not bases:
            classdict['__slots__'] += ('_DDAtomic__mutex',)
            classdict['__new__'] = cls._atomic_new
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _atomic_new(cls, iterable=(), **pairs):
        instance = object.__new__(cls, iterable, **pairs)
        instance.__mutex = threading.RLock()
        instance.clear()
        return instance

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def atomic(self, *args, **kwargs):
            with self.__mutex:
                return function(self, *args, **kwargs)
        return atomic

################################################################################

class _DDAtomicChecker(_DDAtomic):

    @staticmethod
    def _wrap(function):
        return _DDAtomic._wrap(_DDChecker._wrap(function))

################################################################################

class DoubleDict(metaclass=_DDAtomicChecker):

    __slots__ = '__forward', '__reverse'

    def __new__(cls, iterable=(), **pairs):
        instance = super().__new__(cls, iterable, **pairs)
        instance.clear()
        return instance

    def __init__(self, iterable=(), **pairs):
        self.update(iterable, **pairs)

    ########################################################################

    def __repr__(self):
        return repr(self.__forward)

    def __lt__(self, other):
        return self.__forward < other

    def __le__(self, other):
        return self.__forward <= other

    def __eq__(self, other):
        return self.__forward == other

    def __ne__(self, other):
        return self.__forward != other

    def __gt__(self, other):
        return self.__forward > other

    def __ge__(self, other):
        return self.__forward >= other

    def __len__(self):
        return len(self.__forward)

    def __getitem__(self, key):
        if key in self:
            return self.__forward[key]
        return self.__missing_key(key)

    def __setitem__(self, key, value):
        if self.in_values(value):
            del self[self.get_key(value)]
        self.__set_key_value(key, value)
        return value

    def __delitem__(self, key):
        self.pop(key)

    def __iter__(self):
        return iter(self.__forward)

    def __contains__(self, key):
        return key in self.__forward

    ########################################################################

    def clear(self):
        self.__forward = {}
        self.__reverse = {}

    def copy(self):
        return self.__class__(self.items())

    def del_value(self, value):
        self.pop_key(value)

    def get(self, key, default=None):
        return self[key] if key in self else default

    def get_key(self, value):
        if self.in_values(value):
            return self.__reverse[value]
        return self.__missing_value(value)

    def get_key_default(self, value, default=None):
        return self.get_key(value) if self.in_values(value) else default

    def in_values(self, value):
        return value in self.__reverse

    def items(self):
        return self.__dict_view('items', ((key, self[key]) for key in self))

    def iter_values(self):
        return iter(self.__reverse)

    def keys(self):
        return self.__dict_view('keys', self.__forward)

    def pop(self, key, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if key in self:
            value = self[key]
            self.__del_key_value(key, value)
            return value
        if default:
            return default[0]
        raise KeyError(key)

    def pop_key(self, value, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if self.in_values(value):
            key = self.get_key(value)
            self.__del_key_value(key, value)
            return key
        if default:
            return default[0]
        raise KeyError(value)

    def popitem(self):
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError('popitem(): dictionary is empty')
        return key, self.pop(key)

    def set_key(self, value, key):
        if key in self:
            self.del_value(self[key])
        self.__set_key_value(key, value)
        return key

    def setdefault(self, key, default=None):
        if key not in self:
            self[key] = default
        return self[key]

    def setdefault_key(self, value, default=None):
        if not self.in_values(value):
            self.set_key(value, default)
        return self.get_key(value)

    def update(self, iterable=(), **pairs):
        for key, value in (((key, iterable[key]) for key in iterable.keys())
                           if hasattr(iterable, 'keys') else iterable):
            self[key] = value
        for key, value in pairs.items():
            self[key] = value

    def values(self):
        return self.__dict_view('values', self.__reverse)

    ########################################################################

    def __missing_key(self, key):
        if hasattr(self.__class__, '__missing__'):
            return self.__missing__(key)
        if not hasattr(self, 'default_factory') \
           or self.default_factory is None:
            raise KeyError(key)
        return self.__setitem__(key, self.default_factory())

    def __missing_value(self, value):
        if hasattr(self.__class__, '__missing_value__'):
            return self.__missing_value__(value)
        if not hasattr(self, 'default_key_factory') \
           or self.default_key_factory is None:
            raise KeyError(value)
        return self.set_key(value, self.default_key_factory())

    def __set_key_value(self, key, value):
        self.__forward[key] = value
        self.__reverse[value] = key

    def __del_key_value(self, key, value):
        del self.__forward[key]
        del self.__reverse[value]

    ########################################################################

    class __dict_view(frozenset):

        __slots__ = '__name'

        def __new__(cls, name, iterable=()):
            instance = super().__new__(cls, iterable)
            instance.__name = name
            return instance

        def __repr__(self):
            return 'dict_{}({})'.format(self.__name, list(self))

4

नहीं, आप सभी कुंजियों को देखने और उनके सभी मूल्यों की जांच किए बिना कुशलतापूर्वक ऐसा नहीं कर सकते। इसलिए आपको ऐसा करने के लिए O(n)समय की आवश्यकता होगी । यदि आपको बहुत से ऐसे लुकअप करने की आवश्यकता है, तो आपको उल्टे शब्दकोश का निर्माण करके इसे कुशलतापूर्वक करने की आवश्यकता होगी (यह भी किया जा सकता है O(n)) और फिर इस उलट शब्दकोष के अंदर एक खोज करना (प्रत्येक खोज औसतन लगेगी।O(1)

एक सामान्य शब्दकोश से एक उल्टा शब्दकोष (जो एक से कई मानचित्रण करने में सक्षम होगा) का निर्माण करने का एक उदाहरण है:

for i in h_normal:
    for j in h_normal[i]:
        if j not in h_reversed:
            h_reversed[j] = set([i])
        else:
            h_reversed[j].add(i)

उदाहरण के लिए यदि आपकी

h_normal = {
  1: set([3]), 
  2: set([5, 7]), 
  3: set([]), 
  4: set([7]), 
  5: set([1, 4]), 
  6: set([1, 7]), 
  7: set([1]), 
  8: set([2, 5, 6])
}

आपका h_reversedहोगा

{
  1: set([5, 6, 7]),
  2: set([8]), 
  3: set([1]), 
  4: set([5]), 
  5: set([8, 2]), 
  6: set([8]), 
  7: set([2, 4, 6])
}

2

जहाँ तक मुझे मालूम नहीं है, एक तरीका यह है कि इसे करने का एक तरीका यह है कि कुंजी द्वारा सामान्य लुकअप के लिए एक तानाशाही बनाई जाए और दूसरे को वैल्यू के हिसाब से रिवर्स लुकअप के लिए डिक्टेट किया जाए।

इस तरह के कार्यान्वयन का एक उदाहरण यहाँ है:

http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/

इसका मतलब यह है कि मूल्य के लिए कुंजियों को देखने से कई परिणाम हो सकते हैं जिन्हें एक साधारण सूची के रूप में वापस किया जा सकता है।


ध्यान दें कि कई, कई संभावित मान हैं जो मान्य कुंजी नहीं हैं।
इग्नासियो वाज़केज़-अब्राम्स

1

मुझे पता है कि इसे 'बेकार' माना जा सकता है, लेकिन इस परिदृश्य में मैं अक्सर मूल्य रिकॉर्ड में एक अतिरिक्त कॉलम के रूप में कुंजी संग्रहीत करता हूं:

d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) }

यह एक व्यापार है और गलत लगता है, लेकिन यह सरल है और काम करता है और निश्चित रूप से मूल्यों पर निर्भर करता है न कि सरल मूल्यों के बजाय ट्यूपल्स पर।


1

एक उल्टा शब्दकोष बनाएं

reverse_dictionary = {v:k for k,v in dictionary.items()} 

यदि आपके पास करने के लिए बहुत सारे रिवर्स लुकअप हैं


0

शब्दकोश में मूल्यों के माध्यम से किसी भी तरह की वस्तु हो सकती है जिसे वे हैशेड या अन्य तरीके से अनुक्रमित नहीं कर सकते। इसलिए मूल्य द्वारा कुंजी खोजना इस संग्रह प्रकार के लिए अप्राकृतिक है। किसी भी क्वेरी को ओ (n) समय में ही निष्पादित किया जा सकता है। इसलिए यदि यह लगातार काम है, तो आपको जॉन की तरह कुछ महत्वपूर्ण अनुक्रमण के लिए एक नज़र रखना चाहिए या शायद कुछ स्थानिक सूचकांक (DB या http://pypi.python.org/pypi/Rtree/ )।


-1

मैं "डेटाबेस" के एक प्रकार के रूप में शब्दकोशों का उपयोग कर रहा हूं, इसलिए मुझे एक कुंजी खोजने की आवश्यकता है जिसे मैं पुन: उपयोग कर सकता हूं। मेरे मामले के लिए, यदि एक कुंजी का मान है None, तो मैं इसे ले सकता हूं और इसे फिर से उपयोग किए बिना किसी अन्य आईडी को "आवंटित" कर सकता हूं। मुझे लगा कि मैं इसे साझा करूंगा।

db = {0:[], 1:[], ..., 5:None, 11:None, 19:[], ...}

keys_to_reallocate = [None]
allocate.extend(i for i in db.iterkeys() if db[i] is None)
free_id = keys_to_reallocate[-1]

मुझे यह पसंद है क्योंकि मुझे इस तरह की StopIterationया किसी भी त्रुटि को पकड़ने की कोशिश नहीं करनी है IndexError। यदि कोई कुंजी उपलब्ध है, तो उसमें एक free_idहोगी। अगर वहाँ नहीं है, तो यह बस होगा None। शायद अजगर नहीं, लेकिन मैं वास्तव में tryयहाँ का उपयोग नहीं करना चाहता था ...

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