एक पायथन तानाशाह के पास एक ही हैश के साथ कई कुंजियाँ क्यों हो सकती हैं?


90

मैं hashहुड के तहत पायथन फ़ंक्शन को समझने की कोशिश कर रहा हूं । मैंने एक कस्टम वर्ग बनाया जहाँ सभी उदाहरण समान हैश मान लौटाते हैं।

class C:
    def __hash__(self):
        return 42

मैंने अभी यह माना है कि उपरोक्त वर्ग का केवल एक उदाहरण dictकिसी भी समय हो सकता है , लेकिन वास्तव में एक dictही एचएच के साथ कई तत्व हो सकते हैं।

c, d = C(), C()
x = {c: 'c', d: 'd'}
print(x)
# {<__main__.C object at 0x7f0824087b80>: 'c', <__main__.C object at 0x7f0823ae2d60>: 'd'}
# note that the dict has 2 elements

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

class D:
    def __hash__(self):
        return 42
    def __eq__(self, other):
        return True

p, q = D(), D()
y = {p: 'p', q: 'q'}
print(y)
# {<__main__.D object at 0x7f0823a9af40>: 'q'}
# note that the dict only has 1 element

इसलिए मैं यह जानने के लिए उत्सुक हूं कि एक dictही हैश के साथ कई तत्व कैसे हो सकते हैं।


3
जैसा कि आपने खुद पता लगाया है, सेट और डाइक में समान हैश के साथ कई ऑब्जेक्ट्स हो सकते हैं यदि ऑब्जेक्ट स्वयं समान नहीं हैं। आप क्या पूछ रहे हो? टेबल कैसे काम करती है? मौजूदा सामग्री के बहुत सारे के साथ यह एक सामान्य सवाल है ...

@delnan मैं सवाल पोस्ट करने के बाद इस बारे में अधिक सोच रहा था; यह व्यवहार अजगर के लिए प्रतिबंधित नहीं किया जा सकता है। और तुम सही हो। मुझे लगता है कि मुझे सामान्य हैश टेबल साहित्य में गहराई से जाना चाहिए। धन्यवाद।
प्रवीण गोलकोटा

जवाबों:


55

पायथन हैशिंग कैसे काम करता है, इसके विस्तृत विवरण के लिए मेरा उत्तर अन्य की तुलना में जल्दी क्यों धीमा है?

मूल रूप से यह तालिका में एक स्लॉट लेने के लिए हैश का उपयोग करता है। यदि स्लॉट में कोई मान है और हैश मेल खाता है, तो यह आइटम की तुलना यह देखने के लिए करता है कि क्या वे समान हैं।

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

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


7
हैश टेबल कार्यान्वयन के बारे में मुझे सही दिशा में इंगित करने के लिए धन्यवाद। मैंने हैश टेबल के बारे में जितना चाहा है, उससे कहीं अधिक पढ़ा है और मैंने अपने निष्कर्षों को एक अलग उत्तर में समझाया है। stackoverflow.com/a/9022664/553995
प्रवीण गोलकोटा

112

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

  • पायथन शब्दकोशों को हैश टेबल के रूप में लागू किया जाता है ।
  • हैश तालिकाओं को हैश टकराने की अनुमति देनी चाहिए, भले ही दो कुंजियों में समान हैश मूल्य हो, तालिका के कार्यान्वयन में कुंजी और मूल्य जोड़े को असंदिग्ध रूप से सम्मिलित करने और पुनः प्राप्त करने की रणनीति होनी चाहिए।
  • पाइथन तानाशाही ने हैश टकराने (नीचे समझाया गया) को हल करने के लिए खुला संबोधन का उपयोग किया है (देखें तानाशाह296-297 )।
  • पायथन हैश टेबल सिर्फ मेमोरी का एक कंटीन्यू ब्लॉक होता है (एक एरे की तरह होता है, जिससे आप O(1)इंडेक्स द्वारा लुकअप कर सकते हैं )।
  • तालिका में प्रत्येक स्लॉट एक और केवल एक प्रविष्टि संग्रहीत कर सकता है। यह महत्वपूर्ण है
  • तालिका में प्रत्येक प्रविष्टि वास्तव में तीन मूल्यों का एक संयोजन है -। यह (देखें एक सी struct के रूप में कार्यान्वित किया जाता है : 51-56 dictobject.h )
  • नीचे दिया गया आंकड़ा एक अजगर हैश तालिका का एक तार्किक प्रतिनिधित्व है। नीचे दिए गए आंकड़े में, 0, 1, ..., i, ... बाईं ओर हैश तालिका में स्लॉट्स के सूचक हैं (वे सिर्फ उदाहरण के लिए हैं और स्पष्ट रूप से तालिका के साथ संग्रहीत नहीं हैं!)।

    # Logical model of Python Hash table
    -+-----------------+
    0| <hash|key|value>|
    -+-----------------+
    1|      ...        |
    -+-----------------+
    .|      ...        |
    -+-----------------+
    i|      ...        |
    -+-----------------+
    .|      ...        |
    -+-----------------+
    n|      ...        |
    -+-----------------+
    
  • जब एक नया तानाशाही शुरू की जाती है तो यह 8 स्लॉट के साथ शुरू होता है । (देखें dictobject.h: 49 )

  • तालिका में प्रविष्टियां जोड़ते समय, हम कुछ स्लॉट से शुरू करते हैं, iजो कि कुंजी के हैश पर आधारित होता है। सीपीथॉन प्रारंभिक का उपयोग करता है i = hash(key) & mask। कहाँ mask = PyDictMINSIZE - 1, लेकिन यह वास्तव में महत्वपूर्ण नहीं है)। बस ध्यान दें कि प्रारंभिक स्लॉट, i, जो कि चेक किया गया है, कुंजी के हैश पर निर्भर करता है ।
  • यदि वह स्लॉट खाली है, तो प्रविष्टि को स्लॉट में जोड़ा जाता है (प्रविष्टि से मेरा मतलब है, <hash|key|value>)। लेकिन क्या होगा अगर उस स्लॉट पर कब्जा है! सबसे अधिक संभावना है क्योंकि एक और प्रविष्टि में एक ही हैश (हैश टक्कर!) है
  • यदि स्लॉट पर कब्जा कर लिया गया है, तो CPython (और यहां तक ​​कि PyPy) हैश और कुंजी की तुलना करता है (तुलना से मेरा मतलब ==तुलना नहीं है is) स्लॉट में प्रविष्टि की प्रविष्टि वर्तमान प्रविष्टि की कुंजी के विरुद्ध होती है ( तानाशाह) 337 , 344-345 )। यदि दोनों मेल खाते हैं, तो यह सोचता है कि प्रविष्टि पहले से मौजूद है, छोड़ देता है और अगली प्रविष्टि में सम्मिलित होने के लिए आगे बढ़ता है। यदि हैश या कुंजी मेल नहीं खाते हैं, तो यह जांच शुरू करता है ।
  • प्रोबिंग का अर्थ है कि यह एक खाली स्लॉट खोजने के लिए स्लॉट्स द्वारा स्लॉट्स को खोजता है। तकनीकी रूप से हम सिर्फ एक-एक करके, i + 1, i + 2, ... जा सकते हैं और पहले उपलब्ध एक का उपयोग करें (यह रैखिक जांच है)। लेकिन कारणों के लिए टिप्पणियों में खूबसूरती से समझाया गया है (देखें हुक्मबॉक्ट । सी। 33-126 ), सीपीथॉन यादृच्छिक जांच का उपयोग करता है । यादृच्छिक जांच में, अगले स्लॉट को छद्म यादृच्छिक क्रम में चुना जाता है। प्रविष्टि को पहले खाली स्लॉट में जोड़ा जाता है। इस चर्चा के लिए, अगले स्लॉट को चुनने के लिए उपयोग किया जाने वाला वास्तविक एल्गोरिदम वास्तव में महत्वपूर्ण नहीं है (देखें प्रोब के लिए एल्गोरिथ्म के लिए dictobject.c: 33-126 )। महत्वपूर्ण यह है कि स्लॉट्स तब तक जांचे जाते हैं जब तक कि पहला खाली स्लॉट नहीं मिल जाता।
  • लुकअप के लिए भी यही बात होती है, बस आरंभिक स्लॉट i से शुरू होता है (जहाँ मैं कुंजी के हैश पर निर्भर करता हूँ)। यदि हैश और कुंजी दोनों स्लॉट में प्रविष्टि से मेल नहीं खाते हैं, तो यह जांच शुरू करता है, जब तक कि यह एक मैच के साथ स्लॉट नहीं पाता। यदि सभी स्लॉट समाप्त हो जाते हैं, तो यह विफल हो जाता है।
  • यदि दो-तिहाई भरा हुआ है, तो BTW, डिक्टेट का आकार बदल दिया जाएगा। यह लुक्स को धीमा करने से बचाता है। (देखें तानाशाह। 64: 65 )

तुम वहाँ जाओ! ==वस्तुओं को सम्मिलित करते समय दो चाबियों की हैश समानता और कुंजी की सामान्य समानता ( ) दोनों के लिए तानाशाह चेक का पायथन कार्यान्वयन । तो सारांश में, अगर वहाँ दो चाबियाँ, कर रहे हैं aऔर bऔर hash(a)==hash(b), लेकिन a!=b, तो दोनों सौहार्दपूर्वक एक अजगर dict में मौजूद कर सकते हैं। लेकिन अगर hash(a)==hash(b) और a==b फिर, वे दोनों एक ही तरह से नहीं हो सकते।

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

मुझे लगता है कि मेरे प्रश्न का संक्षिप्त उत्तर है, "क्योंकि यह स्रोत कोड में कैसे लागू किया गया है;)"

जबकि यह जानना अच्छा है (गीक अंक के लिए?), मुझे यकीन नहीं है कि वास्तविक जीवन में इसका उपयोग कैसे किया जा सकता है। क्योंकि जब तक आप स्पष्ट रूप से कुछ तोड़ने की कोशिश नहीं कर रहे हैं, तो दो वस्तुएं जो समान नहीं हैं, समान हैश क्यों है?


8
यह बताता है कि शब्दकोश कैसे काम करता है। लेकिन क्या होगा अगर एक key_value जोड़ी की पुनर्प्राप्ति के दौरान हैश टकराव हो। मान लें कि हमारे पास 2 ऑब्जेक्ट्स A और B हैं, जिनमें से दोनों में हैश 4 है। तो पहले A को 4 स्लॉट दिए गए हैं और फिर B को रैंडम बुकिंग के जरिए स्लॉट सौंपा गया है। क्या होता है जब मैं बी। बी। हैश को ४ से प्राप्त करना चाहता हूँ, तो अजगर पहले स्लॉट ४ की जाँच करता है, लेकिन कुंजी मेल नहीं खाती है इसलिए यह ए वापस नहीं आ सकता है। क्योंकि बी के स्लॉट को यादृच्छिक जांच द्वारा सौंपा गया था, बी फिर से कैसे लौटा है O (1) समय में?
सायंकालीन

4
@ Bolt64 यादृच्छिक जांच वास्तव में यादृच्छिक नहीं है। समान कुंजी मानों के लिए यह हमेशा समान क्रमों का अनुसरण करता है, इसलिए अंततः यह पता चलेगा कि बी। डिक्शनरी ओ (1) होने की गारंटी नहीं है, यदि आपको बहुत अधिक टक्कर मिलती है तो उन्हें अधिक समय लग सकता है। पायथन के पुराने संस्करणों के साथ कुंजियों की एक श्रृंखला का निर्माण करना आसान है जो टकराएगा और उस स्थिति में शब्दकोश लुकअप O (n) हो जाएगा। यह DoS के हमलों के लिए एक संभावित वेक्टर है इसलिए नए पायथन संस्करणों ने हैशिंग को संशोधित करके इसे जानबूझकर ऐसा करना कठिन बना दिया है।
डंकन

2
@ डंकन क्या होगा यदि ए हटा दिया जाता है और फिर हम बी पर एक लुकअप करते हैं? मुझे लगता है कि आप वास्तव में प्रविष्टियों को हटाते नहीं हैं, लेकिन उन्हें हटाए गए के रूप में चिह्नित करते हैं? इसका मतलब होगा कि डक्ट लगातार आवेषण और हटाने के लिए उपयुक्त नहीं हैं ....
जीन- ys

2
@ gen-y हाँ हटाए गए और अप्रयुक्त को देखने के लिए अलग तरीके से नियंत्रित किया जाता है। अप्रयुक्त एक मैच के लिए खोज बंद कर देता है, लेकिन नष्ट नहीं करता है। डालने पर या तो हटाए गए या अप्रयुक्त उपयोग किए जाने वाले खाली स्लॉट के रूप में व्यवहार किए जाते हैं। निरंतर आवेषण और हटाएं ठीक हैं। जब अप्रयुक्त (हटाए नहीं गए) स्लॉट्स की संख्या बहुत कम हो जाती है, तो हैश तालिका को उसी तरह से फिर से बनाया जाएगा जैसे कि वह वर्तमान तालिका के लिए बहुत बड़ी हो गई थी।
डंकन

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

20

संपादित करें : नीचे दिए गए उत्तर हैश टकराव से निपटने के संभावित तरीकों में से एक है, हालांकि यह नहीं है कि पायथन इसे कैसे करता है। नीचे उल्लिखित अजगर का विकी भी गलत है। नीचे @Duncan द्वारा दिया गया सबसे अच्छा स्रोत कार्यान्वयन ही है: https://github.com/python/cpython/blob/master/Objects/dictobject.c मैं मिक्स-अप के लिए माफी माँगता हूँ।


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

हैश टेबल

यहाँ आप देखें John Smithऔर Sandra Deeदोनों हैश करने के लिए 152। बाल्टी 152में दोनों शामिल हैं। जब Sandra Deeयह देखते हुए पहले बाल्टी में सूची पाता है 152, तो उस सूची के माध्यम से छोरों तक Sandra Deeपाया जाता है और वापस लौटता है 521-6955

निम्नलिखित गलत है यह केवल यहाँ संदर्भ के लिए है: पर अजगर का विकी है आप कैसे अजगर देखने करता है (? छद्म) कोड पा सकते हैं।

वास्तव में इस समस्या के कई संभावित समाधान हैं, एक अच्छे अवलोकन के लिए विकिपीडिया लेख देखें: http://en.wikipedia.org/wiki/Hash_table#Collision_resolution


स्पष्टीकरण के लिए धन्यवाद और विशेष रूप से पायस कोड के साथ पायथन विकी प्रविष्टि के लिंक के लिए!
प्रवीण गोलकोटा

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

@ डंकन, पायथन की विकी का कहना है कि इसे इस तरह से लागू किया गया है। मुझे बेहतर स्रोत मिलने पर खुशी होगी। Wikipedia.org पेज निश्चित रूप से गलत नहीं है, यह संभव समाधानों में से एक है जैसा कि कहा गया है।
रॉब वाउटर्स

@ डंकन क्या आप समझा सकते हैं ... जब तक संभव होश के अप्रयुक्त भागों में खींच रहे हैं? मेरे मामले में सभी हैश 42 का मूल्यांकन करते हैं। धन्यवाद!
प्रवीण गोलकोटा

@PraveenGollakota मेरे जवाब में लिंक का अनुसरण करें, जो कि गोर विस्तार से बताता है कि हैश का उपयोग कैसे किया जाता है। 42 के एक हैश और 8 स्लॉट वाली एक तालिका के लिए शुरू में स्लॉट नंबर 2 को खोजने के लिए केवल सबसे कम 3 बिट्स का उपयोग किया जाता है, लेकिन अगर उस स्लॉट का उपयोग पहले से ही किया जाता है तो शेष बिट्स प्ले में आते हैं। यदि दो मान बिल्कुल एक जैसे हैश हैं तो पहला पहले स्लॉट में जाता है और दूसरा अगले स्लॉट में जाता है। यदि समान हैश के साथ 1000 मान हैं तो हम मूल्य खोजने से पहले 1000 स्लॉट्स की कोशिश कर रहे हैं और शब्दकोश लुकअप बहुत धीमा हो जाता है !
डंकन

4

हैश तालिकाओं, सामान्य रूप से हैश टकराव के लिए अनुमति देने के लिए है! आपको अशुभ मिलेगा और दो चीजें अंततः एक ही चीज़ के लिए हैश होंगी। नीचे, वस्तुओं की एक सूची में वस्तुओं का एक सेट होता है जिसमें वही हैश कुंजी होती है। आमतौर पर, उस सूची में केवल एक ही चीज़ होती है, लेकिन इस मामले में, यह उन्हें एक ही में रखता है। एकमात्र तरीका यह जानता है कि वे भिन्न हैं समान ऑपरेटर के माध्यम से।

जब ऐसा होता है, तो आपका प्रदर्शन समय के साथ कम हो जाएगा, यही कारण है कि आप चाहते हैं कि आपका हैश फ़ंक्शन "यादृच्छिक रूप से संभव" हो।


2

धागे में मैंने यह नहीं देखा कि उपयोगकर्ता द्वारा परिभाषित कक्षाओं के उदाहरणों के साथ अजगर वास्तव में क्या करता है जब हम इसे एक कुंजी के रूप में एक शब्दकोश में डालते हैं। आइए कुछ दस्तावेज़ीकरण पढ़ें: यह घोषित करता है कि केवल धुलाई योग्य वस्तुओं को एक कुंजी के रूप में इस्तेमाल किया जा सकता है। हशेबल सभी अपरिवर्तनीय अंतर्निहित कक्षाएं और सभी उपयोगकर्ता-परिभाषित कक्षाएं हैं।

उपयोगकर्ता-परिभाषित कक्षाओं में __cmp __ () और __hash __ () डिफ़ॉल्ट रूप से विधियां हैं; उनके साथ, सभी ऑब्जेक्ट असमान (खुद को छोड़कर) और x .__ हैश __ () आईडी (x) से प्राप्त परिणाम लौटाते हैं।

इसलिए यदि आपके पास अपनी कक्षा में लगातार __hash__ है, लेकिन कोई __cmp__ या __eq__ विधि प्रदान नहीं कर रहा है, तो आपके सभी उदाहरण शब्दकोश के लिए असमान हैं। दूसरी ओर, यदि आप कोई __cmp__ या __eq__ विधि प्रदान करते हैं, लेकिन __hash__ प्रदान नहीं कर रहे हैं, तो आपके उदाहरण अभी भी शब्दकोश के संदर्भ में असमान हैं।

class A(object):
    def __hash__(self):
        return 42


class B(object):
    def __eq__(self, other):
        return True


class C(A, B):
    pass


dict_a = {A(): 1, A(): 2, A(): 3}
dict_b = {B(): 1, B(): 2, B(): 3}
dict_c = {C(): 1, C(): 2, C(): 3}

print(dict_a)
print(dict_b)
print(dict_c)

उत्पादन

{<__main__.A object at 0x7f9672f04850>: 1, <__main__.A object at 0x7f9672f04910>: 3, <__main__.A object at 0x7f9672f048d0>: 2}
{<__main__.B object at 0x7f9672f04990>: 2, <__main__.B object at 0x7f9672f04950>: 1, <__main__.B object at 0x7f9672f049d0>: 3}
{<__main__.C object at 0x7f9672f04a10>: 3}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.