Hashing — Operations Cookbook (dict / set / Counter)¶
যে operation-গুলো সত্যিই ব্যবহার করবে — সবগুলো, cost আর gotcha সহ। Average cost ধরে নিচ্ছে Python-এর built-in hash (যেটা সাধারণ input-এর জন্য চমৎকার)।
এক নজরে cost table¶
| Operation | Average | Worst | Comment |
|---|---|---|---|
d[k] = v (insert/update) |
O(1) | O(n) | Worst case শুধু adversarial collision বা resize-এর সময় |
d[k] (lookup) |
O(1) | O(n) | Key না থাকলে KeyError |
d.get(k, default) |
O(1) | O(n) | Miss হলেও exception নেই |
k in d / x in s |
O(1) | O(n) | List-এর বদলে set পছন্দ করার আসল কারণ |
del d[k] / s.discard(x) |
O(1) | O(n) | s.remove(x) miss-এ error দেয়; discard দেয় না |
d.pop(k, default) |
O(1) | O(n) | Lookup + delete + value ফেরত |
len(d) |
O(1) | O(1) | Size cache করা থাকে |
for k in d (iterate) |
O(n) | O(n) | Python 3.7+-এ insertion order |
d.setdefault(k, v) |
O(1) | O(n) | না থাকলে insert, তারপর current value ফেরত |
s1 & s2, s1 \| s2, s1 - s2 |
O(len) | — | Set algebra, set-এর size-এর সমানুপাতিক |
Counter(iterable) |
O(n) | — | এক pass-এ সব গোনা |
c.most_common(k) |
O(n log n) | — | Count দিয়ে sort করে (ছোট k-র জন্য ভেতরে heap দিয়ে O(n log k)) |
একটা সাধারণ list-এর সাথে তুলনা করো: x in my_list প্রতিবারই O(n)। Loop-এর ভেতরে সেটা হয়ে যায় O(n^2) — beginner code-এ সবচেয়ে common accidental slowdown।
dict operations¶
Insert আর update¶
Lookup: তিন রকম¶
d = {"apple": 5}
d["apple"] # 5
d["pear"] # KeyError! use only when key MUST exist
d.get("pear") # None quiet miss
d.get("pear", 0) # 0 miss with a default — perfect for counting
get দিয়ে বানানো counting idiom:
Delete¶
d = {"a": 1, "b": 2}
del d["a"] # KeyError if absent
v = d.pop("b") # removes AND returns 2
v = d.pop("z", None) # safe pop with default → None
Iterate¶
d = {"a": 1, "b": 2}
for k in d: # keys
...
for v in d.values(): # values
...
for k, v in d.items(): # both — the one you want most often
...
setdefault — এক ধাপে insert-if-absent¶
groups = {}
groups.setdefault("fruit", []).append("apple")
groups.setdefault("fruit", []).append("pear")
# {'fruit': ['apple', 'pear']}
বারবার এটা করতে হলে defaultdict(list) একই কাজ আরো পরিষ্কারভাবে করে।
set operations¶
s = set()
s.add(3) # insert
s.add(3) # no-op (sets hold each value once)
3 in s # True, O(1)
s.discard(99) # no error even though absent
s.remove(3) # KeyError if absent — prefer discard when unsure
a = {1, 2, 3}
b = {3, 4}
a & b # {3} intersection
a | b # {1,2,3,4} union
a - b # {1, 2} difference
a ^ b # {1,2,4} symmetric difference (in exactly one)
Order ঠিক রেখে de-duplicate করা (একটা classic):
def dedupe(items):
seen = set()
out = []
for x in items:
if x not in seen:
seen.add(x)
out.append(x)
return out
dedupe([3, 1, 3, 2, 1]) # [3, 1, 2]
Counter operations¶
from collections import Counter
c = Counter("mississippi")
c["s"] # 4
c["z"] # 0 <- missing keys give 0, NOT KeyError. Huge convenience.
c.most_common(2) # [('i', 4), ('s', 4)]
a = Counter("aab") # {'a': 2, 'b': 1}
b = Counter("ab") # {'a': 1, 'b': 1}
a - b # Counter({'a': 1}) subtraction drops zero/negative
a + b # Counter({'a': 3, 'b': 2})
a - b উত্তর দেয় "a-র এমন কী আছে যেটা b cover করতে পারে না?" — এক expression-এ একদম Ransom Note-এর প্রশ্নটাই।
20 সেকেন্ডে defaultdict¶
from collections import defaultdict
g = defaultdict(list) # missing key → fresh []
g["x"].append(1) # no 'if "x" not in g' dance
g["x"].append(2) # {'x': [1, 2]}
cnt = defaultdict(int) # missing key → 0
cnt["a"] += 1
সাবধান: শুধু g["typo"] পড়লেই key-টা default value সহ তৈরি হয়ে যায়। শুধু membership check করতে হলে "typo" in g ব্যবহার করো।
Pitfalls (দুইবার পড়ো)¶
1. Iterate করতে করতে mutate করা¶
d = {"a": 1, "b": 2}
for k in d:
if d[k] == 1:
del d[k] # RuntimeError: dictionary changed size during iteration
Fix: একটা snapshot-এর উপর iterate করো।
2. Key হিসেবে unhashable type¶
d = {}
d[[1, 2]] = "x" # TypeError: unhashable type: 'list'
d[(1, 2)] = "x" # fine — tuples are immutable, hence hashable
নিয়ম: key-কে immutable হতে হবে। list → tuple, set → frozenset, dict → sorted item-দের tuple।
3. Key হিসেবে float¶
Float-এ rounding error থাকে, তাই equality (এবং সেই সাথে hashing) চুপচাপ fail করে। Key হিসেবে ব্যবহার করো integer (scale বাড়িয়ে: dollar না, cent), Fraction, বা rounded string।
4. is বনাম in গুলিয়ে ফেলা¶
x in s membership টেস্ট করে (hashing ব্যবহার করে)। x is y identity টেস্ট করে (একই object কিনা)। Beginner-রা মাঝে মাঝে if x is in s লিখে ফেলে — সেটা syntax error; শুধু in-ই যথেষ্ট।
5. "Worst case হবেই না" ধরে নেওয়া¶
Competitive programming-এ adversary-রা এমন input বানাতে পারে যেটা পুরনো setup-এ Python-এর default string hash-এর নিচে collide করে। Built-in dict প্রতি run-এ string hashing randomize করে, তাই তুমি সুরক্ষিত; কিন্তু custom rolling hash-এর নিজস্ব randomization লাগে (দেখো patterns.md)।
Quick self-test¶
- Loop-এর ভেতরে
x in some_listকেন বিপজ্জনক? (প্রতিবার O(n) → মোট O(n^2)।) Counter("aa")["b"]কী ফেরত দেয়? (0 — কোনো KeyError নেই।)- List কেন dict-এর key হতে পারে না? (Mutate করতে পারে, তাই তার hash মিথ্যা বলত।)
- Iteration-এর সময় key delete করার নিরাপদ উপায় কী? (
list(d)-এর উপর iterate করো।)
পরেরটা: patterns.md — এই operation-গুলোর উপর দাঁড়ানো named problem-solving pattern।