Regular Expression (Zero to Hero) কমপ্লিট গাইড
ইন্টারনেটে হাজার হাজার টেক্সট, আর্টিকেল বা বিশাল সাইজের লগ (Log) ফাইল থেকে নির্দিষ্ট কোনো প্যাটার্ন (যেমন: শুধু ইমেইলগুলো, অথবা শুধু ফোন নাম্বারগুলো) খুঁজে বের করা সাধারণ স্ট্রিংয়ের ফাংশন (find, replace) দিয়ে প্রায় অসম্ভব।
এই সমস্যার সমাধানের নামই হলো Regular Expression (বা রেজেক্স - Regex)। এটি এক প্রকার গোপন ভাষার মতো! প্রথম দেখাতে এর কোডগুলোকে এলিয়েনদের ভাষা (যেমন: ^[a-z]+@[a-z]+\.[a-z]{2,3}$) মনে হলেও, একবার শিখে ফেললে এটি টেক্সট প্রসেসিংয়ের সবচেয়ে পাওয়ারফুল হাতিয়ার!
পাইথনে এই ম্যাজিক করার জন্য বিল্ট-ইন re মডিউল ব্যবহার করা হয়।
এই টিউটোরিয়ালে আমরা একদম বিগিনার (Beginner) লেভেল থেকে শুরু করে অ্যাডভান্সড (Advanced) লেভেলের Lookaheads এবং Named Groups পর্যন্ত বিস্তারিত শিখবো।
🟢 বিগিনার লেভেল (Beginner)
১. রেজেক্সের বেসিক ক্যারেক্টারগুলো (Cheat Sheet)
\d: যেকোনো নাম্বার (০-৯)\w: যেকোনো অক্ষর বা নাম্বার (a-z, A-Z, 0-9)\s: স্পেস বা হোয়াইটস্পেস (Space)+: ১ বার বা তার বেশি থাকতে পারে*: ০ বার বা তার বেশি থাকতে পারে?: ০ বা ১ বার থাকতে পারে (Optional).: যেকোনো ক্যারেক্টার (নিউলাইন ছাড়া)^: স্ট্রিংয়ের শুরু$: স্ট্রিংয়ের শেষ
(বিঃদ্রঃ পাইথনে রেজেক্স লেখার সময় সব সময় স্ট্রিংয়ের আগে r (Raw String) দিতে হয়, যেমন: r"\d+", নাহলে পাইথন \ কে ব্যাকস্ল্যাশ এস্কেপ ক্যারেক্টার ভেবে ভুল করবে!)
২. টেক্সট থেকে প্যাটার্ন খোঁজা (search এবং match)
match() শুধু স্ট্রিংয়ের একদম শুরুতেই প্যাটার্নটি আছে কি না চেক করে, আর search() পুরো স্ট্রিংয়ের যেকোনো জায়গায় প্যাটার্নটি খুঁজবে (তবে প্রথমটি পেয়ে গেলেই থেমে যাবে)।
import re
text = "My phone number is 01712345678. Call me!"
# ১. search() - পুরো টেক্সট থেকে প্রথম ফোন নাম্বারটি খোঁজা
# \d+ মানে হলো এক বা একাধিক নাম্বারের সিকোয়েন্স
match_result = re.search(r"\d+", text)
if match_result:
print("Found Number:", match_result.group()) # group() দিলে রেজাল্ট স্ট্রিং হিসেবে আসে
৩. সবগুলো প্যাটার্ন একসাথে বের করা (findall)
search শুধু প্রথম ম্যাচ হওয়া আইটেমটি দেয়। কিন্তু যদি আমরা টেক্সটের ভেতরের সবগুলো নাম্বার চাই?
import re
text = "John: 45, Alice: 30, Bob: 22"
# findall() একটি লিস্ট (List) রিটার্ন করে
all_numbers = re.findall(r"\d+", text)
print("All Numbers:", all_numbers) # ['45', '30', '22']
# শুধু নামগুলো (Words) বের করা
all_names = re.findall(r"[A-Za-z]+", text)
print("All Names:", all_names) # ['John', 'Alice', 'Bob']
🟡 ইন্টারমিডিয়েট লেভেল (Intermediate)
৪. রেজেক্স দিয়ে রিপ্লেস বা ডিলিট করা (sub)
সাধারণ স্ট্রিংয়ের replace() দিয়ে নির্দিষ্ট ওয়ার্ড চেঞ্জ করা যায়। কিন্তু প্যাটার্ন চেঞ্জ করতে হলে re.sub() (Substitute) লাগে।
import re
text = "Please contact support@company.com or sales@company.com"
# সবগুলো ইমেইল এড্রেসকে হাইড করে '***' বসিয়ে দেওয়া
# \S+ মানে হলো স্পেস ছাড়া যেকোনো ক্যারেক্টারের সিকোয়েন্স (ইমেইলের জন্য)
hidden_text = re.sub(r"\S+@\S+", "***", text)
print(hidden_text)
# আউটপুট: Please contact *** or ***
৫. ডেটা আলাদা করা (Grouping ())
প্যাটার্নের মধ্যে ব্র্যাকেট () ব্যবহার করে আমরা নির্দিষ্ট অংশকে আলাদা গ্রুপ (Group) করে বের করে আনতে পারি। এটি ডেটা স্ক্র্যাপিংয়ে খুব লাগে!
import re
text = "User: admin, Email: admin@gmail.com"
# এখানে আমরা ইমেইলটিকে ৩টি গ্রুপে ভাগ করেছি: (নাম) @ (ডোমেইন) . (এক্সটেনশন)
pattern = r"(\w+)@(\w+)\.(\w+)"
match = re.search(pattern, text)
if match:
print("Full Email:", match.group(0)) # admin@gmail.com (পুরোটা)
print("Username:", match.group(1)) # admin (১ম গ্রুপ)
print("Domain:", match.group(2)) # gmail (২য় গ্রুপ)
print("Extension:", match.group(3)) # com (৩য় গ্রুপ)
🔴 অ্যাডভান্সড লেভেল (Advanced)
৬. পারফরম্যান্স বাড়ানোর জন্য কম্পাইল করা (re.compile)
আপনি যদি একটি বড় ফর লুপের (For Loop) ভেতরে বারবার re.search() কল করেন, তবে পাইথন বারবার ওই রেজেক্স প্যাটার্নটিকে মেমোরিতে বিল্ড করবে, যা অনেক স্লো!
এই সমস্যার সমাধানে আমরা প্যাটার্নটিকে আগে থেকেই Compile করে মেমোরিতে সেভ করে রাখি।
import re
# ১. প্যাটার্নটিকে একবার কম্পাইল করে মেমোরিতে রাখা হলো (সুপার ফাস্ট!)
email_pattern = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b")
texts = [
"Contact me at hello@world.com",
"Invalid email @world.com",
"Admin: admin@system.org"
]
for text in texts:
# ২. সরাসরি প্যাটার্ন অবজেক্ট দিয়ে সার্চ করা
match = email_pattern.search(text)
if match:
print("Found:", match.group())
৭. নেমড গ্রুপস (Named Groups ?P<name>)
ইন্টারমিডিয়েট লেভেলে আমরা গ্রুপ বের করার জন্য group(1), group(2) ব্যবহার করেছি। কিন্তু বড় কোডে নাম্বার মনে রাখা কঠিন। আমরা চাইলে গ্রুপের নাম দিয়ে ডিকশনারির মতো ডেটা বের করতে পারি!
import re
text = "Date: 2026-07-05"
# প্যাটার্ন: (?P<নাম>প্যাটার্ন)
pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
match = re.search(pattern, text)
if match:
# groupdict() দিলে সে সুন্দর করে ডিকশনারি বানিয়ে দেয়!
print("Data Dict:", match.groupdict())
# আউটপুট: {'year': '2026', 'month': '07', 'day': '05'}
৮. লুকএহেড এবং লুকবিহাইন্ড (Lookaheads & Lookbehinds)
এটি রেজেক্সের সবচেয়ে অ্যাডভান্সড এবং কনফিউজিং টপিক। ধরুন, আপনি এমন একটি শব্দ খুঁজতে চাচ্ছেন যার ঠিক পরেই নির্দিষ্ট কিছু লেখা আছে, কিন্তু আপনি ওই নির্দিষ্ট লেখাকে আউটপুটে চান না!
- Lookahead
(?=...): সামনে অমুক লেখা থাকলে ম্যাচ করবে। - Lookbehind
(?<=...): পেছনে অমুক লেখা থাকলে ম্যাচ করবে।
import re
# ধরুন আমরা শুধু ডলারের পরিমাণটা চাই, কিন্তু '$' সাইন বা 'USD' চাই না!
text = "I have $100 and 50 USD in my account. Also 200 EUR."
# Lookbehind (?<=\$): যার ঠিক পেছনে $ আছে
print("Lookbehind:", re.findall(r"(?<=\$)\d+", text)) # ['100']
# Lookahead (?= USD): যার ঠিক সামনে ' USD' আছে
print("Lookahead:", re.findall(r"\d+(?= USD)", text)) # ['50']
# দেখুন, 200 EUR এর নাম্বারটি সে নেয়নি, কারণ তার কন্ডিশন মিলেনি!
সারসংক্ষেপ (Conclusion)
যদিও বিশাল টেক্সট থেকে ডেটা বের করার জন্য NLP (Natural Language Processing) বা AI মডেলে ব্যবহার করা হয়, কিন্তু যেকোনো ফর্মে ইউজারের ইমেইল বা পাসওয়ার্ড ভ্যালিডেশন, লগ ফাইল থেকে এরর (Error) বের করা বা স্ক্র্যাপিং করে ময়লা ডেটা (Messy Data) ক্লিন করার জন্য re (Regular Expression) এর কোনো বিকল্প পৃথিবীতে তৈরি হয়নি!