রেগুলার এক্সপ্রেশন — ফরমাল ডেফিনিশন
এই পাঠে যা শিখবেন
- regex-এর নির্ভুল রিকার্সিভ সংজ্ঞা — বেস কেস ও ইনডাক্টিভ কেস
- প্রতিটি regex কীভাবে একটি ভাষা $L(R)$ বর্ণনা করে, ধাপে ধাপে
- regex AST নেস্টেড টাপল হিসেবে কীভাবে উপস্থাপন করা যায়
- একটি সত্যিকারের set-based ভাষা জেনারেটর, matcher নয় — সরাসরি সংজ্ঞা অনুসরণ করে
১ · রিকার্সিভ, ফরমাল সংজ্ঞা
একটি রেগুলার এক্সপ্রেশনRegular Expressionভাষা বর্ণনার একটি রিকার্সিভ নোটেশন — L03-এর স্ট্রাকচারাল ইনডাকশনের ধাঁচে সংজ্ঞায়িত। সংজ্ঞায়িত হয় নিচের বেস কেস ও ইনডাক্টিভ কেস দিয়ে — L03-এর স্ট্রাকচারাল ইনডাকশনের একই প্যাটার্ন (একটি বেস কেস, তারপর ইতিমধ্যে-তৈরি অংশ থেকে বড় অংশ বানানোর নিয়ম)।
খালি ভাষা — কিছুই ম্যাচ করে না।
শুধু খালি স্ট্রিং ম্যাচ করে।
শুধু একক-ক্যারেক্টার স্ট্রিং "a" ম্যাচ করে।
ইউনিয়ন/অল্টারনেশন — R অথবা S।
কনক্যাটেনেশন — R-এর পর S।
ক্লিনি স্টার — R শূন্য বা তার বেশিবার।
এবং — রিকার্সিভ সংজ্ঞায় প্রমিত একটি গুরুত্বপূর্ণ ক্লোজিং শর্ত — এর বাইরে আর কিছুই regex নয়। এই শর্তটিই সংজ্ঞাকে সম্পূর্ণ ও নির্ভুল করে তোলে (base cases + inductive cases + "nothing else" — L03-এর যেকোনো রিকার্সিভ সংজ্ঞার স্ট্যান্ডার্ড কাঠামো)।
২ · regex-এর ভাষা $L(R)$
প্রতিটি regex $R$ একটি ভাষা $L(R)$ বর্ণনা করে — এটিও ঠিক একই স্ট্রাকচারাল প্যাটার্নে সংজ্ঞায়িত:
$$L(\emptyset) = \{\} \qquad L(\varepsilon) = \{\varepsilon\} \qquad L(a) = \{a\}$$
$$L(R \cup S) = L(R) \cup L(S) \qquad L(RS) = \{xy : x \in L(R), y \in L(S)\}$$
$$L(R^*) = \bigcup_{i=0}^{\infty} L(R)^i$$
শেষ সূত্রটি ($L(R^*)$) মানে: $L(R)$ থেকে শূন্য বা তার বেশি স্ট্রিং একের পর এক জোড়া লাগিয়ে যত স্ট্রিং বানানো যায়, সেই সবের সেট — $i=0$ কেসে শুধু ε (কিছুই না জোড়া লাগিয়ে)।
../programming-languages-compilers/-এর M4/L17 আগেই একটি regex-AST-matcher বানিয়েছে
প্র্যাকটিক্যাল লেক্সিং-এর জন্য — সেখানে লক্ষ্য ছিল "একটি স্ট্রিং কি ম্যাচ করে" দ্রুত যাচাই করা। এই পাঠের
লক্ষ্য ভিন্ন — regex ঠিক কোন ভাষা বর্ণনা করে তার নির্ভুল গাণিতিক সংজ্ঞা দেওয়া, যা L11-এর
Kleene's theorem-এর ভিত্তি হিসেবে কাজ করবে।
৩ · কোড: regex AST থেকে সরাসরি ভাষা জেনারেট করা
নিচে regex-কে নেস্টেড Python টাপল হিসেবে উপস্থাপন করা হয়েছে (যেমন ("sym", "a"), ("union",
R, S)), এবং language_of ফাংশনটি Python-এর re মডিউল ব্যবহার না করে সরাসরি
উপরের সংজ্ঞা অনুসরণ করে $L(R)$-এর একটি (দৈর্ঘ্য-সীমাবদ্ধ) সেট তৈরি করে — matcher নয়,
genuine সেট কনস্ট্রাকশন। উদাহরণ regex: $(a \cup b)^* a$ — অর্থাৎ "'a'-তে শেষ হওয়া যেকোনো স্ট্রিং"।
def language_of(node, max_length):
"""genuinely computes L(R) up to a length bound, via structural recursion on the regex AST
(nested tuples) -- never uses Python's `re` module."""
kind = node[0]
if kind == "empty":
return set()
if kind == "eps":
return {""}
if kind == "sym":
a = node[1]
return {a} if len(a) <= max_length else set()
if kind == "union":
_, R, S = node
return language_of(R, max_length) | language_of(S, max_length)
if kind == "concat":
_, R, S = node
LR = language_of(R, max_length)
LS = language_of(S, max_length)
return {x + y for x in LR for y in LS if len(x) + len(y) <= max_length}
if kind == "star":
_, R = node
LR = {w for w in language_of(R, max_length) if w != ""}
result = {""}
frontier = {""}
while frontier:
new = set()
for s in frontier:
for r in LR:
cand = s + r
if len(cand) <= max_length and cand not in result:
new.add(cand)
result |= new
frontier = new
return result
raise ValueError(f"unknown regex node: {node}")
# regex (a U b)* a as nested tuples
regex = ("concat",
("star", ("union", ("sym", "a"), ("sym", "b"))),
("sym", "a"))
MAX_LEN = 3
lang = language_of(regex, MAX_LEN)
print(f"L((a∪b)*a) up to length {MAX_LEN}:", sorted(lang, key=lambda w: (len(w), w)))
# hand-verification: every generated string over {a,b} up to length 3 that ends in 'a'
from itertools import product
expected = set()
for length in range(0, MAX_LEN + 1):
for combo in product("ab", repeat=length):
s = "".join(combo)
if s.endswith("a"):
expected.add(s)
print("expected set (strings ending in 'a', length<=3):", sorted(expected, key=lambda w: (len(w), w)))
assert lang == expected
print("regex-generated ভাষা এবং হাতে-গোনা 'a'-এ শেষ হওয়া স্ট্রিং সেট হুবহু মিলেছে।")
# sanity checks on base cases
assert language_of(("empty",), 3) == set()
assert language_of(("eps",), 3) == {""}
assert language_of(("sym", "a"), 3) == {"a"}
assert language_of(("union", ("sym", "a"), ("sym", "b")), 3) == {"a", "b"}
print("বেস কেসগুলোও সঠিক।")
assert lang == expected পাস করে, যেখানে expected সেটটি সম্পূর্ণ ভিন্নভাবে (regex
সংজ্ঞা ব্যবহার না করে, সরাসরি "শেষে 'a' আছে কি না" চেক করে) গণনা করা হয়েছে — একটি স্বাধীন ক্রস-চেক।
regex একটি রিকার্সিভ নোটেশন যার ভাষা $L(R)$ তার নিজস্ব রিকার্সিভ কাঠামো অনুসরণ করেই সংজ্ঞায়িত — এই precise সংজ্ঞাই L11-এর Kleene's theorem-এর ভিত্তি, যেখানে আমরা দেখব ঠিক এই একই রিকার্সিভ কাঠামো অনুসরণ করে একটি regex থেকে সরাসরি একটি সমতুল্য NFA বানানো যায়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ $\emptyset$ ও $\varepsilon$ — এই দুটো regex কি একই জিনিস? পার্থক্য কী?
না, সম্পূর্ণ আলাদা। $L(\emptyset) = \{\}$ — কোনো স্ট্রিং-ই এই ভাষার অংশ নয়, খালি স্ট্রিংও না। $L(\varepsilon) = \{\varepsilon\}$ — ঠিক একটি স্ট্রিং, খালি স্ট্রিং, এই ভাষার অংশ। একটি "কিছুই ম্যাচ করে না" (empty set), অন্যটি "শুধু খালি স্ট্রিং ম্যাচ করে" (একটি নন-এম্পটি সেট, যার একমাত্র সদস্য খালি স্ট্রিং) — এই পার্থক্যটি অনেক প্রুফে গুরুত্বপূর্ণ (যেমন CFG-তে ε-প্রোডাকশন বনাম অনুৎপাদনশীল ভেরিয়েবল, L19)।
প্র ০২ $L(R^*)$-এর সূত্রে $i=0$ টার্মটি ($L(R)^0$) কী প্রতিনিধিত্ব করে?
$L(R)^0 = \{\varepsilon\}$ — অর্থাৎ $L(R)$ থেকে "শূন্যটি" স্ট্রিং জোড়া লাগানো, যার ফল সবসময় খালি স্ট্রিং। এটিই নিশ্চিত করে $\varepsilon$ সবসময় $L(R^*)$-এর সদস্য, R যাই হোক না কেন — এমনকি $R = \emptyset$ হলেও $L(\emptyset^*) = \{\varepsilon\}$ (খালি ভাষা থেকে শূন্যবার জোড়া লাগিয়ে শুধু ε পাওয়া যায়)।
প্র ০৩
কোড সেলের language_of ফাংশন কেন max_length প্যারামিটার নেয় — সরাসরি পুরো $L(R)$ কেন রিটার্ন করে না?
কারণ $R^*$ (ক্লিনি স্টার) প্রায়ই একটি অসীম ভাষা বর্ণনা করে (যেমন $(a\cup b)^*$-এ
যেকোনো দৈর্ঘ্যের স্ট্রিং আছে) — একটি Python সেট কখনো অসীম উপাদান ধরে রাখতে পারে না। তাই
language_of একটি দৈর্ঘ্য-সীমা পর্যন্ত সত্যিকারের, সম্পূর্ণ সঠিক (truncated) ভাষা জেনারেট
করে — এটি একটি বাস্তবসম্মত সীমাবদ্ধতা, ভুল সংজ্ঞা নয়।
অনুশীলন
-
চিন্তা করুন: regex $ab^*$ (অর্থাৎ
("concat", ("sym","a"), ("star", ("sym","b")))) -এর $L(R)$ কী হবে (শব্দে বর্ণনা করুন), এবং দৈর্ঘ্য ≤৩ পর্যন্ত কোন কোন স্ট্রিং এতে থাকবে?$L(ab^*) = \{a, ab, abb, abbb, \dots\}$ — একটি 'a', তারপর শূন্য বা তার বেশি 'b'। দৈর্ঘ্য ≤৩ পর্যন্ত: "a" (দৈর্ঘ্য ১), "ab" (দৈর্ঘ্য ২), "abb" (দৈর্ঘ্য ৩) — মোট ৩টি স্ট্রিং। লক্ষ্য করুন "b" বা "" এই ভাষায় নেই, কারণ প্রতিটি স্ট্রিং অবশ্যই একটি 'a' দিয়ে শুরু হতে হবে (কনক্যাটেনেশনের প্রথম অংশ)।
-
পরীক্ষা করুন: কোড সেলে
regex-কে("concat", ("sym","a"), ("star", ("sym","b")))-এ পরিবর্তন করে (এবংexpected-এর তুলনা লাইনটি বাদ দিয়ে) Run চেপে প্রিন্ট হওয়া সেটটি উপরের অনুশীলন ১-এর হাতে-গোনা উত্তরের সাথে মেলে কি না দেখুন।আউটপুট হবে
{'a', 'ab', 'abb'}(দৈর্ঘ্য ≤৩ অনুযায়ী সাজানো) — ঠিক অনুশীলন ১-এর হাতে-গোনা উত্তরের সাথে মেলে, নিশ্চিত করে যেlanguage_ofকনক্যাটেনেশন ও স্টার উভয়ই সংজ্ঞা অনুযায়ী সঠিকভাবে হ্যান্ডল করছে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ — ক্লিনির থিওরেম L11 এই পাঠের regex সংজ্ঞা ব্যবহার করে দেখানো হবে regex ও ফাইনাইট অটোমাটার ঠিক একই এক্সপ্রেসিভ পাওয়ার — একটি সম্পূর্ণ, code-verified কনস্ট্রাকশনসহ।
- আগের পাঠ — এপসিলন-NFA ও এপসিলন-ক্লোজার L09 L11-এ regex থেকে NFA বানাতে ঠিক এই পাঠের ε-জোড়া-লাগানো কৌশল ব্যবহৃত হবে।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ DFA থেকে টুরিং মেশিন পর্যন্ত ধাপে ধাপে বাড়তে থাকা গণনাশক্তির সম্পূর্ণ মানচিত্র।
- Programming Languages & Compiler Design কোর্স সঙ্গী কোর্স সেই কোর্সের M4/L17-এ regex-এর প্র্যাকটিক্যাল ম্যাচার বানানো দেখুন — এই পাঠ তার গাণিতিক ভিত্তি দেয়।