এই পর্বে যা শিখবেন
- dictionary কী, কেন “অবস্থান দিয়ে” নয় “নাম দিয়ে” খোঁজা এত কাজের, আর Python-এ dict কোথায় কোথায় লুকিয়ে আছে
- hash table-এর ভেতরের ছবি — চাবি → hash → ঘর → মান, collision, আর খোঁজা কেন গড়ে O(1)
- dict তৈরি, পড়া, যোগ, হালনাগাদ, মোছা —
get,update,|,pop,popitemকখন কোনটি - view, insertion order, hashable চাবির শর্ত, আর লুপের মাঝে dict বদলানোর ফাঁদ
- গণনা, দলে ভাগ, উল্টে দেওয়া, nested dict, lookup table আর মান অনুযায়ী সাজানো — বাস্তব প্যাটার্ন
১ · dictionary কী — অবস্থান নয়, নাম দিয়ে খোঁজা
পর্ব ১৪-এ list আর পর্ব ১৫-এ tuple দেখেছেন। দুটোতেই জিনিস থাকে ক্রমিক অবস্থানে: প্রথম, দ্বিতীয়, তৃতীয়। কিছু চাইলে বলতে হয় “৩ নম্বর ঘরেরটা দাও”. কিন্তু বাস্তব জীবনে আমরা কদাচিৎ এভাবে খুঁজি। মোবাইলে কাউকে কল করতে চাইলে আপনি ভাবেন না “আমার কন্টাক্টের ২৪৭ নম্বর মানুষটা”; আপনি টাইপ করেন “সুমি”. দোকানে চালের দাম জানতে চাইলে দোকানি “তালিকার ৫ নম্বর জিনিস” খোঁজেন না, “চাল” খোঁজেন।
এই কাজের জন্য Python-এর dictionary (সংক্ষেপে dict). একটি dict হলো কয়েকটি key-value pair-এর সংগ্রহ: প্রতিটি চাবি (key)-র সাথে একটি মান (value) জোড়া। আপনি চাবি দেন, dict মান ফেরত দেয়।
contacts = {"সুমি": "01711-000001", "রহিম": "01811-000002"}
print(contacts["সুমি"]) # 01711-000001
bn_en = {"বই": "book", "কলম": "pen", "পানি": "water"}
print(bn_en["কলম"]) # penগঠনটি খেয়াল করুন: দ্বিতীয় বন্ধনী { }, প্রতিটি জোড়ায় চাবি: মান, জোড়াগুলোর মাঝে কমা। খোঁজার সময় list-এর মতোই তৃতীয় বন্ধনী [ ], কিন্তু ভেতরে index নয় — চাবি। দ্বিতীয় উদাহরণটি একটি ছোট বাংলা-ইংরেজি অভিধান; “dictionary” নামটাও এসেছে এই ছবি থেকে — শব্দ দিয়ে খুঁজলে মানে পাওয়া যায়।
একটি স্কুলের অফিসের কথা ভাবুন। রোল নম্বর দিয়ে খাতা খুঁজলে ফাইল আলমারির সামনে গিয়ে প্রথম ফাইল থেকে একটা একটা উল্টাতে হয় — এটা list-এ কিছু খোঁজার মতো। কিন্তু অফিস সহকারী যদি প্রতিটি রোলের জন্য আলমারিতে একটি নির্দিষ্ট তাক বানিয়ে রাখেন, আর একটি সহজ নিয়ম জানেন — “রোল দেখে তাকের নম্বর বের করো” — তবে তিনি সোজা সেই তাকে হাত দেন। একটি ফাইলও উল্টাতে হয় না। dict ঠিক এই সহকারী: চাবি দেখে সরাসরি ঠিক জায়গায় যায়।
dict সবখানে — Python-এর মেরুদণ্ড
dict শুধু একটি ডেটা স্ট্রাকচার নয়; Python ভাষাটিই এর ওপর দাঁড়িয়ে আছে। কয়েকটি উদাহরণ, যা পরের পর্বগুলোতে বারবার দেখবেন:
- ওয়েবের ডেটা: API থেকে যে JSON আসে (পর্ব ২৬, ৪৭), Python-এ পড়লে সেটি সাধারণত dict আর list-এর মিশ্রণ হয়ে যায় —
{"name": "রিয়া", "marks": [78, 91]}. - ফাংশনের keyword argument:
f(name="রিয়া", age=20)— ফাংশনের ভেতরে**kwargsআসলে একটি dict (পর্ব ২০)। - object-এর attribute: একটি সাধারণ object-এর ভেতরের মানগুলো সাধারণত একটি dict-এ থাকে, নাম
__dict__(পর্ব ২৯)। - ভ্যারিয়েবলের নাম নিজেই: একটি module-এর global ভ্যারিয়েবলগুলো আসলে একটি dict-এ রাখা — নাম চাবি, object মান।
globals()চালিয়ে দেখতে পারেন।
list প্রশ্ন করে “কত নম্বর ঘরে?” — dict প্রশ্ন করে “কোন নামে?” ডেটার নিজের যদি একটি স্বাভাবিক নাম বা পরিচয় থাকে (রোল, ফোন নম্বর, পণ্যের নাম, শব্দ), আর আপনি সেই পরিচয় দিয়ে খুঁজবেন — তবে dict. শুধু সারি বা ক্রম গুরুত্বপূর্ণ হলে list.
২ · dict তৈরির কয়েকটি উপায়
একই dict বানানোর একাধিক রাস্তা আছে। কোনটি কখন সুবিধাজনক, দেখে নিন:
| লেখা | ফল | কখন |
|---|---|---|
{} | খালি dict | পরে একটি একটি করে ভরবেন |
{101: 78, 102: 91} | রোল → নম্বর | মানগুলো আগে থেকেই জানা |
dict(চাল=75, ডাল=140) | {"চাল": 75, "ডাল": 140} | চাবিগুলো বৈধ ভ্যারিয়েবল-নামের মতো string হলে |
dict(zip(rolls, marks)) | দুটি list জোড়া লাগিয়ে dict | চাবি এক list-এ, মান আরেক list-এ |
dict([("ক", 1), ("খ", 2)]) | জোড়ার list থেকে dict | ডেটা আগে থেকেই জোড়ায় জোড়ায় আছে |
{r: 0 for r in rolls} | প্রত্যেক রোলের জন্য 0 | নিয়ম মেনে বানানো — পর্ব ১৮ |
dict(zip(...)) সবচেয়ে কাজের রূপগুলোর একটি। পর্ব ১২-এ দেখেছেন zip দুটি সারি থেকে জোড়া বানায়; dict() সেই জোড়াগুলোর প্রথমটিকে চাবি আর দ্বিতীয়টিকে মান বানায়। dict(zip([101, 102, 103], [78, 91, 65])) দেয় {101: 78, 102: 91, 103: 65}. শেষ লাইনের {r: 0 for r in rolls} ধাঁচটির নাম dict comprehension — এক লাইনে নিয়ম মেনে dict বানানো; পুরো আলোচনা পর্ব ১৮-এ।
{} মানে খালি dict, খালি set নয়। পরের পর্বে দেখবেন set-ও দ্বিতীয় বন্ধনী দিয়ে লেখা হয় — {1, 2, 3}. কিন্তু খালি বন্ধনী {} ঐতিহাসিক কারণে dict-এর জন্য বরাদ্দ, কারণ dict ভাষায় আগে এসেছিল। খালি set চাইলে লিখতে হয় set(). আরেকটি কথা: একই চাবি দুবার লিখলে কোনো error হয় না — {"ক": 1, "ক": 2} চুপচাপ {"ক": 2} হয়ে যায়, শেষেরটি জেতে। বড় হাতে-লেখা dict-এ এমন ভুল চোখ এড়িয়ে যেতে পারে।
চাবি আর মানের ধরন নিয়ে নিয়ম অসমান। মান যেকোনো কিছু হতে পারে — সংখ্যা, string, list, আরেকটি dict, এমনকি একটি ফাংশন। চাবির ক্ষেত্রে একটি শর্ত আছে, যার কারণ বুঝতে হলে dict-এর ভেতরে উঁকি দিতে হবে। চলুন সেটাই করি।
৩ · ভেতরের রহস্য — hashing আর গড়ে O(1) খোঁজা
একটি list-এ “রহিম” আছে কি না জানতে Python-কে প্রথম থেকে একটি একটি করে তুলনা করতে হয় — এটা linear search. দশ লাখ নামের list-এ সবচেয়ে খারাপ ক্ষেত্রে দশ লাখ তুলনা। dict এই সমস্যার সমাধান করে একটি পুরোনো, চমৎকার কৌশলে: hash table.
কৌশলটি তিন ধাপের। প্রথমে একটি hash function চাবিটিকে একটি পূর্ণসংখ্যায় বদলায় — Python-এ এটাই hash(key). একই চাবি থেকে সবসময় একই সংখ্যা আসে। দ্বিতীয়ত, dict-এর ভেতরে একটি নির্দিষ্ট সংখ্যক ঘর (slot) থাকে; সংখ্যাটিকে ঘরের সংখ্যা দিয়ে ভাগ করে ভাগশেষ নিলে পাওয়া যায় কোন ঘরে যেতে হবে। তৃতীয়ত, সেই ঘরে গিয়ে দেখা হয় চাবিটি সত্যিই এটাই কি না — মিললে মান ফেরত। পুরো কাজে চাবির সংখ্যা যত বড়ই হোক, ধাপ মোটামুটি একই। নিচের ছবিতে সংখ্যাগুলো বোঝানোর জন্য বানানো — আসল hash মান অনেক বড়, আর প্রতিবার Python চালালে string-এর hash বদলে যায় (প্রশ্ন ০২-এ কারণ)।
- চাবি
"ডাল"আপনি যা দিলেন - hash()
…4813একটি বড় সংখ্যা - ঘর
% 8 → 5৮টি ঘরের কোনটি - collision
ঘর 5 = "তেল"অন্য চাবি বসে আছে - মান
140চাবি মিলল
prices = {"চাল": 75, "তেল": 190, "ডাল": 140}
# খুঁজছি: prices["ডাল"]
# list হলে: "চাল"? না। "তেল"? না। "ডাল"? হ্যাঁ — ৩টি তুলনা
# dict কি সবকটি দেখবে? দেখা যাক…
hash("ডাল") → …4813 (উদাহরণ; আসল সংখ্যা ১৯ অঙ্কের মতো লম্বা)
hash("ডাল") → …4813 একই রানে আবার — একই সংখ্যা
hash("ডাব") → …0272 একটি অক্ষর বদলালেই সম্পূর্ণ আলাদা
4813 % 8 = 5 ঘর: 0 1 2 3 4 5 6 7 — "চাল" — — — "তেল" — — বাকি ৭টি ঘরের দিকে তাকানোই হলো না
ঘর 5-এ আছে "তেল" — চাবি মেলে না! "তেল" ঢোকানোর সময় তার ঘরও 5 হয়েছিল, সে আগে এসেছিল। "ডাল" ঢোকানোর সময় ঘর 5 ভরা পেয়ে পরের ঘর খোঁজা হয়েছিল। খোঁজার সময়ও একই পথ ধরা হয় → ঘর 6
ঘর 6: hash মেলে? হ্যাঁ। চাবি == "ডাল"? হ্যাঁ prices["ডাল"] → 140 মোট দেখা হলো ২টি ঘর — dict-এ ৩টি চাবি থাকুক বা ৩০ লাখ
- প্রশ্ন: “ডাল”-এর দাম কত? একটি list হলে Python শুরু থেকে প্রতিটির সাথে তুলনা করত। ৩টি জিনিসে সমস্যা নেই, কিন্তু ৩০ লাখ পণ্যের অনলাইন দোকানে প্রতিটি খোঁজায় ৩০ লাখ তুলনা হলে সাইট বসে যাবে। dict-এর ভেতরে চাবিগুলো একটি টেবিলের নির্দিষ্ট ঘরে রাখা — কোন ঘরে, তা চাবি নিজেই বলে দেয়।
- চাবি → সংখ্যা। Python চাবিটির
hash()বের করে। এটি একটি নিয়ম, যা যেকোনো hashable object থেকে একটি পূর্ণসংখ্যা বানায়। শর্ত দুটি: একই চাবি থেকে (একই রানে) সবসময় একই সংখ্যা, আর যে দুটি চাবি==হিসেবে সমান, তাদের hash-ও সমান। একটু আলাদা চাবিতে সংখ্যা সাধারণত সম্পূর্ণ আলাদা হয় — তাতে চাবিগুলো ঘরে ঘরে ছড়িয়ে পড়ে। - সংখ্যা → ঘর। টেবিলে ৮টি ঘর থাকলে সংখ্যাটির ভাগশেষ (৮ দিয়ে) জানিয়ে দেয় কোন ঘর — এখানে 5. এটাই গতির রহস্য: হিসাবটি এক ধাপের, চাবির সংখ্যার ওপর নির্ভর করে না। বাকি ঘরগুলোর দিকে তাকানোরও দরকার নেই।
- Collision — একই ঘরে আরেকজন। সব চাবি আলাদা ঘর পাবে, এমন নিশ্চয়তা নেই। “তেল” আর “ডাল” দুজনেরই ঘর 5 — একে বলে hash collision. CPython ঘরে hash আর চাবি দুটোই রাখে, তাই তুলনা করে বোঝে এটা অন্য চাবি। তখন একটি নির্দিষ্ট সূত্র ধরে পরের ঘর দেখে (আসল সূত্রটি একটু জটিল; এখানে সরল করে পাশের ঘর দেখানো হলো)। এই পদ্ধতির নাম open addressing.
- মিলল — মান ফেরত। ঘর 6-এ চাবি মিলল, মান 140. মোট দুটি ঘর দেখা হলো। টেবিল কখনো খুব ভরে যায় না — প্রায় দুই-তৃতীয়াংশ ভরলেই Python বড় টেবিল বানিয়ে সব চাবি নতুন করে বসায়। তাই collision সাধারণত কম, আর গড়ে খোঁজা লাগে প্রায় স্থির সময় — Big-O ভাষায়
O(1).
৩টি চাবি হোক বা ৩০ লাখ — গড়ে প্রায় একই সময়
“গড়ে” শব্দটি কেন গুরুত্বপূর্ণ
dict-এর খোঁজা average case-এ O(1), সবসময় নয়। যদি দুর্ভাগ্যক্রমে (বা কেউ ইচ্ছে করে) অনেকগুলো চাবি একই ঘরের দিকে যায়, তবে প্রতিটি খোঁজায় লম্বা পথ হাঁটতে হয় — সবচেয়ে খারাপ ক্ষেত্রে O(n), ঠিক list-এর মতো। ভালো hash function আর টেবিল সময়মতো বড় করার কারণে বাস্তবে এটা প্রায় ঘটে না। তবে আক্রমণকারীরা ইচ্ছে করে এমন চাবি বানাতে পারে কি না — সেই গল্প প্রশ্ন ০২-এ।
| কাজ | list | dict |
|---|---|---|
“X আছে কি?” (x in ...) | O(n) — একে একে তুলনা | গড়ে O(1) — চাবির ক্ষেত্রে |
| নাম/পরিচয় দিয়ে মান পাওয়া | O(n) — আগে খুঁজে বের করতে হয় | গড়ে O(1) |
| অবস্থান দিয়ে পাওয়া (৫ নম্বর) | O(1) | সরাসরি সম্ভব নয় |
| মেমরি | কম | বেশি — ফাঁকা ঘর, hash রাখার জায়গা |
অর্থাৎ dict গতি কেনে মেমরি দিয়ে। একটি বাস্তব হিসাব ভাবুন: ১০,০০০ গ্রাহকের প্রত্যেকের জন্য ১০,০০০ লেনদেনের তালিকায় খোঁজা। list দিয়ে প্রতিটি খোঁজা গড়ে কয়েক হাজার তুলনা — মোট কয়েক কোটি। গ্রাহকদের একটি dict-এ রাখলে প্রতিটি খোঁজা প্রায় এক ধাপ — মোট ১০,০০০-এর কাছাকাছি। অনেক “ধীর প্রোগ্রাম” শুধু একটি list-কে dict (বা পরের পর্বের set) দিয়ে বদলালেই দ্রুত হয়ে যায়।
৪ · পড়া — d[key], get() আর in
dict থেকে মান পড়ার দুটি প্রধান উপায়, আর তাদের পার্থক্য শুধু একটি জায়গায় — চাবি না থাকলে কী হবে.
🚨 d[key] — “থাকতেই হবে”
চাবি থাকলে মান দেয়। না থাকলে সঙ্গে সঙ্গে KeyError — প্রোগ্রাম থেমে যায় (যদি না আপনি exception ধরেন, পর্ব ২৪)। যখন চাবি না থাকা মানেই কোথাও ভুল হয়েছে, তখন এটাই ঠিক।
marks[101]🛟 d.get(key, default) — “না থাকলে এটা দাও”
চাবি থাকলে মান, না থাকলে default. default না দিলে None. কোনো error নেই। যখন চাবি না থাকা স্বাভাবিক ঘটনা — যেমন নতুন শব্দ, নতুন গ্রাহক — তখন এটা।
চাবি না থাকলে d[key] যে traceback দেয়, সেটি দেখতে এমন:
>>> prices = {"চাল": 75, "ডাল": 140}
>>> prices["চিনি"]
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
prices["চিনি"]
~~~~~~^^^^^^^^
KeyError: 'চিনি'
শেষ লাইনটি পড়ুন: KeyError-এর পরে সেই চাবিটি, যা পাওয়া যায়নি। প্রায়ই এখানেই ভুলটি ধরা পড়ে — বানান ভুল, শেষে বাড়তি ফাঁকা জায়গা ("চিনি "), বা সংখ্যা আর string গুলিয়ে ফেলা (101 বনাম "101" — dict-এর কাছে এরা সম্পূর্ণ আলাদা চাবি)।
get() সবসময় “নিরাপদ” নয়। AI প্রায়ই সব জায়গায় .get() বসিয়ে দেয়, কারণ তাতে error আসে না। কিন্তু error না আসা মানেই ঠিক নয়। ধরুন user.get("email") বানান ভুলের কারণে সবসময় None দিচ্ছে — প্রোগ্রাম চুপচাপ চলছে, কেউ ইমেইল পাচ্ছে না, আর ভুলটি ধরা পড়বে অনেক দূরে, অনেক পরে। নিয়ম: যে চাবি থাকার কথা, তার জন্য d[key] — ভুল হলে জোরে, তাড়াতাড়ি জানুক। যে চাবি না থাকতেও পারে, তার জন্য get() আর একটি অর্থপূর্ণ default.
in — চাবি আছে কি?
পর্ব ০৮-এর membership operator in dict-এ শুধু চাবি দেখে, মান নয়। "ডাল" in prices → True, কিন্তু 140 in prices → False, যদিও 140 একটি মান হিসেবে আছে। মানের মধ্যে খুঁজতে চাইলে লিখতে হয় 140 in prices.values() — কিন্তু সেটি আবার list-এর মতো একে একে দেখা, O(n). চাবিতে in hash table ব্যবহার করে, তাই গড়ে O(1).
“চাবি আছে কি দেখি, থাকলে পড়ি” — এই দুই ধাপ প্রায়ই একটি get()-এ মিলিয়ে ফেলা যায়:
# দুই ধাপে: চাবি দুবার খোঁজা হয়
if "চিনি" in prices:
price = prices["চিনি"]
else:
price = 0
# এক ধাপে: একই কাজ
price = prices.get("চিনি", 0)৫ · যোগ, হালনাগাদ, মিলানো আর মোছা
dict একটি mutable object — তৈরির পরেও বদলানো যায়। বদলানোর উপায়গুলো একসাথে:
| লেখা | কী করে | চাবি না থাকলে |
|---|---|---|
d[k] = v | চাবি থাকলে মান বদলায়, না থাকলে নতুন জোড়া যোগ করে | যোগ করে |
d.update(other) | আরেকটি dict-এর সব জোড়া এনে বসায় | যোগ করে |
d | other | দুটি মিলিয়ে নতুন dict (Python 3.9 থেকে) | — |
d |= other | update-এর মতো, জায়গায় বদলায় (3.9 থেকে) | যোগ করে |
del d[k] | জোড়াটি মুছে ফেলে | KeyError |
d.pop(k, default) | মুছে দিয়ে মানটি ফেরত দেয় | default; না দিলে KeyError |
d.popitem() | সবশেষে যোগ হওয়া জোড়াটি মুছে (k, v) ফেরত দেয় | খালি dict-এ KeyError |
d.clear() | সব মুছে খালি করে | — |
সবচেয়ে বেশি ব্যবহৃত হলো প্রথমটি — d[k] = v. লক্ষ করুন, যোগ আর হালনাগাদের জন্য আলাদা কোনো লেখা নেই। এর সুবিধাও আছে, অসুবিধাও: একটি বানান ভুলে আপনি পুরোনো মান বদলানোর বদলে চুপচাপ একটি নতুন চাবি যোগ করে ফেলতে পারেন (stock["চাল "] = 50 — শেষে ফাঁকা জায়গা)।
দুটি dict মিলানো — | আর |=
Python 3.9 থেকে dict-এ merge operator আছে (PEP 584). দোকানের পুরোনো দামের তালিকা আর আজকের নতুন দামের তালিকা মিলাতে চান:
old = {"চাল": 75, "ডাল": 140, "তেল": 190}
today = {"তেল": 185, "চিনি": 135}
print(old | today) # {'চাল': 75, 'ডাল': 140, 'তেল': 185, 'চিনি': 135}
print(today | old) # {'তেল': 190, 'চিনি': 135, 'চাল': 75, 'ডাল': 140}নিয়ম: একই চাবি দুই দিকে থাকলে ডান দিকেরটি জেতে. তাই old | today-তে তেলের নতুন দাম 185, আর today | old-তে পুরোনো 190 ফিরে এসেছে। চাবির ক্রম আসে বাঁ দিকের dict থেকে, নতুন চাবিগুলো শেষে যুক্ত হয়। | কোনোটিকেই বদলায় না, নতুন dict বানায়; |= বাঁ দিকেরটিকে জায়গায় বদলায়। এই কাজটি আগে লেখা হতো {**old, **today} দিয়ে, যা পুরোনো কোডে এখনো দেখবেন — ফল একই।
মোছা — del, pop, popitem
del prices["তেল"] শুধু মুছে দেয়। prices.pop("তেল") মুছে দিয়ে মানটি হাতে দেয় — “দোকান থেকে তেল সরাও, আর বলো দাম কত ছিল”. চাবি না থাকার সম্ভাবনা থাকলে default দিন: prices.pop("লবণ", None) কখনো error দেয় না। popitem() সবশেষে যোগ হওয়া জোড়াটি সরায় — Python 3.7 থেকে এই ক্রম নিশ্চিত, তাই dict-কে একটি স্ট্যাকের মতোও ব্যবহার করা যায় (শেষে ঢুকেছে, আগে বেরোবে)।
dict-এ যোগ, বদলানো আর মোছা — তিনটিই গড়ে O(1), কারণ তিনটিই প্রথমে hash দিয়ে ঘর খোঁজে। list-এর মাঝখান থেকে মুছলে পরের সবাইকে সরাতে হয় (O(n)), dict-এ সেই খরচ নেই।
৬ · লুপ, view আর চাবির ক্রম
পর্ব ১২-এ দেখেছেন, dict-এর ওপর সরাসরি for লুপ চালালে প্রতি ঘোরায় একটি চাবি আসে। মান বা দুটিই চাইলে তিনটি method আছে:
| লেখা | প্রতি ঘোরায় পান | উদাহরণ |
|---|---|---|
for k in d: বা d.keys() | চাবি | সব রোল নম্বর |
d.values() | মান | সব নম্বর — sum(marks.values()) |
d.items() | (চাবি, মান) জোড়া | for roll, mark in marks.items(): |
সবচেয়ে প্রচলিত রূপ for roll, mark in marks.items(): — প্রতিটি জোড়াকে পর্ব ১৫-এর unpacking দিয়ে দুটি নামে ভাগ করা। for roll in marks: লিখে ভেতরে marks[roll] পড়াও কাজ করে, কিন্তু প্রতিবার একটি বাড়তি খোঁজা হয়, আর পড়তেও কম পরিষ্কার।
View — কপি নয়, জীবন্ত জানালা
keys(), values(), items() কোনো নতুন list বানায় না। এরা দেয় একটি view — dict-এর দিকে খোলা একটি জানালা। dict বদলালে জানালা দিয়ে তাকালে বদলটাও দেখা যায়। কোনো কপি হয় না বলে লাখো চাবির dict-এও items() তৈরি হয় সঙ্গে সঙ্গে। নিচের প্রথম কোডে এটি নিজে দেখবেন। একটি স্থির তালিকা দরকার হলে স্পষ্ট করে বানিয়ে নিন: list(d.keys()) বা সংক্ষেপে list(d).
চাবির ক্রম — ঢোকানোর ক্রমই থাকে
dict-এ লুপ চালালে চাবিগুলো কোন ক্রমে আসবে? উত্তর: যে ক্রমে ঢোকানো হয়েছিল — একে বলে insertion order. আগে থেকে থাকা চাবির মান বদলালে তার জায়গা বদলায় না; মুছে আবার যোগ করলে সে শেষে যায়। এই নিশ্চয়তার একটি ইতিহাস আছে:
- Python 3.5 পর্যন্ত: ক্রম ছিল কার্যত এলোমেলো — hash অনুযায়ী। নির্দিষ্ট ক্রম চাইলে
collections.OrderedDictলাগত। - CPython 3.6: নতুন “compact dict” নকশায় ক্রম রক্ষা হতে লাগল, কিন্তু সেটি ছিল বাস্তবায়নের খুঁটিনাটি (implementation detail) — ভাষার প্রতিশ্রুতি নয়।
- Python 3.7 থেকে: insertion order ভাষার নিয়ম। যেকোনো Python বাস্তবায়নকে এটি মানতে হবে।
তাই আজকের Python-এ print(d)-এর আউটপুট নির্ধারিত — একই কোড প্রতিবার একই ক্রমে ছাপে। (পরের পর্বের set-এর ক্ষেত্রে কিন্তু এই নিশ্চয়তা নেই।)
লুপ চলার সময় dict বদলাবেন না
পর্ব ১২-এ দেখেছেন, list-এর ওপর লুপ চালাতে চালাতে তাকে বদলালে একটি জিনিস চুপচাপ বাদ পড়ে। dict এখানে আরও কড়া — লুপ চলার সময় চাবি যোগ বা মুছলে Python সরাসরি থামিয়ে দেয়:
>>> stock = {"চাল": 50, "চিনি": 0, "ডাল": 0}
>>> for item in stock:
... if stock[item] == 0:
... del stock[item]
...
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
for item in stock:
^^^^^
RuntimeError: dictionary changed size during iteration
RuntimeError-এর বার্তাটি স্পষ্ট: “লুপের মাঝে dictionary-র আকার বদলেছে”. কারণ চাবি যোগ বা মোছায় ভেতরের টেবিল নতুন করে সাজানো হতে পারে, তখন লুপ কোথায় ছিল তার হিসাব আর অর্থবহ থাকে না। Python ভুল উত্তর দেওয়ার চেয়ে থামিয়ে দেওয়া ভালো মনে করে। দুটি সমাধান:
- চাবিগুলোর কপির ওপর লুপ:
for item in list(stock):—list(stock)লুপ শুরুর আগেই চাবিগুলোর একটি আলাদা list বানায়, তাই আসল dict থেকে মোছা নিরাপদ। - নতুন dict বানানো:
stock = {k: v for k, v in stock.items() if v > 0}— যা রাখতে চান শুধু তা দিয়ে (comprehension, পর্ব ১৮)।
একটি সূক্ষ্ম বিষয়: লুপের মাঝে আগে থেকে থাকা চাবির মান বদলানো (stock[item] = 0) নিষেধ নয়, কারণ তাতে আকার বা কাঠামো বদলায় না। নিষেধ শুধু চাবি যোগ আর মোছা। এবার এখন পর্যন্ত শেখা সব একসাথে চালিয়ে দেখুন:
prices = {"চাল": 75, "ডাল": 140, "তেল": 190}
print(prices["ডাল"], prices.get("চিনি"), prices.get("চিনি", 0))
prices |= {"চিনি": 135, "তেল": 185} # নতুন চাবি যোগ, পুরোনোটি হালনাগাদ
print(prices)
print("লবণ" in prices, 140 in prices) # in শুধু চাবি দেখে
names = prices.keys() # view — জীবন্ত জানালা
prices["ডিম"] = 150
print(len(names), list(names)[-1])
for item in list(prices): # কপির ওপর লুপ, তাই মোছা নিরাপদ
if prices[item] >= 150:
del prices[item]
print(prices)
print(prices.pop("লবণ", "নেই"), prices.popitem())প্রথম লাইনের আউটপুট 140 None 0 — “ডাল” আছে, তাই 140; “চিনি” নেই, তাই get default ছাড়া None, default দিলে 0. এরপর |= তেলের দাম 185 করল আর চিনি শেষে যোগ করল: {'চাল': 75, 'ডাল': 140, 'তেল': 185, 'চিনি': 135} — খেয়াল করুন, তেল নিজের পুরোনো জায়গাতেই আছে। False False: লবণ চাবি নয়, আর 140 মান হলেও চাবি নয়। তারপর view-এর জীবন্ততা: names নেওয়া হয়েছিল ৪টি চাবি থাকার সময়, কিন্তু ডিম যোগ করার পর সেটিই 5 ডিম দেখায় — কোনো কপি ছিল না। লুপে list(prices)-এর কারণে তেল (185) আর ডিম (150) মুছে গেল কোনো RuntimeError ছাড়া: {'চাল': 75, 'ডাল': 140, 'চিনি': 135}. শেষ লাইনে pop লবণ না পেয়ে default “নেই” দিল, আর popitem() সবশেষে ঢোকা জোড়াটি সরাল: ('চিনি', 135).
৭ · চাবির শর্ত — কেন hashable হতে হয়
অংশ ৩-এর ছবিটি মনে করুন: চাবি ঢোকানোর সময় তার hash দেখে ঘর ঠিক হয়েছিল। এখন ভাবুন, চাবিটি যদি পরে বদলে যায়? তার নতুন hash অন্য ঘর দেখাবে, অথচ সে বসে আছে পুরোনো ঘরে। খুঁজতে গেলে Python ভুল ঘরে যাবে — চাবিটি dict-এর ভেতরে থেকেও “হারিয়ে” যাবে। এই বিপদ এড়াতে Python-এর নিয়ম: dict-এর চাবি হতে হবে hashable — অর্থাৎ এমন object, যার hash তার জীবনকালে বদলায় না, আর যাকে অন্যের সাথে == দিয়ে তুলনা করা যায়।
বাস্তবে এর মানে: Python-এর built-in immutable ধরনগুলো চাবি হতে পারে, বদলানো যায় এমন ধরনগুলো পারে না।
| ধরন | চাবি হতে পারে? | উদাহরণ |
|---|---|---|
| str, int, float, bool, None | হ্যাঁ | {"ঢাকা": 1, 101: "রিয়া"} |
| tuple — ভেতরের সবকিছু hashable হলে | হ্যাঁ | {(23.8, 90.4): "ঢাকা"} — অক্ষাংশ-দ্রাঘিমাংশ |
| tuple — ভেতরে list থাকলে | না | (1, [2, 3]) |
| list, dict, set | না | বদলানো যায়, তাই hash স্থির নয় |
| frozenset | হ্যাঁ | set-এর অপরিবর্তনীয় রূপ — পর্ব ১৭ |
list-কে চাবি বানাতে গেলে কী হয়, Python 3.14-এ দেখুন:
>>> seats = {}
>>> seats[[3, 4]] = "রিয়া"
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
seats[[3, 4]] = "রিয়া"
~~~~~^^^^^^^^
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
এটি একটি TypeError. পুরোনো Python version-গুলোতে বার্তাটি ছিল ছোট — শুধু TypeError: unhashable type: 'list'; সাম্প্রতিক version বার্তাটিকে আরও ব্যাখ্যামূলক করেছে। দুটিরই মানে এক। সমাধান সহজ: সারি-কলামের মতো জোড়া চাবি হিসেবে চাইলে list নয়, tuple ব্যবহার করুন — seats[(3, 4)] = "রিয়া", বা বন্ধনী ছাড়া seats[3, 4] = "রিয়া". পর্ব ১৫-এ দেখেছেন, tuple-এর এটাই একটি বড় কাজ।
একটি মজার ফাঁদ: 1, 1.0 আর True — তিনটিই == হিসেবে সমান, আর তাদের hash-ও সমান। তাই dict-এর চোখে এরা একই চাবি. {1: "ক", 1.0: "খ", True: "গ"} লিখলে পাবেন {1: 'গ'} — চাবি প্রথমটির, মান শেষটির। পরীক্ষার “আউটপুট কী হবে” প্রশ্নে এটি জনপ্রিয়।
৮ · বাস্তব প্যাটার্ন — গণনা, দলে ভাগ, উল্টানো, lookup
প্যাটার্ন ১: গণনা — কোন শব্দ কতবার
একটি SMS-এ কোন শব্দ কতবার এসেছে? এটি dict-এর সবচেয়ে ক্লাসিক কাজ — চাবি শব্দ, মান গণনা। কৌশলের মূলে একটি লাইন: counts[word] = counts.get(word, 0) + 1. শব্দটি আগে না এলে get দেয় 0, তার সাথে 1 — প্রথমবার গণনা 1. আগে এলে পুরোনো গণনার সাথে 1. ধাপে ধাপে দেখুন:
1words = ["কাল", "ক্লাস", "কাল", "পরীক্ষা"]2counts = {}3for word in words:4 counts[word] = counts.get(word, 0) + 15print(counts)
| word | |
| counts.get(word, 0) | |
| counts |
- শুরু — খালি খাতা। চারটি শব্দের list আর একটি খালি dict. এটি পর্ব ১২-এর accumulator প্যাটার্নেরই আরেক রূপ — তবে একটি সংখ্যার বদলে প্রতিটি শব্দের জন্য আলাদা একটি “জমার খাতা”.
- “কাল” — প্রথম দেখা।
counts.get("কাল", 0)— চাবি নেই, তাই default 0. তার সাথে 1, আরcounts["কাল"] = 1নতুন জোড়া যোগ করল।counts["কাল"] + 1লিখলে এখানেইKeyErrorহতো। - “ক্লাস” — আরেকটি নতুন শব্দ। একই ঘটনা: get দিল 0, গণনা 1. dict-এ এখন দুটি চাবি, ঢোকানোর ক্রমে।
- “কাল” — আবার! এবার চাবিটি আছে, তাই
getদিল পুরোনো গণনা 1. যোগ হলো 1, নতুন মান 2. একই লাইন, কিন্তু এবার যোগ নয় — হালনাগাদ। “কাল” নিজের প্রথম জায়গাতেই রইল। - “পরীক্ষা” — নতুন। get দিল 0, গণনা 1, শেষে যোগ হলো। প্রতিটি ঘোরায় একটি hash খোঁজা আর একটি লেখা — দুটোই গড়ে O(1). তাই দশ লাখ শব্দের লেখাতেও এই লুপ সরলরেখায় বাড়ে, শব্দের সংখ্যার সমানুপাতে।
- ছাপা হলো।
{'কাল': 2, 'ক্লাস': 1, 'পরীক্ষা': 1}— চাবিগুলো প্রথম দেখার ক্রমে। Python-এরprintstring দেখায় একক উদ্ধৃতি'…'দিয়ে; এটি শুধু দেখানোর ধরন।
এই কাজটি এত প্রচলিত যে standard library-তে এর জন্য একটি তৈরি জিনিস আছে — collections.Counter. Counter(words) একাই পুরো লুপের কাজ করে, আর .most_common(3) সবচেয়ে বেশি আসা তিনটি দেয়। Counter আসলে dict-এরই একটি উপশ্রেণি (subclass), তাই dict-এর সব কিছুই এতে চলে; বাড়তি সুবিধা হলো, না থাকা চাবি পড়লে KeyError নয়, 0 দেয়।
প্যাটার্ন ২: দলে ভাগ — কোন জেলায় কে
শিক্ষার্থীদের জেলা অনুযায়ী দলে ভাগ করতে চান: চাবি জেলা, মান সেই জেলার শিক্ষার্থীদের list. সমস্যা হলো, একটি জেলার প্রথম শিক্ষার্থী এলে তার জন্য খালি list আগে বানাতে হয়। তিনটি উপায়:
- হাতে:
if district not in groups: groups[district] = [], তারপরgroups[district].append(name)— লম্বা, কিন্তু সবাই বোঝে। - setdefault:
groups.setdefault(district, []).append(name)— “চাবি না থাকলে খালি list বসাও, তারপর যেটা আছে সেটা দাও”. এক লাইন, তবে নামটি অনেকের কাছে বিভ্রান্তিকর। - defaultdict:
groups = defaultdict(list)— না থাকা চাবি পড়লেই সে নিজেlist()ডেকে একটি খালি list বসিয়ে দেয়। সবচেয়ে পরিষ্কার।
1by_district = defaultdict(list)2for name, district in students:3 by_district[district].append(name)
| name, district | |
| চাবি ছিল? | |
| by_district |
- একটি “কারখানা” সহ dict.
defaultdict(list)-এlistলেখা হয়েছে বন্ধনী ছাড়া — অর্থাৎ ফাংশনটি নিজে, তার ফল নয়। defaultdict এটি মনে রাখে: “কেউ না থাকা চাবি চাইলেlist()ডেকে নতুন খালি list বানাব”. এটিcollectionsmodule থেকে আনতে হয়। - রিয়া, রাজশাহী।
by_district["রাজশাহী"]— চাবি নেই। সাধারণ dict হলেKeyError. defaultdict কিন্তু চুপচাপ একটি খালি list বসিয়ে সেটাই ফেরত দিল, আর.append("রিয়া")তাতে নাম যোগ করল। - জয়, সিলেট। আবার নতুন চাবি, আবার নতুন খালি list — প্রত্যেক জেলার নিজস্ব আলাদা list. (সব জেলা একই list ভাগাভাগি করত যদি আপনি
[]একবার বানিয়ে সবখানে দিতেন — পর্ব ১৯-এ এই ফাঁদ বিস্তারিত।) - সুমি, রাজশাহী — চাবি আছে। এবার defaultdict কিছু বানাল না, রিয়ার list-টিই দিল। append করে হলো
["রিয়া", "সুমি"]. কোনোifলেখা হয়নি, তবু “প্রথমবার” আর “পরের বার” দুটোই ঠিকঠাক সামলানো হলো। - তানভীর, খুলনা — শেষ। খুলনার জন্য নতুন list. ফল একটি dict: প্রতিটি জেলা একবার, তাদের শিক্ষার্থীরা আসার ক্রমে। সতর্কতা: defaultdict-এ শুধু পড়তে গেলেও (
by_district["বরিশাল"]) একটি খালি list তৈরি হয়ে যায়। শুধু আছে কি না জানতেinব্যবহার করুন।
প্যাটার্ন ৩: উল্টে দেওয়া, nested dict আর মান অনুযায়ী সাজানো
উল্টে দেওয়া: রোল → নম্বর থেকে নম্বর → রোল চাই। {mark: roll for roll, mark in marks.items()}. সাবধান: দুজন একই নম্বর পেলে উল্টানো dict-এ একজন হারিয়ে যাবে — চাবি অনন্য হতে হয়, শেষেরটি জেতে। একাধিক থাকতে পারলে উল্টানোর সময় defaultdict(list) দিয়ে দলে ভাগ করুন।
Nested dict: মান নিজেই একটি dict হতে পারে — একটি কন্টাক্ট বুকে প্রত্যেকের নম্বর, জেলা, ইমেইল। ঠিক JSON-এর মতো রেকর্ড:
contacts = {
"সুমি": {"phone": "01711-000001", "district": "রাজশাহী"},
"রহিম": {"phone": "01811-000002", "district": "খুলনা", "email": "rahim@example.com"},
}
print(contacts["রহিম"]["district"]) # খুলনা
print(contacts["সুমি"].get("email", "ইমেইল নেই")) # ইমেইল নেইদুই স্তরে খুঁজতে দুটি তৃতীয় বন্ধনী। ভেতরের কোনো চাবি সবার না থাকলে সেখানে get. অনেক স্তরের nested dict পড়া কঠিন হয়ে যায়; তখন পর্ব ৩২-এর dataclass ভালো বিকল্প।
মান অনুযায়ী সাজানো: dict চাবি ঢোকানোর ক্রমে থাকে, নিজে সাজায় না। সবচেয়ে বেশি আসা শব্দগুলো আগে চাইলে sorted(counts, key=counts.get, reverse=True) — চাবিগুলো সাজাও, প্রতিটির তুলনার মাপকাঠি তার মান। জোড়াসহ চাইলে sorted(counts.items(), key=lambda pair: pair[1], reverse=True) — lambda হলো এক লাইনের ছোট ফাংশন, পর্ব ৩৮-এ শিখবেন।
from collections import Counter, defaultdict
sms = "কাল ক্লাস নেই কাল পরীক্ষা আছে পরীক্ষা কাল সকালে"
counts = {}
for word in sms.split():
counts[word] = counts.get(word, 0) + 1
print(counts)
print(sorted(counts, key=counts.get, reverse=True)[:2])
print(Counter(sms.split()).most_common(2))
students = [("রিয়া", "রাজশাহী"), ("জয়", "সিলেট"), ("সুমি", "রাজশাহী")]
by_district = defaultdict(list)
for name, district in students:
by_district[district].append(name)
print(dict(by_district))
marks = {101: 78, 102: 91, 103: 65}
roll_of = {mark: roll for roll, mark in marks.items()} # উল্টে দেওয়া
print(roll_of[91], roll_of)প্রথম লাইন হাতে গোনা ফল: {'কাল': 3, 'ক্লাস': 1, 'নেই': 1, 'পরীক্ষা': 2, 'আছে': 1, 'সকালে': 1} — প্রথম দেখার ক্রমে। sorted(..., key=counts.get, reverse=True) গণনা অনুযায়ী বড় থেকে ছোট সাজিয়ে প্রথম দুটি দিল: ['কাল', 'পরীক্ষা']. সমান গণনার শব্দগুলো (১ করে) নিজেদের আগের ক্রম রাখে, কারণ Python-এর sorted stable. Counter তিন লাইনের কাজ এক লাইনে করে একই উত্তর দিল, গণনাসহ: [('কাল', 3), ('পরীক্ষা', 2)]. defaultdict দিয়ে দলে ভাগের ফল {'রাজশাহী': ['রিয়া', 'সুমি'], 'সিলেট': ['জয়']} — dict(...) দিয়ে সাধারণ dict-এ বদলানো হয়েছে, যাতে ছাপা পরিষ্কার হয়। শেষে উল্টানো dict: 91 নম্বর পেয়েছে রোল 102, আর পুরো dict {78: 101, 91: 102, 65: 103}.
প্যাটার্ন ৪: লম্বা if/elif-এর বদলে lookup table
পর্ব ১১-এ if/elif দিয়ে সিদ্ধান্ত নিয়েছেন। কিন্তু কিছু if/elif আসলে সিদ্ধান্ত নয় — শুধু “এই চাবির জন্য এই মান” খোঁজা:
# if/elif দিয়ে — প্রতিটি নতুন জেলায় দুই লাইন বাড়ে
if district == "ঢাকা":
charge = 60
elif district == "গাজীপুর":
charge = 80
elif district == "চট্টগ্রাম":
charge = 120
else:
charge = 150
# lookup table দিয়ে — ডেটা এক জায়গায়, যুক্তি এক লাইনে
DELIVERY_CHARGE = {"ঢাকা": 60, "গাজীপুর": 80, "চট্টগ্রাম": 120}
charge = DELIVERY_CHARGE.get(district, 150)দ্বিতীয় রূপটি একটি lookup table. সুবিধা অনেক: নতুন জেলা মানে শুধু একটি জোড়া; তালিকাটি পরে একটি ফাইল বা ডেটাবেস থেকে আনা যায় (পর্ব ২৬); আর if/elif-এ ৬৪টি জেলার জন্য গড়ে কয়েক ডজন তুলনা লাগত, dict-এ একটি hash খোঁজা। মান হিসেবে ফাংশনও রাখা যায় — {"add": add, "sub": sub}[op](a, b) — যা একটি ছোট ক্যালকুলেটর বা মেনু-চালিত প্রোগ্রামের ভিত্তি। তবে যেখানে শর্তগুলো সত্যিকারের তুলনা (mark >= 80), সেখানে if/elif বা match-ই স্বাভাবিক; dict কাজ করে শুধু “ঠিক এই মান” খোঁজায়।
C-তে কোনো built-in dictionary নেই — hash table নিজে লিখতে হয়, বা লাইব্রেরি খুঁজতে হয়। C++-এ দুটি আছে: std::map (সাজানো, ভেতরে balanced tree, খোঁজা O(log n)) আর std::unordered_map (hash table, গড়ে O(1)). Python-এর dict ধারণায় unordered_map-এর কাছাকাছি, কিন্তু ঢোকানোর ক্রম মনে রাখে আর যেকোনো hashable object-কে চাবি নেয়। আরেকটি পার্থক্য: C++-এ m[key] পড়তে গেলে না থাকা চাবি নিজে তৈরি হয়ে যায় (অনেকটা defaultdict-এর মতো), Python-এ KeyError. বিস্তারিত তুলনা দেখুন C++ কোর্সের associative container পর্বে।
- list কেন dict-এর চাবি হতে পারে না? — list বদলানো যায়, তাই তার hash স্থির থাকে না; চাবি বদলালে সে ভুল ঘরে পড়ে থাকত। এজন্য list unhashable,
TypeErrorদেয়। d[k]আরd.get(k)-এর পার্থক্য? — চাবি না থাকলে প্রথমটিKeyErrorদেয়, দ্বিতীয়টিNoneবা দেওয়া default.- dict-এ খোঁজার time complexity? — গড়ে O(1), সবচেয়ে খারাপ ক্ষেত্রে (অনেক collision) O(n).
- dict কি ক্রম মনে রাখে? — হ্যাঁ, Python 3.7 থেকে insertion order ভাষার নিয়ম (CPython 3.6-এ বাস্তবায়নের খুঁটিনাটি ছিল)।
{1: "a", True: "b"}-এর ফল? —{1: 'b'};1 == Trueআর hash সমান, তাই একই চাবি।
৯ · AI-এর যুগে — AI-এর লেখা dict কোড যাচাই
dict-এর কোড AI খুব দ্রুত লেখে, আর বেশিরভাগ সময় চলে। কিন্তু চারটি ভুল বারবার দেখা যায় — কোনোটি প্রোগ্রাম থামিয়ে দেয়, কোনোটি চুপচাপ ভুল ফল দেয়, কোনোটি শুধু কোডকে অকারণে লম্বা করে।
| AI প্রায়ই যা লেখে | সমস্যা | ভালো রূপ |
|---|---|---|
data["email"] — যাচাই ছাড়া, যেখানে চাবি না থাকতেও পারে | আসল ডেটায় KeyError, প্রোগ্রাম থামে | data.get("email") আর None সামলানো |
সব জায়গায় .get(), এমনকি আবশ্যিক চাবিতে | বানান ভুল লুকিয়ে যায়, ভুল অনেক পরে ধরা পড়ে | আবশ্যিক চাবিতে d[key] |
| ২০ লাইনের if/elif — শুধু মান খোঁজার জন্য | লম্বা, বদলানো কঠিন, ধীর | একটি lookup dict + get |
লুপের মাঝে del d[k] বা নতুন চাবি | RuntimeError | for k in list(d): বা নতুন dict |
হাতে লেখা গণনা বা if k not in d: d[k] = [] | লম্বা, ভুলের সুযোগ | Counter, defaultdict(list) |
AI-কে dict-নির্ভর কোড লিখতে বলার সময় এই শর্তগুলো জুড়ে দিন:
AI-এর লেখা dict কোড পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Python 3.5 পর্যন্ত dict-এর ক্রম ছিল এলোমেলো, অথচ Python 3.7 থেকে ঢোকানোর ক্রম ভাষার নিয়ম। এই বদল কীভাবে এল? একটি মেমরি বাঁচানোর নকশা কেন একটি ভাষার প্রতিশ্রুতিতে পরিণত হলো?
পুরোনো CPython dict ছিল একটি সরল hash table: একটি বড় array, যার প্রতিটি ঘরে hash, চাবি আর মানের ঠিকানা — তিনটি জিনিস। টেবিল কখনো পুরো ভরানো হয় না (প্রায় দুই-তৃতীয়াংশে পৌঁছালেই বড় করা হয়), তাই অনেক ঘর ফাঁকা পড়ে থাকে — আর প্রতিটি ফাঁকা ঘরও তিনটি জিনিসের জায়গা দখল করে রাখে। লুপ চালালে চাবিগুলো আসত array-এর ঘরের ক্রমে, অর্থাৎ hash-এর ক্রমে, যা মানুষের চোখে এলোমেলো।
২০১২ সালের ডিসেম্বরে Python core developer Raymond Hettinger python-dev মেইলিং লিস্টে একটি নতুন নকশা প্রস্তাব করেন। ধারণাটি হলো টেবিলটিকে দুই ভাগে ভাঙা: (১) একটি ঘন (dense) entries array, যেখানে hash-চাবি-মান জোড়াগুলো একটির পর একটি, ঢোকানোর ক্রমে জমা হয়, কোনো ফাঁক ছাড়া; (২) একটি ছোট indices array — আসল hash table — যার প্রতিটি ঘরে শুধু একটি ছোট সংখ্যা: entries-এর কোন জায়গায় জোড়াটি আছে। ফাঁকা ঘরগুলো এখন শুধু একটি ছোট সংখ্যার জায়গা নেয়, পুরো তিনটি জিনিসের নয়। ফলে মেমরি অনেক কমে, আর লুপ চালানো দ্রুত হয়, কারণ entries ঘন — ফাঁকা ঘর টপকাতে হয় না।
লক্ষ করুন, ঢোকানোর ক্রম রক্ষা ছিল এই নকশার পার্শ্ব-ফল, মূল লক্ষ্য নয়। নকশাটি প্রথম বাস্তবে আসে PyPy-তে, তারপর CPython 3.6-এ (২০১৬)। কিন্তু তখন আনুষ্ঠানিকভাবে বলা হয়েছিল — ক্রম রক্ষা “implementation detail”, এর ওপর ভরসা করবেন না। কারণ একটি ভাষার নিয়ম মানে সব বাস্তবায়নকে (PyPy, MicroPython, ভবিষ্যতের যেকোনো) চিরকাল তা মানতে হবে — এটি একটি বড় প্রতিশ্রুতি।
তবু বাস্তবতা ছিল এই যে মানুষ এক বছরের মধ্যেই এর ওপর নির্ভর করতে শুরু করে। ২০১৭ সালের ডিসেম্বরে Guido van Rossum ঘোষণা করেন, Python 3.7 থেকে insertion order ভাষার অংশ। যুক্তি: এটি এত কাজের (JSON-এর ক্রম রক্ষা, **kwargs-এর ক্রম, পড়ার মতো আউটপুট), আর যেহেতু সবচেয়ে বড় বাস্তবায়ন ইতোমধ্যে এটি করছে — তাই নিশ্চয়তা দেওয়াই ভালো। OrderedDict এখনো আছে, কারণ এতে কিছু বাড়তি সুবিধা আছে (যেমন move_to_end, আর ক্রমসহ সমতা-তুলনা)।
মূল উপলব্ধি: ভালো প্রকৌশল প্রায়ই একসাথে কয়েকটি সমস্যা মেটায় — মেমরির জন্য করা নকশা ক্রমও দিল। আর “implementation detail” কতদিন detail থাকবে, তা ঠিক করে ব্যবহারকারীদের নির্ভরতা; জনপ্রিয় আচরণ একসময় প্রতিশ্রুতিতে পরিণত হয়।
প্র ০২একই string-এর hash() দুবার Python চালালে আলাদা আসে কেন? এটা কি বাগ, নাকি ইচ্ছাকৃত? এর সাথে নিরাপত্তার সম্পর্ক কী?
REPL খুলে hash("ঢাকা") লিখুন, একটি বড় সংখ্যা পাবেন। REPL বন্ধ করে আবার খুলে একই কাজ করুন — সংখ্যাটি আলাদা। কিন্তু একই রানের ভেতরে যতবার খুশি জিজ্ঞেস করুন, একই উত্তর। এটি বাগ নয়, ইচ্ছাকৃত নকশা — নাম hash randomization. প্রতিবার Python শুরু হওয়ার সময় একটি গোপন এলোমেলো মান (salt) বেছে নেয়, আর str ও bytes-এর hash সেই মান মিশিয়ে হিসাব হয়। (ছোট int-এর hash সাধারণত সংখ্যাটি নিজেই, তাই সেগুলো বদলায় না।)
কেন? অংশ ৩-এ দেখেছেন, dict-এর গতি নির্ভর করে চাবিগুলো ঘরে ঘরে ছড়িয়ে পড়ার ওপর। কেউ যদি আগে থেকে জানে hash কীভাবে হিসাব হয়, সে ইচ্ছে করে হাজার হাজার এমন চাবি বানাতে পারে যেগুলো সব একই ঘরের দিকে যায়। তখন প্রতিটি নতুন চাবি ঢোকাতে আগের সবগুলোর সাথে তুলনা লাগে — O(1)-এর বদলে O(n), আর পুরো কাজ O(n²)। একটি ওয়েব সার্ভার সাধারণত ফর্মের ঘরগুলো (POST parameter) একটি dict-এ পড়ে; তাই একটি মাত্র সাজানো অনুরোধ সার্ভারের CPU অনেকক্ষণ আটকে রাখতে পারে। এটি এক ধরনের denial-of-service, যার নাম হয় “hash flooding”.
২০১১ সালের ডিসেম্বরে জার্মানির Chaos Communication Congress-এ নিরাপত্তা গবেষকরা দেখান যে তখনকার অনেক জনপ্রিয় ওয়েব প্ল্যাটফর্ম — বিভিন্ন ভাষার — এই সমস্যায় আক্রান্ত। ধারণাটি নতুন ছিল না (২০০৩ সালেই গবেষণাপত্রে এসেছিল), কিন্তু এবার ব্যাপক প্রভাব দেখানো হলো। Python দ্রুত সাড়া দেয়: ২০১২ সালে পুরোনো version-গুলোতে -R নামে একটি ঐচ্ছিক সুইচ যোগ হয়, আর Python 3.3 থেকে hash randomization ডিফল্টভাবে চালু. পরে Python 3.4-এ string hashing-এর জন্য SipHash নামের একটি শক্তিশালী অ্যালগরিদম নেওয়া হয় (PEP 456), কারণ আগের সমাধানটি যথেষ্ট শক্ত ছিল না বলে দেখা গিয়েছিল।
পরীক্ষা বা ডিবাগিংয়ে একই hash বারবার দরকার হলে PYTHONHASHSEED environment variable একটি নির্দিষ্ট সংখ্যায় সেট করা যায় (0 দিলে randomization বন্ধ)। তবে আসল সার্ভারে এটি বন্ধ করা নিরাপত্তা দুর্বল করে। এই কারণেই এই কোর্সের চালানো যায় এমন কোডে কখনো hash()-এর মান ছাপা হয় না — প্রতিবার আলাদা আসত। dict-এর ক্রম কিন্তু এতে বদলায় না, কারণ 3.7 থেকে ক্রম আসে ঢোকানোর ক্রম থেকে, hash থেকে নয়। set-এর ক্রম অবশ্য hash-নির্ভর, তাই string-এর set-এর ছাপা ক্রম রান থেকে রানে বদলাতে পারে।
মূল উপলব্ধি: “গড়ে দ্রুত” অ্যালগরিদম আক্রমণকারীর হাতে “সবচেয়ে খারাপ ক্ষেত্রে ধীর” হয়ে যেতে পারে। নিরাপদ সিস্টেম শুধু গড় নিয়ে নয়, সবচেয়ে খারাপ ক্ষেত্র কেউ ইচ্ছে করে ঘটাতে পারে কি না — তা নিয়েও ভাবে।
প্র ০৩list কেন dict-এর চাবি হতে পারে না, অথচ tuple পারে? Python কি চাইলে list-এর hash হিসাব করতে পারত না? নিজের বানানো class-এর object কি চাবি হতে পারে?
প্রযুক্তিগতভাবে list-এর ভেতরের জিনিসগুলো থেকে একটি hash হিসাব করা কঠিন নয় — tuple-এর ক্ষেত্রে Python ঠিক সেটাই করে। সমস্যা হিসাবে নয়, সময়ে. ধরুন key = [3, 4] চাবি হিসেবে ঢোকানো হলো; তার hash অনুযায়ী সে ঘর 5-এ বসল। এখন কেউ key.append(5) করল — একই list object, কিন্তু ভেতরের জিনিস বদলেছে, তাই নতুন hash হয়তো ঘর 2 দেখায়। এবার d[[3, 4, 5]] খুঁজলে Python ঘর 2-এ যাবে, কিছু পাবে না; আর d[[3, 4]] খুঁজলে ঘর 5-এ গিয়ে দেখবে সেখানকার চাবি এখন [3, 4, 5], যা [3, 4]-এর সমান নয়। চাবিটি dict-এর ভেতরেই আছে, কিন্তু কোনো উপায়ে আর খুঁজে পাওয়া যাবে না। এর চেয়ে বিভ্রান্তিকর বাগ কল্পনা করা কঠিন।
Python এখানে একটি স্পষ্ট নীতি নিয়েছে: যে built-in ধরন বদলানো যায় (list, dict, set), তার __hash__ ইচ্ছাকৃতভাবে বন্ধ — hash([1]) সরাসরি TypeError দেয়। ভুলটি ঘটার আগেই, চাবি ঢোকানোর মুহূর্তে ধরা পড়ে। tuple বদলানো যায় না, তাই তার hash চিরকাল একই — চাবি হিসেবে নিরাপদ। তবে tuple-এর hash তার ভেতরের জিনিসগুলোর hash থেকে আসে, তাই (1, [2]) hashable নয়: ভেতরের list বদলাতে পারে।
নিজের class-এর ক্ষেত্রে (পর্ব ২৯–৩০) ডিফল্ট আচরণ হলো: প্রতিটি object শুধু নিজের সমান, আর hash আসে তার পরিচয় (identity) থেকে — তাই চাবি হতে পারে, তবে দুটি আলাদা object একই ডেটা রাখলেও আলাদা চাবি। আপনি যদি __eq__ লিখে “সমান”-এর নিজস্ব সংজ্ঞা দেন, Python স্বয়ংক্রিয়ভাবে __hash__ বন্ধ করে দেয় — কারণ নিয়ম হলো “সমান object-এর hash সমান হতেই হবে”, আর পুরোনো identity-hash সেটি মানবে না। তখন নিজে __hash__ লিখতে হয়, সাধারণত সেই অপরিবর্তনীয় ঘরগুলো দিয়ে যেগুলো __eq__-এ তুলনা হয়। @dataclass(frozen=True) এই কাজ নিজেই করে দেয় (পর্ব ৩২)।
অন্য ভাষাও একই সমস্যায় পড়ে, কিন্তু সবাই Python-এর মতো কড়া নয়। Java-তে একটি ArrayList-কে HashMap-এর চাবি বানানো যায়; পরে বদলালে ঠিক উপরের “হারানো চাবি” বাগ ঘটে, আর ডকুমেন্টেশনে শুধু সতর্ক করা থাকে। Python নিয়মটিকে ভাষার স্তরে নিশ্চিত করেছে।
মূল উপলব্ধি: hash table কাজ করে একটি চুক্তির ওপর — চাবির hash কখনো বদলাবে না। “বদলানো যায় এমন জিনিস চাবি হতে পারবে না” নিয়মটি সেই চুক্তি রক্ষার সবচেয়ে সরল উপায়: ভুলকে চলার সময় নয়, শুরুতেই আটকানো।
প্র ০৪বলা হয় “Python-এ সবকিছুই dict-এর ওপর দাঁড়িয়ে”. ভ্যারিয়েবলের নাম, object-এর attribute, module, class — এগুলোর সাথে dict-এর সম্পর্ক কী? আর এর জন্য dict-কে কত দ্রুত হতে হয়?
পর্ব ০৬-এ শিখেছেন, Python-এ ভ্যারিয়েবল হলো একটি নাম, যা একটি object-এর গায়ে লাগানো লেবেল। সেই “নাম → object” সম্পর্ক কোথাও তো রাখতে হয়। একটি module-এর global নামগুলো সত্যিই একটি dict-এ রাখা — globals() সেটিই ফেরত দেয়। আপনি যখন print লেখেন, Python খুঁজে দেখে নামটি local-এ আছে কি, না থাকলে global dict-এ, না থাকলে builtins module-এর dict-এ। (ফাংশনের local ভ্যারিয়েবলগুলো অবশ্য গতির জন্য আলাদা, দ্রুততর ব্যবস্থায় রাখা হয়।)
object-এর ক্ষেত্রেও তাই। student.name লিখলে সাধারণ একটি object-এর জন্য Python আসলে student.__dict__["name"] খোঁজে; না পেলে class-এর __dict__-এ (method সেখানেই থাকে), তারপর parent class-গুলোতে। একটি import করা module-ও একটি object, যার attribute-গুলো একটি dict-এ। ফাংশন ডাকার সময় দেওয়া keyword argument **kwargs-এ একটি dict হয়ে আসে। এমনকি class লেখার সময় তার ভেতরের সব সংজ্ঞা প্রথমে একটি dict-এ জমা হয়, তারপর class তৈরি হয়।
এর মানে, একটি সাধারণ Python প্রোগ্রাম প্রতি সেকেন্ডে লাখো dict খোঁজা করে — আপনি নিজে একটিও না লিখলেও। তাই CPython-এর dict ভাষার সবচেয়ে বেশি টিউন করা অংশগুলোর একটি। কয়েকটি উদাহরণ: str নিজের hash একবার হিসাব করে মনে রাখে, তাই একই নাম বারবার খুঁজতে আবার হিসাব লাগে না; ভ্যারিয়েবল ও attribute-এর নামগুলো সাধারণত interned (পর্ব ০৮), তাই তুলনা প্রায়ই শুধু ঠিকানা মেলানো। Python 3.3-এ (PEP 412) “key-sharing dictionary” আসে: একই class-এর হাজারটি object-এর __dict__ একই চাবির সেট ভাগাভাগি করে, প্রত্যেকে শুধু নিজের মানগুলো রাখে — মেমরির বড় সাশ্রয়। সাম্প্রতিক version-গুলোতে (পর্ব ০৫-এর specializing interpreter) attribute খোঁজার বহু ক্ষেত্রে dict খোঁজা পুরোপুরি এড়িয়ে যাওয়ার কৌশলও যোগ হয়েছে।
উল্টো দিকে, যেখানে লাখো ছোট object আর মেমরি গুরুত্বপূর্ণ, সেখানে __slots__ দিয়ে object-এর __dict__ বাদ দেওয়া যায় — attribute-গুলো তখন নির্দিষ্ট জায়গায় থাকে, নতুন attribute যোগ করা যায় না। নমনীয়তা কমে, মেমরি বাঁচে।
মূল উপলব্ধি: dict Python-এর একটি “ফিচার” মাত্র নয় — ভাষার নমনীয়তার (যেকোনো সময় নতুন attribute, নতুন নাম) ভিত্তিই এটি। dict দ্রুত বলে Python ব্যবহারযোগ্য; আর dict-কে বুঝলে Python-এর ভেতরের অনেক আচরণ হঠাৎ পরিষ্কার হয়ে যায়।
প্র ০৫JSON দেখতে প্রায় হুবহু Python dict-এর মতো। এই মিল কোথা থেকে এল, আর দুটির মধ্যে কোন কোন পার্থক্য না জানলে বাগ হয়?
JSON (JavaScript Object Notation) জনপ্রিয় করেন আমেরিকান প্রোগ্রামার Douglas Crockford, ২০০০-এর দশকের শুরুতে। তিনি দাবি করেননি যে তিনি এটি “আবিষ্কার” করেছেন — বরং বলেছেন, এটি JavaScript-এর object লেখার ধরনের একটি ছোট, কড়া উপসেট, যা অনেকে আগে থেকেই ব্যবহার করছিলেন; তিনি এর নাম দেন, সরল একটি স্পেসিফিকেশন লেখেন আর json.org সাইটে প্রকাশ করেন। লক্ষ্য ছিল XML-এর চেয়ে হালকা একটি ডেটা আদান-প্রদানের ফরম্যাট। পরে এটি RFC 4627 (২০০৬) হিসেবে লেখা হয়, আর আজকের মানদণ্ড ECMA-404 ও RFC 8259. আজ প্রায় সব ওয়েব API JSON-এ কথা বলে।
Python dict আর JavaScript object-এর লেখার ধরন একই রকম ({"key": value}), কারণ দুটোই আরও পুরোনো ভাষাগুলোর ধারা নিয়েছে। তাই Python-এর json module (standard library-তে Python 2.6 থেকে) প্রায় সরাসরি অনুবাদ করে: JSON object ↔ dict, array ↔ list, string ↔ str, সংখ্যা ↔ int/float. কিন্তু মিলটা পুরোপুরি নয়, আর পার্থক্যগুলো না জানলে বাগ হয়:
- লেখার ছোট পার্থক্য: JSON-এ
true,false,null; Python-এTrue,False,None. JSON-এ string শুধু দ্বিগুণ উদ্ধৃতিতে, শেষে বাড়তি কমা নিষেধ। তাইprint(d)-এর আউটপুট (একক উদ্ধৃতিসহ) বৈধ JSON নয় — JSON চাইলেjson.dumps(d). - চাবি শুধু string: JSON-এ object-এর চাবি সবসময় string. Python dict
{101: 78}-কে JSON বানিয়ে ফিরিয়ে আনলে পাবেন{"101": 78}— এরপরd[101]দিলেKeyError! tuple চাবি থাকলেjson.dumpsসরাসরি error দেয়। - ধরন হারানো: tuple JSON-এ গিয়ে list হয়ে ফেরে; set, datetime, Decimal সরাসরি JSON-এ যায়ই না।
- ডুপ্লিকেট চাবি: JSON-এর মানদণ্ড ডুপ্লিকেট চাবি নিয়ে অস্পষ্ট; Python-এর
jsonশেষেরটি রাখে, ঠিক dict literal-এর মতো।
এই কারণেই পর্ব ২৬-এ JSON পড়ার পর ডেটা যাচাই করার কথা বলা হবে — বাইরের ডেটায় কোন চাবি থাকবে, কোনটির ধরন কী, তার কোনো নিশ্চয়তা নেই। সেখানেই অংশ ৪-এর d[key] বনাম get-এর সিদ্ধান্ত সবচেয়ে গুরুত্বপূর্ণ হয়ে ওঠে।
মূল উপলব্ধি: দেখতে এক রকম হলেই দুটি জিনিস এক নয়। dict হলো চলমান প্রোগ্রামের ভেতরের একটি জীবন্ত object; JSON হলো ভাষা-নিরপেক্ষ একটি লেখার ফরম্যাট। দুইয়ের সীমানায় (ফাইলে লেখা, API থেকে পড়া) ধরন আর চাবি সবসময় যাচাই করুন।
অনুশীলন
এগুলোর বেশিরভাগে নতুন কোড লিখতে হবে না — অনুমান করা, হাতে চালানো আর মিলিয়ে নেওয়াই আসল কাজ।
-
হাতে চালান: চালানোর আগে লিখুন কী ছাপা হবে — (ক)
d = {"ক": 1, "খ": 2, "ক": 3}, তারপরprint(d, len(d)); (খ)d = {"x": 1},print(d.get("y"), d.get("y", 0), "x" in d, 1 in d); (গ)d = {1: "এক", True: "সত্য", 1.0: "দশমিক"}, তারপরprint(d).(ক)
{'ক': 3, 'খ': 2} 2— একই চাবি দুবার; শেষ মান 3 জেতে, কিন্তু চাবির জায়গা প্রথমবারের (প্রথমে)। (খ)None 0 True False—inশুধু চাবি দেখে, 1 একটি মান। (গ){1: 'দশমিক'}— 1, True, 1.0 সমান ও একই hash-এর, তাই একটিই চাবি; চাবি প্রথমটির (1), মান শেষটির। -
list না dict? প্রতিটির জন্য ঠিক করুন কোনটি ভালো, আর কেন: (ক) একটি ক্রিকেট ইনিংসের প্রতিটি বলের রান, ক্রমানুসারে; (খ) মোবাইল নম্বর দিয়ে গ্রাহকের নাম খোঁজা; (গ) একটি পরীক্ষার ৫০০ জনের রোল দিয়ে নম্বর খোঁজা, যেখানে রোল ১ থেকে ৫০০ পর্যন্ত ধারাবাহিক; (ঘ) একটি বাক্যে কোন অক্ষর কতবার।
(ক) list — ক্রমই আসল তথ্য, আর “৫ নম্বর বল” অবস্থান দিয়ে খোঁজা। (খ) dict — চাবি মোবাইল নম্বর (string হিসেবে, যাতে শুরুর 0 না হারায়)। (গ) দুটিই চলে; রোল ধারাবাহিক বলে list-এ
marks[roll - 1]দ্রুত ও কম মেমরির। তবে রোল বাদ পড়তে পারলে বা ভবিষ্যতে রোল-নম্বর পদ্ধতি বদলাতে পারলে dict নিরাপদ, আরroll - 1-এর off-by-one ঝুঁকিও নেই। (ঘ) dict বাCounter— চাবি অক্ষর, মান গণনা। -
বাগ খুঁজুন: একটি AI এই কোড দিয়েছে বাকির খাতা থেকে যারা পুরো টাকা দিয়েছেন তাদের মুছতে:
for name in dues:, ভেতরেif dues[name] == 0: del dues[name]. কী ঘটবে? দুটি উপায়ে ঠিক করুন।কোনো একজনকে মোছার পরই পরের ঘোরায়
RuntimeError: dictionary changed size during iterationআসবে — এমনকি শেষ চাবিটি মুছলেও, কারণ লুপ পরের চাবি চাওয়ার সময়ই আকার মিলিয়ে দেখে। তবে কারও বাকি শূন্য না থাকলে কিছুই মোছা হয় না, error-ও আসে না — তাই পরীক্ষার ছোট ডেটায় বাগটি লুকিয়ে থাকতে পারে। সমাধান ১:for name in list(dues):— চাবিগুলোর কপির ওপর লুপ। সমাধান ২:dues = {n: amt for n, amt in dues.items() if amt != 0}— যা রাখতে চান তা দিয়ে নতুন dict. -
ছোট কোড লিখুন:
marks = {"রিয়া": 78, "জয়": 91, "সুমি": 65, "তানভীর": 91}. (ক) সর্বোচ্চ নম্বর পাওয়া একজনের নাম বের করুন, এক লাইনে। (খ) নম্বর → নামের list (একই নম্বরের সবাই) বানান। (গ) নম্বরের ক্রমে বড় থেকে ছোট নাম ছাপুন।(ক)
max(marks, key=marks.get)→'জয়'— সমান হলে প্রথমে আসা জন। (খ) সাধারণ উল্টানোয় জয় বা তানভীর হারিয়ে যেত, তাই defaultdict:from collections import defaultdict by_mark = defaultdict(list) for name, mark in marks.items(): by_mark[mark].append(name) print(dict(by_mark)) # {78: ['রিয়া'], 91: ['জয়', 'তানভীর'], 65: ['সুমি']}(গ)
sorted(marks, key=marks.get, reverse=True)→['জয়', 'তানভীর', 'রিয়া', 'সুমি']; সমান নম্বরের দুজন নিজেদের আগের ক্রম রাখে (stable sort).
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমPython-এ d[key] লিখলে dict ভেতরে যে ধাপে মান খোঁজে, তা সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔑 চাবিটির
hash()হিসাব করা - 🗄️ hash থেকে টেবিলের একটি ঘরের নম্বর বের করা
- 🔍 সেই ঘরের চাবির সাথে hash আর
==দিয়ে মিলিয়ে দেখা - ↪️ না মিললে (collision) নির্দিষ্ট নিয়মে পরের ঘরে যাওয়া
- ✅ মিললে মান ফেরত, ফাঁকা ঘরে পৌঁছালে
KeyError
Python-এ d = {"a": 1} হলে d.get("b", 0)-এর মান কী?
- KeyError
- None
- 0
- 1
Python-এ নিচের কোনটি dict-এর চাবি হিসেবে ব্যবহার করা যায় না?
(3, 4)[3, 4]"রাজশাহী"3.5
Python 3.9 বা নতুন version-এ {"x": 1, "y": 2} | {"y": 5, "z": 3}-এর ফল কী?
{'x': 1, 'y': 2, 'z': 3}- TypeError
{'x': 1, 'y': 2, 'y': 5, 'z': 3}{'x': 1, 'y': 5, 'z': 3}
একটি dict-এর ওপর for k in d: লুপের ভেতরে del d[k] চালালে সাধারণত কী ঘটে?
- RuntimeError
- কিছু চাবি চুপচাপ বাদ পড়ে, কোনো error নেই
- সব চাবি মুছে খালি dict হয়
- KeyError
Python-এর dict-এ একটি চাবি খোঁজার গড় time complexity কত?
- O(n)
- O(log n)
- O(1)
- O(n²)
Python 3.7 বা নতুন version-এ d = {"b": 1, "a": 2}, তারপর d["c"] = 3 আর d["b"] = 9 চালালে list(d)-এর মান কী?
['a', 'b', 'c']['b', 'a', 'c']['a', 'c', 'b'][9, 2, 3]
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- dict চাবি দিয়ে মান খোঁজে, অবস্থান দিয়ে নয়। ভেতরে hash table — চাবি → hash → ঘর — তাই খোঁজা, যোগ, মোছা গড়ে O(1).
- থাকার কথা এমন চাবিতে
d[key](না থাকলেKeyError), না থাকতে পারে এমন চাবিতেd.get(key, default).inশুধু চাবি দেখে। d[k] = vযোগ বা হালনাগাদ করে;|আর|=মিলায় (ডান দিক জেতে);pop(k, default)মুছে মান দেয়।- চাবি ঢোকানোর ক্রমে থাকে (Python 3.7 থেকে নিয়ম);
keys()/values()/items()জীবন্ত view; লুপের মাঝে চাবি যোগ বা মোছা নয় —list(d)-এর ওপর লুপ চালান। - চাবি হতে হবে hashable (str, int, অপরিবর্তনীয় tuple); গণনায়
Counter, দলে ভাগেdefaultdict(list), লম্বা if/elif-এর বদলে lookup dict.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৭ · set — অনন্য মান ও সেট-গণিতপরবর্তী পর্বএকই hash table, শুধু চাবি — ডুপ্লিকেট সরানো আর দ্রুত “আছে কি?” প্রশ্ন।
- Python প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — ৮টি মডিউল, কোন পর্ব কখন।
- C++ প্রোগ্রামিং · associative containerতুলনা করুনstd::map আর std::unordered_map — tree বনাম hash table, আর কখন কোনটি।