چجوری aras رو اضافه کنیم به grades ؟ چون aras از همون حروفی که sara داره تشکیل شده با هش فانکشنی که ما نوشتیم دوباره بهمون ۴۲۳ میده و اگه باقی مانده بگیریم میشه ۳ یا درواقع همون ایندکسی که برای سارا اختصاص داده شده.
مشکل بوجود اومد… به این مشکل میگن hash collision یا تداخل هش ها!
هش فانکشنی که انتخاب کردیم شاید زیاد جالب نبود چون درواقع به ازای تمام جای گشت های یک کلمه همون هش رو بهمون میده.
هش فانکشن خوب توی hashmap ها دو تا ویژگی داره:
۱- باید محاسباتش سبک باشه. چون دائما داره برای همه ی کلید ها حساب میشه.
۲- “سعی کنه” مقدار های یونیک تولید کنه تا به hash collision بر نخوریم.
بیایم کمی تغییرش بدیم: علاوه بر اینکه از عدد اسکیشون استفاده میکنیم، بیایم اون عدد رو در جایگاهی که داره(حرف چندمه) ضرب هم بکنیم به این شکل:
def hash_func(string):
hash_value = 0
for i, char in enumerate(string, start=1):
hash_value += ord(char) * i
return hash_value
الان هش collision رو بر طرف کردیم:
for name in (“ali”, “sara”, “reza”, “aras”):
hash_value = hash_func(name)
print(f”{name}: {hash_value} : {hash_value % 5}”)
خروجیش میشه:
ali: 628 : 3
sara: 1039 : 4
reza: 1070 : 0
aras: 1076 : 1
ولی همونطور که حدس میزنید باز هم با کلید های مختلف ما به hash collision بر میخوریم… مثلا جای aras بذارید nima …
چه کنیم؟ بیایم یه هش فانکشن معقول داشته باشیم که سعی کنه با سرعت بالا hash value رو محاسبه کنه (چیزی که الان داریم) ولی اگه collision پیش اومد رفعش کنیم! چطور؟
روش اول، separate chaining :
تو این روش میگه به جای اینکه ما بیایم slot ها رو خالی بذاریم (None) ، بیایم به جاش از لیست خالی استفاده کنیم! هر موقع hash collision داشتیم میایم اضافش میکنیم به لیست.
یعنی اگه ۴ تا دانش آموزش ما باشن: ali, sara, reza, nima
با هش فانکشن جدیدی که نوشتیم slot های ما به این صورت میشن:
grades = [
[(“reza”, 17), (“nima”, 20)],
[],
[],
[(“ali”, 18)],
[(“sara”, 19)],
]
مشکل حل شد. الان با اینکه وقتی نمره ی نیما رو بخوایم باید قبلش یه رضا رو هم چک کنیم ولی خیلی جلو افتادیم نسبت به اینکه بخوایم همه رو چک کنیم! یعنی کلی کلید رو محاسبه نمیکنیم فقط اون چندتایی که collision داشتن سرچ میشن. و خب باقی کلید ها که collision نداشتن مستقیم پیدا میشن.
اگه دقت کنیم میبینیم هرچی hash collision بیشتر داشته باشیم به رفتار خطی بیشتر نزدیک میشیم.
این روش اول بود که پیاده سازی خیلی ساده ای هم داره. یه مشکلی ریزی داریم اینجا. یه سری فضای خالی الان توی slot های ما بوجود اومده. آیا میتونیم بیایم از این فضاها استفاده کنیم؟
روش دوم، open addressing:
شرایطی و در نظر بگیرید که الان reza و ali و sara ذخیره شدن و ما میخوایم nima رو اضافه کنیم:
grades = [
(“reza”, 17),
None,
None,
(“ali”, 18),
(“sara”, 19),
]
میایم nima رو هش میکنیم ایندکس و پیدا میکنیم میبینیم میشه صفر. و نگاه میکنیم میبینیم پر هست! میایم یه sequence ای تولید میکنیم به اسم probing sequence. به طوری که از همون اون ایندکسی که محاسبه کردیم شروع میشه(اینجا شد صفر برای نیما) و یه دور میزنه:
0 -> 1 -> 2 -> 3 -> 4
اگه برای ali میخواستیم probing sequence چی میشد؟
3 -> 4 -> 0 -> 1 -> 2
و به همین ترتیب میریم جلو تا به جای خالی برسیم. الان برای نیما ایندکس بعدی میشه ۱. خالی هست؟ بله. پس میذاریمش اونجا و تبدیل میشه به:
grades = [
(“reza”, 17),
(“nima”, 20),
None,
(“ali”, 18),
(“sara”, 19),
]
ما ۳ شکل probe sequence داریم:
1- linear probing
2- quadratic probing
3- double hashing
کاری که بالا کردیم linear probing بود. چون نمیخوام بیشتر از این طولانی بشه دوتای دیگه رو اینجا نمیگم(پیاده سازیش رو در انتها گذاشتم) ولی حدس زدنش سادس. مثلا تو دومی به جای اینکه دونه دونه بره بالا ، با توان های ۲ میره بالا (کمک میکنه که توده ای از کلید ها رو یک جای hash table مون نداشته باشیم پخش بشن) و آخری میگه یه هش دیگه(هش دوم) انجام بدیم برای پیدا کردن ایندکس بعدی!
اینا هر کدوم مزایا و معایبی دارن که میشه کلی دربارشون بحث کرد که کدوم کجا چرا بهتره.
پست تموم شد ولی یه سری نکته های تکمیلی باقی موند:
(پست بعدی و آخر)
پست ۲ از ۳
منبع https://t.me/PSFarsi