अध्याय 17

नेस्टेड डेटा — डिक्शनरी की लिस्ट

वास्तविक डेटा का स्वरूप: लिस्ट के अंदर डिक्शनरी, डिक्शनरी के अंदर लिस्ट। अंदर तक पहुँचना, ग्रुप बनाना, और copy() यहाँ पर्याप्त क्यों नहीं है।

35 मिनटPython 3.12
  1. 1समस्या
  2. 2समझें
  3. 3उदाहरण
  4. 4अनुमान
  5. 5स्वयं करें
  6. 6चुनौती

वह समस्या जिसे हम हल कर रहे हैं

पिछले तीन अध्यायों ने चार कंटेनर सिखाए — लिस्ट, टपल, डिक्शनरी, सेट। प्रत्येक उदाहरण ने उनमें से किसी एक का उपयोग किया, एक स्तर गहरा।

वास्तविक डेटा उस तरह नहीं आता है। ऑर्डर की एक सूची इस तरह दिखती है:

text
customer   item     quantity   price
rafi       pen      3          15.0
ahmed      bag      1          850.0

यहाँ दो स्तर हैं। बाहर एक संग्रह — ऑर्डर की संख्या ज्ञात नहीं है, इसलिए एक लिस्ट। अंदर, प्रत्येक ऑर्डर एक रिकॉर्ड है जिसके हर हिस्से का एक नाम है — इसलिए एक डिक्शनरी।

एपीआई से जो वापस आता है, सीएसवी पढ़ने से जो आपको मिलता है, डेटाबेस जो लौटाता है: लगभग हमेशा यही रूप होता है, डिक्शनरी की एक लिस्ट। आज का अध्याय कोई नया कंटेनर पेश नहीं करता है। यह आपके पास मौजूद कंटेनरों को एक-दूसरे के अंदर रखने और आने वाली गलतियों को पहचानने के बारे में है।

इस मिशन के अंत तक आप सीखेंगे

  • डिक्शनरी की एक लिस्ट के अंदर तक पहुँचना और उस पर लूप चलाना
  • एक गहरे KeyError या IndexError को पढ़ना और यह बताना कि किस स्तर पर गलती हुई
  • डिक्शनरी की एक लिस्ट को समूहीकृत (group) और सारांशित करना
  • .get() के डिफ़ॉल्ट मान के साथ अपूर्ण रिकॉर्ड्स को संभालना
  • यह समझना कि नेस्टेड डेटा के लिए copy() पर्याप्त क्यों नहीं है

पूर्वापेक्षाएँ: सेट — डुप्लिकेट्स के बिना एक संग्रह.


संरचना को पहचानना

python
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"])
text
2
{'customer': 'rafi', 'item': 'pen', 'quantity': 3, 'price': 15.0}
rafi
850.0

पंक्ति orders[0]["customer"] इस अध्याय का मुख्य कौशल है, और इसे बाएं से दाएं, एक समय में एक कदम पढ़ा जाता है:

  • orders — एक लिस्ट
  • orders[0] — इसका पहला आइटम, जो एक डिक्शनरी है
  • orders[0]["customer"] — उस डिक्शनरी का customer मान

संदेह होने पर, रुकें और मध्यवर्ती चरण को प्रिंट करें। print(orders[0]) लिखकर यह देखना कि यह एक डिक्शनरी है, एक ऐसी आदत है जो नेस्टेड डेटा की आधी परेशानी दूर कर देती है।

इस पर लूप चलाना

python
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}")
text
rafi     pen       45.00
ahmed    bag      850.00

for order in orders हर बार एक डिक्शनरी देता है, और इसके अंदर आप नाम से चीज़ों तक पहुँचते हैं। कहीं भी कोई इंडेक्स गणित नहीं है।

f-string के अंदर कोट्स: f"{order['customer']}" अंदर सिंगल कोट्स का उपयोग करता है क्योंकि f-string स्वयं डबल कोट्स के साथ लिखी गई है। अंदर और बाहर एक ही प्रकार का उपयोग करने पर पायथन यह नहीं बता सकता कि टेक्स्ट कहाँ समाप्त होता है। एक प्रकार अंदर रखें और दूसरा बाहर।

अपूर्ण रिकॉर्ड्स

वास्तविक डेटा में, प्रत्येक रिकॉर्ड में प्रत्येक की (key) नहीं होती:

python
orders = [{"customer": "rafi"}]
print(orders[0]["discount"])
text
KeyError: 'discount'

KeyError कहता है कि की गायब है, लेकिन यह नहीं कि कौन से रिकॉर्ड में यह गायब है। जब बारह हज़ार में से किसी एक ऑर्डर में ऐसा होता है तो इसे खोजना मुश्किल होता है — यही कारण है कि enumerate के साथ लूप चलाना संख्या को हाथ में रखता है।

जब किसी की का अनुपस्थित होना सामान्य हो, तो .get():

python
orders = [{"customer": "rafi"}]
print(orders[0].get("discount", 0))
text
0

दूसरा रूप — डिक्शनरी के अंदर लिस्ट

python
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)}")
text
{'rafi': ['pen', 'ink', 'bottle'], 'ahmed': ['bag'], 'dia': ['eraser']}
rafi: 3 item(s) - pen, ink, bottle
ahmed: 1 item(s) - bag
dia: 1 item(s) - eraser

पंक्ति by_customer["rafi"].append("bottle") वास्तव में दो कदम हैं: by_customer["rafi"] एक सूची निकालता है, और फिर append उस सूची में जोड़ता है।

यह रूप अध्याय 15 की एक समस्या का उत्तर देता है, जहाँ एक की केवल एक बार मौजूद हो सकती थी। मान को एक सूची बनाएं और वे एक से अधिक रिकॉर्ड रख सकते हैं।

समूहीकरण (Grouping) — सबसे उपयोगी पैटर्न

डिक्शनरी की एक सूची को "किसने क्या खरीदा" में बदलना:

python
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)
text
{'rafi': ['pen', 'ink'], 'ahmed': ['bag']}

बीच की तीन पंक्तियाँ पूरी तकनीक हैं: यदि की गायब है, तो पहले वहाँ एक खाली सूची रखें, फिर बिना किसी चिंता के append करें।

वही चीज़ एक पंक्ति में भी आ सकती है:

python
orders = [
    {"customer": "rafi", "item": "pen"},
    {"customer": "rafi", "item": "ink"},
]

grouped = {}
for order in orders:
    grouped.setdefault(order["customer"], []).append(order["item"])

print(grouped)
text
{'rafi': ['pen', 'ink']}

setdefault(key, []) कहता है "मुझे इस की का मान दें, और यदि इसमें कोई मान नहीं है तो वहाँ यह खाली सूची डालें और फिर मुझे वह दें" — और जो कुछ भी वापस आता है उसमें append कर दिया जाता है।

नेस्टेड डेटा के लिए copy() पर्याप्त नहीं है

अध्याय 12 ने दिखाया कि b = a एक ही सूची पर दो नाम छोड़ता है, और copy() इसका समाधान है। नेस्टेड डेटा के साथ, copy() केवल एक स्तर की सुरक्षा करता है:

python
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)
text
{'name': '', 'items': ['pen']}
{'name': '', 'items': ['pen']}
{'name': '', 'items': ['bag']}

a को copy() के साथ बनाया गया था — एक नई बाहरी डिक्शनरी, लेकिन आंतरिक सूची वही मूल सूची है। इसलिए a["items"].append("pen") ने मूल template को भी बदल दिया।

b को deepcopy() के साथ बनाया गया था, जो अंदर की हर चीज़ की भी प्रतिलिपि बनाता है। इसलिए b में किए गए परिवर्तन कहीं और नहीं फैले।


एक संपूर्ण उदाहरण

orders.py:

python
# डेटा वास्तव में जिस रूप में आता है: रिकॉर्ड्स की एक सूची
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()

# ग्राहक द्वारा खर्च को समूहीकृत करें
spend = {}
for order in orders:
    customer = order["customer"]
    spend[customer] = spend.get(customer, 0.0) + order["quantity"] * order["price"]

# टपल में राशि को पहले रखकर राशि के अनुसार सॉर्ट करें
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])
text
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 में प्रत्येक जोड़ी के लिए। राशि को टपल में पहले रखा गया है, क्योंकि पायथन टपल को उनके पहले मान के आधार पर सॉर्ट करता है।

एक सेट distinct के लिए, क्योंकि सवाल था "कितने अलग-अलग"।


जब यह टूटता है

KeyError: 'discount' — लेकिन किस रिकॉर्ड में? for i, order in enumerate(orders): के साथ लूप चलाएं ताकि विफल होने पर i ज्ञात हो।

TypeError: string indices must be integers लगभग हमेशा इसका मतलब है कि आपने सोचा था कि आपके पास एक डिक्शनरी है जबकि आपके पास एक स्ट्रिंग है। print(type(order)) से जाँचें।

TypeError: list indices must be integers or slices, not str विपरीत गलती — आपके पास एक सूची है और आप नाम से पूछ रहे हैं। आमतौर पर एक [0] छूट गया है: orders[0]["customer"], न कि orders["customer"]।

मैंने एक प्रतिलिपि बदली और मूल भी बदल गया copy() उथला (shallow) है — आंतरिक सूचियाँ और डिक्शनरी साझा रहती हैं। import copy करें और copy.deepcopy(...) का उपयोग करें।