অধ্যায় 15

ডিকশনারি — কী আর মানের জোড়া

নাম ধরে মান রাখা ও তোলা, KeyError এড়াতে .get(), কী যোগ-বদল-বাদ, .items() দিয়ে ঘোরা, আর কেন লিস্ট কী হতে পারে না।

32 মিনিটPython 3.12
  1. 1সমস্যা
  2. 2বোঝা
  3. 3উদাহরণ
  4. 4অনুমান
  5. 5নিজে করা
  6. 6কঠিন করা

যে সমস্যাটা আমরা সমাধান করছি

তিনজনের নম্বর রাখতে হবে — কার কত, সেটাসহ।

লিস্ট দিয়ে একটাই উপায়: দুটো লিস্ট, পাশাপাশি।

python
names = ["rafi", "ahmed", "bilal"]
marks = [72, 45, 90]

print(names[1], marks[1])
text
ahmed 45

কাজ করছে। কিন্তু এই ব্যবস্থাটা সম্পূর্ণ আপনার শৃঙ্খলার উপর দাঁড়িয়ে আছে, আর পাইথনের কোনো সাহায্য এখানে নেই।

names-এ একজনকে যোগ করে marks-এ ভুলে গেলে? দুটো লিস্টের দৈর্ঘ্য আলাদা হয়ে যায়, আর তারপর names[2] আর marks[2] দুজন আলাদা মানুষের কথা বলে। পাইথন কিছুই বলবে না। names সাজালে? সম্পর্কটা পুরোপুরি ভেঙে যায়, নীরবে।

সমস্যাটার গোড়া এখানে: দুটি জিনিসের মধ্যে সম্পর্কটা কোথাও লেখা নেই। সম্পর্কটা কেবল «একই সূচক» — একটি কাকতাল, যা যেকোনো মুহূর্তে ভেঙে যেতে পারে।

ডিকশনারি সম্পর্কটাকে কোডে লিখে ফেলে। সূচকের বদলে নাম, আর জোড়াটা এক জায়গায় বাঁধা।

এই অধ্যায় শেষে আপনি পারবেন

  • ডিকশনারি বানাতে, আর কী দিয়ে মান তুলতে
  • KeyError এড়াতে .get() ব্যবহার করতে, আর কখন কোনটা ঠিক জানতে
  • কী যোগ করতে, বদলাতে আর বাদ দিতে
  • .keys(), .values(), .items() দিয়ে ঘুরতে
  • বলতে কেন লিস্ট কী হতে পারে না, কিন্তু টাপল পারে

আগে যা জানা লাগবে: টাপল আর আনপ্যাকিং।


ডিকশনারি — কী আর মানের জোড়া

কার্লি বন্ধনী, আর ভেতরে কী: মান জোড়া:

python
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}

print(marks)
print(marks["ahmed"])
print(len(marks))
text
{'rafi': 72, 'ahmed': 45, 'bilal': 90}
45
3

marks["ahmed"] — সূচক নয়, নাম। আর নামটাই কোডে লেখা আছে, তাই ছয় মাস পরেও পড়লে বোঝা যায় কী চাওয়া হচ্ছে।

len() জোড়ার সংখ্যা গোনে, তিনটি।

ক্রম নিয়ে একটি কথা। পাইথন ৩.৭ থেকে ডিকশনারি যে ক্রমে জিনিস ঢোকানো হয়েছে সেই ক্রমই মনে রাখে, আর ছাপার সময়ও সেই ক্রমে দেখায়। কিন্তু ডিকশনারি ব্যবহার করা হয় নাম ধরে খোঁজার জন্য, অবস্থান ধরে নয় — marks[0] লিখলে পাইথন শূন্যকে একটি কী ভেবে খুঁজবে এবং না পেয়ে থেমে যাবে।

নেই এমন কী — KeyError আর .get()

python
marks = {"rafi": 72}
print(marks["dia"])
text
KeyError: 'dia'

KeyError মানে «এই নামে কোনো কী নেই»। লিস্টে একই ভুল IndexError দিত — ভিন্ন নাম, একই ধরনের ভুল।

থেমে যাওয়ার বদলে একটি নিরাপদ উত্তর চাইলে .get():

python
marks = {"rafi": 72}

print(marks.get("dia"))
print(marks.get("dia", 0))
print(marks.get("rafi", 0))
text
None
0
72

দ্বিতীয় আর্গুমেন্টটি হলো «না পেলে এটা দিও»। না দিলে None আসে।

কোনটা কখন? কী-টা থাকারই কথা হলে marks["rafi"] লিখুন — না থাকলে সেটি একটি বাগ, আর KeyError সেটিকে সাথে সাথে ধরিয়ে দেয়। কী-টা না থাকা স্বাভাবিক হলে .get() লিখুন। সবখানে .get() লিখে বেড়ালে ভুল ডেটাও নীরবে পার হয়ে যাবে, আর সেটাই আপনি খুঁজতে চাইছিলেন।

যোগ, বদল, বাদ

python
marks = {"rafi": 72}

marks["dia"] = 88
marks["rafi"] = 75

print(marks)

del marks["rafi"]
print(marks)
text
{'rafi': 75, 'dia': 88}
{'dia': 88}

একই লাইন দুটো কাজ করে: কী-টা না থাকলে যোগ হয়, থাকলে বদলে যায়। আলাদা কোনো «যোগ করো» মেথড নেই।

আর এখান থেকেই একটি গুরুত্বপূর্ণ নিয়ম আসে — একটি কী একবারই থাকতে পারে:

python
marks = {"rafi": 72, "rafi": 90}
print(marks)
text
{'rafi': 90}

কোনো ত্রুটি নেই। দ্বিতীয়টি প্রথমটিকে চুপচাপ মুছে দিয়েছে। ফাইল থেকে ডেটা পড়ার সময় এটি নিয়মিত ঘটে, আর রেকর্ড হারানোর একটি সাধারণ কারণ।

in কী খোঁজে, মান নয়

python
marks = {"rafi": 72, "ahmed": 45}

print("rafi" in marks)
print(72 in marks)
text
True
False

72 মান হিসেবে আছে ঠিকই, কিন্তু in কেবল কী-গুলোর মধ্যে খোঁজে। মানের মধ্যে খুঁজতে হলে স্পষ্ট করে বলতে হবে: 72 in marks.values()।

ঘোরা — তিনভাবে

python
marks = {"rafi": 72, "ahmed": 45}

for name in marks:
    print(name)

print("---")
for name, mark in marks.items():
    print(name, mark)

print("---")
print(list(marks.keys()))
print(list(marks.values()))
text
rafi
ahmed
---
rafi 72
ahmed 45
---
['rafi', 'ahmed']
[72, 45]

প্রথমটি খেয়াল করার মতো: for name in marks লিখলে কী পাওয়া যায়, মান নয়। প্রথমবার এটি সবাইকে অবাক করে।

দুটোই দরকার হলে .items(), যা প্রতিটি চক্রে একটি টাপল দেয় — আর name, mark সেটিকে আনপ্যাক করে নেয়। গত অধ্যায়ের আনপ্যাকিং এখানেই সবচেয়ে বেশি কাজে লাগে।

মান নিয়ে হিসাব করতে হলে .values():

python
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}

print(sum(marks.values()))
print(max(marks.values()))
print(round(sum(marks.values()) / len(marks), 2))
text
207
90
69.0

কী কী হতে পারে

কী হতে হলে জিনিসটাকে অপরিবর্তনীয় হতে হয়। লেখা, সংখ্যা, টাপল — চলে। লিস্ট চলে না:

python
d = {}
d[["a", "b"]] = 1
text
TypeError: unhashable type: 'list'

«Unhashable» শব্দটি ব্যাখ্যা করার মতো। ডিকশনারি এত দ্রুত খুঁজে পায় কারণ সে প্রতিটি কী থেকে একটি সংখ্যা হিসাব করে রাখে আর সেই সংখ্যা ধরে সরাসরি জায়গাটায় পৌঁছে যায়। কী-টা পরে বদলে গেলে সেই হিসাবটা ভুল হয়ে যেত, আর জিনিসটা হারিয়ে যেত। তাই পাইথন বদলানো যায় এমন কিছুকে কী হতেই দেয় না।

টাপল বদলানো যায় না, তাই টাপল চমৎকার কী:

python
grid = {}
grid[(0, 0)] = "start"
grid[(1, 2)] = "tree"

print(grid)
print(grid[(1, 2)])
text
{(0, 0): 'start', (1, 2): 'tree'}
tree

গত অধ্যায়ে টাপলের অপরিবর্তনীয়তাকে সুবিধা বলা হয়েছিল। এটি তার প্রথম ব্যবহারিক প্রমাণ।

সবচেয়ে কাজের প্যাটার্ন — গোনা

python
words = ["pen", "bag", "pen", "ink", "pen"]
counts = {}

for word in words:
    counts[word] = counts.get(word, 0) + 1

print(counts)
text
{'pen': 3, 'bag': 1, 'ink': 1}

লাইনটি ভেতর থেকে পড়ুন: counts.get(word, 0) বলছে «এই শব্দের এখনকার গণনা দাও, আগে না থাকলে শূন্য ধরো», তারপর তাতে এক যোগ করে আবার রেখে দেওয়া হচ্ছে।

.get()-এর ডিফল্ট মানটাই এখানে পুরো কাজ করছে — সেটা না থাকলে প্রতিটি নতুন শব্দে KeyError আসত, আর if word in counts: লিখে দুটো শাখা সামলাতে হতো। এই এক লাইনের প্যাটার্নটা আপনি বহুবার লিখবেন।


একটা সম্পূর্ণ উদাহরণ

marks.py:

python
# A dictionary keeps a name attached to its value
marks = {"rafi": 72, "ahmed": 45, "bilal": 90, "dia": 88}

print("Everyone:")
for name, mark in marks.items():
    status = "pass" if mark >= 40 else "fail"
    print(f"  {name:<8} {mark:>3}  {status}")

print()
print("Count    :", len(marks))
print("Total    :", sum(marks.values()))
print("Average  :", round(sum(marks.values()) / len(marks), 2))
print("Highest  :", max(marks.values()))

best = max(marks, key=marks.get)
print("Best     :", best, marks[best])

print("Has dia  :", "dia" in marks)
print("Missing  :", marks.get("nadia", "not recorded"))

marks["dia"] = 91
marks["nadia"] = 60
del marks["ahmed"]

print()
print("After edits:", marks)
text
Everyone:
  rafi      72  pass
  ahmed     45  pass
  bilal     90  pass
  dia       88  pass

Count    : 4
Total    : 295
Average  : 73.75
Highest  : 90
Best     : bilal 90
Has dia  : True
Missing  : not recorded

After edits: {'rafi': 72, 'bilal': 90, 'dia': 91, 'nadia': 60}

তিনটি জিনিস লক্ষ করার মতো।

status = "pass" if mark >= 40 else "fail" — এটি একটি শর্তকে এক লাইনে লেখার উপায়, যা পড়া হয় ডান দিক থেকে: «mark >= 40 হলে "pass", নইলে "fail"»। নবম অধ্যায়ের চার লাইনের if/else এখানে একটিমাত্র মান বেছে নিচ্ছে, তাই এক লাইনই যথেষ্ট। বড় সিদ্ধান্তের জন্য পূর্ণ if লিখুন — এটি কেবল «দুটোর একটি মান» ধরনের কাজের জন্য।

max(marks, key=marks.get) — ত্রয়োদশ অধ্যায়ের key=, নতুন কাজে। max(marks) কী-গুলোর মধ্যে সবচেয়ে বড়টা দিত, অর্থাৎ বর্ণানুক্রমে শেষ নামটা। key=marks.get বলছে প্রতিটি কী-কে তার মান দিয়ে তুলনা করো — তাই সবচেয়ে বেশি নম্বরওয়ালা নামটা আসে। খেয়াল করুন সর্বোচ্চ নম্বরটা পাওয়া সহজ (max(marks.values())), কিন্তু সেটা কার তা জানতে এই কৌশলটা লাগে।

শেষ লাইনের ক্রম। dia আগে থেকেই ছিল, তাই তার মান বদলেছে কিন্তু জায়গা বদলায়নি। nadia নতুন, তাই শেষে যোগ হয়েছে। আর ahmed মুছে গেছে। ডিকশনারি ঢোকানোর ক্রম মনে রাখে — বদলানো একটি মান সেই ক্রমকে নাড়ায় না।


কিছু ভাঙা অবস্থা ও তার সমাধান

KeyError: 'dia' এই নামে কোনো কী নেই। বানান আর অক্ষরের আকার মিলিয়ে দেখুন — "Rafi" আর "rafi" দুটি আলাদা কী। ফাইল থেকে পড়া কী-তে প্রায়ই শেষে একটি স্পেস লেগে থাকে; print(list(d.keys())) দিয়ে দেখলে কোটেশনের ভেতরে সেটা ধরা পড়ে। কী-টা না থাকা স্বাভাবিক হলে .get() ব্যবহার করুন।

TypeError: unhashable type: 'list' একটি লিস্টকে কী বানানোর চেষ্টা। টাপল ব্যবহার করুন — tuple(my_list) সেটি বানিয়ে দেয়।

for x in my_dict লিখলে মান পাচ্ছি না পাওয়ার কথাও নয় — ওটা কী দেয়। মানের জন্য .values(), দুটোর জন্য .items()।

দুটি কী-ই একই, একটি রেকর্ড হারিয়ে গেছে একটি কী একবারই থাকে, আর পরেরটি আগেরটিকে নীরবে বদলে দেয়। একই নামে একাধিক রেকর্ড রাখতে হলে মান হিসেবে একটি লিস্ট রাখুন — সপ্তদশ অধ্যায়ের বিষয়।

TypeError: 'dict' object is not subscriptable বা KeyError: 0 my_dict[0] লেখা হয়েছে। ডিকশনারিতে অবস্থান বলে কিছু নেই; 0 একটি কী হিসেবেই খোঁজা হয়।

ডিকশনারির উপর .sort() চলে না ঠিক তাই — ডিকশনারির .sort() নেই। sorted(d.items()) একটি সাজানো লিস্ট দেয়, আর সেটিই সাধারণত যা দরকার।