রেগুলার ল্যাঙ্গুয়েজের ক্লোজার প্রপার্টি
এই পাঠে যা শিখবেন
- ক্লোজার প্রপার্টি কী এবং কেন এটি রেগুলার ল্যাঙ্গুয়েজ নিয়ে কাজ করাকে সহজ করে তোলে
- ইউনিয়ন, কনক্যাটেনেশন, ক্লিন স্টার — regex-ভিত্তিক ক্লোজার প্রমাণ (L11-এর পুনর্ব্যবহার)
- কমপ্লিমেন্ট — DFA-ভিত্তিক ভিন্ন প্রমাণ কৌশল, এবং কেন টোটালিটি জরুরি
- ইন্টারসেকশন — প্রোডাক্ট কনস্ট্রাকশন, এবং De Morgan's law দিয়ে ক্রস-ভেরিফিকেশন
- Python-এ real
complement_dfaওproduct_dfaলিখে কোড দিয়ে যাচাই
১ · ক্লোজার প্রপার্টি কী
ক্লোজার প্রপার্টিClosure Propertyএকটি সেট (এখানে: রেগুলার ল্যাঙ্গুয়েজের সংগ্রহ) কোনো একটি অপারেশনের অধীনে "বন্ধ" কি না — অর্থাৎ অপারেশনটি সেটের ভেতরের উপাদানে প্রয়োগ করলে ফলাফল সবসময় সেটের ভেতরেই থাকে কি না। হলো প্রশ্ন — যদি $L_1$ এবং $L_2$ রেগুলার ল্যাঙ্গুয়েজ হয়, তাহলে এদের উপর একটি নির্দিষ্ট অপারেশন (যেমন union) প্রয়োগ করলে ফলাফলও কি নিশ্চিতভাবে রেগুলার? এই পাঠ দেখাবে — হ্যাঁ, পাঁচটি গুরুত্বপূর্ণ অপারেশনের অধীনেই রেগুলার ল্যাঙ্গুয়েজ ক্লোজড, এবং প্রতিটির জন্য একটি গঠনমূলক (constructive) প্রমাণ আছে — অর্থাৎ প্রমাণ নিজেই বলে দেয় কীভাবে ফলাফলের DFA/regex বানাতে হবে।
২ · ইউনিয়ন, কনক্যাটেনেশন, ক্লিন স্টার — regex দিয়ে সরাসরি
L11-এ regex-এর ফরমাল সংজ্ঞা মনে করুন — regex $R \cup S$, $RS$, ও $R^*$ নিজেরাই বৈধ regex। তাই যদি $L_1 = L(R)$ এবং $L_2 = L(S)$ রেগুলার হয় (অর্থাৎ কোনো regex দিয়ে বর্ণিত হয়), তাহলে —
- ইউনিয়ন: $L_1 \cup L_2 = L(R \cup S)$ — সরাসরি regex union, তাই রেগুলার।
- কনক্যাটেনেশন: $L_1 L_2 = L(RS)$ — সরাসরি regex concatenation, তাই রেগুলার।
- ক্লিন স্টার: $L_1^* = L(R^*)$ — সরাসরি regex star, তাই রেগুলার।
এই তিনটির প্রমাণ আসলে নতুন কিছু নয় — L11-এর ক্লিনির থিওরেম-প্রমাণিত regex ⇔ ফাইনাইট অটোমাটা ইকুইভ্যালেন্স সরাসরি পুনর্ব্যবহার করছি মাত্র।
৩ · কমপ্লিমেন্ট — DFA-ভিত্তিক ভিন্ন প্রমাণ
কমপ্লিমেন্টের জন্য regex-ভিত্তিক প্রমাণ সহজ নয়, কিন্তু DFA-ভিত্তিক প্রমাণ চমৎকার সহজ। যদি $L$ রেগুলার হয়, তাহলে L06-এর সংজ্ঞা অনুযায়ী একটি টোটাল DFA $M = (Q, \Sigma, \delta, q_0, F)$ থাকবে যার $L(M) = L$। এখন শুধু accept ও non-accept স্টেট উল্টে দিন —
$$\overline{M} = (Q, \Sigma, \delta, q_0, Q - F)$$
$\overline{M}$ ঠিক সেই স্ট্রিংগুলোই accept করবে যেগুলো $M$ reject করত — অর্থাৎ $L(\overline{M}) = \overline{L} = \Sigma^* - L$। এই প্রমাণের একটি সূক্ষ্ম কিন্তু জরুরি শর্ত আছে — $\delta$ অবশ্যই টোটাল হতে হবে (L06-এ যে রিকোয়্যারমেন্ট দেওয়া হয়েছিল)। যদি DFA-টি আংশিক (partial) হতো — কিছু স্ট্রিং-এ কোনো ট্রানজিশনই না থাকত — তাহলে accept/reject উল্টে দিলে সেই "কোনো ট্রানজিশন নেই" অবস্থার স্ট্রিংগুলো ভুলভাবে reject-ই থেকে যেত, কমপ্লিমেন্টে সেগুলোর accept হওয়ার কথা থাকা সত্ত্বেও।
৪ · ইন্টারসেকশন — প্রোডাক্ট কনস্ট্রাকশন
ইন্টারসেকশনের জন্য একটি সুন্দর, গঠনমূলক কৌশল আছে — প্রোডাক্ট কনস্ট্রাকশন। ধরুন $M_1=(Q_1,\Sigma,\delta_1,q_1,F_1)$ এবং $M_2=(Q_2,\Sigma,\delta_2,q_2,F_2)$ যথাক্রমে $L_1, L_2$-এর DFA। একটি নতুন DFA বানান যা $M_1$ ও $M_2$-কে একসাথে, লকস্টেপে চালায় —
$$M_\cap = (Q_1 \times Q_2,\ \Sigma,\ \delta,\ (q_1,q_2),\ F_1 \times F_2)$$ $$\text{যেখানে } \delta((p,q), a) = (\delta_1(p,a),\ \delta_2(q,a))$$
$M_\cap$-এর প্রতিটি স্টেট আসলে $M_1$ ও $M_2$-এর স্টেটের একটি জোড়া — এবং accept ঘটে শুধু তখনই যখন দুটো কম্পোনেন্ট স্টেটই accepting ($F_1 \times F_2$)। ফলাফল সরাসরি $L_1 \cap L_2$ recognize করে। একই স্টেট-স্পেস $Q_1 \times Q_2$ ব্যবহার করে accept রুল $F_1 \times F_2$-এর বদলে "কমপক্ষে একটি accepting" করলে ঠিক এই একই কনস্ট্রাকশন ইউনিয়নও দেয় — একটিই কাঠামো, দুটো ব্যবহার।
সেট থিওরির De Morgan's law — $L_1 \cap L_2 = \overline{\overline{L_1} \cup \overline{L_2}}$ (Discrete Mathematics কোর্সে প্রমাণিত) — ইন্টারসেকশন ক্লোজারের একটি সম্পূর্ণ বিকল্প প্রমাণ দেয়, শুধু ইতিমধ্যে প্রতিষ্ঠিত ইউনিয়ন ও কমপ্লিমেন্ট ক্লোজার ব্যবহার করে — সরাসরি প্রোডাক্ট কনস্ট্রাকশনের প্রয়োজনই নেই। দুটো ভিন্ন প্রমাণ একই সিদ্ধান্তে পৌঁছানো একটি সুন্দর সঙ্গতি-পরীক্ষা (sanity check)।
regex-ভিত্তিক প্রমাণ — L11-এর ক্লিনির থিওরেম সরাসরি পুনর্ব্যবহার।
DFA-ভিত্তিক প্রমাণ — টোটাল DFA-র accept/non-accept স্টেট swap।
প্রোডাক্ট কনস্ট্রাকশন — $Q_1 \times Q_2$ স্টেট-স্পেসে লকস্টেপ সিমুলেশন।
৫ · কোডে যাচাই — complement_dfa ও product_dfa
নিচের কোড সেলে L06-এর DFA ক্লাস পুনর্ব্যবহার করে দুটো real ফাংশন লেখা হলো — complement_dfa
(accept/non-accept স্টেট swap, টোটালিটি assert-সহ) এবং product_dfa (mode="intersection" বা
"union")। দুটো ছোট উদাহরণ DFA — "even length" (জোড় দৈর্ঘ্য) এবং "contains 01" (উপস্ট্রিং হিসেবে "01" আছে)
— নিয়ে তাদের প্রোডাক্ট DFA-র ফলাফল সরাসরি প্রত্যাশিত AND/OR শর্তের বিপরীতে ভেরিফাই করা হচ্ছে।
class DFA:
def __init__(self, states, alphabet, transitions, start, accept_states):
self.states = set(states)
self.alphabet = set(alphabet)
self.transitions = transitions # dict (state, symbol) -> state
self.start = start
self.accept_states = set(accept_states)
# টোটালিটি -- প্রতিটি (state, symbol) জোড়ার জন্য ট্রানজিশন থাকতেই হবে
for q in self.states:
for a in self.alphabet:
assert (q, a) in self.transitions, f"delta not total at ({q},{a})"
def run(self, s):
state = self.start
for ch in s:
state = self.transitions[(state, ch)]
return state
def accepts(self, s):
return self.run(s) in self.accept_states
def complement_dfa(dfa):
# শুধু accept/non-accept স্টেট উল্টে দিলেই কমপ্লিমেন্ট -- DFA টোটাল হওয়া আবশ্যক
return DFA(dfa.states, dfa.alphabet, dfa.transitions, dfa.start,
dfa.states - dfa.accept_states)
def product_dfa(dfa1, dfa2, mode="intersection"):
assert dfa1.alphabet == dfa2.alphabet
states = [(q1, q2) for q1 in dfa1.states for q2 in dfa2.states]
transitions = {}
for (q1, q2) in states:
for a in dfa1.alphabet:
transitions[((q1, q2), a)] = (dfa1.transitions[(q1, a)], dfa2.transitions[(q2, a)])
start = (dfa1.start, dfa2.start)
if mode == "intersection":
accept_states = [(q1, q2) for (q1, q2) in states
if q1 in dfa1.accept_states and q2 in dfa2.accept_states]
else: # "union"
accept_states = [(q1, q2) for (q1, q2) in states
if q1 in dfa1.accept_states or q2 in dfa2.accept_states]
return DFA(states, dfa1.alphabet, transitions, start, accept_states)
# DFA1: জোড় দৈর্ঘ্যের স্ট্রিং (even length)
even_len = DFA(
states={"e", "o"}, alphabet={"0", "1"},
transitions={("e","0"):"o", ("e","1"):"o", ("o","0"):"e", ("o","1"):"e"},
start="e", accept_states={"e"},
)
# DFA2: "01" উপস্ট্রিং হিসেবে আছে
contains01 = DFA(
states={"q0", "q1", "q2"}, alphabet={"0", "1"},
transitions={
("q0","0"):"q1", ("q0","1"):"q0",
("q1","0"):"q1", ("q1","1"):"q2",
("q2","0"):"q2", ("q2","1"):"q2",
},
start="q0", accept_states={"q2"},
)
# কমপ্লিমেন্ট যাচাই -- L(contains01) আর L(complement) সবসময় বিপরীত হওয়া উচিত
comp = complement_dfa(contains01)
test_strings = ["", "0", "1", "01", "10", "0011", "1111", "0101",
"0000", "101", "010", "1001", "11011", "0110"]
for s in test_strings:
assert contains01.accepts(s) != comp.accepts(s)
print("কমপ্লিমেন্ট চেক পাস: সব টেস্ট স্ট্রিং-এ contains01 আর তার complement বিপরীত ফলাফল দিচ্ছে")
inter = product_dfa(even_len, contains01, mode="intersection")
uni = product_dfa(even_len, contains01, mode="union")
print()
print(f"{'string':10s} even_len contains01 -> AND(expect) OR(expect)")
all_ok = True
for s in test_strings:
e, c = even_len.accepts(s), contains01.accepts(s)
i, u = inter.accepts(s), uni.accepts(s)
exp_i, exp_u = (e and c), (e or c)
if i != exp_i or u != exp_u:
all_ok = False
print(f"{s!r:10s} {str(e):8s} {str(c):10s} -> {str(i):5s}({str(exp_i):5s}) {str(u):5s}({str(exp_u):5s})")
print()
print("প্রোডাক্ট DFA-র ফলাফল সব টেস্ট স্ট্রিং-এ প্রত্যাশিত AND/OR-এর সাথে মিলছে:", all_ok)
রেগুলার ল্যাঙ্গুয়েজ ইউনিয়ন, কনক্যাটেনেশন, ক্লিন স্টার, কমপ্লিমেন্ট ও ইন্টারসেকশনের অধীনে ক্লোজড — প্রতিটি প্রমাণ গঠনমূলক, অর্থাৎ ফলাফলের DFA/regex সরাসরি বানানোর একটি রেসিপি দেয়। এই ক্লোজার প্রপার্টিগুলোই পরের দুই পাঠের (L13-L14) পাম্পিং লেমা ও নন-রেগুলারিটি প্রমাণের হাতিয়ার হয়ে উঠবে — "যদি $L$ রেগুলার হতো, তাহলে এই ক্লোজার প্রপার্টি দিয়ে তৈরি ভাষাটিও রেগুলার হতো" ধরনের যুক্তিতে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ কমপ্লিমেন্ট ক্লোজারের প্রমাণে DFA টোটাল হওয়া কেন এত জরুরি?
কারণ প্রমাণটা নির্ভর করে ঠিক এই ফ্যাক্টের উপর — প্রতিটি স্ট্রিং $\hat\delta(q_0,w)$-এর মাধ্যমে ঠিক একটি নির্দিষ্ট স্টেটে পৌঁছায়, হয় $F$-এ (accept) নয়তো $Q-F$-এ (reject)। যদি DFA আংশিক হতো, কিছু স্ট্রিং-এ কোনো ট্রানজিশনই না থাকত — সেই স্ট্রিং না accept, না reject, বরং "undefined"। accept/non-accept স্টেট উল্টে দিলে এই undefined স্ট্রিংগুলোর অবস্থা বদলায় না, ফলে $Q-F$ আসলে $\overline{L}$-এর সমান হয় না। টোটালিটি নিশ্চিত করে যে প্রতিটি স্ট্রিং-এর জন্য ঠিক দুটো সম্ভাবনার একটিই ঘটে — accept অথবা reject — যা কমপ্লিমেন্টের প্রমাণকে সঠিক রাখে।
প্র ০২ NFA-এর accept/non-accept স্টেট সরাসরি উল্টে দিলে কমপ্লিমেন্ট পাওয়া যায় না কেন?
কারণ NFA-এর accept করার শর্ত হলো "কমপক্ষে একটি পথ accept state-এ পৌঁছায়" (∃ quantifier) — এটি কমপ্লিমেন্টের প্রয়োজনীয় শর্তের ("সব সম্ভাব্য পথের জন্যই reject" বা ∀ quantifier) সাথে মেলে না। উদাহরণ হিসেবে ধরুন একটি NFA-এর একটি স্ট্রিং-এর জন্য দুটো সম্ভাব্য পথ আছে — একটি accept স্টেটে যায়, আরেকটি non-accept স্টেটে। মূল NFA স্ট্রিংটি accept করে (একটি পথ কাজ করেছে)। স্টেট উল্টালে, একই দুটো পথ এখন একটি non-accept আর একটি accept স্টেটে যায় — নতুন NFA-ও স্ট্রিংটি accept করবে (আবারও একটি পথ কাজ করেছে) — যদিও সেটির কমপ্লিমেন্টে থাকার কথা ছিল না। তাই কমপ্লিমেন্টের আগে L08-এর সাবসেট কনস্ট্রাকশন দিয়ে প্রথমে একটি (টোটাল) DFA বানাতে হয়।
প্র ০৩ প্রোডাক্ট কনস্ট্রাকশনের স্টেট-স্পেস $Q_1 \times Q_2$ আকারে বড় হলেও এটি কি এখনও "রেগুলার প্রমাণ করে"?
হ্যাঁ — নিয়মিততা (regularity) নির্ভর করে স্টেটের সংখ্যা ফাইনাইট কি না তার উপর, স্টেট কতগুলো তার উপর নয়। যেহেতু $Q_1$ ও $Q_2$ উভয়েই ফাইনাইট (DFA-এর সংজ্ঞা অনুযায়ী), তাদের কার্তেসীয় গুণফল $Q_1 \times Q_2$-ও ফাইনাইট ($|Q_1| \times |Q_2|$টি স্টেট)। তাই $M_\cap$ এখনও একটি বৈধ, সম্পূর্ণ DFA — ফলে $L_1 \cap L_2$ প্রমাণিতভাবে রেগুলার, স্টেট-সংখ্যা যতই বড় হোক না কেন (L16-এ দেখা যাবে, এই বড় DFA-টি প্রায়ই মিনিমাইজ করে ছোট করা যায়)।
অনুশীলন
-
চিন্তা করুন: "সেট-ডিফারেন্স" $L_1 - L_2$ (যেসব স্ট্রিং $L_1$-এ আছে কিন্তু $L_2$-এ নেই) কি
রেগুলার ল্যাঙ্গুয়েজের অধীনে ক্লোজড? ইতিমধ্যে প্রমাণিত ক্লোজার প্রপার্টি ব্যবহার করে যুক্তি সাজান।
হ্যাঁ, ক্লোজড। লক্ষ্য করুন $L_1 - L_2 = L_1 \cap \overline{L_2}$ — এটি ইন্টারসেকশন ও কমপ্লিমেন্টের একটি কম্পোজিশন মাত্র। যেহেতু রেগুলার ল্যাঙ্গুয়েজ উভয় অপারেশনের অধীনেই ক্লোজড (এই পাঠে প্রমাণিত), এবং ক্লোজড অপারেশনের কম্পোজিশনও ক্লোজড থাকে, তাই সেট-ডিফারেন্সও অবশ্যই রেগুলার ল্যাঙ্গুয়েজ তৈরি করে। এটি একটি সাধারণ প্যাটার্ন — নতুন অপারেশনের জন্য আলাদা প্রমাণ না লিখে, ইতিমধ্যে প্রমাণিত ক্লোজার প্রপার্টির কম্পোজিশন হিসেবে দেখানো।
-
পরীক্ষা করুন: উপরের কোড সেলে
mode="union"-এর বদলে দুটো ভিন্ন DFA (যেমন "শুরু হয় '1' দিয়ে" আর "শেষ হয় '0' দিয়ে") নিয়ে প্রোডাক্ট DFA বানিয়ে intersection মোডে টেস্ট চালান — ফলাফল কি প্রত্যাশিত AND শর্তের সাথে মেলে?হ্যাঁ মিলবে, কারণ
product_dfa-এর লজিক নির্দিষ্ট কোনো দুটো DFA-এর উপর নির্ভর করে না — এটি জেনেরিক, যেকোনো দুটো বৈধ (টোটাল) DFA-এর জন্যই কাজ করে, যেহেতু প্রমাণটি নিজেই জেনেরিক (কোনো নির্দিষ্ট ভাষার বৈশিষ্ট্যের উপর নির্ভর করে না, শুধু "DFA কীভাবে ট্রানজিশন করে" তার উপর নির্ভর করে)। এটিই গঠনমূলক প্রমাণের শক্তি — একবার সঠিকভাবে ইমপ্লিমেন্ট করলে, যেকোনো ইনপুট DFA জোড়ার জন্য কাজ করবে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — পাম্পিং লেমা — এই ক্লোজার প্রপার্টিগুলো ব্যবহার করেই নন-রেগুলারিটি প্রমাণের হাতিয়ার তৈরি করবে।
- L06 · DFA — ফরমাল ডেফিনিশন পূর্ববর্তী ভিত্তি টোটালিটি রিকোয়্যারমেন্ট ও DFA ক্লাস — এই পাঠের কমপ্লিমেন্ট ও প্রোডাক্ট কনস্ট্রাকশনের ভিত্তি।
- Discrete Mathematics কোর্স সহোদর কোর্স De Morgan's law, সেট অপারেশন ও ইকুইভ্যালেন্স রিলেশনের ফরমাল ভিত্তি সেই কোর্সেই তৈরি হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git ও Theory of Computation — সব এক জায়গায়।