ডিকশনারি — কী আর মানের জোড়া
নাম ধরে মান রাখা ও তোলা, KeyError এড়াতে .get(), কী যোগ-বদল-বাদ, .items() দিয়ে ঘোরা, আর কেন লিস্ট কী হতে পারে না।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
তিনজনের নম্বর রাখতে হবে — কার কত, সেটাসহ।
লিস্ট দিয়ে একটাই উপায়: দুটো লিস্ট, পাশাপাশি।
names = ["rafi", "ahmed", "bilal"]
marks = [72, 45, 90]
print(names[1], marks[1])ahmed 45কাজ করছে। কিন্তু এই ব্যবস্থাটা সম্পূর্ণ আপনার শৃঙ্খলার উপর দাঁড়িয়ে আছে, আর পাইথনের কোনো সাহায্য এখানে নেই।
names-এ একজনকে যোগ করে marks-এ ভুলে গেলে? দুটো লিস্টের দৈর্ঘ্য আলাদা হয়ে যায়, আর তারপর names[2] আর marks[2] দুজন আলাদা মানুষের কথা বলে। পাইথন কিছুই বলবে না। names সাজালে? সম্পর্কটা পুরোপুরি ভেঙে যায়, নীরবে।
সমস্যাটার গোড়া এখানে: দুটি জিনিসের মধ্যে সম্পর্কটা কোথাও লেখা নেই। সম্পর্কটা কেবল «একই সূচক» — একটি কাকতাল, যা যেকোনো মুহূর্তে ভেঙে যেতে পারে।
ডিকশনারি সম্পর্কটাকে কোডে লিখে ফেলে। সূচকের বদলে নাম, আর জোড়াটা এক জায়গায় বাঁধা।
এই অধ্যায় শেষে আপনি পারবেন
- ডিকশনারি বানাতে, আর কী দিয়ে মান তুলতে
KeyErrorএড়াতে.get()ব্যবহার করতে, আর কখন কোনটা ঠিক জানতে- কী যোগ করতে, বদলাতে আর বাদ দিতে
.keys(),.values(),.items()দিয়ে ঘুরতে- বলতে কেন লিস্ট কী হতে পারে না, কিন্তু টাপল পারে
আগে যা জানা লাগবে: টাপল আর আনপ্যাকিং।
ডিকশনারি — কী আর মানের জোড়া
কার্লি বন্ধনী, আর ভেতরে কী: মান জোড়া:
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
print(marks)
print(marks["ahmed"])
print(len(marks)){'rafi': 72, 'ahmed': 45, 'bilal': 90}
45
3marks["ahmed"] — সূচক নয়, নাম। আর নামটাই কোডে লেখা আছে, তাই ছয় মাস পরেও পড়লে বোঝা যায় কী চাওয়া হচ্ছে।
len() জোড়ার সংখ্যা গোনে, তিনটি।
ক্রম নিয়ে একটি কথা। পাইথন ৩.৭ থেকে ডিকশনারি যে ক্রমে জিনিস ঢোকানো হয়েছে সেই ক্রমই মনে রাখে, আর ছাপার সময়ও সেই ক্রমে দেখায়। কিন্তু ডিকশনারি ব্যবহার করা হয় নাম ধরে খোঁজার জন্য, অবস্থান ধরে নয় — marks[0] লিখলে পাইথন শূন্যকে একটি কী ভেবে খুঁজবে এবং না পেয়ে থেমে যাবে।নেই এমন কী — KeyError আর .get()
marks = {"rafi": 72}
print(marks["dia"])KeyError: 'dia'KeyError মানে «এই নামে কোনো কী নেই»। লিস্টে একই ভুল IndexError দিত — ভিন্ন নাম, একই ধরনের ভুল।
থেমে যাওয়ার বদলে একটি নিরাপদ উত্তর চাইলে .get():
marks = {"rafi": 72}
print(marks.get("dia"))
print(marks.get("dia", 0))
print(marks.get("rafi", 0))None
0
72দ্বিতীয় আর্গুমেন্টটি হলো «না পেলে এটা দিও»। না দিলে None আসে।
কোনটা কখন? কী-টা থাকারই কথা হলে marks["rafi"] লিখুন — না থাকলে সেটি একটি বাগ, আর KeyError সেটিকে সাথে সাথে ধরিয়ে দেয়। কী-টা না থাকা স্বাভাবিক হলে .get() লিখুন। সবখানে .get() লিখে বেড়ালে ভুল ডেটাও নীরবে পার হয়ে যাবে, আর সেটাই আপনি খুঁজতে চাইছিলেন।
যোগ, বদল, বাদ
marks = {"rafi": 72}
marks["dia"] = 88
marks["rafi"] = 75
print(marks)
del marks["rafi"]
print(marks){'rafi': 75, 'dia': 88}
{'dia': 88}একই লাইন দুটো কাজ করে: কী-টা না থাকলে যোগ হয়, থাকলে বদলে যায়। আলাদা কোনো «যোগ করো» মেথড নেই।
আর এখান থেকেই একটি গুরুত্বপূর্ণ নিয়ম আসে — একটি কী একবারই থাকতে পারে:
marks = {"rafi": 72, "rafi": 90}
print(marks){'rafi': 90}কোনো ত্রুটি নেই। দ্বিতীয়টি প্রথমটিকে চুপচাপ মুছে দিয়েছে। ফাইল থেকে ডেটা পড়ার সময় এটি নিয়মিত ঘটে, আর রেকর্ড হারানোর একটি সাধারণ কারণ।
in কী খোঁজে, মান নয়
marks = {"rafi": 72, "ahmed": 45}
print("rafi" in marks)
print(72 in marks)True
False72 মান হিসেবে আছে ঠিকই, কিন্তু in কেবল কী-গুলোর মধ্যে খোঁজে। মানের মধ্যে খুঁজতে হলে স্পষ্ট করে বলতে হবে: 72 in marks.values()।
ঘোরা — তিনভাবে
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()))rafi
ahmed
---
rafi 72
ahmed 45
---
['rafi', 'ahmed']
[72, 45]প্রথমটি খেয়াল করার মতো: for name in marks লিখলে কী পাওয়া যায়, মান নয়। প্রথমবার এটি সবাইকে অবাক করে।
দুটোই দরকার হলে .items(), যা প্রতিটি চক্রে একটি টাপল দেয় — আর name, mark সেটিকে আনপ্যাক করে নেয়। গত অধ্যায়ের আনপ্যাকিং এখানেই সবচেয়ে বেশি কাজে লাগে।
মান নিয়ে হিসাব করতে হলে .values():
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
print(sum(marks.values()))
print(max(marks.values()))
print(round(sum(marks.values()) / len(marks), 2))207
90
69.0কী কী হতে পারে
কী হতে হলে জিনিসটাকে অপরিবর্তনীয় হতে হয়। লেখা, সংখ্যা, টাপল — চলে। লিস্ট চলে না:
d = {}
d[["a", "b"]] = 1TypeError: unhashable type: 'list'«Unhashable» শব্দটি ব্যাখ্যা করার মতো। ডিকশনারি এত দ্রুত খুঁজে পায় কারণ সে প্রতিটি কী থেকে একটি সংখ্যা হিসাব করে রাখে আর সেই সংখ্যা ধরে সরাসরি জায়গাটায় পৌঁছে যায়। কী-টা পরে বদলে গেলে সেই হিসাবটা ভুল হয়ে যেত, আর জিনিসটা হারিয়ে যেত। তাই পাইথন বদলানো যায় এমন কিছুকে কী হতেই দেয় না।
টাপল বদলানো যায় না, তাই টাপল চমৎকার কী:
grid = {}
grid[(0, 0)] = "start"
grid[(1, 2)] = "tree"
print(grid)
print(grid[(1, 2)]){(0, 0): 'start', (1, 2): 'tree'}
treeগত অধ্যায়ে টাপলের অপরিবর্তনীয়তাকে সুবিধা বলা হয়েছিল। এটি তার প্রথম ব্যবহারিক প্রমাণ।
সবচেয়ে কাজের প্যাটার্ন — গোনা
words = ["pen", "bag", "pen", "ink", "pen"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts){'pen': 3, 'bag': 1, 'ink': 1}লাইনটি ভেতর থেকে পড়ুন: counts.get(word, 0) বলছে «এই শব্দের এখনকার গণনা দাও, আগে না থাকলে শূন্য ধরো», তারপর তাতে এক যোগ করে আবার রেখে দেওয়া হচ্ছে।
.get()-এর ডিফল্ট মানটাই এখানে পুরো কাজ করছে — সেটা না থাকলে প্রতিটি নতুন শব্দে KeyError আসত, আর if word in counts: লিখে দুটো শাখা সামলাতে হতো। এই এক লাইনের প্যাটার্নটা আপনি বহুবার লিখবেন।
একটা সম্পূর্ণ উদাহরণ
marks.py:
# 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)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()) একটি সাজানো লিস্ট দেয়, আর সেটিই সাধারণত যা দরকার।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
একটি ডিকশনারির উপর সরাসরি লুপ চালানো হয়েছে। কী ছাপা হবে?
marks = {"rafi": 72, "ahmed": 45}
for name in marks:
print(name)- Arafi ahmed
- B72 45
- Crafi 72 ahmed 45
- D('rafi', 72) ('ahmed', 45)
নেই এমন একটি কী চাওয়া হয়েছে। কী হবে, আর নিরাপদ বিকল্প কী?
marks = {"rafi": 72}
print(marks["dia"])- A`KeyError` — নিরাপদ বিকল্প `marks.get("dia")`, যা `None` দেয়
- B`None` ছাপা হয়
- C`IndexError`
- Dখালি লাইন ছাপা হয়
গোনার প্যাটার্ন। কী ছাপা হবে?
words = ["pen", "bag", "pen"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts)- A{'pen': 2, 'bag': 1}
- B{'pen': 1, 'bag': 1}
- C{'pen': 3, 'bag': 1}
- Dএকটি `KeyError`
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
stock.py নামে একটি ফাইল লিখুন, যেখানে অন্তত ছয়টি পণ্যের একটি ডিকশনারি থাকবে — পণ্যের নাম কী, আর দাম মান।
তারপর:
- প্রতিটি পণ্য আর তার দাম সাজানো কলামে ছাপান
- মোট, গড়, সর্বোচ্চ আর সর্বনিম্ন দাম ছাপান
- সবচেয়ে দামি পণ্যটির নাম বের করুন — শুধু দাম নয়
- একটি নতুন পণ্য যোগ করুন, একটির দাম বদলান, আর একটি বাদ দিন; তারপর আবার পুরোটা ছাপান
.get()দিয়ে এমন একটি পণ্যের দাম চান যা নেই, আর ডিফল্ট হিসেবে একটি বার্তা দিন
তারপর একটি গোনার প্রোগ্রাম লিখুন: একটি লিস্টে কয়েকটি শব্দ রাখুন (কিছু পুনরাবৃত্তিসহ), আর counts.get(word, 0) + 1 প্যাটার্নটি দিয়ে প্রতিটি কতবার আছে গুনে ছাপান।
শেষে ইচ্ছে করে ভাঙুন: নেই এমন একটি কী বর্গবন্ধনী দিয়ে চান, আর একটি লিস্টকে কী বানানোর চেষ্টা করুন। দুটি আলাদা ত্রুটি আসবে — কোনটা কেন, নিজেকে বুঝিয়ে বলুন।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন