3.3.9 · Coding › Hashing
Intuition Ek sentence mein poori picture
Ek Python dict/set ek open-addressing hash table hai: yeh ek bada array of slots hai, aur ek key ka hash decide karta hai ki pehle kaunsa slot index try karna hai. Agar woh slot liya hua hai, toh hum ek deterministic order mein doosre slots ko probe karte hain jab tak key ya ek empty slot na mile. Average lookup O(1) hai kyunki, average par, hum sirf constant number of slots touch karte hain.
Intuition WHY list ya tree ki jagah hashing?
list lookup x in lst O(n) hai — aap har element scan karte ho.
Ek balanced tree O(log n) hai aur ordering chahiye.
Ek hash table key ki value ko hi ek hash function ke zariye array index mein convert karta hai, toh hum (almost) directly wahan jump karte hain jahan key rehti hai → O(1) average .
Iska price: hume chahiye (1) ek hash function, (2) collisions handle karne ka tarika (do keys ek hi slot mein land ho jaayein), aur (3) resizing jab table bhar jaaye.
Slot : backing array ka ek cell. Ek slot EMPTY, DELETED (dummy), ya ek entry hold karta hai.
Entry (CPython dict): (hash, key, value) store karta hai. Ek set sirf (hash, key) store karta hai.
Hash : hash(key) se ek integer. Zaroori: ==agar a == b toh hash(a) == hash(b)==. Hashable objects itne immutable hone chahiye ki unka hash kabhi change na ho.
Load factor α = fill / m jahan m = table size aur ==fill = active entries plus DUMMY (deleted) slots==. CPython actually is fill count par resize trigger karta hai, sirf active entries par nahin, aur α ≤ 2/3 rakhta hai. (Hum neeche active entries ke liye n likhte hain.)
Definition Open addressing vs chaining
CPython open addressing use karta hai (saari entries array ke andar rehti hain; collisions doosre slots par jaati hain), not separate chaining (har slot se linked lists latkti hain). Yeh cache-friendly hai.
fill mein count karte hain?
Ek DUMMY slot searching ke liye reusable nahin hai (probes usse guzarni padhti hain). Agar bohot saare deletes table ko dummies se bhar dein, toh probe chains lambi ho jaati hain, chahe kam active keys baaki hon. Dummies ko fill mein count karne se resize force hoti hai (jo dummies purge karta hai) lookup degrade hone se pehle.
Hum probe formula dump nahin karte — aaiye ise build karte hain.
Step 1 — Hash ko starting slot par map karo.
Table mein m slots hain, aur CPython mein m hamesha power of two hota hai. Ek possibly-huge hash ko valid index mein badhane ke liye hum low bits lete hain:
i 0 = h & ( m − 1 )
Yeh step kyun? Jab m = 2 k ho, h & (m-1) lowest k bits rakhta hai, jo h % m ke barabar hai lekin faster hai — aur yeh [ 0 , m − 1 ] mein ek value deta hai, ek legal index.
Step 2 — Agar slot doosri key se occupied hai toh probe karo.
Ek naive probe linear probing i n + 1 = ( i n + 1 ) mod m hai. Problem: yeh lambi runs banata hai (clustering ). CPython ek perturbed probe use karta hai jo hash ke high bits mix karta hai taaki same low bits wali different keys jaldi diverge hon:
i n + 1 = ( 5 i n + 1 + perturb ) & ( m − 1 ) , perturb ≫ = 5
Yeh step kyun? h ke high bits (jo perturb mein carry hote hain) i_0 mein sirf & (m-1) mask se enter hote hain, toh pehle woh ignore hote hain. Unhe successive probes par feed karna guarantee karta hai ki sequence eventually har slot visit kare, toh koi item kabhi lost nahin hoti.
Step 3 — Stop conditions.
Har probed slot par:
EMPTY → key present nahin (stop). Insert ke liye, yahan rakh do.
Same hash AND == true → mil gaya.
Different key → probing jaari rakho.
Intuition WHY hum simply deleted slot ko blank nahin kar sakte?
Agar aap ek deleted slot ko EMPTY set karo, toh koi bhi key jiska probe sequence us slot se guzra tha woh ab EMPTY early hit karega aur "not found" report hoga — chahe woh chain mein aage ho. Fix: deleted slots ko ek special ==DUMMY== (deleted) marker se mark karo. Probing dummies ko skip over karta hai (searching ke liye unhe occupied treat karta hai) lekin insertion ke liye unhe reuse kar sakta hai. Kyunki dummies linge karte hain, CPython unhe fill mein count karta hai toh woh abhi bhi table ko ek cleansing resize ki taraf push karte hain.
Worked example Example 1 — Ek tiny table mein insert (
m = 8 )
Suppose karo hash(k1)=18, hash(k2)=10.
i 0 ( k 1 ) = 18 & 7 = 2 . Slot 2 empty → k1 ko slot 2 par rakh do.
Kyun? 18 = 1001 0 2 , low 3 bits = 010 = 2 .
i 0 ( k 2 ) = 10 & 7 = 2 . Collision! Slot 2 mein k1 hai.
Collision kyun? 10 = 101 0 2 , low 3 bits bhi = 010 = 2 . Same low bits → same start.
Probe: i 1 = ( 5 ⋅ 2 + 1 + perturb ) & 7 with perturb = 10 :
( 10 + 1 + 10 ) &7 = 21&7 = 5 . Slot 5 empty → k2 ko slot 5 par rakh do.
3 ki jagah 5 par kyun gaye? Perturbed step high bits use karta hai, key ko scatter karta hai.
Worked example Example 2 — WHY
1, 1.0, aur True keys ke roop mein collide karte hain
d = {}
d[ 1 ] = "a" ; d[ 1.0 ] = "b" ; d[ True ] = "c"
print (d) # {1: 'c'}
Ek hi entry kyun? 1 == 1.0 == True True hai, aur hash(1)==hash(1.0)==hash(True)==1. Table
unhe same key samajhta hai; baad ki writes overwrite kar deti hain. Pehla key object rehta hai, value last-write-wins hoti hai.
Worked example Example 3 — Resize 2/3 line ke baad trigger hoti hai
Resize ka threshold: fill > 3 2 m . m = 8 ke liye woh 3 2 ⋅ 8 = 5.33 hai, toh resize tab fire hoti hai jab fill 6 reach kare (yaani α = 6/8 = 0.75 > 2/3 ). n = 5 par, α = 5/8 = 0.625 < 2/3 hai — abhi resize nahin .
Yeh kyun matter karta hai: ise eyeball mat karo — bilkul 3 2 m se compare karo. Note karo fill active entries plus dummies count karta hai, toh 5 active keys + 1 dummy = fill 6 bhi trigger karta. Resize par, CPython next power of two tak grow karta hai jo itna bada ho (yahan 16) aur har key ko rehash karta hai . Rehash kyun? Index formula h & (m-1) m par depend karta hai; m badlo aur har key ka slot badal jaata hai. O(n) rehash ke bawajood O(1) amortized kyun? m se 2 m tak resize sirf ∼ m inserts ke baad hoti hai, toh O(n) cost n operations mein spread ho jaati hai → O(1) amortized.
hamesha O(1) hai."
Kyun sahi lagta hai: average O(1) hai aur hum kehte hain "hash tables O(1) hain."
Sach: worst case O(n) hai jab saari keys collide hon (adversarial hashes, ya saari keys same low bits ke saath). Resizing average accha rakhti hai; yeh pathological hash fix nahin kar sakti.
list ko dict key ki tarah use kar sakta hoon agar main careful rahoon."
Kyun sahi lagta hai: lists data hold karti hain, bilkul tuples ki tarah.
Fix: list mutable hai, isliye yeh unhashable hai (hash([]) TypeError raise karta hai). Agar insertion ke baad key ka hash change ho sake, toh table use kabhi nahin dhundh sakta. tuple/frozenset use karo.
Common mistake "Ek item delete karne se uska slot EMPTY ho jaata hai."
Kyun sahi lagta hai: deletion logically key remove karti hai.
Fix: yeh DUMMY ban jaata hai, EMPTY nahin, toh probe chains intact rehti hain. Dummies phir bhi fill mein count hoti hain, isliye woh resize ki taraf push karte hain; sirf resize unhe truly purge karta hai.
Common mistake "Resize exactly tab hoti hai jab α 2/3 reach kare, sirf active keys count karke."
Kyun sahi lagta hai: hum rule ko summarize karte hain "keep α ≤ 2/3 ."
Fix: test hai fill > 2/3 * m, aur fill = active entries plus DUMMY slots. Toh dummies se bhari table resize ho sakti hai chahe live keys kam hon; aur 5/8 = 0.625 2/3 se neeche hai, toh yeh trigger nahin karega.
set aur dict insertion order preserve karte hain, toh order hashing ka hissa hai."
Kyun sahi lagta hai: 3.7 se dict insertion order preserve karta hai.
Fix: Order ek separate compact index array / entry order list se preserved hoti hai, hash slots se nahin . set order guarantee nahin karta. Hashing abhi bhi keys ko slots mein scatter karti hai.
Recall Feynman: ek 12-saal-ke bachche ko explain karo
Imagine karo ek wall of numbered lockers. Apna bag store karne ke liye aap randomly locker nahin chunte — aap apna naam ek chhoti machine se guzaarte ho jo ek locker number nikaalti hai. Agli baar jab aap apna bag chahte ho, aap apna naam usi machine se guzaarte ho, wohi number milta hai, aur seedha us locker par jaate ho. Fast! Kabhi kabhi do logon ko same number milta hai (ek "clash"); tab ek fixed rule hai ("is special tarike se agla try karo") taaki sabko apna samaan mile. Jab lockers zyada crowded ho jaayein, sab ko ek badi wall par shift karte hain taaki searching fast rahe.
"HIPRD" — H ash → I ndex (h & (m-1)) → P robe (perturbed) →
R esize at 2/3 → D ummy on delete. "H ungry I guanas P robe R otten D ates."
Recall Active recall — answers cover karo
CPython hash h ke liye size m ki table mein pehle kaunsa index try karta hai?
h & (m-1) kaam karne ke liye m power of two kyun hona chahiye?
Delete par EMPTY ki jagah DUMMY marker kyun?
Kaunsa count (aur threshold) resize trigger karta hai, aur resize ki cost kya hai?
CPython dict/set kaunsi collision strategy use karta hai? Open addressing (ek array ke andar probing), separate chaining nahin.
Hash h, table size m (power of two) ke liye pehla slot index kya hai? i0 = h & (m - 1), h % m ke barabar lekin faster.
CPython ka perturbed probe recurrence kya hai? i = (5*i + 1 + perturb) & (m-1); perturb >>= 5 har step mein, high bits mix karta hai.
Hash ke high bits probe sequence mein kyun feed karte hain? Starting index sirf low bits use karta hai; high bits ensure karte hain ki equal low bits wali keys diverge hon aur har slot eventually visit ho.
CPython resize kon sa count aur threshold par trigger karta hai? Jab fill > (2/3)*m ho, jahan fill = active entries PLUS DUMMY (deleted) slots.
Kya α = 5/8 m=8 par resize trigger karne ke liye kaafi hai? Nahin — 5/8 = 0.625 < 2/3 ≈ 0.667; resize ke liye fill ≥ 6 chahiye (kyunki (2/3)*8 = 5.33).
Unsuccessful open-addressing search ke liye expected probes kitne hain? 1/(1-α); α=2/3 par woh ~3 probes hai.
Deletion DUMMY kyun mark hota hai EMPTY kyun nahin? EMPTY probe chains ko prematurely terminate kar deta, chain mein aage stored keys hide kar deta; DUMMY chains intact rakhta hai, insert par reusable hai, aur fill mein count hota hai.
Dummies ko fill mein kyun count karte hain? Woh live keys hue bina probe chains lambi karte hain, toh unhe count karna lookups degrade hone se pehle cleansing resize force karta hai.
List dict key kyun nahin ho sakti? Lists mutable hain → unhashable; changing hash key ko unfindable bana deta.
d[1], d[1.0], d[True] ek entry mein kyun collapse ho jaate hain? 1 1.0 True aur unke hashes sab 1 hain, toh table unhe same key treat karta hai.
Dict ka insertion order hash slots mein stored hai? Nahin — order ek separate compact entry/index array mein rakha jaata hai; sets order bilkul preserve nahin karte.
O(n) resize ke bawajood n inserts ki amortized cost kya hai? O(1) amortized; resize sirf ~m inserts ke baad hoti hai, apni O(n) cost spread karti hai.
Equality aur hashing ko link karne ki requirement kya hai? a b ka matlab hona chahiye hash(a) hash(b).
Hash Functions — woh integer hash(key) produce karta hai.
Collision Resolution — open addressing vs separate chaining trade-offs.
Load Factor and Rehashing — fill > 2/3 m resize rule.
Amortized Analysis — doubling O(1) amortized inserts kyun deta hai.
Big-O Notation — hash lookups ke liye average vs worst-case.
Immutability and Hashability — keys immutable kyun honi chahiye.
Java HashMap Internals — contrast: chaining + treeify.
Slot EMPTY DELETED or Entry