লিস্ট, ডিকশনারি, সেট, লুপ আর কম্প্রিহেনশন
পাইথনের চারটি কন্টেইনার কখন কোনটি, লুপ ও শর্ত, কম্প্রিহেনশন, আর অগোছালো টিকিট থেকে শব্দ গুনে এমনভাবে শীর্ষ পাঁচ বের করা যাতে প্রতিবার একই ফল আসে।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
সাপোর্ট টিমের প্রধান জানতে চান, এই সপ্তাহের টিকিটগুলোতে গ্রাহকেরা সবচেয়ে বেশি কী নিয়ে অভিযোগ করছেন। টিকিটগুলো অগোছালো লেখা — কেউ বড় হাতের অক্ষরে লেখেন, কেউ দাঁড়ি-কমা দিয়ে, কেউ একই কথা দুবার। একজন সহকর্মী পাঁচ মিনিটে একটি স্ক্রিপ্ট লিখে তালিকা পাঠালেন: শীর্ষে the, তারপর refund, আর তারপর আলাদা আলাদা করে Refund আর refund.।
দ্বিতীয় বার চালাতেই আরেক সমস্যা: টিকিটের ক্রম একটু বদলাতেই শীর্ষ পাঁচের পঞ্চম শব্দটি বদলে গেল, যদিও কোনো শব্দের গণনা বদলায়নি। রিপোর্ট দেখে কেউ বুঝতে পারলেন না কোনটা ঠিক।
এই সমস্যার উত্তর কোনো বিশেষ লাইব্রেরি নয়; উত্তর হলো পাইথনের চারটি মৌলিক কন্টেইনার — লিস্ট, টাপল, ডিকশনারি, সেট — আর লুপ ও শর্ত ঠিকভাবে ব্যবহার করা। এআই ইঞ্জিনিয়ারের দৈনন্দিন কাজের বড় অংশই এটি: লেখা পরিষ্কার করা, গোনা, ছাঁকা, সাজানো — এমনভাবে, যাতে একই ইনপুট সবসময় একই ফল দেয়।
এই অধ্যায় শেষে আপনি পারবেন
- কাজ অনুযায়ী লিস্ট, টাপল, ডিকশনারি আর সেট থেকে সঠিকটি বেছে নিতে, আর কেন সেটি বলতে
for,while,if/elif/else,enumerateআর.items()দিয়ে পরিষ্কার লুপ লিখতে- লিস্ট, ডিকশনারি আর সেট কম্প্রিহেনশন দিয়ে ডেটা ছাঁকতে ও রূপান্তর করতে
- অগোছালো লেখা থেকে শব্দ বের করে ডিকশনারি ও
Counterদিয়ে গুনতে - সমান গণনার শব্দগুলো একটি স্পষ্ট নিয়মে সাজিয়ে প্রতিবার একই ফল পেতে
আগে যা জানা লাগবে: pathlib, JSON/YAML কনফিগ আর এনভায়রনমেন্ট ভেরিয়েবল — আর uv run দিয়ে একটি স্ক্রিপ্ট চালাতে পারা।
চারটি কন্টেইনার, চারটি কাজ
প্রথমে চারটিকে পাশাপাশি দেখি:
tickets = ["refund", "login", "refund", "billing"]
latest = ("T-1042", "refund", 3)
owners = {"refund": "Maya", "login": "Omar"}
seen = set(tickets)
print(tickets[0], tickets[-1], len(tickets))
print(latest[1])
print(owners["login"])
print(sorted(seen))
print("login" in seen, "shipping" in seen)refund billing 4
refund
Omar
['billing', 'login', 'refund']
True Falseপ্রতিটির একটি আলাদা প্রতিশ্রুতি আছে, আর বাছাই করতে হয় সেই প্রতিশ্রুতি দেখে:
- লিস্ট (
[...]) — ক্রম মেনে চলে, একই মান একাধিকবার রাখে, বদলানো যায়। যখন «পরপর আসা জিনিসের একটি সারি» দরকার: টিকিটের তালিকা, একটি ব্যাচের প্রম্পট। - টাপল (
(...)) — ক্রম মেনে চলে, কিন্তু বানানোর পর বদলানো যায় না। যখন কয়েকটি মান মিলে একটি রেকর্ড, যেমন("T-1042", "refund", 3)— আইডি, বিভাগ, অগ্রাধিকার। অবস্থান এখানে অর্থ বহন করে। - ডিকশনারি (
{key: value}) — কী দিয়ে মান খোঁজা। «কোন বিভাগের দায়িত্বে কে», «কোন শব্দ কতবার» — যেখানেই একটি জিনিস থেকে আরেকটি জিনিসে যেতে হয়। - সেট (
set(...)বা{a, b}) — প্রতিটি মান একবারই, কোনো নির্দিষ্ট ক্রম নেই। যখন প্রশ্ন শুধু «এটি আছে কি না» বা «কতগুলো আলাদা মান»।
sorted(seen) দিয়ে ছাপা হয়েছে কারণ সেট ছাপলে উপাদানগুলোর ক্রম নিশ্চিত নয় — এক রানে এক রকম, অন্য রানে আরেক রকম দেখাতে পারে। প্রোগ্রামের আউটপুট যদি সেটের ক্রমের উপর নির্ভর করে, সেটিও প্রতিবার একই থাকবে না।
inদিয়ে খোঁজা লিস্টে একটি একটি করে সব উপাদান দেখে, আর সেট বা ডিকশনারিতে প্রায় সঙ্গে সঙ্গে উত্তর দেয় — উপাদান দশটি হোক বা দশ লক্ষ। বড় একটি তালিকায় বারবারif word in stop_wordsদেখতে হলেstop_wordsসেট হওয়া উচিত, লিস্ট নয়। কেন এমন হয় আর কতটা পার্থক্য হয়, তা মাপার উপায় কোর্সে সামনে আসছে।
লুপ: কী দিয়ে ঘুরছেন তা নিজেই বলে দিন
টাপল আর ডিকশনারির সাথে লুপ সবচেয়ে পরিষ্কার হয় আনপ্যাকিং দিয়ে — মানগুলো সরাসরি নাম দেওয়া ভেরিয়েবলে:
ticket_id, category, priority = ("T-1042", "refund", 3)
print(ticket_id, category, priority)
owners = {"refund": "Maya", "login": "Omar"}
for category, owner in owners.items():
print(f"{category} -> {owner}")
for position, category in enumerate(["refund", "login"], start=1):
print(position, category)T-1042 refund 3
refund -> Maya
login -> Omar
1 refund
2 login.items() প্রতিবার একটি (key, value) জোড়া দেয়, আর enumerate প্রতিটি উপাদানের সাথে তার ক্রমিক নম্বর — range(len(...)) দিয়ে ইনডেক্স গোনার দরকার পড়ে না। ডিকশনারি পাইথন ৩.৭ থেকে যে ক্রমে কী ঢোকানো হয়েছে সেই ক্রম মনে রাখে, তাই owners-এর লুপ refund দিয়েই শুরু হলো।
.items() ভুলে গেলে কী হয়?
owners = {"refund": "Maya", "login": "Omar"}
for category, owner in owners:
print(category, owner)Traceback (most recent call last):
File "main.py", line 2, in <module>
for category, owner in owners:
^^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)ডিকশনারির উপর সরাসরি লুপ শুধু কী দেয়। প্রথম কী "refund" একটি ছয় অক্ষরের স্ট্রিং, আর পাইথন সেটিকে দুটি ভেরিয়েবলে ভাগ করার চেষ্টা করে — ছয়টি অক্ষর, দুটি জায়গা। বার্তাটি অদ্ভুত লাগে যতক্ষণ না বোঝেন যে স্ট্রিংও একটি ক্রম।
if/elif/else প্রথম যে শর্তটি সত্য হয় সেখানেই থামে, তাই শর্তগুলোর ক্রমই যুক্তি:
latencies_ms = [120, 340, 95, 1800, 210, 2600]
for latency in latencies_ms:
if latency >= 2000:
label = "critical"
elif latency >= 1000:
label = "slow"
else:
label = "ok"
print(latency, label)120 ok
340 ok
95 ok
1800 slow
210 ok
2600 criticalশর্তগুলো উল্টো ক্রমে লিখলে — আগে>= 1000, পরে>= 2000— কোনো ত্রুটি আসে না, কিন্তু2600হয়ে যায়slow, কারণ প্রথম শর্তটিই সত্য হয়ে থেমে যায়।criticalশাখাটি আর কখনো চলে না। সীমা দিয়ে ভাগ করার সময় সবচেয়ে কড়া শর্তটি আগে রাখুন।
for চলে একটি নির্দিষ্ট সংগ্রহের উপর। while চলে যতক্ষণ একটি শর্ত সত্য — যখন আগে থেকে জানা নেই কতবার লাগবে। এপিআই থেকে পাতায় পাতায় ডেটা আনা তার সাধারণ উদাহরণ। এখানে একটি ডিকশনারি এপিআইয়ের ভূমিকা নিয়েছে:
pages = {1: ["t1", "t2"], 2: ["t3"], 3: []}
page = 1
collected = []
while True:
batch = pages[page]
if not batch:
break
collected.extend(batch)
page += 1
print(collected, "pages read:", page - 1)['t1', 't2', 't3'] pages read: 2খালি পাতা পেলেই break। if not batch কাজ করে কারণ খালি লিস্ট মিথ্যাবাচক। এমন লুপে থামার শর্তটি আগে লিখুন, তারপর বাকিটা — while True লিখে থামার শর্ত ভুলে গেলে প্রোগ্রাম চিরকাল চলতে থাকে, কোনো ত্রুটি ছাড়াই।
কম্প্রিহেনশন: একটি সংগ্রহ থেকে আরেকটি
«একটি খালি লিস্ট, একটি লুপ, একটি if, একটি append» — এই চার লাইনের ধাঁচ এত সাধারণ যে পাইথনে তার এক লাইনের রূপ আছে:
latencies_ms = [120, 340, 95, 1800, 210, 2600]
slow = [ms for ms in latencies_ms if ms >= 1000]
seconds = [ms / 1000 for ms in latencies_ms]
labels = {ms: ("slow" if ms >= 1000 else "ok") for ms in latencies_ms}
buckets = {ms // 1000 for ms in latencies_ms}
print(slow)
print(seconds)
print(labels)
print(sorted(buckets))[1800, 2600]
[0.12, 0.34, 0.095, 1.8, 0.21, 2.6]
{120: 'ok', 340: 'ok', 95: 'ok', 1800: 'slow', 210: 'ok', 2600: 'slow'}
[0, 1, 2]পড়ার নিয়ম: [ফল for উপাদান in সংগ্রহ if শর্ত]। বাঁয়ের অংশটি প্রতিটি উপাদান থেকে কী বানাবে; শেষের if কোনগুলো রাখবে। একই আকারে {key: value for ...} ডিকশনারি বানায়, আর {value for ...} সেট — buckets-এ তিনটি আলাদা সেকেন্ড-বালতি, প্রতিটি একবার।
দুটি আলাদা if গুলিয়ে ফেলবেন না। শেষের if ms >= 1000 একটি ছাঁকনি — শর্ত মিথ্যা হলে উপাদানটি বাদ। আর "slow" if ms >= 1000 else "ok" একটি বাছাই — প্রতিটি উপাদান থাকে, শুধু তার মান দুটির একটি হয়। এজন্যই labels-এ ছয়টিই আছে।
কম্প্রিহেনশন একটি নতুন সংগ্রহ বানানোর জন্য। শুধু কিছু ছাপতে বা ফাইলে লিখতে — অর্থাৎ ফল যেখানে ফেলে দেওয়া হবে — সাধারণ for লুপ লিখুন। আর তিন স্তরের নেস্টেড কম্প্রিহেনশন এক লাইনে ধরলেও, পাঁচ লাইনের লুপ প্রায়ই সহকর্মীর জন্য বেশি পাঠযোগ্য।
শব্দ গোনা: হাতে, তারপর Counter দিয়ে
ডিকশনারি দিয়ে গোনার প্রথম চেষ্টা প্রায় সবাই এভাবে করেন:
words = ["refund", "late", "refund", "login", "refund", "late"]
counts = {}
for word in words:
counts[word] += 1Traceback (most recent call last):
File "main.py", line 5, in <module>
counts[word] += 1
~~~~~~^^^^^^
KeyError: 'refund'counts[word] += 1 মানে counts[word] = counts[word] + 1 — আর ডানের counts[word] পড়তে গিয়ে দেখা যায় "refund" কী-টি এখনো নেই। প্রথমবার দেখা শব্দের জন্য একটি শুরুর মান লাগে। .get(key, 0) ঠিক সেটিই দেয়:
words = ["refund", "late", "refund", "login", "refund", "late"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts){'refund': 3, 'late': 2, 'login': 1}কাজটি এত সাধারণ যে স্ট্যান্ডার্ড লাইব্রেরিতে এর জন্য আলাদা একটি ধরন আছে — collections.Counter, ডিকশনারিরই একটি রূপ:
from collections import Counter
words = ["refund", "late", "login", "refund", "login", "late", "slow"]
counts = Counter(words)
print(counts)
print(counts.most_common(2))
print(counts["missing"])Counter({'refund': 2, 'late': 2, 'login': 2, 'slow': 1})
[('refund', 2), ('late', 2)]
0Counter-এ অনুপস্থিত কী পড়লে KeyError নয়, 0 আসে — গোনার জন্য ঠিক যা দরকার। কিন্তু most_common(2) দেখুন: refund, late আর login — তিনটিরই গণনা ২, আর ফেরত এল প্রথম দুটি। কেন login বাদ পড়ল? কারণ সমান গণনায় most_common সেই ক্রম রাখে যে ক্রমে শব্দগুলো প্রথম দেখা গিয়েছিল। লিস্টে login তৃতীয়তে এসেছিল, তাই বাদ। টিকিটের ক্রম বদলালে ফলও বদলাবে — অধ্যায়ের শুরুর সেই রহস্য।
লেখা পরিষ্কার করা: শব্দ আসলে কোনটি?
split() শুধু ফাঁকা জায়গায় ভাঙে:
text = "Refund not received. REFUND please! Login fails; login page is slow."
print(text.split())
print(text.lower().split())['Refund', 'not', 'received.', 'REFUND', 'please!', 'Login', 'fails;', 'login', 'page', 'is', 'slow.']
['refund', 'not', 'received.', 'refund', 'please!', 'login', 'fails;', 'login', 'page', 'is', 'slow.'].lower() বড়-ছোট হাতের পার্থক্য মেটাল, কিন্তু received., please!, slow. এখনো দাঁড়ি-কমাসহ — গোনার সময় slow. আর slow দুটি আলাদা শব্দ। এখানে রেগুলার এক্সপ্রেশন কাজে আসে: কী ফেলে দিতে হবে তা না বলে, বলে দেওয়া হয় শব্দ দেখতে কেমন:
import re
text = "Refund not received. REFUND please! Login fails; login page is slow. Can't pay."
words = re.findall(r"[a-z']+", text.lower())
print(words)['refund', 'not', 'received', 'refund', 'please', 'login', 'fails', 'login', 'page', 'is', 'slow', "can't", 'pay'][a-z']+ মানে «এক বা একাধিক ছোট হাতের ইংরেজি অক্ষর বা অ্যাপোস্ট্রফি»। re.findall লেখায় যত জায়গায় এই নকশা মেলে সব ফেরত দেয়, আর বাকি সব — দাঁড়ি, কমা, সংখ্যা — বাদ পড়ে। can't একটি শব্দই থাকল।
এই নকশায় একটি ফাঁক আছে: কেউ'invalid token'লিখলে প্রথম শব্দটি হবে'invalid— সামনে কোটেশনসহ। পরের উদাহরণে নকশাটি কড়া করা হয়েছে যাতে অ্যাপোস্ট্রফি কেবল দুটি অক্ষরের মাঝে থাকতে পারে। আর মনে রাখুন,[a-z]শুধু ইংরেজি অক্ষর; অন্য লিপির লেখার জন্য আলাদা নকশা লাগবে।
সমান গণনা: সাজানোর নিয়মটি নিজে ঠিক করুন
গণনা থেকে তালিকা বানাতে সাজাতে হয়। তিনটি উপায়, একটি ডিকশনারিতে যেখানে refund আর login দুটিরই গণনা ৩:
counts = {"refund": 3, "late": 2, "login": 3, "slow": 1}
print(sorted(counts, reverse=True))
print(sorted(counts.items(), key=lambda item: item[1], reverse=True))
print(sorted(counts.items(), key=lambda item: (-item[1], item[0])))['slow', 'refund', 'login', 'late']
[('refund', 3), ('login', 3), ('late', 2), ('slow', 1)]
[('login', 3), ('refund', 3), ('late', 2), ('slow', 1)]প্রথম লাইনটি সবচেয়ে বিপজ্জনক, কারণ দেখতে যুক্তিসংগত। ডিকশনারি সাজালে সাজানো হয় কী, মান নয় — তাই এটি বর্ণানুক্রমের উল্টো তালিকা, গণনার সাথে কোনো সম্পর্ক নেই। slow মাত্র একবার এসেছে, অথচ শীর্ষে। কোনো ত্রুটি নেই।
দ্বিতীয় লাইন গণনা দিয়ে সাজায়। কিন্তু refund আর login-এর মধ্যে কে আগে? পাইথনের sorted «স্থিতিশীল» (stable) — সমান মানের উপাদানগুলো ইনপুটে যে ক্রমে ছিল সেই ক্রমেই থাকে। ডিকশনারিতে refund আগে ঢুকেছিল, তাই আগে। অর্থাৎ ফলাফল নির্ভর করছে ডেটা কোন ক্রমে এসেছিল তার উপর।
তৃতীয় লাইন নিয়মটি নিজে বলে দেয়। key একটি টাপল ফেরত দেয়, আর টাপল তুলনা হয় উপাদান ধরে ধরে: প্রথমে -item[1] — ঋণাত্মক গণনা, তাই বড় গণনা আগে — আর সমান হলে item[0], শব্দটি নিজে, বর্ণানুক্রমে। এখন ইনপুট যে ক্রমেই আসুক, ফল একই। reverse=True-এর দরকার নেই, কারণ ঋণাত্মক চিহ্নটিই গণনার ক্রম উল্টে দিয়েছে আর বর্ণানুক্রম সোজা রেখেছে।
রিপোর্ট, টেস্ট বা মূল্যায়নে যেখানেই ফল তুলনা হবে, সমান মানের ক্রমের জন্য একটি স্পষ্ট নিয়ম দিন। নইলে আজকের আর কালকের রিপোর্টে পার্থক্য দেখা যাবে যেখানে ডেটায় আসলে কোনো পার্থক্য নেই।
একটা সম্পূর্ণ উদাহরণ
এবার সব একসাথে: টিকিট থেকে শীর্ষ পাঁচটি শব্দ। স্টপ-ওয়ার্ড — the, and-এর মতো যেসব শব্দ সব লেখায় থাকে কিন্তু কিছু বলে না — বাদ, দুই অক্ষরের শব্দ বাদ, সমান গণনায় বর্ণানুক্রম। top_words.py:
import re
from collections import Counter
STOP_WORDS = {
"a", "and", "for", "i", "is", "it", "my", "not", "of", "on", "the", "to", "was", "with",
}
TICKETS = [
"Refund not received for order 1182. I was charged twice!",
"The app is slow and the login page freezes.",
"Charged twice again. Please refund the second charge.",
"Login fails with 'invalid token' on my phone.",
"Still waiting on my refund, it was promised last week.",
"App slow after the update; login takes a minute.",
]
def tokenize(text: str) -> list[str]:
return re.findall(r"[a-z]+(?:'[a-z]+)?", text.lower())
def top_words(texts: list[str], n: int = 5) -> list[tuple[str, int]]:
counts = Counter(
word
for text in texts
for word in tokenize(text)
if word not in STOP_WORDS and len(word) > 2
)
ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
return ranked[:n]
def print_ranking(ranking: list[tuple[str, int]]) -> None:
if not ranking:
print("no words to rank")
return
width = max(len(word) for word, _ in ranking)
for rank, (word, count) in enumerate(ranking, start=1):
print(f"{rank}. {word:<{width}} {count} {'#' * count}")
if __name__ == "__main__":
print_ranking(top_words(TICKETS))
print_ranking(top_words([]))uv run top_words.py1. login 3 ###
2. refund 3 ###
3. app 2 ##
4. charged 2 ##
5. slow 2 ##
no words to rankএর পেছনের সিদ্ধান্তগুলো:
শব্দের নকশা [a-z]+(?:'[a-z]+)?। এক বা একাধিক অক্ষর, তারপর ঐচ্ছিকভাবে একটি অ্যাপোস্ট্রফি আর আরও অক্ষর। can't একটি শব্দ থাকে, কিন্তু 'invalid token'-এর কোটেশন আর শব্দের অংশ হয় না। (?:...) একটি গ্রুপ যা আলাদা করে ধরে রাখা হয় না — সাধারণ (...) দিলে re.findall পুরো শব্দের বদলে শুধু গ্রুপের অংশ ফেরত দিত।
স্টপ-ওয়ার্ড একটি সেট। প্রতিটি শব্দের জন্য word not in STOP_WORDS দেখা হয়; সেটে সেই খোঁজ তালিকার আকার যাই হোক প্রায় সঙ্গে সঙ্গে হয়।
Counter-কে সরাসরি একটি জেনারেটর দেওয়া। দুটি for আর একটি if মিলে একটি প্রবাহ — প্রতিটি টিকিটের প্রতিটি শব্দ, ছাঁকনি পার হলে। মাঝখানে সব শব্দের একটি বড় লিস্ট বানাতে হয় না। দুটি for-এর ক্রম সাধারণ নেস্টেড লুপের মতোই: বাইরেরটি আগে।
স্পষ্ট টাই-ব্রেক। login আর refund দুটিরই ৩; app, charged, slow আর twice — চারটিরই ২। পঞ্চম স্থানে কে থাকবে তা ঠিক করেছে বর্ণানুক্রম, তাই twice বাদ পড়েছে। নিয়মটি নিখুঁত নয় — twice কম গুরুত্বপূর্ণ বলে বাদ পড়েনি — কিন্তু এটি ঘোষিত আর প্রতিবার এক। রিপোর্ট পড়া কেউ জিজ্ঞেস করলে উত্তর দেওয়া যায়।
খালি ইনপুট আলাদাভাবে সামলানো। max() খালি সংগ্রহ পেলে ValueError: max() iterable argument is empty দেয়। একটি শান্ত সপ্তাহে টিকিট না থাকা স্বাভাবিক ঘটনা, ত্রুটি নয় — তাই print_ranking আগেই দেখে নেয় তালিকা খালি কি না। দ্বিতীয় লাইনের আউটপুট সেটিই দেখাচ্ছে।
গোনা আর দেখানো আলাদা। top_words একটি তালিকা ফেরত দেয়, কিছু ছাপে না; print_ranking শুধু ছাপে। তাই একই গণনা একটি টেস্ট, একটি এপিআই বা একটি ড্যাশবোর্ড থেকে ব্যবহার করা যায়। {word:<{width}} শব্দগুলোকে সবচেয়ে লম্বা শব্দের সমান জায়গায় বাঁয়ে সাজায়, তাই গণনার কলাম এক সারিতে থাকে।
একটি সীমাবদ্ধতা: charged আর charge এখানে দুটি আলাদা শব্দ। একই শব্দের বিভিন্ন রূপকে এক করা আলাদা একটি সমস্যা, আর এআই সিস্টেমে সাধারণত শব্দ গোনার বদলে অর্থ দিয়ে মেলানো হয় — সেই পদ্ধতি কোর্সে সামনে আসছে। কিন্তু সেখানেও লেখা পরিষ্কার করা, ছাঁকা আর স্থির ক্রমে সাজানো — এই অধ্যায়ের কাজগুলোই — প্রথম ধাপ।
কিছু ভাঙা অবস্থা ও তার সমাধান
KeyError: 'refund' counts[word] += 1 এমন একটি কী-এর জন্য, যা ডিকশনারিতে এখনো নেই। counts[word] = counts.get(word, 0) + 1 লিখুন, অথবা collections.Counter ব্যবহার করুন।
ValueError: too many values to unpack (expected 2) ডিকশনারির উপর for key, value in d: লেখা হয়েছে। ডিকশনারির সরাসরি লুপ শুধু কী দেয়; জোড়া পেতে d.items() লিখুন।
TypeError: unhashable type: 'list' একটি লিস্টকে সেটে রাখা বা ডিকশনারির কী বানানোর চেষ্টা হয়েছে। সেটের উপাদান আর ডিকশনারির কী বদলানো যায় না এমন হতে হয়। লিস্টের বদলে টাপল দিন: seen.add(("refund", "late"))।
RuntimeError: dictionary changed size during iteration একটি ডিকশনারির উপর লুপ চলাকালীন তার থেকে কী মোছা বা যোগ করা হয়েছে। মুছে ফেলার বদলে একটি নতুন ডিকশনারি বানান: counts = {word: n for word, n in counts.items() if n >= 2}।
AttributeError: 'tuple' object has no attribute 'append' টাপল বানানোর পর বদলানো যায় না। মানগুলো যদি সত্যিই বাড়তে-কমতে থাকে তবে সেটি লিস্ট হওয়া উচিত ছিল; আর যদি টাপলটি একটি রেকর্ড হয়, তবে নতুন মানসহ একটি নতুন টাপল বানান।
ValueError: max() iterable argument is empty খালি সংগ্রহের উপর max() বা min()। আগে দেখে নিন সংগ্রহটি খালি কি না, অথবা একটি ডিফল্ট দিন: max(lengths, default=0)।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
একজন সহকর্মী গণনা থেকে «সবচেয়ে বেশি আসা শব্দ» বের করছেন। কী ছাপা হবে?
counts = {"refund": 5, "slow": 1, "login": 3}
print(sorted(counts, reverse=True)[0])- A`refund`
- B`('refund', 5)`
- C`slow`
- D`5`
label(2600) চালালে "critical" আসা উচিত, কিন্তু আসে "slow" — কোনো ত্রুটি ছাড়াই। ভুলটি কোথায়?
def label(latency_ms):
if latency_ms >= 1000:
return "slow"
elif latency_ms >= 2000:
return "critical"
return "ok"
print(label(2600))- Aশর্তের ক্রম: `>= 1000` আগে সত্য হয়ে থামে, তাই `>= 2000` কখনো পৌঁছায় না — কড়া শর্তটি আগে রাখতে হবে
- B`return "ok"`-এর আগে একটি `else:` লাগবে
- C`elif`-এর বদলে দ্বিতীয় শর্তেও `if` লিখতে হবে
- D`2600` একটি পূর্ণসংখ্যা, তাই `>=` তুলনার জন্য আগে `float()` করতে হবে
একটি পাইপলাইন দশ লাখ শব্দের প্রতিটির জন্য দেখে সেটি ৩০০টি স্টপ-ওয়ার্ডের একটি কি না। স্টপ-ওয়ার্ডগুলো কোন কন্টেইনারে রাখা উচিত?
- Aস্ট্রিং, সব শব্দ একটি লম্বা লেখায় জুড়ে
- Bটাপল, কারণ স্টপ-ওয়ার্ড বদলায় না
- Cসেট, কারণ `in` দিয়ে খোঁজা আকার যাই হোক প্রায় সঙ্গে সঙ্গে হয়
- Dলিস্ট, কারণ বর্ণানুক্রমে সাজিয়ে রাখা যায়
প্লে প্রো-এর অংশ
+30 XP অপেক্ষায়
প্রো দিয়ে এই অধ্যায়টি খেলুন
পড়া ফ্রি, পুরো অধ্যায়টিই খোলা। প্রতিটি ফ্রি কোর্সের প্রথম 5টি অধ্যায় ফ্রিতে খেলা যায়। অধ্যায় 6 থেকে প্রশ্নের উত্তর দেওয়া, XP পাওয়া আর স্ট্রিক বাড়ানো প্রো-এর অংশ।
প্রো দিয়ে প্লে চালু করুননিজে করুন
শীর্ষ শব্দগুলো এবার বিভাগ ধরে বের করুন। একটি categories.py ফাইলে অধ্যায়ের STOP_WORDS আর tokenize কপি করুন, আর এই টিকিটগুলো যোগ করুন:
TICKETS = [
("billing", "Charged twice for one order."),
("billing", "Refund for the double charge, please."),
("login", "Login fails on the new phone."),
("billing", "Still no refund after the charge."),
("login", "Password reset email never arrives."),
("login", "Login page shows a blank screen."),
]তারপর এমন কোড লিখুন যাতে:
- প্রতিটি বিভাগের জন্য আলাদা গণনা থাকে — একটি ডিকশনারি, যার কী বিভাগের নাম আর মান সেই বিভাগের
Counter(সংকেত:dict.setdefaultআরCounter.update) - স্টপ-ওয়ার্ড আর দুই বা তার কম অক্ষরের শব্দ গোনা হয় না
- প্রতিটি বিভাগের শীর্ষ দুটি শব্দ দেখানো হয়, বেশি গণনা আগে আর সমান গণনায় বর্ণানুক্রমে
- বিভাগগুলো বর্ণানুক্রমে ছাপা হয়, টিকিটের ক্রম যাই হোক
uv run categories.py চালালে ঠিক এটি আসা উচিত:
billing charge=2, refund=2
login login=2, arrives=1তারপর TICKETS লিস্টের ক্রম উল্টে দিয়ে (TICKETS[::-1]) আবার চালান — আউটপুট একটুও বদলায় কি? বদলালে কোন শর্তটি পূরণ হয়নি? শেষে টাই-ব্রেকের অংশটি সরিয়ে শুধু গণনা দিয়ে সাজিয়ে দেখুন: login বিভাগের দ্বিতীয় শব্দটি কোনটি হয়, আর ক্রম উল্টালে সেটি কি একই থাকে?
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
প্রো দিয়ে খেলুন