সরাসরি পর্বে যান
পর্ব ০৯ · ৫৫-এর মধ্যে · মডিউল ২

string — slicing, method আর বাংলা Unicode

Python Strings — Slicing, Methods & Bangla Unicode

Taught in Bangla, this lesson explains Python strings: indexing and slicing, immutability, everyday string methods, how Bangla text is stored as Unicode code points with ord() and chr(), comparison and look-alike text traps, and str versus bytes in UTF-8.

এই পর্বে যা শিখবেন

  • string আসলে কী — অপরিবর্তনীয় Unicode code point-এর সারি, আর কেন “বদলানো” মানে নতুন string বানানো
  • string লেখার সব উপায় — একক, জোড়া ও তিন কোট, escape sequence আর raw string
  • Indexing, negative index আর slicing দিয়ে যেকোনো অংশ বের করা
  • রোজকার কাজের method — ফোন নম্বর পরিষ্কার, URL slug বানানো, SMS-এ শব্দ গোনা
  • বাংলা লেখায় code point, grapheme, ord()/chr(), UTF-8 বাইট আর normalization — যেখানে AI-ও প্রায়ই ভুল করে

১ · string আসলে কী — অক্ষরের এমন সারি, যা বদলায় না

আগের পর্বগুলোতে আপনি সংখ্যা নিয়ে হিসাব করেছেন, অপারেটর দিয়ে তুলনা করেছেন। কিন্তু বাস্তব প্রোগ্রামের বড় অংশ সংখ্যা নয় — লেখা। মানুষের নাম, ঠিকানা, মোবাইল নম্বর, SMS, পণ্যের নাম, ওয়েবসাইটের লিংক, AI চ্যাটবটের প্রশ্ন-উত্তর — সবই লেখা। Python-এ লেখা রাখার type হলো string, ছোট নামে str.

প্রথমে একটি মানসিক মডেল তৈরি করুন। একটি string হলো অক্ষরের একটি সারি — ঠিক যেন ট্রেনের বগি। প্রতিটি বগিতে একটি করে অক্ষর, আর বগিগুলোর একটি নির্দিষ্ট ক্রম আছে। প্রথম বগির নম্বর 0, তারপর 1, 2… এভাবে। এই ধরনের ক্রমবদ্ধ জিনিসকে Python বলে sequence. পরে list আর tuple-ও (পর্ব ১৪, ১৫) sequence হিসেবে দেখবেন, আর এই পর্বে শেখা indexing-slicing হুবহু সেখানেও কাজ করবে।

কিন্তু “অক্ষর” বলতে Python ঠিক কী বোঝে? Python 3-এ প্রতিটি অক্ষর হলো একটি Unicode code point — পৃথিবীর প্রায় সব লিপির প্রতিটি চিহ্নকে দেওয়া একটি নির্দিষ্ট নম্বর। ইংরেজি A-এর নম্বর 65, বাংলা অ-এর নম্বর 2437 (হেক্সাডেসিমালে U+0985). Python-এর কাছে "অমর" মানে তিনটি নম্বরের সারি। এজন্যই Python 3-এ বাংলা লেখা কোনো বাড়তি ঝামেলা ছাড়াই চলে।

মূল ধারণা

এই পর্বের দুটি বড় সত্য মনে রাখুন। এক: string immutable — একবার তৈরি হলে তার ভেতরের একটি অক্ষরও বদলানো যায় না। আপনি কখনো string “বদলান” না; পুরোনোটি থেকে নতুন string বানান, আর নামটি নতুনটিতে সরিয়ে নেন। দুই: বাংলা লেখা শুধুই Unicode, কিন্তু পর্দায় যা দেখতে একটি অক্ষর, ভেতরে তা কয়েকটি code point হতে পারে। এই দুটি ধারণা বুঝলে string-এর প্রায় সব “অদ্ভুত” আচরণ সহজ হয়ে যায়।

পাথরে খোদাই করা একটি নামফলকের কথা ভাবুন — যেমন কোনো সেতুর উদ্বোধনী ফলক। একবার খোদাই হয়ে গেলে একটি অক্ষর মুছে আরেকটি বসানো যায় না। নাম বদলাতে হলে নতুন একটি ফলক বানাতে হয়, আর পুরোনোটি সরিয়ে নতুনটি টাঙাতে হয়। Python-এর string ঠিক এমন ফলক। আর পর্ব ০৫-এ শেখা লেবেলের ছবি মনে করুন: s = s.upper() লিখলে পুরোনো ফলক বদলায় না — একটি নতুন ফলক তৈরি হয়, আর s লেবেলটি সেখানে সরে যায়।

string লেখার উপায় — একক, জোড়া আর তিন কোট

Python-এ string লেখা যায় একক কোট ('চা') বা জোড়া কোট ("চা") দিয়ে — দুটির মধ্যে কোনো পার্থক্য নেই। সুবিধা হলো, লেখার ভেতরে একটি ধরনের কোট থাকলে বাইরে অন্যটি ব্যবহার করা যায়: "It's Dhaka" বা 'তিনি বললেন "আসছি"'. একাধিক লাইনের লেখার জন্য আছে তিনটি কোট — """...""" বা '''...''', যাকে বলে triple-quoted string. এর ভেতরে Enter চাপলে সেই নতুন লাইনটিও string-এর অংশ হয়ে যায়। SMS-এর টেমপ্লেট, ইমেইলের বডি বা ফাংশনের docstring লিখতে এটি কাজে লাগে।

name = 'রহিম'
quote = "তিনি বললেন, \"কাল আসছি\""
sms = """প্রিয় গ্রাহক,
আপনার বিল পরিশোধ হয়েছে।
ধন্যবাদ।"""
path = r"C:\Users\rahim\new_folder"

Escape sequence আর raw string

কিছু অক্ষর সরাসরি টাইপ করা যায় না বা কোটের সাথে গুলিয়ে যায়। তাদের জন্য backslash (\) দিয়ে শুরু বিশেষ সংকেত আছে, যার নাম escape sequence. পর্ব ০৪-এ কয়েকটি দেখেছেন; এখানে একসাথে:

লিখবেনমানেকখন লাগে
\nনতুন লাইন (newline)এক string-এ কয়েক লাইন
\tTab — বড় ফাঁকা জায়গাকলাম আকারে সাজানো
\\একটি আসল backslashWindows-এর ফাইল path
\" ও \'কোট চিহ্ন, string শেষ না করেকোটের ভেতরে কোট
\u0985নম্বর দিয়ে Unicode অক্ষর (এখানে অ)অদৃশ্য বা টাইপ করা কঠিন অক্ষর

এখন একটি ফাঁদ দেখুন। "C:\new\table" লিখলে Python \n-কে নতুন লাইন আর \t-কে tab ভাবে — আপনার ফাইলের path ভেঙে যায়! সমাধান হলো raw string: কোটের আগে r লিখুন — r"C:\new\table". এতে backslash আর বিশেষ কিছু নয়, সাধারণ অক্ষর মাত্র। Windows path আর regular expression (পর্ব ২৮)-এ backslash অনেক থাকে, তাই সেখানে raw string প্রায় নিয়ম। তবে মনে রাখুন, raw string-এর একেবারে শেষে একটি একা backslash রাখা যায় না — r"C:\folder\" লিখলে SyntaxError আসে। আর ফাইলের path নিয়ে কাজের আধুনিক উপায় pathlib, যা পর্ব ২৫-এ দেখবেন।

Python 2-এ দুই ধরনের লেখা ছিল: str (আসলে বাইটের সারি) আর unicode (আসল অক্ষরের সারি)। দুটি অনেক সময় নিজে থেকে মিশে যেত, আর বাংলা বা যেকোনো অ-ইংরেজি লেখা এলেই হঠাৎ UnicodeDecodeError. ২০০৮ সালের ডিসেম্বরে আসা Python 3 সিদ্ধান্ত নেয় — str মানেই Unicode লেখা, আর বাইটের জন্য আলাদা type bytes. দুটি আর নিজে থেকে মেশে না। এই এক সিদ্ধান্তের জন্যই আজ আপনি name = "রহিম" লিখে নিশ্চিন্তে কাজ করতে পারেন। পুরো গল্প ভাবনার প্রশ্ন ০২-এ।

২ · Indexing আর slicing — সারি থেকে অক্ষর ও টুকরো তোলা

string যেহেতু একটি সারি, প্রতিটি অক্ষরের একটি অবস্থান-নম্বর আছে, যাকে বলে index. Python-এ গোনা শুরু হয় 0 থেকে. s = "Bangladesh" হলে s[0] হলো 'B', s[1] হলো 'a', আর শেষ অক্ষর s[9] হলো 'h'. মোট অক্ষর len(s) = 10, তাই শেষ index সবসময় len(s) - 1.

শেষ থেকে গুনতে চাইলে আছে negative index: s[-1] শেষ অক্ষর, s[-2] শেষের আগেরটি। ফোন নম্বরের শেষ চার অঙ্ক, ফাইলের extension, বাক্যের শেষ চিহ্ন — এসব বের করতে negative index খুব কাজের। আর সীমার বাইরের index চাইলে (s[10] বা s[-11]) Python সোজা IndexError দেয়: IndexError: string index out of range.

Slicing — একসাথে একটি টুকরো

একটি অক্ষর নয়, একটি অংশ চাইলে লিখুন s[start:stop:step]. একে বলে slicing. নিয়ম তিনটি: start-এর ঘর থেকে শুরু, stop-এর ঘরে পৌঁছানোর আগেই থামা (stop নিজে বাদ), আর প্রতিবার step ঘর করে লাফ। যেকোনোটি বাদ দেওয়া যায়: start না দিলে শুরু থেকে, stop না দিলে শেষ পর্যন্ত, step না দিলে 1. এবার "Bangladesh"[1:7:2] ধাপে ধাপে দেখুন:

s = "Bangladesh"start=1, stop=7, step=2
index :   0   1   2   3   4   5   6   7   8   9
অক্ষর  :   B   a   n   g   l   a   d   e   s   h
negative: -10  -9  -8  -7  -6  -5  -4  -3  -2  -1
ঘর 1শুরু এখান থেকে
index :   0   1   2   3   4   5   6   7   8   9
অক্ষর  :   B   a   n   g   l   a   d   e   s   h
            ↑ নেওয়া হলো
ঘর 1 + 2 = 3২ ঘর লাফ
index :   0   1   2   3   4   5   6   7   8   9
অক্ষর  :   B   a   n   g   l   a   d   e   s   h
                    ↑ নেওয়া হলো
ঘর 3 + 2 = 5আবার ২ ঘর লাফ
index :   0   1   2   3   4   5   6   7   8   9
অক্ষর  :   B   a   n   g   l   a   d   e   s   h
                            ↑ নেওয়া হলো
ঘর 5 + 2 = 77 = stop, তাই থামা
index :   0   1   2   3   4   5   6   7   8   9
অক্ষর  :   B   a   n   g   l   a   d   e   s   h
                                    ↑ stop — নেওয়া হয় না
slice-এর অবস্থা
এখনকার ঘর
জমানো ফল
  1. দুই দিক থেকে নম্বর। প্রতিটি অক্ষরের দুটি index: বাম থেকে 0, 1, 2…, আর ডান থেকে -1, -2… তাই s[1] আর s[-9] একই 'a'. আমরা চাইছি s[1:7:2] — ঘর 1 থেকে শুরু, ঘর 7-এর আগে থামা, ২ ঘর করে লাফ।
  2. ঘর 1 → 'a'. start হলো 1, তাই প্রথম অক্ষর 'a'. খেয়াল করুন, 'B' বাদ গেল কারণ তার index 0.
  3. ঘর 3 → 'g'. step 2, তাই ঘর 2-এর 'n' টপকে ঘর 3-এ। ফল এখন 'ag'.
  4. ঘর 5 → 'a'. আবার ২ ঘর লাফ। ফল 'aga'.
  5. ঘর 7 — থামুন। পরের ঘর 7, যা stop-এর সমান। stop-এর ঘর কখনো নেওয়া হয় না, তাই চূড়ান্ত ফল 'aga'. এই “stop বাদ” নিয়মের সুবিধা: s[:4] আর s[4:] জোড়া দিলে হুবহু পুরো s পাওয়া যায়, কোনো অক্ষর দুবার আসে না বা হারায় না।
s[start:stop:step] — start নেওয়া হয়, stop বাদ যায়
s[:k] + s[k:] == s, সবসময়

কয়েকটি প্রচলিত slice মুখস্থ নয়, বুঝে রাখুন:

  • s[:3] — প্রথম তিনটি অক্ষর ('Ban').
  • s[-4:] — শেষ চারটি ('desh'); ফোন নম্বরের শেষ চার অঙ্ক দেখাতে এটাই লাগে।
  • s[::2] — একটি বাদ দিয়ে দিয়ে ('Bnlds').
  • s[::-1] — পুরো string উল্টো ('hsedalgnaB'). step ঋণাত্মক হলে Python ডান থেকে বামে হাঁটে।
  • s[:] — পুরোটা। list-এ এটি কপি বানায়; string immutable বলে CPython এখানে নতুন কপি না বানিয়ে একই object ফেরত দেয় — কোনো ক্ষতি নেই, কারণ কেউ তো ওটা বদলাতে পারবে না।

Slice কখনো IndexError দেয় না। s[10] error দেয়, কিন্তু s[5:100] চুপচাপ যতটুকু আছে ততটুকু দেয় ('adesh'), আর s[20:] দেয় খালি string ''. এটা ইচ্ছাকৃত নকশা: “প্রথম ১০টি অক্ষর দেখাও” লিখতে title[:10] লিখলেই হয়, ছোট শিরোনাম হলেও প্রোগ্রাম ভাঙবে না। তবে এর উল্টো দিকও আছে — ভুল index দিলে error না এসে চুপচাপ খালি string আসতে পারে, যা বাগ লুকিয়ে রাখে।

বাংলা শব্দ উল্টানোর ফাঁদ। "Bangladesh"[::-1] ঠিকঠাক কাজ করে, কিন্তু "বাংলা"[::-1] দেয় 'ালংাব' — একটি ভাঙা, পড়া-যায়-না এমন লেখা। কারণ উল্টানো হয় code point, আর বাংলার কার-চিহ্ন (া, ি) আলাদা code point. উল্টানোর পর া চলে গেছে শুরুতে, কোনো ব্যঞ্জন ছাড়াই। কেন এমন হয়, অংশ ৬-এ পরিষ্কার হবে।

৩ · Immutability — কেন s[0] = "D" চলে না

list-এ marks[0] = 90 লিখে প্রথম ঘর বদলানো যায় (পর্ব ১৪)। string-এ একই চেষ্টা করুন:

>>> city = "dhaka"
>>> city[0] = "D"
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    city[0] = "D"
    ~~~~^^^
TypeError: 'str' object does not support item assignment

শেষ লাইনটি পড়ুন: TypeError — “str object-এর ঘরে নতুন কিছু বসানো যায় না।” এটাই immutability. তাহলে "Dhaka" পাবেন কীভাবে? নতুন একটি string বানিয়ে: city = "D" + city[1:], অথবা সহজে city = city.capitalize(). দুটি ক্ষেত্রেই পুরোনো "dhaka" অক্ষত থাকে; শুধু city নামটি নতুন object-এ সরে যায়। নিচের ধাপগুলোতে দেখুন, বিশেষ করে t নামটির কী হয়:

1s = "dhaka"2t = s3s = s.upper()4print(s, t)
নাম → object (সবুজ লাইনটি এখন চলছে)
s
t
str #1
str #2
  1. Object তৈরি, লেবেল লাগানো। "dhaka" একটি str object তৈরি করল (ধরুন “str #1”), আর s লেবেলটি তার গায়ে লাগল।
  2. t = s — দ্বিতীয় লেবেল। কোনো কপি হয়নি; t-ও str #1-কেই দেখায়। list হলে এখানে বিপদ ছিল (একজন বদলালে অন্যজনও দেখে), কিন্তু string immutable বলে এই শেয়ার পুরোপুরি নিরাপদ।
  3. s.upper() — নতুন object. ডান দিক আগে চলে। upper() str #1-কে ছোঁয়ও না; সে প্রতিটি অক্ষরের বড় হাতের রূপ নিয়ে একেবারে নতুন একটি string — str #2, 'DHAKA' — বানায়। এই মুহূর্তে s এখনো পুরোনোটিতেই।
  4. s = ... — লেবেল সরল। এবার = চলে: s লেবেলটি খুলে str #2-এ লাগানো হলো। str #1 বদলায়নি; শুধু তাকে দেখানো নাম একটি কমল। t এখনো তাকেই দেখাচ্ছে।
  5. আউটপুট DHAKA dhaka. প্রমাণ — আসল string কখনো বদলায়নি। নতুনদের একটি সাধারণ ভুল হলো শুধু s.upper() লিখে ভাবা s বদলে গেছে। ফলটি কোনো নামে না ধরলে নতুন string তৈরি হয়ে সঙ্গে সঙ্গে হারিয়ে যায়।

সবচেয়ে প্রচলিত string-বাগ: name.strip() বা text.replace("a", "b") লিখে ফল না রাখা। কোনো string method আসল string বদলায় না — সবাই নতুন string ফেরত দেয়। তাই সবসময় name = name.strip() লিখুন। এই একটি নিয়ম মনে রাখলে বহু ঘণ্টার debugging বাঁচবে।

জোড়া লাগানো আর পুনরাবৃত্তি — + ও *

দুটি string পাশাপাশি জুড়ে দেওয়াকে বলে concatenation, আর Python-এ এর চিহ্ন +: "ঢাকা" + "-" + "১২০৫" দেয় "ঢাকা-১২০৫". আর * একটি string-কে কয়েকবার পুনরাবৃত্তি করে: "-" * 20 দেয় ২০টি ড্যাশের একটি লাইন, রিপোর্টে ভাগ-রেখা টানতে চমৎকার। মনে রাখুন, "বয়স: " + 25 চলবে না — TypeError, কারণ Python নিজে থেকে সংখ্যাকে লেখায় বদলায় না। লিখতে হবে "বয়স: " + str(25), অথবা আরও সুন্দরভাবে f-string দিয়ে (নিচে দেখুন)।

লুপে += বনাম "".join()

ধরুন হাজার হাজার টুকরো জুড়ে একটি বড় রিপোর্ট বানাবেন। স্বাভাবিক চিন্তা: একটি খালি string নিয়ে লুপে বারবার report += line. কিন্তু string immutable — তাই প্রতিটি += আসলে পুরোনো সব লেখা নতুন একটি string-এ কপি করে, তারপর নতুন টুকরো যোগ করে। টুকরো যত বাড়ে, প্রতিবারের কপিও তত বড় হয়। নিচের ছোট উদাহরণে কপির হিসাব রাখুন (লুপ নিজে পর্ব ১২-এ শিখবেন; এখানে শুধু ধারণা):

1out = ""2out += "ab"3out += "cd"4out += "ef"5out2 = "".join(["ab", "cd", "ef"])
প্রতি ধাপে কী তৈরি হলো
নতুন string
এই ধাপে কপি
মোট কপি
  1. শুরু। একটি খালি string. এখনো কোনো কপি নেই।
  2. প্রথম +=. নতুন string 'ab' তৈরি হলো; ২টি অক্ষর কপি।
  3. দ্বিতীয় +=. পুরোনো 'ab' বদলানো যায় না, তাই নতুন ৪ অক্ষরের string বানাতে 'ab' আবার কপি হলো। মোট কপি এখন ৬।
  4. তৃতীয় +=. এবার 'abcd' পুরোটা আবার কপি। মাত্র ৩টি টুকরোতে ১২টি অক্ষর কপি! n টুকরো হলে কপির পরিমাণ মোটামুটি n²-এর মতো বাড়ে — ১০ হাজার লাইনের রিপোর্টে এটি টের পাওয়ার মতো ধীর হতে পারে।
  5. "".join() — একবারে। join আগে সব টুকরোর মোট দৈর্ঘ্য মেপে নেয়, তারপর ঠিক সেই মাপের একটি string বানিয়ে প্রতিটি টুকরো একবারই কপি করে। মোট কপি ৬ — টুকরোর সংখ্যার সাথে সরলরেখায় বাড়ে। তাই অনেক টুকরো জোড়ার Python-এর প্রচলিত উপায়: টুকরোগুলো একটি list-এ জমান, শেষে একবার join.

সৎ থাকি: CPython-এ একটি ভেতরের কৌশল আছে — +=-এর বাম দিকের string-কে আর কেউ না দেখালে কখনো কখনো জায়গাতেই বড় করে নেয়, তাই ছোট লুপে += প্রায়ই যথেষ্ট দ্রুত। কিন্তু এটি নিশ্চয়তা নয়, অন্য implementation-এ নেই, আর PEP 8 নিজেই বলে পারফরম্যান্স-গুরুত্বপূর্ণ জায়গায় "".join() ব্যবহার করতে। দু-চারটি টুকরো হলে + বা f-string-ই সবচেয়ে পরিষ্কার; শত-হাজার টুকরো হলে join.

৪ · String method — লেখা পরিষ্কারের যন্ত্রপাতি

প্রতিটি string object-এর সাথে কিছু ফাংশন জুড়ে থাকে, যাদের ডাকা হয় ডট দিয়ে: name.upper(). object-এর সাথে জোড়া এমন ফাংশনকে বলে method. পর্ব ০৫-এ দেখেছেন, object-এর type ঠিক করে সে কী কী পারে — str type-এর আছে চল্লিশটির বেশি method. সব মুখস্থ করার দরকার নেই; নিচের দলগুলো চিনে রাখলেই রোজকার ৯০% কাজ হয়ে যায়।

দলMethodউদাহরণ → ফল
বড়-ছোট হাতেরupper, lower, title, casefold"dhaka city".title() → 'Dhaka City'
ফাঁকা ছাঁটাstrip, lstrip, rstrip" রহিম ".strip() → 'রহিম'
ভাঙাsplit, rsplit, splitlines"a,b,c".split(",") → ['a', 'b', 'c']
জোড়াjoin"-".join(["01", "05"]) → '01-05'
বদলানোreplace"মো. করিম".replace("মো.", "মোহাম্মদ")
খোঁজাfind, index, count, in"banana".count("a") → 3
শুরু-শেষ যাচাইstartswith, endswith"photo.jpg".endswith(".jpg") → True
ধরন যাচাইisdigit, isalpha, isnumeric, isspace"2026".isdigit() → True
সাজানোcenter, ljust, rjust, zfill"42".zfill(5) → '00042'

কয়েকটি method-এর সূক্ষ্ম দিক আলাদা করে জেনে নিন:

  • split() বনাম split(" "): কিছু না দিয়ে split() ডাকলে যেকোনো পরিমাণ ফাঁকা জায়গা (space, tab, newline) একসাথে ভাগের চিহ্ন ধরা হয়, আর শুরু-শেষের ফাঁকা বাদ যায় — " x y ".split() দেয় ['x', 'y']. কিন্তু "a b".split(" ") দেয় ['a', '', 'b'] — মাঝে একটি খালি টুকরো। ব্যবহারকারীর লেখা শব্দে ভাঙতে প্রায় সবসময় শুধু split().
  • rsplit আর splitlines: "a/b/c.txt".rsplit("/", 1) ডান দিক থেকে একবারই ভাঙে — ['a/b', 'c.txt'], ফাইলের নাম আলাদা করতে কাজের। splitlines() লেখাকে লাইনে ভাঙে, Windows-এর \r\n আর Linux-এর \n দুটোই ঠিকভাবে বোঝে।
  • find বনাম index: দুটোই প্রথম মিলের অবস্থান দেয়। না পেলে find দেয় -1, আর index দেয় ValueError. সাবধান: -1 একটি বৈধ index-ও (শেষ অক্ষর)! তাই s[s.find(x)] না পেলে চুপচাপ শেষ অক্ষর দেবে। শুধু “আছে কি না” জানতে x in s সবচেয়ে পরিষ্কার।
  • join-এর অদ্ভুত চেহারা: "-".join(parts) — ভাগের চিহ্নটি আগে, টুকরোর list পরে। কারণ যেকোনো sequence-এর টুকরো জোড়া যায়, কিন্তু “কী দিয়ে জোড়া হবে” সেটা একটি string-এর কাজ। আর সব টুকরোকে string হতে হবে; "-".join([1, 2]) দেয় TypeError.
  • title()-এর সীমা: "they're".title() দেয় "They'Re" — apostrophe-র পরের অক্ষরও বড় হয়ে যায়। নামের বানান ঠিক করতে title() চোখ বুজে বিশ্বাস করবেন না।

lower() নয়, casefold() — বড়-ছোট হাত না মেনে তুলনা

ব্যবহারকারী ইমেইল লিখলেন Rahim@Gmail.com, ডেটাবেসে আছে rahim@gmail.com. দুটি মেলাতে বড়-ছোট হাতের পার্থক্য উপেক্ষা করতে হবে। ইংরেজির জন্য lower() প্রায় সবসময় কাজ করে, কিন্তু কিছু ভাষায় করে না। জার্মান ß-এর বড় হাতের রূপ SS; "Straße".lower() == "STRASSE".lower() হলো False. casefold() ঠিক এই তুলনার জন্য তৈরি — সে ß-কে ss বানায়, তাই "Straße".casefold() == "STRASSE".casefold() হলো True. নিয়ম: দেখানোর জন্য lower(), তুলনার জন্য casefold(). বাংলা লিপিতে বড়-ছোট হাত নেই, তাই বাংলা লেখায় এই method-গুলো কিছুই বদলায় না।

বাংলা অঙ্ক আর isdigit() — একটি সুখবর, একটি দুঃসংবাদ

সুখবর: Python বাংলা অঙ্ককে আসল অঙ্ক হিসেবে চেনে। "১২৩".isdigit() হলো True, আর int("১২৩") সরাসরি দেয় সংখ্যা 123. ফর্মে কেউ বাংলায় বয়স লিখলেও আপনার কোড চলবে। কিন্তু এটাই আবার ফাঁদ হতে পারে: আপনি যদি isdigit() দিয়ে যাচাই করে ধরে নেন লেখাটি শুধু 0-9, তাহলে ভুল — বাংলা, আরবি বা অন্য লিপির অঙ্কও পাস করবে। শুধু ইংরেজি অঙ্ক চাইলে s.isascii() and s.isdigit() লিখুন। আরেকটি সূক্ষ্মতা: isnumeric() আরও উদার — "½".isnumeric() True, অথচ "½".isdigit() False.

দুঃসংবাদ: "বাংলা".isalpha() হলো False! অবাক হওয়ার মতো, কিন্তু কারণ যৌক্তিক। Unicode-এর হিসাবে ব, ল হলো “অক্ষর” (letter), কিন্তু া (আ-কার) আর ং (অনুস্বার) হলো “চিহ্ন” (mark). isalpha() শুধু তখনই True দেয় যখন প্রতিটি code point letter. তাই কার-চিহ্ন বা হসন্ত আছে এমন প্রায় সব বাংলা শব্দে isalpha() False. “নামে শুধু অক্ষর আছে কি না” যাচাই করতে AI প্রায়ই name.isalpha() লিখে দেয় — বাংলা নামের ফর্মে সেটা প্রায় প্রতিটি সঠিক নামই বাতিল করবে।

এবার সব একসাথে দেখুন — ফোন নম্বর পরিষ্কার, পণ্যের নাম থেকে URL slug, SMS-এ শব্দ খোঁজা, আর কয়েকটি slice. খেয়াল করুন method-গুলো কীভাবে পরপর জোড়া (chain) দেওয়া হয়েছে — প্রতিটি method নতুন string ফেরত দেয়, তাই তার ওপর সঙ্গে সঙ্গে পরের method ডাকা যায়:

phone = "+880 1712-345678"
digits = phone.replace("+", "").replace(" ", "").replace("-", "")
print(digits, digits.isdigit())
print("0" + digits[3:])          # দেশের ভেতরের রূপ

product = "  Samsung Galaxy A15 (128GB)  "
clean = product.strip().lower().replace("(", "").replace(")", "")
slug = "-".join(clean.split())
print(slug)

sms = "আপনার OTP 4821. কাউকে OTP জানাবেন না।"
print(sms.count("OTP"), sms.find("OTP"), sms.find("PIN"))
print("OTP" in sms, sms.startswith("আপনার"))

word = "Bangladesh"
print(word[::-1], word[1:7:2], word[-4:], repr(word[20:]))
print("42".zfill(5), "Total".center(11, "*"))
print(phone)                     # আসলটি বদলায়নি

প্রথম অংশে তিনটি replace পরপর চলেছে, প্রতিটি আগেরটির ফলের ওপর — ফল 8801712345678, আর isdigit() True. digits[3:] দেশের কোড 880 বাদ দেয়, সামনে 0 জুড়ে পাই 01712345678. Slug-এর লাইনে ফাঁকা ছেঁটে, ছোট হাতের করে, বন্ধনী মুছে, split() দিয়ে শব্দে ভেঙে "-".join দিয়ে জোড়া — ফল samsung-galaxy-a15-128gb, যা একটি ওয়েবসাইটের লিংকে বসানোর উপযোগী। SMS-এ OTP আছে ২ বার; প্রথমটির অবস্থান 6 (“আপনার” ৫টি code point, তারপর একটি space); PIN নেই, তাই -1. শেষে repr(word[20:]) দেখায় '' — সীমার বাইরের slice-এ কোনো error নেই। "Total".center(11, "*") দুই পাশে তারা বসিয়ে মোট ১১ ঘর বানায়: ***Total***. আর শেষ লাইন প্রমাণ করে, এত method চালানোর পরও আসল phone একটুও বদলায়নি।

বাস্তব প্রজেক্টে ফোন নম্বর পরিষ্কার করতে সাধারণত “অঙ্ক ছাড়া সব বাদ দাও” নিয়ম লাগে, কারণ মানুষ বন্ধনী, ডট, স্ল্যাশ — যা খুশি লেখে। সেটা লুপ বা comprehension দিয়ে সহজ (পর্ব ১২, ১৮), আর জটিল প্যাটার্নের জন্য আছে regular expression (পর্ব ২৮)। কিন্তু একটি ভালো অভ্যাস এখনই গড়ে তুলুন: সমস্যাটি সাধারণ string method দিয়ে হলে regex ব্যবহার করবেন না. method পড়তে সহজ, ভুল কম, আর সাধারণত দ্রুতও।

এক লাইনে f-string-এর ঝলক

লেখার মধ্যে মান বসানোর সবচেয়ে আধুনিক উপায় f-string: f"{name}-এর বিল {total} টাকা". কোটের আগে একটি f, আর {}-এর ভেতরে যেকোনো নাম বা হিসাব। + আর str()-এর ঝামেলা নেই। দশমিকের ঘর, কলাম সাজানো, হাজারের কমা — এসব format-এর পুরো কৌশল পরের পর্বে (পর্ব ১০) বিস্তারিত।

৫ · বাংলা লেখা ভেতরে — code point, ord() আর chr()

এবার এই পর্বের সবচেয়ে গুরুত্বপূর্ণ অংশ, বিশেষ করে বাংলাভাষী প্রোগ্রামারের জন্য। প্রথম প্রশ্ন: len("বাংলা") কত? চোখে আপনি দেখেন “বা”, “ং”, “লা” — বড়জোর তিনটি অংশ। কিন্তু Python বলে 5. কেন?

কারণ Python গোনে code point, আর বাংলা লেখায় প্রতিটি কার-চিহ্ন, অনুস্বার, হসন্ত আলাদা code point. “বাংলা” ভেতরে আসলে এই পাঁচটি: ব (U+09AC), া (U+09BE, আ-কার), ং (U+0982, অনুস্বার), ল (U+09B2), া (U+09BE). পর্দায় ফন্ট ও রেন্ডারিং ইঞ্জিন এগুলোকে জুড়ে সুন্দর “বাংলা” আঁকে, কিন্তু মেমরিতে এরা পাঁচটি আলাদা নম্বর।

ord() আর chr() — অক্ষর আর নম্বরের মধ্যে যাতায়াত

ord(ch) একটি অক্ষরের code point নম্বর দেয়, আর chr(n) নম্বর থেকে অক্ষর। ord("A") হলো 65, ord("অ") হলো 2437, আর hex(ord("অ")) দেয় '0x985'. উল্টো দিকে chr(0x0995) দেয় 'ক'. Unicode-এ বাংলা লিপির জন্য একটি পুরো খণ্ড বরাদ্দ — U+0980 থেকে U+09FF, মোট ১২৮টি ঘর (সব ঘর অবশ্য ভরা নয়)। স্বরবর্ণ, ব্যঞ্জনবর্ণ, কার-চিহ্ন, হসন্ত, বাংলা অঙ্ক ০–৯ (U+09E6–U+09EF), এমনকি টাকার চিহ্ন ৳ (U+09F3) — সবই এখানে। অসমীয়া লিপিও একই খণ্ড ব্যবহার করে, তাই Unicode-এর নামগুলোতে বাংলাকে বলা হয় “BENGALI”.

এখন সবচেয়ে মজার উদাহরণ দুটি দেখুন — “কি” আর যুক্তাক্ষর “ক্ষ”:

1a = "কি"2print(len(a), list(a))3b = "ক্ষ"4print(len(b), list(b))5print(b[:2])
চোখে বনাম মেমরিতে
চোখে দেখেন
code point
len()
  1. “কি” — দুটি code point. পর্দায় ই-কার (ি) ক-এর বাম পাশে আঁকা হয়, কিন্তু মেমরিতে সে আসে ক-এর পরে: আগে ক (U+0995), তারপর ি (U+09BF). Unicode লেখে উচ্চারণের ক্রমে; কোথায় আঁকা হবে, তা ঠিক করে ফন্ট। পুরোনো non-Unicode বাংলা টাইপিংয়ে ই-কার আগে টাইপ করার যে অভ্যাস ছিল, Unicode-এ তা চলে না।
  2. len(a) → 2. list(a) প্রতিটি code point আলাদা করে দেখায়: ['ক', 'ি']. একা ি পর্দায় সাধারণত একটি ডটেড বৃত্তের সাথে আঁকা হয়, কারণ তার জোড়ার ব্যঞ্জন নেই।
  3. “ক্ষ” — তিনটি code point. যুক্তাক্ষরের আলাদা কোনো code point নেই। এটি তৈরি হয় ক + হসন্ত ্ (U+09CD) + ষ দিয়ে। হসন্ত ফন্টকে বলে “আগের ব্যঞ্জনটি পরেরটির সাথে জুড়ে দাও”, আর ফন্ট তখন যুক্তাক্ষরের আলাদা আকৃতি আঁকে।
  4. len(b) → 3. চোখে একটি অক্ষর, Python-এর কাছে তিনটি। “ক্ষমা” লিখলে len হবে 5 (ক, ্, ষ, ম, া)। ফর্মে “নাম সর্বোচ্চ ২০ অক্ষর” লিখলে মনে রাখুন — Python-এর ২০ আর মানুষের ২০ এক নয়।
  5. b[:2] → ভাঙা ক্. code point ধরে কাটলে যুক্তাক্ষর মাঝখানে ভেঙে যায়: থাকে শুধু হসন্তযুক্ত ক, ষ হারিয়ে গেল। বাংলা লেখা “প্রথম ১০ অক্ষর” কেটে দেখালে (যেমন নোটিফিকেশনের প্রিভিউ) শেষে এমন ভাঙা অক্ষর আসতে পারে।

Code point বনাম grapheme cluster

মানুষ যাকে “একটি অক্ষর” ভাবে, Unicode তার নাম দিয়েছে grapheme cluster — কয়েকটি code point মিলে পর্দায় একটি একক হিসেবে দেখা যায় এমন গুচ্ছ। “কি” একটি grapheme cluster, দুটি code point. Unicode-এর অফিসিয়াল নিয়মে (UAX #29) “বাংলা” ভাগ হয় দুটি cluster-এ — “বাং” আর “লা” — কারণ অনুস্বার আর কার-চিহ্ন আগের ব্যঞ্জনের সাথে লেগে থাকে। আর ২০২৩ সালের Unicode 15.1 থেকে হসন্ত দিয়ে জোড়া “ক্ষ”-এর মতো যুক্তাক্ষরও একটি cluster ধরা হয়। অর্থাৎ একই শব্দের দৈর্ঘ্য তিন রকম হতে পারে: বাইটে, code point-এ আর grapheme-এ।

🔢 বাইট

ফাইলে বা নেটওয়ার্কে কতটুকু জায়গা লাগে। "বাংলা" UTF-8-এ ১৫ বাইট।

len(s.encode("utf-8"))

🔤 Code point

Python-এর len(), index আর slice যা গোনে। "বাংলা"-তে ৫টি।

len(s)

👁️ Grapheme

মানুষ চোখে যা আলাদা আলাদা দেখে। "বাংলা"-তে ২টি (বাং, লা)। Python 3.14-এর standard library-তে এটি সরাসরি গোনার ফাংশন নেই।

বাইরের library লাগে

কোনটি “সঠিক” দৈর্ঘ্য? প্রশ্নটাই আসলে ভুল — নির্ভর করে কাজের ওপর। ডেটাবেসের কলামের মাপ বা SMS-এর খরচ হিসাবের জন্য বাইট বা code point (বাংলা SMS আলাদা encoding-এ যায়, তাই এক SMS-এ ইংরেজির চেয়ে অনেক কম অক্ষর আঁটে)। ব্যবহারকারীকে “আপনার নাম ২০ অক্ষরের বেশি” বলার জন্য grapheme বেশি মানুষ-বান্ধব। ইমোজিতে ব্যাপারটা আরও চরমে যায়: পরিবারের একটি ইমোজি 👨‍👩‍👧 পর্দায় একটি ছবি, কিন্তু ভেতরে ৫টি code point (তিনজন মানুষ আর তাদের জোড়া দেওয়া দুটি অদৃশ্য ZWJ অক্ষর)। grapheme ধরে কাজ করতে Python-এ তৃতীয় পক্ষের regex package-এর \X প্যাটার্ন বা grapheme-জাতীয় library ব্যবহার করা হয় — pip দিয়ে package ইনস্টল করা শিখবেন পর্ব ২৩-এ।

C/C++ জানলে

C-তে string হলো '\0'-এ শেষ হওয়া char-এর array — বদলানো যায়, দৈর্ঘ্য জানতে strlen-কে পুরোটা হাঁটতে হয়, আর সেটা গোনে বাইট, তাই UTF-8-এ strlen("বাংলা") হবে 15. Python-এর str নিজের দৈর্ঘ্য মনে রাখে, len() সঙ্গে সঙ্গে উত্তর দেয়, গোনে code point, আর কখনো বদলায় না — buffer overflow-র ভয়ও নেই। C++-এর std::string-ও বাইট গোনে এবং mutable. গভীরে দেখতে C কোর্সের string পর্ব আর C++-এর string ও string_view পর্ব দেখুন।

৬ · তুলনা, সাজানো আর “একই দেখতে” লেখার ফাঁদ

দুটি string-এর তুলনা (==, <, >) Python করে code point ধরে, বাম থেকে এক এক করে — যেখানে প্রথম অমিল, সেখানেই সিদ্ধান্ত। ইংরেজিতে এর একটি পরিচিত ফল আছে: সব বড় হাতের অক্ষরের code point ছোট হাতের চেয়ে কম ('A' 65, 'a' 97), তাই sorted(["banana", "Apple", "cherry"]) দেয় ['Apple', 'banana', 'cherry'] — আর "apple" হলে সেটি চলে যেত "Zebra"-র পরে! বড়-ছোট হাত না মেনে সাজাতে লিখুন sorted(words, key=str.casefold).

বাংলায় বেশিরভাগ ক্ষেত্রে code point-এর ক্রম বর্ণমালার ক্রমের সাথে মিলে যায়, কারণ Unicode বাংলা খণ্ড বর্ণমালার ক্রমেই সাজানো: "কলা" < "খেজুর" হলো True. কিন্তু সবসময় নয়। ড়, ঢ়, য়-এর একক code point (U+09DC, U+09DD, U+09DF) বসানো হয়েছে খণ্ডের শেষ দিকে, হ-এর (U+09B9) অনেক পরে। তাই সেগুলো দিয়ে লেখা শব্দ code point অনুযায়ী সাজালে চলে যায় হ-এর পরে — বাংলা অভিধানের ক্রমে নয়। ঠিক অভিধানের মতো সাজাতে ভাষাভিত্তিক collation লাগে (যেমন ICU library), যা Python-এর built-in sorted করে না।

একই দেখতে, তবু == হলো False — normalization

এবার একটি সত্যিকারের মাথাব্যথা। “ড়” লেখার দুটি বৈধ উপায় আছে: একক code point U+09DC, অথবা ড (U+09A1) + নুক্তা ় (U+09BC). পর্দায় দুটি হুবহু এক। কিন্তু Python-এর == code point মেলায়, তাই দুটি সমান নয়! একইভাবে ও-কার ো একটি code point (U+09CB) হিসেবেও লেখা যায়, আবার এ-কার ে + আ-কার া জুড়েও। কেউ মোবাইলে এক কীবোর্ডে, কেউ কম্পিউটারে আরেক কীবোর্ডে টাইপ করলে একই নাম ভিন্ন code point-এ জমা হতে পারে — তখন সার্চে “পাওয়া যায়নি”, ডুপ্লিকেট অ্যাকাউন্ট, লগইন ব্যর্থ।

সমাধানের নাম normalization: সব লেখাকে একটি নির্দিষ্ট আদর্শ রূপে বদলে নেওয়া। Python-এর standard library-র unicodedata.normalize("NFC", s) এই কাজ করে। নিয়ম সহজ — বাইরে থেকে আসা লেখা (ফর্ম, ফাইল, API) তুলনা বা জমা করার আগে একই রূপে normalize করুন. NFC কী, NFD কী, আর বাংলায় এর কী অদ্ভুত ব্যতিক্রম আছে — ভাবনার প্রশ্ন ০৫-এ।

in আর not in

একটি string আরেকটির ভেতরে আছে কি না জানতে "OTP" in sms — উত্তর True বা False. এটা পুরো টুকরো খোঁজে, শুধু একটি অক্ষর নয়: "ঢাকা" in "আমার বাড়ি ঢাকায়" হলো True. উল্টোটি "spam" not in subject. এটিও code point ধরে মেলায়, তাই বাংলায় normalize না করা লেখায় একই ফাঁদ আছে।

৭ · str বনাম bytes — UTF-8-এ বাংলা কত বাইট

Python-এর ভেতরে লেখা থাকে code point হিসেবে। কিন্তু ফাইলে লিখতে বা নেটওয়ার্কে পাঠাতে হলে লেখাকে বাইটে বদলাতে হয় — কারণ ডিস্ক আর তার শুধু বাইট বোঝে। code point থেকে বাইট বানানোর নিয়মকে বলে encoding, আর আজকের পৃথিবীর প্রধান encoding হলো UTF-8. "বাংলা".encode("utf-8") দেয় একটি bytes object, আর .decode("utf-8") তাকে আবার str-এ ফেরায়।

UTF-8 একটি পরিবর্তনশীল-দৈর্ঘ্যের encoding: ইংরেজি অক্ষর (code point 0–127) ১ বাইট, আর বাংলা (U+0980–U+09FF) প্রতিটি code point ৩ বাইট। তাই "বাংলা"-এর ৫টি code point UTF-8-এ ১৫ বাইট. অথচ ৫ অক্ষরের একটি ইংরেজি শব্দে লাগে মাত্র ৫ বাইট। ডেটাবেসের কলাম “VARCHAR(50)” বাইটে মাপা হলে ৫০ বাইটে বাংলা আঁটে মাত্র ১৬টির মতো code point — ডেটাবেস ডিজাইনে এটা মনে রাখা জরুরি।

মূল ধারণা

str হলো লেখা, bytes হলো কাঁচা বাইট. প্রোগ্রামের ভেতরে সবসময় str নিয়ে কাজ করুন; ফাইল বা নেটওয়ার্কের সীমানায় একবার decode (বাইট → লেখা) আর বেরোনোর সময় একবার encode (লেখা → বাইট)। একে বলে “Unicode sandwich”. ভুল encoding দিয়ে decode করলে বাংলা লেখা বা...-এর মতো আবর্জনা হয়ে যায়, বা UnicodeDecodeError আসে। ফাইল খোলার সময় encoding="utf-8" কেন সবসময় লিখবেন, তা দেখবেন পর্ব ২৫-এ।

এখন বাংলা লেখার সব ধারণা একসাথে নিজের চোখে যাচাই করুন — code point, ord/chr, যুক্তাক্ষর, UTF-8 বাইট, বাংলা অঙ্ক, isalpha-র ফাঁদ, normalization আর সাজানো:

import unicodedata

word = "বাংলা"
print(len(word), list(word))
print(ord("অ"), hex(ord("অ")), chr(0x0995))
print(len("ক্ষ"), list("ক্ষ"))

data = word.encode("utf-8")
print(type(data).__name__, len(data), data[:3])
print(data.decode("utf-8") == word)

print("১২৩".isdigit(), int("১২৩") + 1)
print(word.isalpha(), "বল".isalpha())

a = "\u09dc"            # ড় — একটি code point
b = "\u09a1\u09bc"      # ড + নুক্তা — দুটি code point
print(a == b, len(a), len(b))
print(unicodedata.normalize("NFC", a) == unicodedata.normalize("NFC", b))
print(sorted(["হাতি", a + "াক", "ঢাকা"]))

প্রথম লাইনে 5 আর পাঁচটি আলাদা code point. তারপর 2437 0x985 ক — অ-এর নম্বর, তার হেক্স রূপ, আর U+0995 থেকে ক। "ক্ষ"-এর দৈর্ঘ্য 3: ক, হসন্ত, ষ। encode-এর ফল একটি bytes object, দৈর্ঘ্য 15, আর প্রথম তিন বাইট b'\xe0\xa6\xac' — এটাই “ব”-এর UTF-8 রূপ। decode করে আবার আসল string ফেরত পাওয়া যায়, তাই True. বাংলা অঙ্ক isdigit()-এ True, আর int("১২৩") + 1 দেয় 124. কিন্তু "বাংলা".isalpha() False (আ-কার আর অনুস্বার “mark”), অথচ কার-চিহ্নহীন "বল" True. দুই রকম “ড়” দেখতে এক হলেও == দেয় False, দৈর্ঘ্য 1 আর 2; NFC-তে normalize করার পর দুটি সমান। শেষ লাইনে code point-এর ক্রমে “ড়াক” চলে গেছে “হাতি”-র পরে — বাংলা অভিধানের ক্রম নয়।

পরীক্ষা ও ইন্টারভিউতে
  • Python-এ string কি mutable? — না, str immutable; s[0] = "x" দেয় TypeError, আর সব method নতুন string ফেরত দেয়।
  • s[::-1] কী করে? — step -1 দিয়ে শেষ থেকে শুরু পর্যন্ত হেঁটে string উল্টে দেয় (code point ধরে; বাংলায় কার-চিহ্ন ভেঙে যেতে পারে)।
  • find আর index-এর পার্থক্য? — না পেলে find দেয় -1, index দেয় ValueError.
  • অনেক string জোড়ার দক্ষ উপায়? — টুকরোগুলো list-এ রেখে "".join(parts); লুপে বারবার += প্রতিবার কপি করে।
  • len("বাংলা") কত, আর UTF-8-এ কত বাইট? — ৫টি code point, ১৫ বাইট (প্রতিটি বাংলা code point ৩ বাইট)।

৮ · AI-এর যুগে — বাংলা লেখায় AI-এর কোড যাচাই

string-এর কাজে AI চ্যাটবট সাধারণত দ্রুত, কাজের কোড দেয়। সমস্যা হলো, AI-এর শেখার ডেটার বড় অংশ ইংরেজি কোড আর ইংরেজি উদাহরণ। তাই সে প্রায়ই চুপচাপ ধরে নেয় “একটি দেখা অক্ষর = একটি char” — যা ইংরেজিতে ঠিক, বাংলায় ভুল। নিচের ভুলগুলো বাস্তবে বারবার দেখা যায়:

AI প্রায়ই যা লেখেসমস্যাভালো উপায়
if len(name) > 20 দিয়ে বাংলা নামের সীমাcode point গোনে, চোখের অক্ষর নয়; যুক্তাক্ষরে সীমা দ্রুত ফুরায়সীমা কোন এককে, তা ঠিক করুন; দরকারে grapheme গুনুন
name.isalpha() দিয়ে নাম যাচাইকার-চিহ্নযুক্ত প্রায় সব বাংলা নাম বাতিলঅনুমোদিত অক্ষরের নিয়ম আলাদা করে লিখুন, বাংলা নাম দিয়ে টেস্ট করুন
a.lower() == b.lower()কিছু ভাষায় ভুল ফল (ß, তুর্কি i)a.casefold() == b.casefold()
বড় লুপে result += pieceপ্রতিবার পুরো লেখা কপিlist-এ জমিয়ে শেষে "".join()
ছোট কাজে regex (re.sub(r"\s+", ...))পড়তে কঠিন, ভুলের সুযোগ বেশি" ".join(s.split())-এর মতো method
text[:50] + "..." দিয়ে প্রিভিউবাংলা যুক্তাক্ষর মাঝখানে কেটে যায়শব্দের সীমায় কাটুন, বা grapheme ধরে
normalize না করে == বা inএকই দেখতে লেখা অমিলতুলনার আগে unicodedata.normalize("NFC", s)

AI-কে string-এর কোড লিখতে বললে শুরুতেই বলে দিন যে লেখা বাংলাও হতে পারে, আর পরীক্ষার উদাহরণ চেয়ে নিন। নিচের কাঠামোটি কপি করে ব্যবহার করুন:

আমি Python 3.14 ব্যবহার করি। একটি ফাংশন লিখে দাও যা একটি রেজিস্ট্রেশন ফর্মের "পূর্ণ নাম" যাচাই করে — নাম বাংলা বা ইংরেজি দুটোই হতে পারে। শর্ত: ১. মনে রাখো, বাংলায় একটি দেখা অক্ষর কয়েকটি code point হতে পারে (কার-চিহ্ন, হসন্ত, যুক্তাক্ষর)। দৈর্ঘ্যের সীমা কোন এককে গুনছ — code point না grapheme — স্পষ্ট লেখো। ২. isalpha() ব্যবহার করলে আগে বলো বাংলা কার-চিহ্নে এটি কী ফল দেয়। ৩. তুলনা বা জমা করার আগে Unicode normalization (NFC) লাগবে কি না, ব্যাখ্যা দাও। ৪. regex ছাড়া সাধারণ string method দিয়ে হলে সেটাই আগে দাও। ৫. "মোঃ সাকিব আল হাসান", "ক্ষমা রায়", "Rahim Uddin" আর ফাঁকা নাম দিয়ে টেস্ট কেস দাও, প্রতিটির প্রত্যাশিত ফলসহ।

string-এর কাজে AI-এর কোড পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১Unicode কীভাবে জন্মাল, আর বাংলা লিপি সেখানে কীভাবে জায়গা পেল? Unicode আসার আগে কম্পিউটারে বাংলা লেখা কেমন ছিল?

১৯৮০-এর দশকে কম্পিউটারের লেখা ছিল এক বিশৃঙ্খল জগৎ। ASCII মাত্র ১২৮টি ঘর দিত — ইংরেজি অক্ষর, অঙ্ক আর কিছু চিহ্ন। প্রতিটি দেশ, প্রতিটি কোম্পানি বাকি ঘরগুলো নিজের মতো ব্যবহার করত: একই বাইট এক কম্পিউটারে গ্রিক অক্ষর, আরেকটিতে রুশ, আরেকটিতে অন্য কিছু। একটি ফাইল এক দেশ থেকে আরেক দেশে গেলেই আবর্জনা হয়ে যেত।

১৯৮৭–৮৮ সালে Xerox-এর Joe Becker আর Apple-এর Lee Collins ও Mark Davis একটি সার্বজনীন অক্ষর-সংকেতের কাজ শুরু করেন — যেখানে পৃথিবীর প্রতিটি লিপির প্রতিটি অক্ষর একটি অনন্য নম্বর পাবে। Becker-ই “Unicode” নামটি দেন। ১৯৯১ সালে Unicode Consortium প্রতিষ্ঠিত হয়, আর একই বছরের শেষে Unicode 1.0 প্রকাশিত হয়।

বাংলা সেই প্রথম সংস্করণেই ছিল। ভারতীয় লিপিগুলোর জন্য Unicode ভারতের ISCII (১৯৮৮) মানের বিন্যাস প্রায় হুবহু গ্রহণ করে — এজন্যই দেবনাগরী, বাংলা, গুরুমুখী, গুজরাটি খণ্ডগুলো একই ছাঁচে সাজানো: প্রতিটি খণ্ড ১২৮ ঘরের, আর একই ধরনের অক্ষর প্রায় একই আপেক্ষিক অবস্থানে। এই কারণেই বাংলা খণ্ড U+0980-এ শুরু, দেবনাগরীর (U+0900) ঠিক পরে। পরে কিছু সংযোজন এসেছে — যেমন খণ্ড-ত ৎ-এর আলাদা code point (U+09CE) যোগ হয় Unicode 4.1-এ (২০০৫), যার আগে এটি হসন্ত আর ZWJ দিয়ে কৌশলে লিখতে হতো।

বাংলাদেশের অভিজ্ঞতা: Unicode-এর আগে এবং বহু বছর পরেও বাংলাদেশে বাংলা টাইপিং চলত বিজয়ের মতো সফটওয়্যার আর ANSI-ভিত্তিক ফন্টে (যেমন SutonnyMJ). সেখানে ইংরেজি অক্ষরের ঘরেই বাংলা আকৃতি আঁকা থাকত — ফন্ট বদলালেই লেখা হয়ে যেত দুর্বোধ্য ইংরেজি অক্ষরের জঞ্জাল, আর সার্চ ইঞ্জিন এগুলোকে বাংলা হিসেবে চিনতই না। ২০০০-এর দশকে অভ্রের মতো Unicode-ভিত্তিক কীবোর্ড আর মোবাইল ফোনের প্রসারে Unicode বাংলা দ্রুত ছড়ায়। আজও অনেক সরকারি নথি বা পুরোনো পত্রিকার আর্কাইভ ANSI-বাংলায় থাকায় সেগুলো রূপান্তর করতে converter লাগে — এটি বাস্তব ডেটা-পরিষ্কারের একটি কাজ, যেখানে Python প্রায়ই ব্যবহৃত হয়।

মূল উপলব্ধি: একটি মান (standard) তখনই সফল হয় যখন সবাই একই নম্বর মেনে নেয়। Unicode-এর আগে বাংলা লেখা ছিল ফন্টের বন্দি; Unicode-এর পর বাংলা হয়েছে আসল “ডেটা” — সার্চ করা যায়, সাজানো যায়, AI মডেলকে শেখানো যায়।

প্র ০২Python 3 কেন ডিফল্টে str-কে Unicode বানাল, যদিও এতে পুরোনো Python 2-এর কোড ভেঙে গেল?

Python 2-এ "hello" লিখলে পেতেন একটি str — যা আসলে বাইটের সারি। আসল Unicode লেখার জন্য লিখতে হতো u"hello", যার type unicode. সবচেয়ে বড় সমস্যা ছিল, দুটি নিজে থেকে মিশে যেত: str আর unicode যোগ করলে Python চুপচাপ ASCII ধরে বাইটগুলো decode করার চেষ্টা করত। লেখা শুধু ইংরেজি থাকলে সব ঠিক; কিন্তু যেই একজন ব্যবহারকারী বাংলা নাম বা “café” লিখলেন, অমনি UnicodeDecodeError — প্রায়ই প্রোগ্রামের এমন জায়গায়, যেখানে বাগটি তৈরি হয়নি। ইংরেজিভাষী ডেভেলপারের টেস্টে এমন বাগ প্রায় কখনো ধরা পড়ত না — ধরা পড়ত প্রথম অ-ইংরেজি ব্যবহারকারীর হাতে। এই অভিজ্ঞতা থেকেই Python ডেভেলপার Ned Batchelder ২০১২ সালের PyCon-এ “Unicode sandwich” পরামর্শটি জনপ্রিয় করেন: সীমানায় decode, ভেতরে শুধু লেখা, বেরোনোর সময় encode.

Python 3-এর পরিকল্পনা (PEP 3000-ধারার প্রস্তাবগুলো, যার মধ্যে PEP 3100 ছিল পরিবর্তনের তালিকা আর PEP 3137 আলাদা bytes type-এর নকশা) সিদ্ধান্ত নিল এক কঠিন কিন্তু পরিষ্কার পথ: str মানেই Unicode লেখা; বাইট আলাদা type bytes; আর দুটি কখনো নিজে থেকে মিশবে না — মেশাতে চাইলে সঙ্গে সঙ্গে TypeError. সাথে PEP 3120 সোর্স ফাইলের ডিফল্ট encoding করল UTF-8, আর PEP 3131 এমনকি ভ্যারিয়েবলের নামেও অ-ইংরেজি অক্ষর অনুমোদন দিল — Python 3-এ নাম = "রহিম" সত্যিই চলে (যদিও দলগত কাজে ইংরেজি নামই প্রচলিত)।

দাম ছিল চড়া। Python 3.0 আসে ২০০৮ সালে, কিন্তু বড় library আর কোম্পানিগুলোর পুরোপুরি সরে আসতে লেগে যায় প্রায় এক দশক। Python 2-এর অফিসিয়াল সমাপ্তি ঘোষিত হয় ১ জানুয়ারি ২০২০-এ। পরে Python 3.3-এ PEP 393 ভেতরের গঠনকে আরও চতুর করে: string-এর সবচেয়ে বড় code point দেখে প্রতিটি অক্ষরের জন্য ১, ২ বা ৪ বাইট বরাদ্দ করা হয়। শুধু ইংরেজি string-এ ১ বাইট, বাংলা থাকলে ২ বাইট — ফলে Unicode-এর সুবিধা পেয়েও মেমরি নষ্ট হয় না, আর s[i] সবসময় সরাসরি এক লাফে পৌঁছায়।

মূল উপলব্ধি: “চুপচাপ রূপান্তর” সুবিধাজনক মনে হলেও বাগকে লুকিয়ে দূরে সরিয়ে দেয়। Python 3 সীমানা স্পষ্ট করে ভুলকে তাড়াতাড়ি, সঠিক জায়গায় ধরা পড়তে বাধ্য করেছে — আর এর সবচেয়ে বড় লাভ পেয়েছে বাংলার মতো অ-ইংরেজি ভাষার ব্যবহারকারীরা।

প্র ০৩UTF-8 কে, কীভাবে নকশা করেছিলেন? বাংলার জন্য UTF-8-এ ৩ বাইট লাগে, অথচ UTF-16-এ ২ বাইট — তবু UTF-8 কেন পৃথিবী জয় করল?

১৯৯২ সালের সেপ্টেম্বরে Bell Labs-এর দুই কিংবদন্তি — Unix-এর অন্যতম স্রষ্টা Ken Thompson আর Rob Pike (যাঁরা পরে Go ভাষাও তৈরি করেন) — নিউ জার্সির একটি ডাইনারে রাতের খাবার খেতে খেতে একটি প্লেসম্যাটের (খাবারের টেবিলের কাগজ) ওপর নতুন একটি encoding-এর নকশা আঁকেন। কয়েক দিনের মধ্যে তাঁরা তাঁদের Plan 9 অপারেটিং সিস্টেমকে পুরোপুরি এই encoding-এ রূপান্তর করেন। এটাই UTF-8, যা ১৯৯৩ সালের জানুয়ারিতে USENIX সম্মেলনে উপস্থাপিত হয়।

নকশার চতুরতা:

  • ASCII-র সাথে পুরো মিল: code point 0–127 হুবহু এক বাইট, ASCII-র মতোই। ফলে পৃথিবীর কোটি কোটি পুরোনো ইংরেজি ফাইল কোনো পরিবর্তন ছাড়াই বৈধ UTF-8. এটাই ছিল সবচেয়ে বড় জয়।
  • নিজে নিজে খুঁজে পাওয়া: একাধিক বাইটের অক্ষরের প্রথম বাইট আর পরের বাইটগুলোর বিট-নকশা আলাদা। তাই ফাইলের মাঝখান থেকে পড়া শুরু করলেও পরের অক্ষরের শুরু খুঁজে পাওয়া যায়, একটি বাইট নষ্ট হলে শুধু একটি অক্ষর নষ্ট হয়।
  • শূন্য বাইট নেই: ASCII-র NUL ছাড়া কোনো অক্ষরের ভেতরে 0 বাইট আসে না, তাই C-র '\0'-ভিত্তিক string ফাংশনগুলোও ভাঙে না।
  • বাইটের ক্রম = code point-এর ক্রম: UTF-8 বাইট সরাসরি সাজালেও code point-এর ক্রমেই সাজানো হয়।

বাংলার হিসাব: U+0800 থেকে U+FFFF পর্যন্ত code point UTF-8-এ ৩ বাইট, তাই প্রতিটি বাংলা code point ৩ বাইট; UTF-16-এ ২ বাইট। শুধু বাংলা লেখার ফাইলে UTF-16 ছোট হতো। কিন্তু বাস্তবের ফাইল — HTML, JSON, সোর্স কোড — ভরা থাকে ইংরেজি ট্যাগ, চাবি আর চিহ্নে, যেখানে UTF-8 অর্ধেক জায়গা নেয়। আর UTF-16-এর আছে বাইটের ক্রম (endianness) আর BOM-এর ঝামেলা, ASCII-র সাথে অমিল আর surrogate pair-এর জটিলতা। তাই ধীরে ধীরে UTF-8 ওয়েবের প্রায় সর্বজনীন encoding হয়ে উঠেছে; আজ প্রায় সব ওয়েবসাইট UTF-8-এ চলে, আর Python-এ PEP 686 অনুযায়ী 3.15 থেকে UTF-8 mode ডিফল্ট হওয়ার কথা।

মূল উপলব্ধি: সেরা নকশা সবসময় “সবচেয়ে ছোট” নয় — পুরোনো জিনিসের সাথে মিল, ভুল সহ্য করার ক্ষমতা আর সরলতা প্রায়ই কাঁচা দক্ষতার চেয়ে বেশি মূল্যবান। একটি প্লেসম্যাটে আঁকা নকশা টিকে গেছে কারণ সেটি বাস্তব জগতের সীমাবদ্ধতা মাথায় রেখে আঁকা হয়েছিল।

প্র ০৪Python-এর string immutable কেন? বদলানো গেলে তো লুপে জোড়া লাগানো অনেক দ্রুত হতো।

Immutability-র একটি দাম আছে, আপনি দেখেছেন — লুপে += প্রতিবার কপি করে। তবু Python (Java, C#, JavaScript, Go-র মতো) string-কে immutable রেখেছে, কয়েকটি গভীর কারণে।

  • Dictionary-র চাবি হওয়া: dict (পর্ব ১৬) চাবি খোঁজে তার hash মান দিয়ে — চাবি থেকে হিসাব করা একটি সংখ্যা, যা বলে দেয় কোন ঘরে খুঁজতে হবে। চাবি জমা হওয়ার পর বদলে গেলে তার hash-ও বদলাত, আর dict সেটা ভুল ঘরে খুঁজত — ডেটা থেকেও “হারিয়ে” যেত। string প্রায় সবচেয়ে প্রচলিত চাবি (JSON-এর সব চাবি string), তাই এর immutable হওয়া জরুরি। বাড়তি লাভ: CPython একটি string-এর hash একবার হিসাব করে object-এর ভেতরে জমিয়ে রাখে, পরের বার আর হিসাব লাগে না।
  • নিরাপদে শেয়ার: একটি string অসংখ্য জায়গা থেকে দেখানো যায় — ফাংশনের argument, list-এর ঘর, অন্য thread — কেউ তা বদলে দেবে এই ভয় নেই। ফাংশনে ফাইলের নাম পাঠালেন, ফাংশন ভেতরে সেটা বদলে দিল — এমন বাগ Python-এ অসম্ভব। নিরাপত্তার দিক থেকেও: অনুমতি যাচাইয়ের পর কেউ path string বদলে দিতে পারে না।
  • Interning আর মেমরি: যেহেতু বদলানো যায় না, একই লেখার কয়েকটি string-এর বদলে একটি মাত্র কপি রেখে সবাইকে সেটা দেখানো যায়। একে বলে interning. CPython ভ্যারিয়েবল, ফাংশন আর attribute-এর নামগুলো স্বয়ংক্রিয়ভাবে intern করে, ফলে নাম খোঁজার সময় পুরো লেখা না মিলিয়ে শুধু ঠিকানা মিলিয়েই কাজ সারা যায়। sys.intern() দিয়ে নিজেও করা যায়। তবে কোন string intern হবে তা CPython-এর ভেতরের সিদ্ধান্ত — তাই string তুলনায় কখনো is নয়, সবসময় == (পর্ব ০৮)।

বিকল্প কী? বদলানো যায় এমন বাইটের সারি দরকার হলে Python-এ আছে bytearray; অনেক টুকরো জোড়ার জন্য list আর join, অথবা io.StringIO. C-র অভিজ্ঞতা মনে করুন: সেখানে string বদলানো যায় বলে buffer overflow — ইতিহাসের অসংখ্য নিরাপত্তা ত্রুটির উৎস। Python বেছে নিয়েছে নিরাপত্তা আর সরলতা, আর দক্ষতার জন্য আলাদা যন্ত্র রেখেছে।

মূল উপলব্ধি: যে জিনিস বদলায় না, তাকে নিয়ে যুক্তি করা সহজ — শেয়ার করা নিরাপদ, ক্যাশ করা নিরাপদ, চাবি বানানো নিরাপদ। Immutability একটি সীমাবদ্ধতা নয়, একটি নিশ্চয়তা; আর দক্ষতার দাম মেটানো হয় সঠিক যন্ত্র (join) দিয়ে।

প্র ০৫একই দেখতে দুটি বাংলা লেখা ==-এ অসমান হয় কেন? NFC আর NFD কী, আর বাংলায় এর কী অদ্ভুত ব্যতিক্রম আছে?

Unicode-এর একটি মৌলিক নীতি হলো, আগের বিভিন্ন encoding-এর সাথে মিল রাখা। পুরোনো মানগুলোতে কিছু অক্ষর একক সংকেত হিসেবে ছিল (যেমন ফরাসি é), আবার “e + উপরের দাগ” হিসেবে জুড়েও লেখা যেত। তাই Unicode দুটোই রেখেছে, আর ঘোষণা করেছে এরা canonically equivalent — অর্থে ও চেহারায় এক। কিন্তু code point ভিন্ন, তাই সাধারণ তুলনায় অসমান।

এই বিভ্রান্তি দূর করতে Unicode চারটি normalization রূপ সংজ্ঞায়িত করেছে (UAX #15): NFD সবকিছু সম্ভব হলে ভেঙে মূল অংশে নেয়; NFC আগে ভাঙে, তারপর আবার যতটা সম্ভব জোড়া দেয়। আর NFKC/NFKD আরও এক ধাপ এগিয়ে “দেখতে কাছাকাছি” রূপগুলোকেও এক করে (যেমন সুপারস্ক্রিপ্ট ² → 2). ওয়েব আর বেশিরভাগ সিস্টেম NFC পছন্দ করে। মজার তথ্য: Python নিজেও ভ্যারিয়েবলের নাম পড়ার সময় NFKC normalization করে, যাতে দুই রকমে টাইপ করা একই নাম একই ভ্যারিয়েবল বোঝায়।

বাংলায় সমতুল্য জোড়া:

  • ো (U+09CB) ≡ ে + া, আর ৌ (U+09CC) ≡ ে + ৗ (U+09D7). NFC এদের একক code point-এ জোড়ে।
  • ড়, ঢ়, য় (U+09DC, U+09DD, U+09DF) ≡ ড/ঢ/য + নুক্তা ়. কিন্তু এখানেই অদ্ভুত ব্যতিক্রম: এই তিনটি Unicode-এর “composition exclusion” তালিকায় আছে। ফলে NFC এদের জোড়ে না, উল্টো ভেঙে দুটি code point বানায়! অর্থাৎ NFC করার পর “ড়”-এর দৈর্ঘ্য 1 থেকে 2 হয়ে যায়। অনেকে ধরে নেন “NFC মানে সবসময় ছোট” — বাংলায় তা সত্য নয়।

এর বাইরে আছে অদৃশ্য অক্ষরের সমস্যা: “র‍্যাব”-এর মতো র-ফলা দেখাতে ZWJ (Zero Width Joiner) লাগে, আর কোথাও যুক্তাক্ষর ভাঙতে ZWNJ. এগুলো normalization-এ সরে না; দুটি লেখার একটিতে ZWJ থাকলে NFC-র পরও অসমান। বাস্তব সিস্টেমে (সার্চ ইঞ্জিন, জাতীয় পরিচয়পত্রের ডেটাবেস, ব্যাংকের নাম-মিলানো) তাই NFC-র সাথে প্রায়ই নিজস্ব পরিষ্কার-নিয়ম যোগ করা হয় — যেমন অদৃশ্য অক্ষর মুছে ফেলা, পুরোনো টাইপিংয়ের ভুল ক্রম ঠিক করা।

মূল উপলব্ধি: কম্পিউটারের কাছে “একই লেখা” মানে একই code point-এর সারি, চোখের মিল নয়। বহুভাষিক সিস্টেম বানাতে হলে normalization কোনো ঐচ্ছিক বিলাসিতা নয় — বাইরে থেকে আসা প্রতিটি লেখার প্রবেশপথে বসানো একটি দারোয়ান।

অনুশীলন

বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — আগে অনুমান, তারপর চালিয়ে মিলিয়ে নেওয়াই আসল শেখা।

  1. Slice অনুমান: s = "Programming". খাতায় লিখুন এগুলোর ফল কী: s[3:7], s[-3:], s[::3], s[7:3], s[7:3:-1], s[50:]. তারপর string_tools.py-এর শেষে যোগ করে মিলিয়ে নিন।

    s[3:7] → 'gram' (ঘর 3, 4, 5, 6; ঘর 7 বাদ)। s[-3:] → 'ing'. s[::3] → 'Pgmn' (ঘর 0, 3, 6, 9). s[7:3] → '' — step ধনাত্মক কিন্তু start stop-এর ডানে, তাই কিছুই নেওয়া হয় না, কোনো error-ও নয়। s[7:3:-1] → 'mmar' (ঘর 7, 6, 5, 4 — ডান থেকে বামে, ঘর 3 বাদ)। s[50:] → '', কারণ slice কখনো IndexError দেয় না।

  2. ফর্মের নামের সীমা: একটি ফর্মে লেখা আছে “নাম সর্বোচ্চ ১০ অক্ষর” আর কোডে len(name) <= 10. “ক্ষিতীশ চন্দ্র” আর “Rahim Khan” — কোনটি পাস করবে? ব্যবহারকারীর দিক থেকে কী সমস্যা দেখছেন, আর সমাধানে কী কী বিকল্প আছে?

    “Rahim Khan” ১০টি code point (space-সহ), তাই পাস। “ক্ষিতীশ চন্দ্র”-এ: ক্ষিতীশ = ক, ্, ষ, ি, ত, ী, শ = ৭; space ১; চন্দ্র = চ, ন, ্, দ, ্, র = ৬ — মোট ১৪, তাই বাতিল, যদিও চোখে দুটি নামই প্রায় সমান লম্বা। ব্যবহারকারী ভাববেন ফর্মটি বাংলা-বিরোধী। সমাধান: (১) সীমাটা আসলে কেন দরকার, তা ঠিক করুন — ডেটাবেসের জায়গা হলে বাইট বা code point-এ বড় সীমা রাখুন (যেমন ১০০); (২) দেখানোর সীমা হলে grapheme গুনুন; (৩) বার্তায় স্পষ্ট লিখুন, আর অবশ্যই বাংলা নাম দিয়ে টেস্ট করুন।

  3. Normalization পরীক্ষা: bangla_unicode.py-তে দুটি “কো” বানান: x = "ক\u09cb" আর y = "ক\u09c7\u09be". চালানোর আগে অনুমান করুন x == y, len(x), len(y) কী হবে, আর unicodedata.normalize("NFC", y) == x কী দেবে। তারপর একই কাজ “ড়” দিয়ে করে দেখুন NFC-র পর দৈর্ঘ্য কত হয়।

    x == y → False; len(x) 2, len(y) 3. পর্দায় দুটিই “কো”. NFC-তে y-র ে + া জোড়া লেগে একক U+09CB হয়, তাই normalize("NFC", y) == x → True. কিন্তু “ড়”-এর বেলায় উল্টো: len(unicodedata.normalize("NFC", "\u09dc")) হবে 2, কারণ U+09DC composition exclusion তালিকায় আছে, NFC তাকে ড + নুক্তায় ভেঙে দেয়। শিক্ষা: কোন রূপে normalize হবে, তা অনুমান নয় — চালিয়ে দেখুন, আর সব জায়গায় একই রূপ ব্যবহার করুন।

  4. AI অডিট: যেকোনো AI-কে বলুন: “Python-এ একটি ফাংশন লেখো যা একটি শব্দ উল্টে দেয় আর বলে সেটি palindrome কি না।” তারপর তার কোড “Bangladesh”, “Madam”, “বাংলা” আর “নয়ন” দিয়ে চালান। কোথায় সমস্যা পেলেন?

    সাধারণত AI লিখবে return word == word[::-1]. “Madam”-এ ফল আসবে False, কারণ বড় হাতের M আর ছোট m আলাদা — casefold() লাগত। “বাংলা”-র উল্টো রূপ 'ালংাব' — ভাঙা লেখা, যা পর্দায় পড়াই যায় না। “নয়ন”-এর ফল নির্ভর করে “য়” কীভাবে টাইপ হয়েছে তার ওপর: একক U+09DF হলে উল্টেও “নয়ন”, তাই palindrome; কিন্তু য + নুক্তা হলে উল্টানোর পর নুক্তা য-এর আগে চলে যায়, লেখা ভাঙে, আর ফল False. ভালো সমাধানে লাগবে: তুলনার আগে casefold() আর normalization, আর বাংলার জন্য code point নয়, grapheme ধরে উল্টানো। AI-কে এই উদাহরণগুলো দেখিয়ে আবার লিখতে বলুন, আর লক্ষ করুন সে নিজে থেকে বাংলার কথা ভেবেছিল কি না।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি বাংলা লেখা টাইপ করা থেকে ফাইলে জমা হয়ে আবার পড়া পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ⌨️ কীবোর্ডে “বাংলা” টাইপ করা হলো
  2. 🔤 Python-এ এটি একটি str — ৫টি code point
  3. ⚙️ encode("utf-8") — code point থেকে বাইট
  4. 💾 ১৫ বাইট ফাইলে জমা হলো
  5. 🔁 decode("utf-8") — বাইট থেকে আবার str

Python-এ "Bangladesh"[1:7:2]-এর মান কী?

  • 'Bnl'
  • 'agae'
  • 'aga'
  • 'anglad'

city = "dhaka" লেখার পর city[0] = "D" চালালে কী ঘটে?

  • city হয়ে যায় "Dhaka"
  • TypeError দেখা দেয়, কারণ str immutable
  • IndexError দেখা দেয়
  • কিছুই ঘটে না, city আগের মতো থাকে, কোনো error নেই

Python 3-এ len("বাংলা")-এর মান কত?

  • 2
  • 3
  • 15
  • 5

বড়-ছোট হাতের পার্থক্য উপেক্ষা করে দুটি string তুলনার সবচেয়ে নির্ভরযোগ্য উপায় কোনটি?

  • a.casefold() == b.casefold()
  • a.lower() == b.lower()
  • a.lower() is b.lower()
  • a.title() == b.title()

"বাংলা".encode("utf-8")-এর দৈর্ঘ্য কত বাইট?

  • 5
  • 10
  • 15
  • 20

s = "abc" হলে কোন উক্তিটি সঠিক?

  • s[10] আর s[5:10] দুটোই IndexError দেয়
  • s[10] IndexError দেয়, কিন্তু s[5:10] খালি string '' দেয়
  • দুটোই খালি string '' দেয়
  • s[10] শেষ অক্ষর 'c' দেয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. str হলো Unicode code point-এর অপরিবর্তনীয় সারি. কোনো method আসল string বদলায় না — ফল সবসময় নতুন string, তাই s = s.strip().
  2. Index শুরু 0 থেকে, শেষ থেকে -1. s[start:stop:step]-এ stop বাদ, s[::-1] উল্টায়, আর slice কখনো IndexError দেয় না।
  3. রোজকার কাজ: strip, split(), join, replace, find/in; তুলনায় casefold(); অনেক টুকরো জোড়ায় "".join(), লুপে += নয়।
  4. বাংলায় চোখের অক্ষর ≠ code point: len("বাংলা") 5, “ক্ষ” = ক + ্ + ষ; "বাংলা".isalpha() False, "১২৩".isdigit() True; সাজানো হয় code point ধরে।
  5. str লেখা, bytes বাইট: UTF-8-এ বাংলা code point ৩ বাইট (“বাংলা” = ১৫)। বাইরে থেকে আসা লেখা তুলনার আগে NFC normalize.

পরের পর্বে: এতক্ষণ লেখা নিয়ে কাজ করেছেন প্রোগ্রামের ভেতরে। এবার ব্যবহারকারীর সাথে কথা বলার পালা — input() দিয়ে লেখা নেওয়া, সংখ্যায় রূপান্তর, আর f-string ও format spec দিয়ে টাকার অঙ্ক, দশমিক আর কলাম সুন্দর করে সাজানো।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান