__ হ্যাশ __ () প্রয়োগের সঠিক ও ভাল উপায় কী?


150

কার্যকর করার একটি সঠিক এবং ভাল উপায় কী __hash__()?

আমি সেই ফাংশনটির কথা বলছি যা একটি হ্যাশকোড ফেরত দেয় যা হ্যাশটবেল ওরফে অভিধানগুলিতে বস্তুগুলি সন্নিবেশ করানোর জন্য ব্যবহৃত হয়।

যেমন __hash__()একটি পূর্ণসংখ্যা ফেরত দেয় এবং হ্যাশ টেবিলগুলিতে "বিন্নিং" অবজেক্টগুলির জন্য ব্যবহৃত হয় আমি ধরে নিয়েছি যে প্রত্যাবর্তিত পূর্ণসংখ্যার মানগুলি সাধারণ ডেটার (সংঘর্ষকে হ্রাস করতে) জন্য সমানভাবে বিতরণ করা উচিত। এই জাতীয় মান পেতে একটি ভাল অনুশীলন কি? সংঘর্ষের সমস্যা কি? আমার ক্ষেত্রে আমার একটি ছোট বর্গ রয়েছে যা কিছু ধারক, কিছু ভাসমান এবং একটি স্ট্রিং ধারণকারী ধারক শ্রেণির হিসাবে কাজ করে।

উত্তর:


185

কার্যকর করার একটি সহজ, সঠিক উপায় __hash__()হ'ল কী টিপল ব্যবহার করা। এটি কোনও বিশেষায়িত হ্যাশের মতো দ্রুত হবে না তবে আপনার যদি এটির প্রয়োজন হয় তবে আপনার সম্ভবত সি তে প্রকারটি প্রয়োগ করা উচিত

হ্যাশ এবং সমতার জন্য কী ব্যবহার করার উদাহরণ এখানে:

class A:
    def __key(self):
        return (self.attr_a, self.attr_b, self.attr_c)

    def __hash__(self):
        return hash(self.__key())

    def __eq__(self, other):
        if isinstance(other, A):
            return self.__key() == other.__key()
        return NotImplemented

এছাড়াও, ডকুমেন্টেশনের__hash__ আরও তথ্য রয়েছে যা কিছু বিশেষ পরিস্থিতিতে মূল্যবান হতে পারে।


1
__keyফাংশনটি বাস্তবায়ন করা থেকে অপ্রাপ্তবয়স্ক ওভারহেড বাদে , এটি যে কোনও হ্যাশ হিসাবে দ্রুত হতে পারে is অবশ্যই, যদি বৈশিষ্ট্যগুলি পূর্ণসংখ্যার হিসাবে পরিচিত হয় এবং এর মধ্যে খুব বেশি কিছু না ঘটে থাকে তবে আমি মনে করি আপনি কিছু হোম-রোলড হ্যাশ দিয়ে সামান্য দ্রুত চালাতে পারবেন , তবে সম্ভবত এটি বিতরণ করা সম্ভব হবে না। hash((self.attr_a, self.attr_b, self.attr_c))আশ্চর্যজনকভাবে দ্রুত (এবং সঠিক ) হতে চলেছে , যেহেতু ছোট tupleগুলি তৈরি করা বিশেষভাবে অনুকূলিত হয়েছে, এবং এটি সি বিল্টিনগুলিতে হ্যাশগুলি প্রাপ্ত ও সংমিশ্রনের কাজকে ধাক্কা দেয় যা পাইথন স্তরের কোডের চেয়ে সাধারণত দ্রুত faster
শ্যাডোর্যাঞ্জার

আসুন ধরা যাক ক্লাস এ এর ​​একটি অবজেক্ট একটি অভিধানের জন্য কী হিসাবে ব্যবহৃত হচ্ছে এবং যদি শ্রেণি A এর একটি বৈশিষ্ট্য পরিবর্তন হয় তবে এর হ্যাশটির মানও পরিবর্তিত হবে। তাতে কি সমস্যা তৈরি হবে না?
মিঃ ম্যাট্রিক্স

1
নীচে @ প্রিয়.by. জেসাসের উত্তরের উল্লেখ হিসাবে, হ্যাশ পদ্ধতির কোনও পরিবর্তনীয় বস্তুর জন্য ডিফল্টরূপে সংজ্ঞায়িত করা এবং সমতা এবং তুলনার জন্য আইডি ব্যবহার করা হয়) এর জন্য সংজ্ঞায়িত / ওভাররাইড করা উচিত নয়।
মিঃ ম্যাট্রিক্স

@ মিগুয়েল, আমি সঠিক সমস্যার দিকে দৌড়ে এসেছি , কী কী হয় অভিধানে Noneকী কী পরিবর্তন হয়ে গেলে তা ফিরে আসে । আমি যেভাবে সমাধান করেছি তা হ'ল অবজেক্টের আইডিটি কেবলমাত্র বস্তুর পরিবর্তে কী হিসাবে সংরক্ষণ করা।
জসওয়ন্ত পি

@ যাসবন্তপি পাইথন ডিফল্টরূপে কোনও হ্যাশেবল অবজেক্টের কী হিসাবে অবজেক্টের আইডি ব্যবহার করে।
মিঃ ম্যাট্রিক্স

22

জন মিলিকিন এর অনুরূপ একটি সমাধান প্রস্তাব করেছিলেন:

class A(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        return (isinstance(othr, type(self))
                and (self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))

    def __hash__(self):
        return hash((self._a, self._b, self._c))

এই সমাধান সঙ্গে সমস্যা হ'ল hash(A(a, b, c)) == hash((a, b, c))। অন্য কথায়, হ্যাশটির মূল সদস্যদের টিপলের সাথে সংঘর্ষ হয়। অনুশীলনে খুব সম্ভবত এটি ব্যাপার না?

আপডেট: পাইথন ডক্স এখন উপরের উদাহরণের মতো একটি টুপল ব্যবহার করার পরামর্শ দেয়। নোট করুন যে ডকুমেন্টেশন

কেবলমাত্র প্রয়োজনীয় সম্পত্তি হ'ল সমান তুলনা করা অবজেক্টগুলির একই হ্যাশ মান রয়েছে

মনে রাখবেন যে বিপরীতটি সত্য নয়। যে বস্তুগুলি সমান তুলনা করে না তাদের একই হ্যাশ মান থাকতে পারে। যেমন হ্যাশ সংঘর্ষের ফলে ডিক্ট কী বা সেট উপাদান হিসাবে ব্যবহৃত হয় যতক্ষণ না অবজেক্টগুলি সমান তুলনা না করে ততক্ষণ একটি বস্তুর অন্যটিকে প্রতিস্থাপন করতে পারে না

পুরানো / খারাপ সমাধান

উপর পাইথন ডকুমেন্টেশন__hash__ XOR যাও ভালো কিছু ব্যবহার উপ-উপাদান হ্যাশ একত্রিত করতে প্রস্তাব দেওয়া , যা আমাদের এই দেয়:

class B(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        if isinstance(othr, type(self)):
            return ((self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))
        return NotImplemented

    def __hash__(self):
        return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
                hash((self._a, self._b, self._c)))

আপডেট: ব্ল্যাকএনজিএইচটি হিসাবে উল্লেখ করা হয়েছে, ক, বি এবং সি এর ক্রম পরিবর্তন করা সমস্যার কারণ হতে পারে। আমি ^ hash((self._a, self._b, self._c))মানগুলি হ্যাশ হচ্ছে ক্রম ক্যাপচার একটি অতিরিক্ত যোগ । এই চূড়ান্ত ^ hash(...)মান হচ্ছে মিলিত পুনর্বিন্যাস করা যাবে না যদি মুছে ফেলা হতে পারে (উদাহরণস্বরূপ, তারা বিভিন্ন ধরনের এবং সেইজন্য মান আছে যদি _aনির্ধারিত করা হবে না _bবা _c, ইত্যাদি)।


5
আপনি সাধারণত কোনও সরল এক্সওর বৈশিষ্ট্যগুলি একসাথে করতে চান না, কারণ যদি আপনি মানগুলির ক্রম পরিবর্তন করেন তবে এটি সংঘর্ষগুলি আপনাকে দেবে। অর্থাৎ hash(A(1, 2, 3))সমান হবে hash(A(3, 1, 2))(এবং তারা উভয় হ্যাশ কোনো অপরের সমান করব Aএকটি বিন্যাস সঙ্গে উদাহরণস্বরূপ 1, 2এবং 3তার মান হিসাবে)। যদি আপনি নিজের উদাহরণটি তাদের আর্গুমেন্টের মূল অংশ হিসাবে একই হ্যাশ এড়াতে চান, কেবল একটি সেন্ডিনেল মান তৈরি করুন (হয় শ্রেণি ভেরিয়েবল হিসাবে, বা বিশ্বব্যাপী হিসাবে) তবে এটি হ্যাশ করার জন্য টিপলে অন্তর্ভুক্ত করুন: রিটার্ন হ্যাশ ((_ সেন্ডিনেল , স্ব ._এ, স্ব ._ বি, স্ব.আ.সি))
ব্ল্যাকঙ্কহ্ট

1
আপনার ব্যবহার isinstanceসমস্যাযুক্ত হতে পারে, যেহেতু একটি সাবক্লাসের একটি অবজেক্ট type(self)এখন কোনও অবজেক্টের সমান হতে পারে type(self)। আপনি যে একটি যোগ খুঁজে পেতে পারেন তাই Carএবং Fordএকটি থেকে set()শুধুমাত্র এক বস্তু সন্নিবেশ করানো হয়েছে, সন্নিবেশ ক্রম উপর নির্ভর করে হতে পারে। অতিরিক্তভাবে, আপনি এমন পরিস্থিতিতে যেতে পারেন যেখানে a == bসত্য তবে b == aএটি মিথ্যা।
মারাটসি

1
আপনি যদি সাবক্লাসিং করছেন তবে আপনি এটি Bপরিবর্তন করতে চাইতে পারেনisinstance(othr, B)
মিলারদেব ২

7
একটি চিন্তার: কী tuple বর্গ ধরন, যা সমান হতে দেখানো থেকে গুণাবলীর একই কী সেট দিয়ে অন্য ক্লাসের প্রতিরোধ করবে অন্তর্গত হল: hash((type(self), self._a, self._b, self._c))
বেন মোশার

2
Bপরিবর্তে ব্যবহার সম্পর্কে বিন্দু ছাড়াও type(self), প্রায়শই পরিবর্তে NotImplementedকোনও অপ্রত্যাশিত ধরণের মুখোমুখি হওয়ার সময় ফিরে আসার পক্ষে এটি আরও ভাল অনুশীলন হিসাবে বিবেচিত __eq__হয় False। এটি অন্যান্য ব্যবহারকারী-সংজ্ঞায়িত প্রকারকে এমন একটি প্রয়োগ করতে অনুমতি দেয় __eq__যা তারা জানে Bএবং এর সাথে তুলনা করতে পারে they
মার্ক আমেরিকা

16

মাইক্রোসফ্ট রিসার্চের পল লারসন বিভিন্ন হ্যাশ ফাংশন অধ্যয়ন করেছিলেন। সে আমাকে বলেছে

for c in some_string:
    hash = 101 * hash  +  ord(c)

স্ট্রিং বিভিন্ন জন্য আশ্চর্যজনকভাবে ভাল কাজ। আমি দেখতে পেয়েছি যে অনুরূপ বহুভুজ কৌশলগুলি পৃথক সাবফিল্ডগুলির একটি হ্যাশ গণনার জন্য ভাল কাজ করে।


8
স্পষ্টতই জাভা এটি একইভাবে করে তবে 101 এর পরিবর্তে 31 ব্যবহার করে
ব্যবহারকারী 229898

3
এই সংখ্যাগুলি ব্যবহার করার পিছনে যুক্তি কি? 101 বা 31 বাছাই করার কোনও কারণ আছে?
বিগব্লিন্ড

1
প্রধান গুণকগুলির জন্য এখানে একটি ব্যাখ্যা: স্ট্যাকওভারফ্লো . com / প্রশ্নস / 1336১13১০২/২ । 101 পল লারসনের পরীক্ষাগুলির উপর ভিত্তি করে বিশেষত খুব ভাল কাজ করছে বলে মনে হচ্ছে।
জর্জ ভি। রিলি

4
পাইথন (hash * 1000003) XOR ord(c)32-বিট রেকারপাউন্ড গুন সহ স্ট্রিংয়ের জন্য ব্যবহার করে। [উদ্ধৃতি ]
টাইলার

4
এমনকি যদি এটি সত্য হয় তবে এই প্রসঙ্গে এটি ব্যবহারিক ব্যবহারের নয় কারণ বিল্টিন পাইথন স্ট্রিংয়ের ধরণগুলি ইতিমধ্যে একটি __hash__পদ্ধতি সরবরাহ করে; আমাদের নিজস্ব রোল করার দরকার নেই। প্রশ্নটি কীভাবে __hash__একটি সাধারণ ব্যবহারকারী-সংজ্ঞায়িত শ্রেণীর জন্য প্রয়োগ করা যায় (একগুচ্ছ বৈশিষ্ট্যগুলি অন্তর্নির্মিত ধরণের বা সম্ভবত এই জাতীয় ব্যবহারকারী-সংজ্ঞায়িত শ্রেণীর দিকে নির্দেশ করে), যা এই উত্তরটি একেবারেই দেখায় না।
মার্ক আমেরিকা

3

আমি আপনার প্রশ্নের দ্বিতীয় অংশের উত্তর দেওয়ার চেষ্টা করতে পারি।

সংঘর্ষগুলির ফলাফল সম্ভবত হ্যাশ কোড থেকে নয়, হ্যাশ কোডটি ম্যাপিং থেকে সংগ্রহের সূচকে তৈরি করা হবে। সুতরাং উদাহরণস্বরূপ আপনার হ্যাশ ফাংশনটি 1 থেকে 10000 পর্যন্ত এলোমেলো মানগুলি ফিরিয়ে আনতে পারে, তবে যদি আপনার হ্যাশ টেবিলটিতে কেবল 32 টি প্রবেশ থাকে তবে আপনি সন্নিবেশের সাথে সংঘর্ষ পেয়ে যাবেন।

এছাড়াও, আমি ভাবি যে সংঘর্ষগুলি অভ্যন্তরীণভাবে সংগ্রহের মাধ্যমে সমাধান করা হবে এবং সংঘর্ষগুলি সমাধান করার জন্য অনেকগুলি পদ্ধতি রয়েছে। সবচেয়ে সহজ (এবং সবচেয়ে খারাপ) হ'ল, আপনাকে সূচক i তে সন্নিবেশ করানোর জন্য একটি এন্ট্রি দেওয়া হবে, যতক্ষণ না আপনি একটি খালি জায়গা খুঁজে পান এবং সেখানে প্রবেশ না করেন ততক্ষণ 1 যুক্ত করুন। পুনরুদ্ধার একইভাবে কাজ করে। এটি কিছু এন্ট্রিগুলির জন্য অকার্যকর পুনরুদ্ধারগুলির ফলস্বরূপ, আপনার কোনও প্রবেশিকা থাকতে পারে যাতে অনুসন্ধানের জন্য পুরো সংগ্রহটি অতিক্রম করতে হয়!

অন্যান্য সংঘর্ষের সমাধানের পদ্ধতিগুলি হ্যাশ টেবিলের এন্ট্রিগুলি সরানোর সময় পুনরুদ্ধারের সময় হ্রাস করে যখন জিনিসগুলি বাইরে ছড়িয়ে দেওয়ার জন্য .োকানো হয়। এটি সন্নিবেশের সময়টিকে বাড়িয়ে দেয় তবে অনুমান করে যে আপনি inোকানোর চেয়ে বেশি পড়েন। এমন বিভিন্ন পদ্ধতি রয়েছে যা চেষ্টা করে বিভিন্ন বিপরীতমুখী এন্ট্রিগুলিকে শাখাগুলি দেয় যাতে একটি নির্দিষ্ট জায়গায় ক্লাস্টারে প্রবেশের প্রবেশ ঘটে।

এছাড়াও, আপনার যদি সংগ্রহটি পুনরায় আকার দেওয়ার দরকার হয় তবে আপনাকে সমস্ত কিছু পুনঃস্থাপন করতে হবে বা একটি গতিশীল হ্যাশিং পদ্ধতি ব্যবহার করতে হবে।

সংক্ষেপে, আপনি হ্যাশ কোডটি কী ব্যবহার করছেন তার উপর নির্ভর করে আপনার নিজের সংঘর্ষের সমাধানের পদ্ধতিটি প্রয়োগ করতে হতে পারে। আপনি যদি এগুলি কোনও সংগ্রহে সংরক্ষণ না করে থাকেন তবে আপনি সম্ভবত একটি হ্যাশ ফাংশনটি নিয়ে চলে যেতে পারেন যা কেবলমাত্র খুব বড় পরিসরে হ্যাশ কোড উত্পন্ন করে। যদি তা হয় তবে আপনার মেমোরির উদ্বেগগুলির উপর নির্ভর করে আপনি নিশ্চিত করতে পারেন যে আপনার ধারকটি যতটা প্রয়োজন তার চেয়ে বড় (অবশ্যই আরও ভাল)।

আপনি যদি আরও আগ্রহী হন তবে এখানে কয়েকটি লিঙ্ক রয়েছে:

উইকিপিডিয়াতে কোয়েলসড হ্যাশিং

উইকিপিডিয়ায় বিভিন্ন সংঘর্ষের সমাধানের পদ্ধতির সংক্ষিপ্তসার রয়েছে:

এছাড়াও, থার্প দ্বারা " ফাইল অর্গানাইজেশন এবং প্রসেসিং " প্রচুর সংঘর্ষের সমাধানের পদ্ধতিগুলি ব্যাপকভাবে কভার করে। আইএমও এটি হ্যাশিং অ্যালগরিদমের জন্য একটি দুর্দান্ত রেফারেন্স।


1

প্রোগ্রামেজ ওয়েবসাইটে কখন এবং কীভাবে এই __hash__কার্যকারিতাটি বাস্তবায়িত হয় তার একটি খুব ভাল ব্যাখ্যা :

একটি ওভারভিউ সরবরাহ করার জন্য একটি স্ক্রিনশট: (2019-12-13 পুনরুদ্ধার করা হয়েছে)

Https://www.programiz.com/python-programming/methods/built-in/hash এর স্ক্রিনশট 2019-12-13

পদ্ধতির ব্যক্তিগত বাস্তবায়ন হিসাবে, উপরে বর্ণিত সাইট মিলারদেবের উত্তরের সাথে মেলে এমন একটি উদাহরণ সরবরাহ করে

class Person:
def __init__(self, age, name):
    self.age = age
    self.name = name

def __eq__(self, other):
    return self.age == other.age and self.name == other.name

def __hash__(self):
    print('The hash is:')
    return hash((self.age, self.name))

person = Person(23, 'Adam')
print(hash(person))

0

আপনি ফিরে আসা হ্যাশ মানের আকারের উপর নির্ভর করে। এটি সাধারণ যুক্তিযুক্ত যে আপনি যদি 32 32 বিট ইনট চারটি 32 বিট ইনটের হ্যাশের উপর ভিত্তি করে ফিরিয়ে নিতে চান তবে আপনি সংঘর্ষের শিকার হবেন।

আমি বিট অপারেশন সমর্থন করব। পছন্দ করুন, নিম্নলিখিত সি সিউডো কোড:

int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);

এই জাতীয় সিস্টেমটি ভাসমানদের জন্যও কার্যকর হতে পারে, যদি আপনি কেবল তাদের ভাসমান-বিন্দু মানের প্রতিনিধিত্ব করার পরিবর্তে তাদের বিট মান হিসাবে গ্রহণ করেন তবে এটি আরও ভাল।

স্ট্রিংগুলির জন্য, আমার কাছে সামান্য / ধারণা নেই।


আমি জানি যে সংঘর্ষ হবে। তবে কীভাবে এগুলি পরিচালনা করা হয় সে সম্পর্কে আমার কোনও ধারণা নেই। এবং ফারহোরমোর সংমিশ্রণে আমার বৈশিষ্ট্যগুলির মানগুলি খুব কম বিতরণ করা হয়েছে তাই আমি একটি স্মার্ট সমাধান খুঁজছিলাম। এবং কোনওভাবেই আমি আশা করেছি যে কোথাও একটি সেরা অনুশীলন হবে।
ব্যবহারকারী 229898
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.