पायथन हैश डीकट्स


94

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

समस्या यह है कि अजगर dicts अन्य dicts की कुंजी के रूप में प्रकट नहीं हो सकते हैं। यहां तक ​​कि एक टपल का उपयोग करना (जैसा कि मैं वैसे भी कर रहा हूं) मदद नहीं करता है।

>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

मुझे लगता है कि यह नीचे सभी तरह से tuples होना चाहिए। अब अजगर मानक पुस्तकालय लगभग वही प्रदान करता है collections.namedtupleजिसकी मुझे आवश्यकता होगी, एक बहुत अलग सिंटैक्स है, लेकिन इसे एक कुंजी के रूप में इस्तेमाल किया जा सकता है। उपरोक्त सत्र से जारी:

>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}

ठीक। लेकिन मुझे उस नियम में कुंजियों के प्रत्येक संभावित संयोजन के लिए एक वर्ग बनाना होगा जो मैं उपयोग करना चाहता हूं, जो इतना बुरा नहीं है, क्योंकि प्रत्येक पार्स नियम यह जानता है कि यह किस पैरामीटर का उपयोग करता है, ताकि कक्षा को उसी समय परिभाषित किया जा सके। फ़ंक्शन के रूप में जो नियम को पार्स करता है।

संपादित करें: namedtupleएस के साथ एक अतिरिक्त समस्या यह है कि वे कड़ाई से स्थित हैं। दो टुपल्स जो दिखते हैं कि उन्हें अलग होना चाहिए वास्तव में एक ही हो सकता है:

>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False

tl'dr: मैं कैसे dicts प्राप्त कर सकता हूं जिसे अन्य dicts की कुंजी के रूप में उपयोग किया जा सकता है ?

जवाबों पर थोड़ा सा हैक होने के बाद, यहाँ और अधिक पूर्ण समाधान का उपयोग कर रहा हूँ। ध्यान दें कि यह व्यावहारिक उद्देश्यों के लिए परिणामी dicts को अपरिवर्तनीय बनाने के लिए थोड़ा अतिरिक्त काम करता है। बेशक, यह अभी भी कॉल करके इसके चारों ओर हैक करना काफी आसान है, dict.__setitem__(instance, key, value)लेकिन हम यहाँ सभी वयस्क हैं।

class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """
    def __key(self):
        return tuple(sorted(self.items()))
    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__,
            ", ".join("{0}={1}".format(
                    str(i[0]),repr(i[1])) for i in self.__key()))

    def __hash__(self):
        return hash(self.__key())
    def __setitem__(self, key, value):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def __delitem__(self, key):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def clear(self):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def pop(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def popitem(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def setdefault(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def update(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

hashdict, अपरिवर्तनीय होना चाहिए कम से कम करने के बाद उसे hashing शुरू करते हैं, तो क्यों कैश नहीं keyऔर hashकी विशेषताओं के रूप में मान hashdictवस्तु? मैंने संशोधित किया __key()और __hash__(), और यह पुष्टि करने के लिए परीक्षण किया कि यह बहुत तेज़ है। SO टिप्पणियों में फ़ॉर्मेट किए गए कोड की अनुमति नहीं देता है, इसलिए मैं इसे यहां लिंक करूंगा
Sam Watkins

जवाबों:


71

यहाँ एक हैशनेबल डिक्शनरी बनाने का आसान तरीका है। बस स्पष्ट कारणों के लिए किसी अन्य शब्दकोश में एम्बेड करने के बाद उन्हें म्यूट नहीं करना याद रखें।

class hashabledict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

7
यह तेजी से eq और हैश की स्थिरता सुनिश्चित नहीं करता है, जबकि मेरा पूर्व उत्तर __key पद्धति के उपयोग के माध्यम से करता है (व्यवहार में या तो दृष्टिकोण काम करना चाहिए, हालांकि यह एक अनावश्यक itermediate सूची बनाकर धीमा हो सकता है - एस / आइटम द्वारा ठीक किया जा सकता है) / iteritems / - पायथन 2 ग्रहण करना। * जैसा कि आप नहीं कहते; ;-)
एलेक्स मार्टेली

5
शायद छँटाई के साथ टपल के बजाय केवल एक फ्रोज़ेनसेट का उपयोग करना बेहतर होगा। न केवल यह तेज़ होगा, बल्कि आप यह नहीं मान सकते कि शब्दकोश कुंजियाँ तुलनीय हैं।
asmeurer

1
ऐसा लगता है कि हैश फ़ंक्शन से बचने का एक तरीका होना चाहिए O(n*log(n))जहां प्रविष्टियों nकी संख्या है dict। क्या किसी को पता है कि पायथन का frozensetहैश फ़ंक्शन रैखिक समय में चलता है?
टॉम करेज ने

2
@HelloGoodbye एक dict भी इस तरह बनाया जा सकता है dict(key1=value1, key2=value2,...)या यह dict([(key1, value1), (key2, value2),...)])। यही बात इस पर लागू होती है। आपके द्वारा पोस्ट की गई रचना
स्मिडो

2
@smido: धन्यवाद। मैंने यह भी पाया कि आप सिर्फ एक शाब्दिक कास्ट कर सकते हैं, अर्थात hashabledict({key_a: val_a, key_b: val_b, ...})
HelloGoodbye

62

हैशबल्स अपरिवर्तनीय होना चाहिए - इसे लागू नहीं करना लेकिन आपको एक कुंजी के रूप में इसके पहले उपयोग के बाद एक तानाशाह को उत्परिवर्तित नहीं करने के लिए, निम्नलिखित दृष्टिकोण काम करेगा:

class hashabledict(dict):
  def __key(self):
    return tuple((k,self[k]) for k in sorted(self))
  def __hash__(self):
    return hash(self.__key())
  def __eq__(self, other):
    return self.__key() == other.__key()

यदि आपको अपने डिट्स को म्यूट करने की आवश्यकता है और STILL उन्हें कुंजियों के रूप में उपयोग करना चाहते हैं, तो जटिलता सौफ्लोट्स का विस्फोट करती है - यह कहने के लिए नहीं कि यह नहीं किया जा सकता है, लेकिन मैं तब तक इंतजार करूंगा जब तक कि मैं अविश्वसनीय नैतिकता में न आ जाऊं! -)


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

फिर मैंने जो उपवर्ग सुझाया वह काम करेगा - ध्यान दें कि यह "स्थिति" मुद्दे को कैसे दरकिनार करता है ( इससे पहले कि आप इसे इंगित करने के लिए अपने प्रश्न को संपादित कर चुके हैं sorted;; __key ;-) में;
एलेक्स मार्टेली

नेमटुपल की स्थिति पर निर्भर व्यवहार ने मुझे बाहर निकाल दिया। मैं इसके साथ खेल रहा था, यह सोचकर कि यह अभी भी समस्या को हल करने का एक आसान तरीका हो सकता है, लेकिन बहुत ज्यादा मेरी सभी आशाओं को धराशायी कर दिया है (और रोलबैक की आवश्यकता होगी :()
एकलकरण

मान लीजिए कि मेरे पास एक तानाशाही है और मैं इसे एक हैशबेडिंक में डालना चाहता हूं। मुझे यह कैसे करना है?
जोंनोमो 22

@JonCrowell इन सवालों को विचारों और स्पष्टीकरणों के लिए देखें: stackoverflow.com/questions/3464061/… , stackoverflow.com/questions/9112300/… , stackoverflow.com/questions/18020074-…
अधिकतम

32

आपके उद्देश्य के लिए शब्दकोशों को प्रयोग करने योग्य बनाने के लिए __hash__ पद्धति को जोड़ना आवश्यक है:

class Hashabledict(dict):
    def __hash__(self):
        return hash(frozenset(self))

ध्यान दें, फ्रोजेनसेट रूपांतरण सभी शब्दकोशों के लिए काम करेगा (अर्थात इसमें कुंजियों को क्रमबद्ध करने की आवश्यकता नहीं है)। इसी तरह, शब्दकोश मूल्यों पर कोई प्रतिबंध नहीं है।

यदि समान कुंजियों वाले कई शब्दकोश हैं, लेकिन अलग-अलग मूल्यों के साथ, हैश को मानों को ध्यान में रखना आवश्यक है। सबसे तेज़ तरीका है:

class Hashabledict(dict):
    def __hash__(self):
        return hash((frozenset(self), frozenset(self.itervalues())))

यह frozenset(self.iteritems())दो कारणों से तेज है । सबसे पहले, frozenset(self)कदम शब्दकोश में संग्रहीत हैश मूल्यों का पुन: उपयोग करता है, अनावश्यक कॉल को सहेजता है hash(key)। दूसरा, itervalues का उपयोग करके सीधे मानों का उपयोग किया जाएगा और हर बार जब आप लुकअप करते हैं तो मेमोरी में नए कई कुंजी / मान ट्यूपल्स बनाने के लिए आइटमों का उपयोग करके कई मेमोरी एलोकेटर कॉल से बचेंगे


@RaymondHettinger सही होने पर मुझे सही करें, लेकिन मुझे लगा कि dictस्वयं इसकी कुंजी हैश मानों को कैश नहीं करती है - हालाँकि व्यक्तिगत वर्ग (जैसे str) अपने कैश को कैश करना चुन सकते हैं। कम से कम जब मैंने dictकुंजी के रूप में उपयोग किए जाने वाले अपने कस्टम वर्ग के उदाहरणों के साथ बनाया , तो उनके __hash__तरीकों को हर एक्सेस ऑपरेशन (अजगर 3.4) पर बुलाया गया था। मैं सही हूं या नहीं, मुझे यकीन नहीं है कि hash(frozenset(self))पूर्व-संकलित हैश मूल्यों का पुन: उपयोग कैसे किया जा सकता है, जब तक कि वे स्वयं कुंजियों के अंदर कैश नहीं किए जाते हैं (जिस स्थिति में, hash(frozenset(self.items())उन्हें पुन: उपयोग करता है)।
अधिकतम

(कुंजी / मान) टुपल निर्माण के बारे में आपके दूसरे बिंदु के अनुसार, मैंने सोचा था कि .items () विधियाँ टुपल्स की सूची के बजाय एक दृश्य लौटाती हैं, और यह कि उस दृश्य के निर्माण में अंतर्निहित कुंजियों और मूल्यों की नकल शामिल नहीं है। (पाइथन 3.4 फिर से।) उस ने कहा, मुझे हैशिंग का फ़ायदा सिर्फ कुंजियों से दिखाई देता है, यदि अधिकांश इनपुट में अलग-अलग कुंजियाँ हैं - क्योंकि (1) हैश मानों के लिए यह काफी महंगा है, और (2) यह काफी प्रतिबंधात्मक है कि मानों को धोने के लिए आवश्यक है
अधिकतम

6
इससे दो अलग-अलग शब्दकोशों के लिए एक ही हैश बनाने की संभावना भी है। विचार करें {'one': 1, 'two': 2}और{'one': 2, 'two': 1}
AgDude

माइक ग्राहम ने अपनी टिप्पणी में कहा है कि किसी अन्य कारण से डिराइविंग तानाशाही है लेकिन परिभाषित __missing__करना एक बुरा विचार है। तुम क्या सोचते हो?
पिओटर डोब्रोगोस्ट

1
पाइथन 2.2 के बाद से तानाशाही से उपवर्ग को अच्छी तरह से परिभाषित किया गया है। संग्रह देखें। संपादित करें। संग्रह और संग्रह। पायथन मानक पुस्तकालय से उदाहरण के लिए मुठभेड़ करें। अन्य टिप्पणी निराधार विश्वास पर आधारित है कि केवल MutableMapping के उपवर्गों को अच्छी तरह से परिभाषित किया गया है।
रेमंड हेटिंगर

23

दिए गए उत्तर ठीक हैं, लेकिन हैश उत्पन्न करने के frozenset(...)बजाय उनका उपयोग करके सुधार किया जा सकता है tuple(sorted(...)):

>>> import timeit
>>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
4.7758948802947998
>>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
1.8153600692749023

प्रदर्शन लाभ डिक्शनरी की सामग्री पर निर्भर करता है, लेकिन जिन मामलों में मैंने परीक्षण किया है, उनमें से अधिकांश के साथ हैशिंग frozensetकम से कम 2 गुना तेज है (मुख्यतः क्योंकि इसे सॉर्ट करने की आवश्यकता नहीं है)।


1
ध्यान दें, कुंजी और मान दोनों को शामिल करने की कोई आवश्यकता नहीं है। इस समाधान के रूप में बहुत तेजी से होगा hash(frozenset(d)):।
रेमंड हेटिंगर

10
@RaymondHettinger: hash(frozenset(d))समान कुंजी लेकिन अलग-अलग मूल्यों के साथ 2 dicts के लिए समान हैश में परिणाम!
ओबेन सोन

4
यह एक समस्या नहीं है। यह __eq__ का काम है कि वे अलग-अलग मूल्यों के रंगों के बीच अंतर कर सकें। __Hash__ का काम केवल खोज स्थान को कम करना है।
रेमंड हेटिंगर

5
यह हैश और मैपिंग की सैद्धांतिक अवधारणा के लिए सही है, लेकिन लुक के रूप में शब्दकोशों के साथ कैश के लिए व्यावहारिक नहीं है - यह असामान्य नहीं है कि समान कुंजी के साथ शब्दकोशों लेकिन विभिन्न मान एक मेम-कैश्ड फ़ंक्शन को पास किए जाते हैं। यदि कैश केवल हैश बनाने के लिए उपयोग किया जाता है तो उस स्थिति में कैश मैपिंग के बजाय सूची में बदल जाता है।
ओबेन सोन

3
इंडेंटिकल कुंजियों और अलग-अलग मूल्यों के साथ डीकट्स के विशेष मामले में, आप केवल एक हैश के आधार पर स्टोर करना बेहतर होगा frozenset(d.itervalues())। ऐसे मामलों में जहां dicts अलग कुंजी में, frozenset(d)है बहुत तेजी से और चाबियों का hashability पर कोई प्रतिबंध नहीं लगाता है। अंत में, याद रखें कि तानाशाही .__ eq__ पद्धति समान कुंजी / मान युग्मों के लिए बहुत तेज़ जाँच करेगी कि कोई भी चीज़ सभी कुंजी / मान युग्म नलिकाओं के लिए हैश की गणना कर सकती है। कुंजी / मान टुपल्स का उपयोग करना भी समस्याग्रस्त है क्योंकि यह सभी कुंजी के लिए संग्रहीत हैश को फेंक देता है (यही कारण frozenset(d)है कि इतनी तेजी से है)।
रेमंड हेटिंगर

11

एक यथोचित स्वच्छ, सीधा कार्यान्वयन है

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

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

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

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        return hash(tuple(sorted(self._d.iteritems())))

यह इतना उचित, साफ और सीधा क्यों है? यानी कृपया अन्य उत्तर करने के लिए मतभेदों को, की आवश्यकता जैसे समझाने __iter__और __len__
कार्ल रिक्टर

1
@ कार्लराइटर, मैंने कभी यह उचित नहीं कहा, बस यथोचित रूप से स्वच्छ। ;)
माइक ग्राहम

@ कार्ललाइटर, मैं परिभाषित करता हूं __iter__और __len__क्योंकि मुझे प्राप्त करना है, क्योंकि मैं प्राप्त कर रहा हूं collections.Mapping; कैसे उपयोग करने के लिए collections.Mappingसंग्रह मॉड्यूल प्रलेखन में बहुत अच्छी तरह से कवर किया गया है। अन्य लोगों की जरूरत नहीं है क्योंकि वे प्राप्त कर रहे हैं dict। पाने dictकिसी अन्य कारण से, लेकिन परिभाषित करने के लिए __missing__एक बुरा विचार है। तानाशाह की कल्पना यह नहीं कहती है कि इस तरह के मामले में कैसे काम करता है, और वास्तव में यह गैर-आभासी तरीकों के टन होगा जो सामान्य रूप से कम उपयोगी होते हैं और इस विशेष मामले में अप्रासंगिक व्यवहार के साथ वासनात्मक तरीके होंगे।
माइक ग्राहम

7

मैं इस विषय पर वापस आता रहता हूँ ... यहाँ एक और भिन्नता है। मैं dictएक __hash__विधि जोड़ने के लिए उपवर्ग के साथ असहज हूं ; वस्तुतः उस समस्या से कोई बच नहीं सकता है जो तानाशाहों के परस्पर हैं, और यह विश्वास करते हुए कि वे नहीं बदलेंगे एक कमजोर विचार की तरह लगता है। इसलिए मैंने इसके बजाय एक बेसिन प्रकार के आधार पर मानचित्रण बनाने पर ध्यान दिया है जो स्वयं अपरिवर्तनीय है। हालांकि tupleयह एक स्पष्ट विकल्प है, इसमें मूल्यों तक पहुंच एक प्रकार और द्विभाजित है; कोई समस्या नहीं है, लेकिन यह उस प्रकार की शक्ति का अधिक लाभ नहीं उठाता है जिस पर इसे बनाया गया है।

क्या होगा यदि आप कुंजी, मूल्य जोड़े को एक में जाम करते हैं frozenset? इसकी क्या आवश्यकता होगी, यह कैसे काम करेगा?

भाग 1, आपको 'आइटम के इस तरह से एन्कोडिंग की आवश्यकता है कि एक फ्रेज़ेन्सेट मुख्य रूप से उनकी कुंजी द्वारा उनका इलाज करेगा; मैं उसके लिए थोड़ा उपवर्ग बनाऊंगा।

import collections
class pair(collections.namedtuple('pair_base', 'key value')):
    def __hash__(self):
        return hash((self.key, None))
    def __eq__(self, other):
        if type(self) != type(other):
            return NotImplemented
        return self.key == other.key
    def __repr__(self):
        return repr((self.key, self.value))

अकेले ही आपको एक अपरिवर्तनीय मानचित्रण की दूरी तय करने में मदद मिलती है:

>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])

डी 'ओह! दुर्भाग्य से, जब आप सेट ऑपरेटरों का उपयोग करते हैं और तत्व समान होते हैं, लेकिन समान वस्तु नहीं; जो वापसी मूल्य में समाप्त होता है , वह अपरिभाषित है , हमें कुछ और परिधि में जाना होगा।

>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'

हालांकि, इस तरह से मूल्यों को देखना बोझिल है, और बदतर है, बहुत सारे मध्यवर्ती सेट बनाता है; ऐसा नहीं होगा! हम इसके चारों ओर पाने के लिए एक 'नकली' कुंजी-मूल्य जोड़ी बनाएंगे:

class Thief(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(pair(self.key, None))
    def __eq__(self, other):
        self.value = other.value
        return pair(self.key, None) == other

जिसके परिणामस्वरूप समस्याएँ कम होती हैं:

>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'

वह सब गहरा जादू है; बाकी सभी इसे एक ऐसी चीज में लपेट रहे हैं जिसमें एक तानाशाही जैसा इंटरफेस है । जब से हम उपवर्ग कर रहे हैं frozenset, जिसमें एक बहुत ही अलग इंटरफ़ेस है, काफी विधियां हैं; हमें इससे थोड़ी मदद मिलती है collections.Mapping, लेकिन अधिकांश कार्य उन frozensetसंस्करणों के लिए तरीकों को ओवरराइड कर रहे हैं जो इसके बजाय डाइट जैसे काम करते हैं:

class FrozenDict(frozenset, collections.Mapping):
    def __new__(cls, seq=()):
        return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
    def __getitem__(self, key):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        raise KeyError(key)
    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            return dict(self.iteritems()) == other
        if len(self) != len(other):
            return False
        for key, value in self.iteritems():
            try:
                if value != other[key]:
                    return False
            except KeyError:
                return False
        return True
    def __hash__(self):
        return hash(frozenset(self.iteritems()))
    def get(self, key, default=None):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        return default
    def __iter__(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def iteritems(self):
        for item in frozenset.__iter__(self):
            yield (item.key, item.value)
    def iterkeys(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def itervalues(self):
        for item in frozenset.__iter__(self):
            yield item.value
    def __contains__(self, key):
        return frozenset.__contains__(self, pair(key, None))
    has_key = __contains__
    def __repr__(self):
        return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
    @classmethod
    def fromkeys(cls, keys, value=None):
        return cls((key, value) for key in keys)

जो, अंततः, मेरे स्वयं के प्रश्न का उत्तर देता है:

>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5

5

@Unogn, साथ ही @AlexMartelli द्वारा उत्तर का स्वीकार किया गया उत्तर पूरी तरह से ठीक काम करता है, लेकिन केवल निम्नलिखित बाधाओं के तहत:

  1. डिक्शनरी के मूल्यों को उपलब्ध होना चाहिए। उदाहरण के लिए, hash(hashabledict({'a':[1,2]}))उठाएंगे TypeError
  2. कुंजी को तुलना ऑपरेशन का समर्थन करना चाहिए। उदाहरण के लिए, hash(hashabledict({'a':'a', 1:1}))उठाएंगेTypeError
  3. कुंजी पर तुलना ऑपरेटर कुल ऑर्डर लगाता है। उदाहरण के लिए, यदि किसी शब्दकोश में दो कुंजी हैं frozenset((1,2,3))और frozenset((4,5,6)), वे दोनों दिशाओं में असमान की तुलना करते हैं। इसलिए, इस तरह की कुंजियों के साथ एक शब्दकोश की वस्तुओं को सॉर्ट करने के परिणामस्वरूप एक मनमाना आदेश हो सकता है, और इसलिए यह नियम का उल्लंघन करेगा कि समान वस्तुओं का समान हैश मान होना चाहिए।

@ObenSonne द्वारा बहुत तेज़ उत्तर 2 और 3 की बाधाओं को उठाता है, लेकिन अभी भी बाधा 1 (मान होना चाहिए) से बाध्य है।

@RaymondHettinger द्वारा तेजी से अभी तक जवाब सभी 3 बाधाओं को उठाता है क्योंकि यह .values()हैश गणना में शामिल नहीं है । हालांकि, इसका प्रदर्शन अच्छा है, यदि:

  1. अधिकांश (गैर-बराबर) शब्दकोशों जिन्हें हैश किए जाने की आवश्यकता है, वे समान नहीं हैं .keys()

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

मुझे लगता है कि निम्नलिखित समाधान भी अच्छी तरह से काम करेंगे, भले ही ऊपर सूचीबद्ध सभी 4 बाधाओं का उल्लंघन किया गया हो। इसका एक अतिरिक्त लाभ यह है कि यह न केवल शब्दकोशों, बल्कि किसी भी कंटेनर को रख सकता है, भले ही उनके पास उत्परिवर्तित कंटेनर हों।

मैं इस पर किसी भी प्रतिक्रिया की सराहना करता हूं, क्योंकि मैंने केवल इस हल्के से अब तक परीक्षण किया है।

# python 3.4
import collections
import operator
import sys
import itertools
import reprlib

# a wrapper to make an object hashable, while preserving equality
class AutoHash:
    # for each known container type, we can optionally provide a tuple
    # specifying: type, transform, aggregator
    # even immutable types need to be included, since their items
    # may make them unhashable

    # transformation may be used to enforce the desired iteration
    # the result of a transformation must be an iterable
    # default: no change; for dictionaries, we use .items() to see values

    # usually transformation choice only affects efficiency, not correctness

    # aggregator is the function that combines all items into one object
    # default: frozenset; for ordered containers, we can use tuple

    # aggregator choice affects both efficiency and correctness
    # e.g., using a tuple aggregator for a set is incorrect,
    # since identical sets may end up with different hash values
    # frozenset is safe since at worst it just causes more collisions
    # unfortunately, no collections.ABC class is available that helps
    # distinguish ordered from unordered containers
    # so we need to just list them out manually as needed

    type_info = collections.namedtuple(
        'type_info',
        'type transformation aggregator')

    ident = lambda x: x
    # order matters; first match is used to handle a datatype
    known_types = (
        # dict also handles defaultdict
        type_info(dict, lambda d: d.items(), frozenset), 
        # no need to include set and frozenset, since they are fine with defaults
        type_info(collections.OrderedDict, ident, tuple),
        type_info(list, ident, tuple),
        type_info(tuple, ident, tuple),
        type_info(collections.deque, ident, tuple),
        type_info(collections.Iterable, ident, frozenset) # other iterables
    )

    # hash_func can be set to replace the built-in hash function
    # cache can be turned on; if it is, cycles will be detected,
    # otherwise cycles in a data structure will cause failure
    def __init__(self, data, hash_func=hash, cache=False, verbose=False):
        self._data=data
        self.hash_func=hash_func
        self.verbose=verbose
        self.cache=cache
        # cache objects' hashes for performance and to deal with cycles
        if self.cache:
            self.seen={}

    def hash_ex(self, o):
        # note: isinstance(o, Hashable) won't check inner types
        try:
            if self.verbose:
                print(type(o),
                    reprlib.repr(o),
                    self.hash_func(o),
                    file=sys.stderr)
            return self.hash_func(o)
        except TypeError:
            pass

        # we let built-in hash decide if the hash value is worth caching
        # so we don't cache the built-in hash results
        if self.cache and id(o) in self.seen:
            return self.seen[id(o)][0] # found in cache

        # check if o can be handled by decomposing it into components
        for typ, transformation, aggregator in AutoHash.known_types:
            if isinstance(o, typ):
                # another option is:
                # result = reduce(operator.xor, map(_hash_ex, handler(o)))
                # but collisions are more likely with xor than with frozenset
                # e.g. hash_ex([1,2,3,4])==0 with xor

                try:
                    # try to frozenset the actual components, it's faster
                    h = self.hash_func(aggregator(transformation(o)))
                except TypeError:
                    # components not hashable with built-in;
                    # apply our extended hash function to them
                    h = self.hash_func(aggregator(map(self.hash_ex, transformation(o))))
                if self.cache:
                    # storing the object too, otherwise memory location will be reused
                    self.seen[id(o)] = (h, o)
                if self.verbose:
                    print(type(o), reprlib.repr(o), h, file=sys.stderr)
                return h

        raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o)))

    def __hash__(self):
        return self.hash_ex(self._data)

    def __eq__(self, other):
        # short circuit to save time
        if self is other:
            return True

        # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first
        # 2) any other situation => lhs.__eq__ will be called first

        # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either
        # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data
        # case 2. neither side is a subclass of the other; self is lhs
        # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented
        # case 3. neither side is a subclass of the other; self is rhs
        # => we can't compare to another type, and the other side already tried and failed;
        # we should return False, but NotImplemented will have the same effect
        # any other case: we won't reach the __eq__ code in this class, no need to worry about it

        if isinstance(self, type(other)): # identifies case 1
            return self._data == other._data
        else: # identifies cases 2 and 3
            return NotImplemented

d1 = {'a':[1,2], 2:{3:4}}
print(hash(AutoHash(d1, cache=True, verbose=True)))

d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True)
print(hash(d))

2

तुम भी इन दो तरीकों को जोड़ने के लिए चाहते हो सकता है v2 अचार प्रोटोकॉल के साथ काम करना है उदाहरण उदाहरण के लिए। अन्यथा CPickle hashdict का उपयोग करने का प्रयास करेगा। दिलचस्प है, प्रोटोकॉल के अन्य दो संस्करणों के साथ आपका कोड ठीक काम करता है।

def __setstate__(self, objstate):
    for k,v in objstate.items():
        dict.__setitem__(self,k,v)
def __reduce__(self):
    return (hashdict, (), dict(self),)

-2

यदि आप शब्दकोश में संख्याएँ नहीं रखते हैं और आप अपने शब्दकोशों वाले चर को कभी नहीं खोते हैं, तो आप यह कर सकते हैं:

cache[id(rule)] = "whatever"

चूंकि आईडी () हर शब्दकोश के लिए अद्वितीय है

संपादित करें:

क्षमा करें, उस मामले में हाँ, जो अन्य लोगों ने कहा कि बेहतर होगा। मुझे लगता है कि आप अपने शब्दकोश को एक स्ट्रिंग के रूप में भी क्रमबद्ध कर सकते हैं, जैसे

cache[ 'foo:bar' ] = 'baz'

यदि आपको कुंजी से अपने शब्दकोशों को पुनर्प्राप्त करने की आवश्यकता है, तो आपको कुछ बदसूरत करना होगा

cache[ 'foo:bar' ] = ( {'foo':'bar'}, 'baz' )

मुझे लगता है कि इसका फायदा यह है कि आपको उतना कोड नहीं लिखना होगा।


हम्म्, नहीं; यह वह नहीं cache[id({'foo':'bar'})] = 'baz'; id({'foo':'bar'}) not in cacheहै जिसकी मैं तलाश कर रहा हूं: जब मैं पहली बार चाबी के रूप में डाइक का उपयोग करना चाहता हूं, तो गतिशील रूप से कुंजी बनाने में सक्षम होना महत्वपूर्ण है।
सिंगलनेशन इलिमिनेशन

1
Dicts को सीरियलाइज़ करना ठीक हो सकता है, क्या आपके पास उन्हें क्रमबद्ध करने के तरीके पर एक प्रतिसंरचना है? यही मैं ढूंढ रहा हूं।
एकलकरण
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.