পাঠ ১২ · ৫৬-এর মধ্যে · মডিউল ৩
Home / Courses / Formal Language & Automata Theory / Theory of Computation / ক্লোজার প্রপার্টি

রেগুলার ল্যাঙ্গুয়েজের ক্লোজার প্রপার্টি

Closure properties of regular languages
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ক্লোজার প্রপার্টি কী এবং কেন এটি রেগুলার ল্যাঙ্গুয়েজ নিয়ে কাজ করাকে সহজ করে তোলে
  • ইউনিয়ন, কনক্যাটেনেশন, ক্লিন স্টার — 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 হওয়ার কথা থাকা সত্ত্বেও।

মনে রাখবেন — এই কমপ্লিমেন্ট ট্রিক শুধু DFA-তে কাজ করে, NFA-তে নয়! একটি NFA-এর accept/non-accept স্টেট উল্টে দিলে সেটি কমপ্লিমেন্ট ল্যাঙ্গুয়েজ accept করে না, কারণ NFA-এর "exists a path" সিমান্টিক্স ("কোনো একটি পথ কাজ করলেই accept") কমপ্লিমেন্টের "for all paths" প্রয়োজনের সাথে মেলে না। তাই কমপ্লিমেন্ট নেওয়ার আগে NFA-কে L08-এর সাবসেট কনস্ট্রাকশন দিয়ে আগে DFA-তে রূপান্তর করতে হবে।

৪ · ইন্টারসেকশন — প্রোডাক্ট কনস্ট্রাকশন

ইন্টারসেকশনের জন্য একটি সুন্দর, গঠনমূলক কৌশল আছে — প্রোডাক্ট কনস্ট্রাকশন। ধরুন $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 দিয়ে ক্রস-চেক

সেট থিওরির De Morgan's law — $L_1 \cap L_2 = \overline{\overline{L_1} \cup \overline{L_2}}$ (Discrete Mathematics কোর্সে প্রমাণিত) — ইন্টারসেকশন ক্লোজারের একটি সম্পূর্ণ বিকল্প প্রমাণ দেয়, শুধু ইতিমধ্যে প্রতিষ্ঠিত ইউনিয়ন ও কমপ্লিমেন্ট ক্লোজার ব্যবহার করে — সরাসরি প্রোডাক্ট কনস্ট্রাকশনের প্রয়োজনই নেই। দুটো ভিন্ন প্রমাণ একই সিদ্ধান্তে পৌঁছানো একটি সুন্দর সঙ্গতি-পরীক্ষা (sanity check)।

$L_1, L_2$ রেগুলার (DFA/regex) ∪ · concat · * (regex, L11) কমপ্লিমেন্ট (DFA স্টেট-swap) ইন্টারসেকশন (প্রোডাক্ট) De Morgan's law ক্রস-চেক ফলাফলও Regular
পাঁচটি অপারেশন — সবগুলোই রেগুলার ল্যাঙ্গুয়েজকে রেগুলার ল্যাঙ্গুয়েজেই রাখে, দুটো ভিন্ন প্রমাণ-কৌশলে (regex-ভিত্তিক ও DFA-ভিত্তিক)।
ইউনিয়ন/কনক্যাট/স্টার
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 শর্তের বিপরীতে ভেরিফাই করা হচ্ছে।

Python
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)

    
মূল কথা · Key takeaway

রেগুলার ল্যাঙ্গুয়েজ ইউনিয়ন, কনক্যাটেনেশন, ক্লিন স্টার, কমপ্লিমেন্ট ও ইন্টারসেকশনের অধীনে ক্লোজড — প্রতিটি প্রমাণ গঠনমূলক, অর্থাৎ ফলাফলের 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-টি প্রায়ই মিনিমাইজ করে ছোট করা যায়)।

অনুশীলন

  1. চিন্তা করুন: "সেট-ডিফারেন্স" $L_1 - L_2$ (যেসব স্ট্রিং $L_1$-এ আছে কিন্তু $L_2$-এ নেই) কি রেগুলার ল্যাঙ্গুয়েজের অধীনে ক্লোজড? ইতিমধ্যে প্রমাণিত ক্লোজার প্রপার্টি ব্যবহার করে যুক্তি সাজান।

    হ্যাঁ, ক্লোজড। লক্ষ্য করুন $L_1 - L_2 = L_1 \cap \overline{L_2}$ — এটি ইন্টারসেকশন ও কমপ্লিমেন্টের একটি কম্পোজিশন মাত্র। যেহেতু রেগুলার ল্যাঙ্গুয়েজ উভয় অপারেশনের অধীনেই ক্লোজড (এই পাঠে প্রমাণিত), এবং ক্লোজড অপারেশনের কম্পোজিশনও ক্লোজড থাকে, তাই সেট-ডিফারেন্সও অবশ্যই রেগুলার ল্যাঙ্গুয়েজ তৈরি করে। এটি একটি সাধারণ প্যাটার্ন — নতুন অপারেশনের জন্য আলাদা প্রমাণ না লিখে, ইতিমধ্যে প্রমাণিত ক্লোজার প্রপার্টির কম্পোজিশন হিসেবে দেখানো।

  2. পরীক্ষা করুন: উপরের কোড সেলে mode="union"-এর বদলে দুটো ভিন্ন DFA (যেমন "শুরু হয় '1' দিয়ে" আর "শেষ হয় '0' দিয়ে") নিয়ে প্রোডাক্ট DFA বানিয়ে intersection মোডে টেস্ট চালান — ফলাফল কি প্রত্যাশিত AND শর্তের সাথে মেলে?

    হ্যাঁ মিলবে, কারণ product_dfa-এর লজিক নির্দিষ্ট কোনো দুটো DFA-এর উপর নির্ভর করে না — এটি জেনেরিক, যেকোনো দুটো বৈধ (টোটাল) DFA-এর জন্যই কাজ করে, যেহেতু প্রমাণটি নিজেই জেনেরিক (কোনো নির্দিষ্ট ভাষার বৈশিষ্ট্যের উপর নির্ভর করে না, শুধু "DFA কীভাবে ট্রানজিশন করে" তার উপর নির্ভর করে)। এটিই গঠনমূলক প্রমাণের শক্তি — একবার সঠিকভাবে ইমপ্লিমেন্ট করলে, যেকোনো ইনপুট DFA জোড়ার জন্য কাজ করবে।

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

আগের পাঠ
L11 · ক্লিনির থিওরেম — রেগেক্স ও ফাইনাইট অটোমাটার ইকুইভ্যালেন্স