পাঠ ০৬ · ৫৬-এর মধ্যে · মডিউল ২
Home / Courses / Formal Language & Automata Theory / Theory of Computation / DFA ফরমাল ডেফিনিশন

DFA — ফরমাল ডেফিনিশন ও ল্যাঙ্গুয়েজ অ্যাকসেপ্টেন্স

DFA — formal definition & language acceptance
৮ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • DFA-এর ফরমাল ৫-টাপল সংজ্ঞা এবং প্রতিটি উপাদানের অর্থ
  • Extended transition function $\hat\delta$ — ইনডাকটিভ সংজ্ঞা
  • DFA-এর ভাষা $L(M)$ ও regular ভাষার সংজ্ঞা
  • টোটালিটি (totality) — কেন এটি DFA-এর সংজ্ঞাগত অংশ, শুধু একটি ইমপ্লিমেন্টেশন ডিটেইল নয়
  • Python-এ একটি real, formal DFA ক্লাস — দুটি ভিন্ন ভাষার জন্য দুটি DFA

১ · DFA-এর ফরমাল সংজ্ঞা

L01-এ আমরা একটি অ্যাডহক DFA প্রিভিউ দেখেছিলাম (dict দিয়ে transitions)। এখন M2-এর প্রথম পাঠে সেটিকে ফরমালাইজ করছি। DFADeterministic Finite Automatonএকটি ৫-টাপল (Q, Σ, δ, q0, F) যেখানে δ প্রতিটি (state, symbol) জোড়ার জন্য ঠিক একটি (সবসময় সংজ্ঞায়িত) পরবর্তী স্টেট দেয়। (Deterministic Finite Automaton) ফরমালি একটি ৫-টাপল —

$$M = (Q, \Sigma, \delta, q_0, F)$$

  • $Q$ — স্টেটের একটি ফাইনাইট সেট
  • $\Sigma$ — ইনপুট আলফাবেট (L02)
  • $\delta: Q \times \Sigma \to Q$ — ট্রানজিশন ফাংশন, একটি টোটাল ফাংশন — প্রতিটি $(q, a) \in Q \times \Sigma$ জোড়ার জন্য সংজ্ঞায়িত ("ডিটারমিনিস্টিক" মানে ঠিক একটি, সবসময়-সংজ্ঞায়িত পরবর্তী স্টেট)
  • $q_0 \in Q$ — স্টার্ট স্টেট
  • $F \subseteq Q$ — accepting/final স্টেটের সেট
টোটালিটি কেন গুরুত্বপূর্ণ

$\delta$ একটি টোটাল ফাংশন হওয়া মানে — DFA কখনো "আটকে যায় না।" প্রতিটি স্টেট থেকে প্রতিটি সিম্বলের জন্য একটি নির্দিষ্ট পরবর্তী স্টেট আছে (এমনকি "রিজেক্ট"-এর জন্যও একটি সিঙ্ক/ট্র্যাপ স্টেট থাকতে পারে)। এই টোটালিটিই M3/L12-এর complement closure প্রমাণের ভিত্তি — accept/non-accept স্টেট অদল-বদল করলেই একটি সম্পূর্ণ, বৈধ DFA পাওয়া যায়, শুধু তখনই যখন মূল DFA টোটাল।

২ · Extended Transition Function $\hat\delta$

$\delta$ শুধু একটি সিম্বল প্রসেস করে। পুরো একটি স্ট্রিং প্রসেস করতে দরকার extended transition function $\hat\delta$Extended Transition Functionδ-কে পুরো স্ট্রিং প্রসেস করার জন্য সম্প্রসারিত রূপ, স্ট্রিং length-এর উপর স্ট্রাকচারাল ইনডাকশনে সংজ্ঞায়িত। — L03-এর স্ট্রাকচারাল ইনডাকশন কাঠামো অনুযায়ী ঠিক সংজ্ঞায়িত —

$$\hat\delta(q, \varepsilon) = q \qquad \text{(base case)}$$ $$\hat\delta(q, wa) = \delta(\hat\delta(q, w), a) \qquad \text{(inductive case)}$$

অর্থাৎ — স্ট্রিং $w$-এর শেষ সিম্বল $a$ বাদে বাকিটুকু ($w$) প্রসেস করে যে স্টেটে পৌঁছানো যায়, সেখান থেকে $a$ পড়ে এক ধাপ আরও এগোনো। বারবার এই সংজ্ঞা প্রয়োগ করলে পুরো স্ট্রিং, সিম্বল-বাই-সিম্বল, প্রসেস হয়ে যায়।

৩ · DFA-এর ল্যাঙ্গুয়েজ ও Regular ভাষা

একটি DFA $M$-এর ল্যাঙ্গুয়েজ —

$$L(M) = \{w \in \Sigma^* : \hat\delta(q_0, w) \in F\}$$

অর্থাৎ, $q_0$ থেকে শুরু করে $w$ প্রসেস করলে যে স্ট্রিংগুলো একটি accept স্টেটে গিয়ে শেষ হয়, তাদের সবার সেট। এখন L05-এর "সমস্যা = ল্যাঙ্গুয়েজ" রিফ্রেমিং মনে করুন — $L(M)$ হলো ঠিক সেই ডিসিশন প্রবলেম যা $M$ "সমাধান করে।" একটি ভাষা $L$-কে regular বলা হয় যদি কোনো DFA $M$-এর জন্য $L(M) = L$ হয় — এটিই M2-M3-এর কেন্দ্রীয় সংজ্ঞা, যার উপর বাকি সব প্রপার্টি (M3), সমতুল্যতা (M2/L08, L11) ও সীমাবদ্ধতা (M3/L13-L14) প্রমাণিত হবে।

৪ · Python-এ একটি ফরমাল DFA ক্লাস

নিচের কোড সেলে ৫-টাপল সংজ্ঞা হুবহু মিলিয়ে একটি DFA ক্লাস — টোটালিটি কনস্ট্রাক্টরেই যাচাই করা হচ্ছে (কনস্ট্রাকশনের সময় assert করা হয় প্রতিটি (state, symbol) জোড়ার জন্য একটি ট্রানজিশন সংজ্ঞায়িত আছে)। L01-এর "জোড়-সংখ্যক 1" ভাষাটি এখন ফরমালি এই ক্লাস দিয়ে বানানো হচ্ছে, এবং একটি সম্পূর্ণ ভিন্ন দ্বিতীয় ভাষা — "01 দিয়ে শেষ হওয়া স্ট্রিং।"

Python
class DFA:
    """(Q, Sigma, delta, q0, F) -- purno formal DFA definition"""
    def __init__(self, states, alphabet, transitions, start_state, accept_states):
        self.states = set(states)
        self.alphabet = set(alphabet)
        self.transitions = transitions          # dict: (state, symbol) -> state
        self.start_state = start_state
        self.accept_states = set(accept_states)
        # totality jachai -- delta protita (state, symbol) jorar jonno songjayito thakte hobe
        for q in self.states:
            for a in self.alphabet:
                if (q, a) not in self.transitions:
                    raise ValueError(f"osomponno DFA -- delta({q!r},{a!r}) songjayito nei (totality longhito)")

    def extended_delta(self, string):
        """delta-hat(q0, w) gonona kore -- base case: delta-hat(q,e)=q,
        inductive case: delta-hat(q,wa)=delta(delta-hat(q,w),a)"""
        state = self.start_state
        for symbol in string:
            if symbol not in self.alphabet:
                raise ValueError(f"'{symbol}' ei DFA-r alphabet-e nei")
            state = self.transitions[(state, symbol)]
        return state

    def accepts(self, string):
        return self.extended_delta(string) in self.accept_states


# L01-er 'jor-shonkhok 1' bhasha, ekhon formal DFA class diye
even_ones_dfa = DFA(
    states={'q0', 'q1'},
    alphabet={'0', '1'},
    transitions={
        ('q0', '0'): 'q0', ('q0', '1'): 'q1',
        ('q1', '0'): 'q1', ('q1', '1'): 'q0',
    },
    start_state='q0',
    accept_states={'q0'},
)

# ekti notun, alada bhasha -- '01' die shesh hoya string
ends_in_01_dfa = DFA(
    states={'r0', 'r1', 'r2'},
    alphabet={'0', '1'},
    transitions={
        ('r0', '0'): 'r1', ('r0', '1'): 'r0',
        ('r1', '0'): 'r1', ('r1', '1'): 'r2',
        ('r2', '0'): 'r1', ('r2', '1'): 'r0',
    },
    start_state='r0',
    accept_states={'r2'},
)

test_strings = ["", "0", "1", "01", "10", "0101", "1100", "111", "010101"]

print(f"{'w':10s} | even-1s DFA | ends-in-01 DFA")
print("-" * 45)
for w in test_strings:
    r1 = even_ones_dfa.accepts(w)
    r2 = ends_in_01_dfa.accepts(w)
    print(f"{w!r:10s} | {str(r1):11s} | {r2}")

    
লক্ষ্য করুন ends_in_01_dfa-এর তিনটি স্টেট আসলে "এখন পর্যন্ত সাফিক্স কী দেখা গেছে" ট্র্যাক করছে — $r_0$ = "শেষ সিম্বল 1 বা কিছুই দেখা যায়নি", $r_1$ = "শেষ সিম্বল 0", $r_2$ = "শেষ দুই সিম্বল ঠিক 01" (accept)। এই "মেমরি" পুরোপুরি ফাইনাইট — DFA কখনো পুরো ইনপুট মনে রাখে না, শুধু এই তিনটির একটি স্টেটে থাকে।
মূল কথা · Key takeaway

DFA-এর ফরমাল সংজ্ঞা — ৫-টাপল, টোটাল $\delta$, ইনডাকটিভ $\hat\delta$, এবং $L(M)$ — এই পুরো M2-M3-এর ভাষা। পরের পাঠে (L07) আমরা $\delta$-কে nondeterministic করে NFA দেখব — একই আকৃতি, কিন্তু একটি মৌলিকভাবে ভিন্ন acceptance নিয়ম।

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

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ $\delta$ কেন একটি টোটাল ফাংশন হতে হয় — একটি partial ফাংশন (কিছু জোড়ার জন্য undefined) দিয়ে DFA সংজ্ঞায়িত করলে কী সমস্যা হতো?

যদি $\delta$ partial হতো, তাহলে কিছু ইনপুট স্ট্রিং প্রসেস করার সময় DFA "আটকে যেতে" পারত (কোনো সংজ্ঞায়িত পরবর্তী স্টেট না থাকায়) — এবং তখন সেই স্ট্রিং-এর জন্য accept/reject কোনোটাই বলা সম্ভব হতো না, যা L05-এর ডিসিশন-প্রবলেম ফ্রেমওয়ার্ককে ভেঙে দিত (প্রতিটি ইনপুটের জন্য একটি নির্দিষ্ট YES/NO উত্তর থাকতেই হবে)। টোটালিটি নিশ্চিত করে প্রতিটি স্ট্রিং $w \in \Sigma^*$-এর জন্য $\hat\delta(q_0,w)$ সবসময় সংজ্ঞায়িত এবং একটি নির্দিষ্ট ফলাফল দেয়। এছাড়াও M3/L12-এর কমপ্লিমেন্ট ক্লোজার প্রুফ (accept/non-accept অদল-বদল) সরাসরি টোটালিটির উপর নির্ভর করে।

প্র ০২ $\hat\delta$-এর সংজ্ঞা কেন স্ট্রিং-এর শেষ সিম্বলের উপর ইনডাকশন করে ($wa$), শুরুর সিম্বলের উপর নয় ($aw$)?

কারণ এটি ঠিক DFA প্রসেসিং-এর দিকের সাথে মেলে — একটি DFA বাম থেকে ডানে, এক-এক করে সিম্বল পড়ে। "$w$ প্রসেস করার পর $a$ পড়া" ($\delta(\hat\delta(q,w),a)$) মানে হলো — প্রথমে $w$-এর প্রভাব হিসাব করে ফেলো (যা নিজেই ছোট, তাই ইনডাকটিভ হাইপোথিসিস প্রযোজ্য), তারপর সবশেষে নতুন সিম্বল $a$-এর একটি মাত্র $\delta$ কল প্রয়োগ করো। এটি L03-এর স্ট্রাকচারাল ইনডাকশনের ঠিক সেই প্যাটার্ন ($w \to wa$) অনুসরণ করে, যা "একটি সিম্বল বাড়ানো"-কে রিকার্সিভ কেস হিসেবে ব্যবহার করে।

প্র ০৩ উপরের কোড সেলে ends_in_01_dfa স্ট্রিং "0101" accept করে কিন্তু "1100" করে না কেন — হাতে ট্রেস করে দেখান।

"0101": $r_0 \xrightarrow{0} r_1 \xrightarrow{1} r_2 \xrightarrow{0} r_1 \xrightarrow{1} r_2$ — শেষ স্টেট $r_2$, যা accept স্টেট, কারণ স্ট্রিংটি সত্যিই "01" দিয়ে শেষ হয়েছে। "1100": $r_0 \xrightarrow{1} r_0 \xrightarrow{1} r_0 \xrightarrow{0} r_1 \xrightarrow{0} r_1$ — শেষ স্টেট $r_1$ (accept নয়), কারণ স্ট্রিংটি "00" দিয়ে শেষ হয়েছে, "01" দিয়ে নয়। উভয় ট্রেসই দেখায় DFA ঠিক সঠিকভাবে "শেষ দুই সিম্বল কী" ট্র্যাক করছে, পুরো স্ট্রিং না মনে রেখেই।

অনুশীলন

  1. হাতে করুন: $\hat\delta$-এর ইনডাকটিভ সংজ্ঞা ব্যবহার করে হাতে দেখান even_ones_dfa-এর জন্য $\hat\delta(q_0, \text{"101"})$ কী — ধাপে ধাপে $\hat\delta(q_0,\varepsilon)$, $\hat\delta(q_0,\text{"1"})$, $\hat\delta(q_0,\text{"10"})$, $\hat\delta(q_0,\text{"101"})$ লিখুন।

    $\hat\delta(q_0,\varepsilon)=q_0$ (base case)। $\hat\delta(q_0,\text{"1"}) = \delta(\hat\delta(q_0,\varepsilon),1) = \delta(q_0,1) = q_1$। $\hat\delta(q_0,\text{"10"}) = \delta(\hat\delta(q_0,\text{"1"}),0) = \delta(q_1,0) = q_1$। $\hat\delta(q_0,\text{"101"}) = \delta(\hat\delta(q_0,\text{"10"}),1) = \delta(q_1,1) = q_0$। যেহেতু $q_0 \in F$, "101" accept হয় — এবং সত্যিই "101"-এ দুটি '1' আছে (জোড়), তাই এটি সঠিক।

  2. নতুন DFA বানান: উপরের কোড সেলের প্যাটার্ন অনুসরণ করে একটি তৃতীয় DFA বানান — "$\Sigma=\{0,1\}$-এর উপর length ঠিক ৩ জোড় (multiple of 3)"-এর ভাষার জন্য নয়, বরং সহজ একটি ভাষা: "স্ট্রিং-এ অন্তত একটি '1' আছে" — এটির জন্য ন্যূনতম কয়টি স্টেট লাগবে?

    ঠিক ২টি স্টেট যথেষ্ট — $s_0$ (এখনো কোনো '1' দেখা যায়নি, non-accept) এবং $s_1$ (অন্তত একটি '1' দেখা গেছে, accept, এবং একবার এখানে পৌঁছালে সবসময় এখানেই থাকবে)। ট্রানজিশন: $\delta(s_0,0)=s_0$, $\delta(s_0,1)=s_1$, $\delta(s_1,0)=s_1$, $\delta(s_1,1)=s_1$ — $s_1$ একটি "স্টিকি" accept স্টেট, যা দেখায় কিছু ভাষার জন্য খুব ছোট, সরল DFA যথেষ্ট।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — NFA: ফরমাল ডেফিনিশন — এখনই পড়া যাবে।
  • Finite Automata — NFA to DFA PLC L18 সেই কোর্সে এই একই DFA/NFA মেশিন ব্যবহৃত হয় বাস্তবে লেক্সার বানাতে — এখানে তার পেছনের ফরমাল সংজ্ঞা ও প্রমাণে ফোকাস করা হচ্ছে।
  • সব 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 — সব এক জায়গায়।
আগের পাঠ
সমস্যাকে ল্যাঙ্গুয়েজ হিসেবে দেখা — ডিসিশন প্রবলেম