নেস্টেড ডেটা — ডিকশনারির লিস্ট
বাস্তব ডেটার আকার: লিস্টের ভেতরে ডিকশনারি, ডিকশনারির ভেতরে লিস্ট। ভেতরে পৌঁছানো, গ্রুপ করা, আর কেন copy() এখানে যথেষ্ট নয়।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
গত তিনটি অধ্যায়ে চারটি পাত্র শেখা হয়েছে — লিস্ট, টাপল, ডিকশনারি, সেট। প্রতিটি উদাহরণে একটি করে, একস্তরে।
বাস্তব ডেটা এভাবে আসে না। একটি অর্ডারের তালিকা আসে এরকম:
customer item quantity price
rafi pen 3 15.0
ahmed bag 1 850.0এখানে দুটি স্তর। বাইরে একটি তালিকা — কয়টি অর্ডার হবে জানা নেই, তাই লিস্ট। ভেতরে প্রতিটি অর্ডার নিজেই একটি রেকর্ড, যার প্রতিটি অংশের একটি নাম আছে — তাই ডিকশনারি।
একটি API থেকে যা আসে, একটি CSV ফাইল পড়লে যা পাওয়া যায়, একটি ডেটাবেস যা ফেরত দেয় — প্রায় সবসময়ই এই আকার: ডিকশনারির একটি লিস্ট। আজকের অধ্যায়ে নতুন কোনো পাত্র নেই। আছে কেবল সেগুলোকে একটির ভেতরে আরেকটি রেখে ব্যবহার করা, আর তাতে যে ভুলগুলো হয় সেগুলো চেনা।
এই অধ্যায় শেষে আপনি পারবেন
- ডিকশনারির লিস্টের ভেতরে পৌঁছাতে আর তার উপর ঘুরতে
- গভীরে থাকা একটি
KeyErrorবাIndexErrorপড়ে বলতে কোন স্তরে সমস্যা - ডিকশনারির লিস্ট থেকে গ্রুপ করে হিসাব বের করতে
.get()-এর ডিফল্ট দিয়ে অসম্পূর্ণ রেকর্ড সামলাতে- বলতে কেন নেস্টেড ডেটায়
copy()যথেষ্ট নয়
আগে যা জানা লাগবে: সেট — পুনরাবৃত্তিহীন সংগ্রহ।
আকারটা চিনে নেওয়া
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
print(len(orders))
print(orders[0])
print(orders[0]["customer"])
print(orders[1]["price"])2
{'customer': 'rafi', 'item': 'pen', 'quantity': 3, 'price': 15.0}
rafi
850.0orders[0]["customer"] লাইনটাই এই অধ্যায়ের মূল দক্ষতা, আর সেটা বাঁ থেকে ডানে এক ধাপ করে পড়তে হয়:
orders— একটি লিস্টorders[0]— তার প্রথম জিনিস, যা একটি ডিকশনারিorders[0]["customer"]— সেই ডিকশনারিরcustomerকী-এর মান
সন্দেহ হলে থেমে গিয়ে মাঝের ধাপটা ছেপে দেখুন। print(orders[0]) লিখে চোখে দেখা যে ওটা একটি ডিকশনারি — এই একটা অভ্যাস নেস্টেড ডেটার অর্ধেক সমস্যা মিটিয়ে দেয়।
ঘোরা
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
for order in orders:
total = order["quantity"] * order["price"]
print(f"{order['customer']:<8} {order['item']:<6} {total:>8.2f}")rafi pen 45.00
ahmed bag 850.00for order in orders প্রতিটি চক্রে একটি ডিকশনারি দেয়, আর তার ভেতরে নাম ধরে পৌঁছানো যায়। সূচকের হিসাব কোথাও নেই।
f-string-এর ভেতরের কোটেশন। f"{order['customer']}" লাইনে ভেতরে একক কোটেশন ব্যবহার করা হয়েছে, কারণ বাইরের f-string দ্বৈত কোটেশনে লেখা। একই ধরনের কোটেশন ভেতরে-বাইরে দিলে পাইথন বুঝবে না লেখাটা কোথায় শেষ। দুটির একটি ভেতরে রাখুন, অন্যটি বাইরে।অসম্পূর্ণ রেকর্ড
বাস্তব ডেটায় সব রেকর্ডে সব কী থাকে না:
orders = [{"customer": "rafi"}]
print(orders[0]["discount"])KeyError: 'discount'KeyError বলছে কী-টা নেই, কিন্তু কোন রেকর্ডে নেই তা বলছে না। বারো হাজার অর্ডারের মধ্যে একটিতে এটি ঘটলে খুঁজে বের করা কষ্টকর — সেজন্য enumerate দিয়ে ঘুরলে সংখ্যাটা হাতে থাকে।
কী-টা না থাকা স্বাভাবিক হলে .get():
orders = [{"customer": "rafi"}]
print(orders[0].get("discount", 0))0পঞ্চদশ অধ্যায়ের নিয়মটি এখানেও: থাকার কথা হলে বর্গবন্ধনী, না থাকা স্বাভাবিক হলে .get()। নেস্টেড ডেটায় প্রলোভনটা বেশি, কারণ .get() দিয়ে সব ত্রুটি চুপ করানো যায় — কিন্তু তাতে অসম্পূর্ণ ডেটা নীরবে শূন্য হয়ে হিসাবে ঢুকে যায়।
অন্য আকার — ডিকশনারির ভেতরে লিস্ট
by_customer = {
"rafi": ["pen", "ink"],
"ahmed": ["bag"],
}
by_customer["rafi"].append("bottle")
by_customer["dia"] = ["eraser"]
print(by_customer)
for customer, items in by_customer.items():
print(f"{customer}: {len(items)} item(s) - {', '.join(items)}"){'rafi': ['pen', 'ink', 'bottle'], 'ahmed': ['bag'], 'dia': ['eraser']}
rafi: 3 item(s) - pen, ink, bottle
ahmed: 1 item(s) - bag
dia: 1 item(s) - eraserby_customer["rafi"].append("bottle") লাইনটি দুটি ধাপ এক লাইনে: আগে by_customer["rafi"] একটি লিস্ট বের করে আনে, তারপর সেই লিস্টে append করে।
এই আকারটি পঞ্চদশ অধ্যায়ের একটি সমস্যার উত্তর — সেখানে বলা হয়েছিল একটি কী একবারই থাকতে পারে, তাই একজনের একাধিক রেকর্ড রাখা যায় না। মান হিসেবে একটি লিস্ট রাখলে সেটা যায়।
গ্রুপ করা — সবচেয়ে কাজের প্যাটার্ন
ডিকশনারির লিস্ট থেকে «কার কী কী» বানানো:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
customer = order["customer"]
if customer not in grouped:
grouped[customer] = []
grouped[customer].append(order["item"])
print(grouped){'rafi': ['pen', 'ink'], 'ahmed': ['bag']}মাঝের তিনটি লাইনই পুরো কৌশল: কী-টা না থাকলে আগে একটি খালি লিস্ট বসিয়ে নাও, তারপর নির্দ্বিধায় append করো। এটি না করলে প্রথম অর্ডারেই KeyError আসত।
একই জিনিস এক লাইনে লেখা যায়:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
grouped.setdefault(order["customer"], []).append(order["item"])
print(grouped){'rafi': ['pen', 'ink']}setdefault(key, []) বলে «এই কী-টা থাকলে তার মান দাও, না থাকলে এই খালি লিস্টটা বসিয়ে তারপর দাও» — আর যা ফেরত আসে তাতেই append করা হয়। .get()-এর সাথে পার্থক্যটা গুরুত্বপূর্ণ: .get() ডিকশনারিতে কিছু বসায় না, setdefault বসায়।
তিন লাইনের রূপটি পড়তে সহজ, এক লাইনেরটি লিখতে দ্রুত। দুটোই বাস্তব কোডে দেখবেন।
আরও গভীরে
shop = {
"name": "Corner Store",
"staff": [
{"name": "rafi", "shifts": ["mon", "tue"]},
{"name": "dia", "shifts": ["wed"]},
],
}
print(shop["staff"][0]["shifts"][1])
print(len(shop["staff"]))
for person in shop["staff"]:
print(f"{person['name']}: {len(person['shifts'])} shift(s)")tue
2
rafi: 2 shift(s)
dia: 1 shift(s)shop["staff"][0]["shifts"][1] — চারটি ধাপ: ডিকশনারি → লিস্ট → ডিকশনারি → লিস্ট। বাঁ থেকে ডানে পড়লে জটিল নয়, শুধু লম্বা।
তিন স্তরের বেশি গভীরে গেলে থামুন আর ভাবুন। লম্বা শিকল ভেঙে মাঝের ধাপটা একটি নামে রাখলে কোডটা পড়া যায়:
first_person = shop["staff"][0]
print(first_person["shifts"][1])নেস্টেড ডেটায় copy() যথেষ্ট নয়
দ্বাদশ অধ্যায়ে দেখেছিলাম b = a দুটি নামকে একই লিস্টে লাগিয়ে দেয়, আর copy() তার সমাধান। নেস্টেড ডেটায় copy() শুধু এক স্তর রক্ষা করে:
import copy
template = {"name": "", "items": []}
a = template.copy()
b = copy.deepcopy(template)
a["items"].append("pen")
b["items"].append("bag")
print(template)
print(a)
print(b){'name': '', 'items': ['pen']}
{'name': '', 'items': ['pen']}
{'name': '', 'items': ['bag']}a বানানো হয়েছিল copy() দিয়ে — নতুন একটি বাইরের ডিকশনারি, কিন্তু ভেতরের লিস্টটি সেই একই লিস্ট। তাই a["items"].append("pen") মূল template-কেও বদলে দিয়েছে।
b বানানো হয়েছিল deepcopy() দিয়ে, যা ভেতরের সবকিছুরও কপি বানায়। তাই b-এর বদল কোথাও ছড়ায়নি।
copy একটি মডিউল, তাই ব্যবহারের আগে import copy লাগে — মডিউল নিয়ে বিস্তারিত দ্বাবিংশ অধ্যায়ে।
কখনdeepcopyলাগবে? যখন একটি নেস্টেড কাঠামোর কপি নিয়ে সেটি বদলাচ্ছেন, আর মূলটি অক্ষত রাখতে চান। খেয়াল রাখবেনdeepcopyধীর, আর বেশিরভাগ সময় দরকারও হয় না — বেশিরভাগ কোড ডেটা বদলায় না, নতুন ডেটা বানায়।
একটা সম্পূর্ণ উদাহরণ
orders.py:
# The shape data actually arrives in: a list of records
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
{"customer": "rafi", "item": "ink", "quantity": 2, "price": 120.0},
{"customer": "dia", "item": "pen", "quantity": 5, "price": 15.0},
]
print("Customer Item Qty Total")
grand_total = 0.0
for order in orders:
total = order["quantity"] * order["price"]
grand_total = grand_total + total
print(f"{order['customer']:<9} {order['item']:<6} {order['quantity']:>3} {total:>10.2f}")
print(f"{'':<20}{grand_total:>10.2f}")
print()
# Group the spend by customer: the key may not exist yet, so .get supplies a start
spend = {}
for order in orders:
customer = order["customer"]
spend[customer] = spend.get(customer, 0.0) + order["quantity"] * order["price"]
# Sort by amount by putting the amount first in a tuple
pairs = []
for customer, amount in spend.items():
pairs.append((amount, customer))
pairs.sort(reverse=True)
print("Spend per customer, highest first:")
for amount, customer in pairs:
print(f" {customer:<8} {amount:>8.2f}")
distinct = set()
for order in orders:
distinct.add(order["item"])
print()
print("Distinct items:", sorted(distinct))
print("Best customer :", pairs[0][1])Customer Item Qty Total
rafi pen 3 45.00
ahmed bag 1 850.00
rafi ink 2 240.00
dia pen 5 75.00
1210.00
Spend per customer, highest first:
ahmed 850.00
rafi 285.00
dia 75.00
Distinct items: ['bag', 'ink', 'pen']
Best customer : ahmedএই ছোট প্রোগ্রামটিতে গত চারটি অধ্যায়ের সবকটি পাত্র কাজে লেগেছে, আর প্রতিটি ঠিক যেখানে তার জায়গা সেখানেই।
লিস্ট — অর্ডারগুলো, কারণ কয়টি হবে জানা নেই আর ক্রম আছে।
ডিকশনারি — প্রতিটি অর্ডার, কারণ প্রতিটি অংশের একটি নাম দরকার; আর spend, কারণ সেখানে নাম ধরে জমা হচ্ছে।
টাপল — pairs-এর প্রতিটি জোড়া। আর এখানে একটি কৌশল: পরিমাণটা টাপলের প্রথমে রাখা হয়েছে, কারণ পাইথন টাপল সাজায় প্রথম মান ধরে। তাই সাধারণ .sort(reverse=True)-ই টাকার অঙ্ক ধরে সাজিয়ে দেয়, আর key= লাগে না।
সেট — distinct, কারণ প্রশ্নটাই ছিল «কয় রকমের», আর pen দুবার এসেছে।
আর spend.get(customer, 0.0) লাইনটি পঞ্চদশ অধ্যায়ের গোনার প্যাটার্নেরই রূপ — শুধু এক করে বাড়ানোর বদলে টাকার অঙ্ক যোগ হচ্ছে।
কিছু ভাঙা অবস্থা ও তার সমাধান
KeyError: 'discount' — কিন্তু কোন রেকর্ডে? for i, order in enumerate(orders): দিয়ে ঘুরুন, তাহলে ত্রুটির সময় i জানা থাকে। অথবা লুপের ভেতরে print(order) বসিয়ে শেষ যেটি ছাপা হয়েছিল সেটিই দোষী।
TypeError: string indices must be integers প্রায় সবসময় এর মানে আপনি ভেবেছিলেন হাতে একটি ডিকশনারি আছে, কিন্তু আছে একটি লেখা। যেমন orders যদি ডিকশনারির লিস্ট না হয়ে নামের লিস্ট হয়, তাহলে order["customer"] এই বার্তাটি দেবে। print(type(order)) দিয়ে দেখে নিন।
TypeError: list indices must be integers or slices, not str উল্টো ভুল — হাতে একটি লিস্ট, কিন্তু নাম দিয়ে খোঁজা হচ্ছে। সম্ভবত একটি [0] বাদ পড়েছে: orders["customer"] নয়, orders[0]["customer"]।
AttributeError: 'list' object has no attribute 'get' একই শ্রেণির ভুল, আরেক চেহারায়। যে জিনিসটিতে .get() ডাকছেন সেটি ডিকশনারি নয়, লিস্ট।
একটি কপি বদলালাম, মূলটাও বদলে গেল copy() অগভীর — ভেতরের লিস্ট বা ডিকশনারিগুলো ভাগ করা থেকে যায়। import copy করে copy.deepcopy(...) ব্যবহার করুন।
গ্রুপ করতে গিয়ে প্রথম চক্রেই KeyError grouped[key].append(...) লেখার আগে কী-টা তৈরি করা হয়নি। if key not in grouped: দিয়ে খালি লিস্ট বসান, অথবা setdefault ব্যবহার করুন।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
দুই স্তর গভীরে পৌঁছানো হয়েছে। কী ছাপা হবে?
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
]
print(orders[1]["item"])- Abag
- Bpen
- Cahmed
- Dএকটি `KeyError`
গ্রুপ করার চেষ্টা। প্রথম চক্রেই কী হবে?
orders = [
{"customer": "rafi", "item": "pen"},
]
grouped = {}
for order in orders:
grouped[order["customer"]].append(order["item"])
print(grouped)- A`KeyError: 'rafi'` — কী-টা এখনো নেই, তাই তাতে `append` করা যাচ্ছে না
- Bকাজ করে, `{'rafi': ['pen']}` ছাপে
- C`AttributeError` — ডিকশনারির `append` নেই
- D`{}` ছাপে
copy() নিয়ে কপিটিতে দুটি বদল করা হয়েছে। মূলটির কী অবস্থা?
import copy
template = {"name": "", "items": []}
a = template.copy()
a["name"] = "rafi"
a["items"].append("pen")
print(template)- A{'name': '', 'items': ['pen']}
- B{'name': '', 'items': []}
- C{'name': 'rafi', 'items': ['pen']}
- D{'name': 'rafi', 'items': []}
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
library.py নামে একটি ফাইল লিখুন, যেখানে অন্তত ছয়টি বইয়ের একটি লিস্ট থাকবে, আর প্রতিটি বই একটি ডিকশনারি — শিরোনাম, লেখক, বছর আর কপির সংখ্যা।
তারপর:
- সবগুলো বই সাজানো কলামে ছাপান
- মোট কপি আর গড় বছর বের করুন
- লেখক ধরে গ্রুপ করুন —
{"লেখকের নাম": ["বই", "বই"]}আকারে — আর প্রতিটি লেখকের কয়টি বই আছে ছাপান - আলাদা লেখক কতজন, সেট দিয়ে বের করুন
- সবচেয়ে বেশি কপিওয়ালা বইটির শিরোনাম ছাপান
তারপর বাস্তব ডেটার মতো করে ভাঙুন:
- একটি বই থেকে
"year"কী-টা পুরোপুরি মুছে দিন, তারপর প্রোগ্রামটি চালান। কোন লাইনে থামল, আর বার্তাটি কি বলে দিল কোন বইটিতে সমস্যা? - এবার সেই লাইনটিতে
.get("year", 0)ব্যবহার করে আবার চালান। এখন গড় বছরের হিসাবটা কী দাঁড়াল, আর সেটা কি সত্যি?
শেষ প্রশ্নটাই আসল শিক্ষা। .get() ত্রুটিটা থামিয়ে দিয়েছে, কিন্তু একটি অনুপস্থিত বছরকে শূন্য ধরে গড় বের করা একটি ভুল উত্তর — নীরবে। ত্রুটি সরানো আর সমস্যা সমাধান এক জিনিস নয়।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন