এই পর্বে যা শিখবেন
- DFA-র নিখুঁত সংজ্ঞা, transition diagram, বাড়ানো transition function δ* আর DFA-র ভাষা
- Invariant আর structural induction দিয়ে একটি DFA-র সঠিকতা প্রমাণ
- NFA, ε-move, subset construction-এর algorithm, তার সঠিকতার প্রমাণ আর 2n state-এর নিচের সীমা
- Regular expression-এর syntax ও অর্থ, Kleene-এর উপপাদ্য, Thompson-এর নির্মাণ আর closure property-র প্রমাণ
- Pumping lemma-র পূর্ণ প্রমাণ ও ব্যবহার, Myhill–Nerode-এর ধারণা, DFA minimisation আর output-ওয়ালা যন্ত্র
১ · DFA — সসীম স্মৃতির একটি যন্ত্র
মেট্রো রেলের স্টেশনের টার্নস্টাইল ভাবুন। এর মাত্র দুটি অবস্থা: “বন্ধ” আর “খোলা”। কার্ড ছোঁয়ালে বন্ধ থেকে খোলা হয়, কেউ পার হলে আবার বন্ধ। যন্ত্রটি অতীতের সব ঘটনা মনে রাখে না; শুধু জানে এখন কোন অবস্থায় আছে। এতটুকু স্মৃতি দিয়েই সে ঠিক সিদ্ধান্ত নেয়। এমন যন্ত্রের গণিতই finite automaton.
পর্ব ১২-এ alphabet Σ, string, empty string ε আর Σ* সংজ্ঞায়িত করেছি; Σ*-এর যেকোনো subset একটি formal language. পর্ব ৭৩ “Grammar আর formal language — BNF থেকে Chomsky-র শ্রেণি” ভাষা তৈরি করার নিয়ম দেখায় (grammar). এই পর্বের প্রশ্ন উল্টো: একটি string দেখে কোনো যন্ত্র কি বলতে পারে সেটি ভাষায় আছে কি না, আর যন্ত্রের স্মৃতি সসীম হলে কোন ভাষা চেনা যায়।
একটি deterministic finite automaton হলো ৫-tuple M = (Q, Σ, δ, q0, F), যেখানে Q একটি সসীম state-এর সেট, Σ alphabet, transition function δ : Q × Σ → Q, শুরুর state q0 ∈ Q, আর accepting state-এর সেট F ⊆ Q. “Deterministic” মানে প্রতিটি (state, চিহ্ন) জোড়ার জন্য পরের state ঠিক একটি।
δ একবারে একটি চিহ্ন সামলায়। পুরো string-এর জন্য তাকে পর্ব ১২-এর ধাঁচে recursive সংজ্ঞায় বাড়ানো হয়।
Extended transition function δ* : Q × Σ* → Q: δ*(q, ε) = q, আর w ∈ Σ*, a ∈ Σ হলে δ*(q, wa) = δ(δ*(q, w), a). M-এর ভাষা L(M) = {w ∈ Σ* : δ*(q0, w) ∈ F}. যে ভাষা কোনো DFA-র ভাষা, তাকে regular language বলে।
প্রথম উদাহরণ: Σ = {0, 1}-এ “1-এর সংখ্যা জোড়”। দুটি state যথেষ্ট: E (এ পর্যন্ত জোড় সংখ্যক 1) আর O (বিজোড়)। 0 পড়লে state বদলায় না, 1 পড়লে E আর O অদলবদল হয়; q0 = E, F = {E}. পুরো ইতিহাস নয়, শুধু একটি parity বিট মনে রাখলেই চলে, ঠিক পর্ব ৬৯-এর XOR-এর মতো।
দ্বিতীয় উদাহরণ বাজারঘর থেকে। ধরি (কাল্পনিক নিয়ম) বৈধ অর্ডার-কোড হলো অঙ্কের যেকোনো string, যার শেষ দুই অঙ্ক “25”, যেমন 40125. DFA-র state মনে রাখে “25”-এর কতটুকু এইমাত্র মিলেছে: 0 (কিছুই না), 1 (শেষে একটি 2), 2 (শেষে 25, accepting). যেকোনো state থেকে 2 পড়লে state 1; state 1-এ 5 পড়লে state 2; বাকি সব অঙ্কে state 0. দশটি অঙ্ক আর তিনটি state মিলে টেবিলে ৩০টি ঘর, কিন্তু নিয়ম মাত্র তিন লাইনের। সংজ্ঞা অনুযায়ী δ প্রতিটি ঘরে সংজ্ঞায়িত; কোনো ঘরে “এখানে আর আশা নেই” বোঝাতে চাইলে একটি dead state রাখা হয়, যেখান থেকে প্রতিটি চিহ্নে নিজের কাছেই ফেরা, আর যা accepting নয়। যেমন কোড যদি অবশ্যই অক্ষর B দিয়ে শুরু হতে হয়, তবে প্রথম চিহ্ন অন্য কিছু হলে যন্ত্র dead state-এ চলে যায়, আর কখনো বের হয় না।
তৃতীয় উদাহরণটিই এই পর্বের প্রধান চরিত্র: binary string-কে সংখ্যা হিসেবে পড়ে বলা, সেটি ৩ দিয়ে বিভাজ্য কি না। State 0, 1, 2; δ(q, b) = (2q + b) mod 3; q0 = 0, F = {0}. নিচের transition diagram-এ বৃত্ত হলো state, তীর হলো transition, দুই দাগের বৃত্ত accepting.
৩ দিয়ে বিভাজ্যতার DFA
State-এর নাম = এ পর্যন্ত পড়া binary মানের ৩ দিয়ে ভাগশেষ। তীরের গায়ের চিহ্নটি পড়লে সেই তীর ধরে যেতে হয়। বাঁ দিকের খোলা তীর শুরুর state দেখায়।
২ · Invariant দিয়ে সঠিকতার প্রমাণ — ৩ দিয়ে বিভাজ্যতা
“যন্ত্রটি ঠিক কাজ করে” দাবিটি প্রমাণ করতে পর্ব ১৩-এর loop invariant-এর ধারণাই কাজে লাগে: প্রতিটি চিহ্ন পড়ার পর একটি বক্তব্য সত্য থাকে। Binary string w-এর মান val(w) recursive: val(ε) = 0, val(wb) = 2 · val(w) + b (একটি বিট ডানে যোগ করা মানে দ্বিগুণ করে সেই বিট যোগ)।
সব w ∈ {0, 1}*-এর জন্য δ*(0, w) = val(w) mod 3. ফলে DFA w গ্রহণ করে ⇔ 3 ∣ val(w).
- Base: w = ε. δ*(0, ε) = 0 = val(ε) mod 3. — δ*-এর সংজ্ঞা
- Inductive hypothesis: কোনো w-এর জন্য δ*(0, w) = val(w) mod 3 = r. — induction, |w|-এর ওপর
- Step: δ*(0, wb) = δ(r, b) = (2r + b) mod 3. — δ*-এর সংজ্ঞা আর DFA-র নিয়ম
- r ≡ val(w) (mod 3), তাই 2r + b ≡ 2 · val(w) + b = val(wb) (mod 3); দুটির ভাগশেষ সমান। — পর্ব ২২-এর congruence-এর যোগ ও গুণ
- অতএব δ*(0, wb) = val(wb) mod 3, আর F = {0} বলে গ্রহণ ⇔ ভাগশেষ 0. ∎
একই যুক্তিতে যেকোনো m-এর জন্য m-টি state-এর DFA “m দিয়ে বিভাজ্য” চেনে: δ(q, b) = (2q + b) mod m. দশমিক অঙ্কের জন্য নিয়মটি δ(q, d) = (10q + d) mod m. m = 3-এ 10 ≡ 1 (mod 3), তাই নিয়ম দাঁড়ায় δ(q, d) = (q + d) mod 3: যন্ত্রটি আসলে অঙ্কগুলোর যোগফলের ভাগশেষ রাখছে। স্কুলের “অঙ্কের যোগফল ৩ দিয়ে বিভাজ্য হলে সংখ্যাটিও” নিয়মটি এই DFA-র সঠিকতারই আরেক রূপ। নিচের stepper 10110 (= 22) নিয়ে যন্ত্রটি চালায়।
1q = q0 (start)2read 1: (2·0+1) mod 33read 0: (2·1+0) mod 34read 1: (2·2+1) mod 35read 1: (2·2+1) mod 36read 0: (2·2+0) mod 37q1 ∉ F → reject
| পড়া অংশ | |
| val | |
| val mod 3 | |
| state |
- শুরু। কিছু পড়া হয়নি, val(ε) = 0, যন্ত্র q0-এ। Invariant সত্য: state = 0 = মান mod 3.
- প্রথম বিট 1. δ(0, 1) = (2 · 0 + 1) mod 3 = 1. পড়া অংশ “1”, মান 1: state q1, মিলে গেল।
- বিট 0. δ(1, 0) = 2. পড়া অংশ “10”, মান 2, ভাগশেষ 2: q2.
- বিট 1. δ(2, 1) = 5 mod 3 = 2. “101” = 5, আর 5 mod 3 = 2: যন্ত্র q2-তেই থাকে।
- বিট 1. আবার δ(2, 1) = 2. “1011” = 11, 11 mod 3 = 2. যন্ত্র 11 সংখ্যাটি মনে রাখেনি, শুধু তার ভাগশেষ।
- শেষ বিট 0. δ(2, 0) = 4 mod 3 = 1. “10110” = 22, 22 mod 3 = 1: q1.
- সিদ্ধান্ত। শেষ state q1 ∉ F = {q0}, তাই string বাতিল। সত্যিই 22 = 3 · 7 + 1, ৩ দিয়ে বিভাজ্য নয়।
প্রমাণ থাকলেও ছোট ক্ষেত্রে পূর্ণ গণনা ভুল ধরার সবচেয়ে সস্তা উপায়। নিচের ব্লক একটি সাধারণ DFA-চালক লেখে, তারপর দৈর্ঘ্য 0 থেকে 12-এর সব binary string-এ mod-3 DFA-কে Python-এর নিজের ভাগশেষের সঙ্গে মেলায়, আর জোড়-1 ও অর্ডার-কোডের DFA-ও চালায়। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।
from itertools import product
def run(delta, start, accept, w):
q, path = start, [start]
for ch in w: # one table lookup per symbol
q = delta[q, ch]
path.append(q)
return q in accept, path
# mod-3 DFA: the state is (value of the bits read so far) mod 3
MOD3 = {(q, b): (2 * q + int(b)) % 3 for q in range(3) for b in "01"}
print("10110 =", int("10110", 2), "->", run(MOD3, 0, {0}, "10110"))
total = bad = 0
for n in range(13): # every binary string of length 0..12
for w in map("".join, product("01", repeat=n)):
value = int(w, 2) if w else 0
total += 1
bad += run(MOD3, 0, {0}, w)[0] != (value % 3 == 0)
print("mod-3 DFA: strings checked", total, "disagreements", bad)
EVEN = {("E", "0"): "E", ("E", "1"): "O", ("O", "0"): "O", ("O", "1"): "E"}
print("even 1s:", [w for w in ("", "1", "11", "1011", "0110") if run(EVEN, "E", {"E"}, w)[0]])
def step25(q, ch): # how much of the tail "25" is matched
if ch == "2":
return 1
return 2 if (ch == "5" and q == 1) else 0
D25 = {(q, c): step25(q, c) for q in range(3) for c in "0123456789"}
print({x: run(D25, 0, {2}, x)[0] for x in ("40125", "4012", "2525", "252")})
ok = all(run(D25, 0, {2}, w)[0] == w.endswith("25")
for n in range(5) for w in map("".join, product("0123456789", repeat=n)))
print("order-code DFA agrees with endswith('25') on 11111 strings:", ok)আসল আউটপুট (ব্রাউজারের Python):
10110 = 22 -> (False, [0, 1, 2, 2, 2, 1])
mod-3 DFA: strings checked 8191 disagreements 0
even 1s: ['', '11', '0110']
{'40125': True, '4012': False, '2525': True, '252': False}
order-code DFA agrees with endswith('25') on 11111 strings: True
প্রথম লাইন stepper-এর পথ হুবহু: 0, 1, 2, 2, 2, 1, আর বাতিল। দৈর্ঘ্য 0 থেকে 12-এর মোট 213 − 1 = 8191টি string-এ একটিও অমিল নেই; উপপাদ্য ২.১-এর প্রমাণ সব দৈর্ঘ্যের জন্য, আর এই গণনা প্রমাণে কোনো হিসাবের ভুল থাকলে ধরত। জোড়-1-এর যন্ত্র ফাঁকা string-ও গ্রহণ করে (শূন্যটি 1, জোড়)। অর্ডার-কোডের DFA দৈর্ঘ্য 4 পর্যন্ত সব 11111টি অঙ্ক-string-এ endswith-এর সঙ্গে মিলেছে; 252 বাতিল, কারণ শেষ দুই অঙ্ক 52.
৩ · NFA আর ε-move — অনুমান করতে পারা যন্ত্র
ভাষা L3 = {w ∈ {0, 1}* : শেষ থেকে তৃতীয় চিহ্ন 1}. DFA বানাতে গেলে সব সময় শেষ তিনটি বিট মনে রাখতে হয়। কিন্তু একটি “অনুমান করতে পারা” যন্ত্র সহজে বলা যায়: পড়তে থাকুন; কোনো 1 দেখে অনুমান করুন “এটিই শেষ থেকে তৃতীয়”, তারপর ঠিক আরও দুটি চিহ্ন পড়ে থামুন। অনুমান ঠিক হলে কোনো একটি পথ accepting state-এ পৌঁছায়।
একটি nondeterministic finite automaton N = (Q, Σ, δ, q0, F), যেখানে এবার δ : Q × (Σ ∪ {ε}) → P(Q), অর্থাৎ একটি state আর চিহ্ন থেকে state-এর একটি সেট (পর্ব ১৪-এর power set; ফাঁকা সেটও চলে)। δ(q, ε)-এর state-গুলোতে কোনো চিহ্ন না পড়েই যাওয়া যায়: ε-move. N একটি string w গ্রহণ করে, যদি w পড়ার অন্তত একটি পথ F-এর কোনো state-এ শেষ হয়।
“শেষ থেকে তৃতীয় চিহ্ন 1”-এর NFA
q0-তে 1 পড়লে দুটি পথ: q0-তেই থাকা, অথবা q1-এ যাওয়া। q3-এ কোনো তীর নেই: আরও চিহ্ন এলে সেই পথ মরে যায়।
NFA চালানোর হিসাব হলো “এই মুহূর্তে যেসব state-এ থাকা সম্ভব”, তার সেট। ε-move থাকলে প্রতিটি ধাপের পরে সেটটিকে বড় করতে হয়: q-এর ε-closure E(q) মানে q থেকে শুধু ε-তীর ধরে যত state-এ যাওয়া যায় (q নিজেও)। এটি ঠিক “ε-তীর” relation-এর reflexive-transitive closure, যা পর্ব ৫৪-এ Warshall দিয়ে গুনেছেন। ε-move কোথায় কাজে লাগে? ধরুন N1 আর N2 দুটি NFA. একটি নতুন শুরুর state থেকে দুই যন্ত্রের শুরুতে দুটি ε-তীর দিলেই L(N1) ∪ L(N2)-এর NFA তৈরি: যন্ত্র কোনো চিহ্ন না পড়েই “কোন যন্ত্র চালাব” অনুমান করে নেয়। §৫-এর Thompson-এর নির্মাণ এই কৌশলই বারবার ব্যবহার করে। ছবির NFA-তে 0110 পড়লে সম্ভাব্য সেট {q0} → {q0} → {q0, q1} → {q0, q1, q2} → {q0, q2, q3}; শেষ সেটে q3 আছে, তাই গ্রহণ (শেষ থেকে তৃতীয় চিহ্ন 1).
৪ · Subset construction — NFA থেকে DFA
আগের অনুচ্ছেদের “সম্ভাব্য state-এর সেট” নিজেই একটি DFA-র state হতে পারে। Michael Rabin আর Dana Scott ১৯৫৯ সালের প্রবন্ধে এই subset construction দেন।
প্রতিটি NFA N = (Q, Σ, δ, q0, F)-এর জন্য একটি DFA D আছে, যাতে L(D) = L(N). D-এর state Q-এর subset; শুরু E(q0); δD(S, a) = E(⋃s∈S δ(s, a)); আর S accepting ⇔ S ∩ F ≠ ∅. D-তে বড়জোর 2|Q|টি state.
প্রমাণ (সরলতার জন্য ε-move ছাড়া; তখন E(S) = S, আর ε থাকলে প্রতিটি ধাপে E বসালে একই যুক্তি চলে)। NFA-র জন্য δN*(q0, w) মানে w পড়ার পর সম্ভাব্য সব state-এর সেট: δN*(q0, ε) = {q0}, δN*(q0, va) = ⋃s ∈ δN*(q0, v) δ(s, a).
- দাবি: সব w-এর জন্য δD*({q0}, w) = δN*(q0, w). |w|-এর ওপর induction. — structural induction
- Base: w = ε হলে দুই দিকই {q0}. — দুই δ*-এর সংজ্ঞা
- Step: w = va. δD*({q0}, va) = δD(δD*({q0}, v), a) = δD(δN*(q0, v), a) = ⋃s ∈ δN*(q0, v) δ(s, a) = δN*(q0, va). — hypothesis, তারপর δD-এর সংজ্ঞা
- D w গ্রহণ করে ⇔ δD*({q0}, w) ∩ F ≠ ∅ ⇔ N-এর অন্তত একটি পথ F-এ শেষ হয় ⇔ N w গ্রহণ করে। ∎
বাস্তবে সব 2|Q|টি subset লেখা হয় না; শুরু থেকে যেগুলোতে পৌঁছানো যায়, শুধু সেগুলো ধাপে ধাপে বের করা হয়। ছবির NFA-তে (state-কে 0, 1, 2, 3 লিখে):
1start = {0}2{0}: 0→{0} 1→{01}3{01}: 0→{02} 1→{012}4{02}: 0→{03} 1→{013}5{012}: 0→{023} 1→{0123}64 more rows: nothing new
| পূর্ণ সারি | |
| জানা state | |
| নতুন পাওয়া |
- শুরু। ε-move নেই, তাই শুরুর state {0}. টেবিলে এখনো কোনো সারি পূরণ হয়নি। (বাঁ দিকের কোডে জায়গা বাঁচাতে {0, 1, 2}-কে {012} লেখা হয়েছে।)
- সারি {0}. 0 পড়লে 0 থেকে শুধু 0: {0}. 1 পড়লে 0 থেকে 0 আর 1: {0, 1}, নতুন state.
- সারি {0, 1}. 0-তে: 0 → {0}, 1 → {2}, মিলে {0, 2}. 1-এ: {0, 1} ∪ {2} = {0, 1, 2}. দুটিই নতুন।
- সারি {0, 2}. 0-তে {0, 3}, 1-এ {0, 1, 3}. দুটিতেই 3 আছে, তাই accepting.
- সারি {0, 1, 2}. 0-তে {0, 2, 3}, 1-এ {0, 1, 2, 3}. এখন মোট ৮টি state জানা।
- বাকি চার সারি। {0, 3}, {0, 1, 3}, {0, 2, 3}, {0, 1, 2, 3} থেকে যা আসে সবই আগে দেখা (3 থেকে কোনো তীর নেই)। মোট ৮টি DFA state, যাদের ৪টিতে 3 আছে বলে accepting.
লক্ষ করুন, i ∈ S ঠিক তখন, যখন শেষ থেকে i-তম চিহ্ন 1 (i = 1, 2, 3). অর্থাৎ DFA শেষ তিনটি বিট মনে রাখছে, আর তার 23 = 8 রকম সম্ভাবনা। এটা কি এড়ানো যায়? না।
Ln = {w : শেষ থেকে n-তম চিহ্ন 1}-এর NFA-তে n + 1টি state যথেষ্ট, কিন্তু Ln-এর যেকোনো DFA-তে অন্তত 2nটি state লাগে।
- দৈর্ঘ্য n-এর 2nটি string নিন। ধরুন কোনো DFA-তে state 2n-এর কম। তাহলে দুটি আলাদা string u ≠ v আছে, যারা শুরু থেকে একই state-এ পৌঁছায়। — পর্ব ৩২-এর pigeonhole
- u আর v কোনো অবস্থান i-তে আলাদা (1 ≤ i ≤ n); ধরি ui = 1, vi = 0. z = 0i−1 যোগ করুন। uz আর vz-এর দৈর্ঘ্য n + i − 1, আর শেষ থেকে n-তম চিহ্ন ঠিক অবস্থান i-এর চিহ্ন। — গণনা
- তাই uz ∈ Ln, vz ∉ Ln. কিন্তু একই state থেকে একই z পড়লে DFA একই state-এ পৌঁছায়, তাই দুটিকে একই সিদ্ধান্ত দেয়। বিরোধ। ∎
এমন u, v-কে বলে distinguishable: কোনো z আছে, যা জুড়লে একটি ভাষায় ঢোকে আর অন্যটি ঢোকে না। এই ধারণাই §৮-এর Myhill–Nerode. নিচের ব্লক subset construction চালায়, টেবিল ছাপায়, দৈর্ঘ্য 10 পর্যন্ত সব string-এ ভাষা মেলায়, আর n = 1 থেকে 8-এ পৌঁছানো DFA state গোনে।
from itertools import product
def nfa_move(S, a, n): # NFA: "n-th symbol from the end is 1"
out = set()
for s in S:
if s == 0:
out.add(0) # state 0 keeps guessing "not yet"
if a == "1":
out.add(1) # ... or guesses "this 1 is the one"
elif s < n:
out.add(s + 1) # count the symbols after the guess
return frozenset(out)
def subset_construction(n):
start = frozenset({0})
dfa, todo = {}, [start]
while todo: # only the reachable subsets
S = todo.pop(0)
if S not in dfa:
dfa[S] = {a: nfa_move(S, a, n) for a in "01"}
todo += dfa[S].values()
return start, dfa
start, dfa = subset_construction(3)
for S, row in dfa.items():
print(sorted(S), "0->", sorted(row["0"]), "1->", sorted(row["1"]), "*" if 3 in S else "")
def accepts(w):
S = start
for a in w:
S = dfa[S][a]
return 3 in S
bad = sum(accepts(w) != (len(w) >= 3 and w[-3] == "1")
for k in range(11) for w in map("".join, product("01", repeat=k)))
print("n=3: DFA states", len(dfa), "| strings up to length 10:", 2**11 - 1, "disagreements", bad)
print("n -> reachable DFA states:", {n: len(subset_construction(n)[1]) for n in range(1, 9)})আসল আউটপুট (ব্রাউজারের Python):
[0] 0-> [0] 1-> [0, 1]
[0, 1] 0-> [0, 2] 1-> [0, 1, 2]
[0, 2] 0-> [0, 3] 1-> [0, 1, 3]
[0, 1, 2] 0-> [0, 2, 3] 1-> [0, 1, 2, 3]
[0, 3] 0-> [0] 1-> [0, 1] *
[0, 1, 3] 0-> [0, 2] 1-> [0, 1, 2] *
[0, 2, 3] 0-> [0, 3] 1-> [0, 1, 3] *
[0, 1, 2, 3] 0-> [0, 2, 3] 1-> [0, 1, 2, 3] *
n=3: DFA states 8 | strings up to length 10: 2047 disagreements 0
n -> reachable DFA states: {1: 2, 2: 4, 3: 8, 4: 16, 5: 32, 6: 64, 7: 128, 8: 256}
টেবিলটি stepper-এর আটটি সারি, একই ক্রমে; * চিহ্ন accepting state. ২০৪৭টি string-এ (দৈর্ঘ্য 0 থেকে 10) DFA আর সরাসরি সংজ্ঞা w[-3] == "1" মিলেছে। শেষ লাইনে NFA-র state n + 1, কিন্তু পৌঁছানো DFA state ঠিক 2n; উপপাদ্য ৪.২ বলে এর কমে হবে না, তাই এগুলো সবচেয়ে ছোট DFA. n = 30 হলে NFA-তে ৩১টি state, DFA-তে একশো কোটির বেশি।
৫ · Regular expression আর Kleene-এর উপপাদ্য
যন্ত্রের বদলে একটি সূত্র দিয়েও ভাষা লেখা যায়। ভাষার তিনটি operation (পর্ব ৭৩-এর বিষয়, star-এর সংজ্ঞা পর্ব ১২-এ): union L ∪ K, concatenation LK = {xy : x ∈ L, y ∈ K}, আর Kleene star L* = {ε} ∪ L ∪ LL ∪ LLL ∪ ⋯ . Regular expression ঠিক এই তিনটি দিয়ে গড়া।
Σ-এর ওপর recursive সংজ্ঞা। Basis: ∅, ε আর প্রতিটি a ∈ Σ regular expression; তাদের ভাষা L(∅) = ∅, L(ε) = {ε}, L(a) = {a}. Recursive step: R আর S regular expression হলে (R | S), (RS) আর (R*)-ও; L(R | S) = L(R) ∪ L(S), L(RS) = L(R)L(S), L(R*) = L(R)*. অগ্রাধিকার: * সবচেয়ে আগে, তারপর concatenation, তারপর |.
উদাহরণ: (0 | 1)*1(0 | 1)(0 | 1) হলো L3; বাজারঘরের অর্ডার-কোড (0 | 1 | ⋯ | 9)*25; আর জোড় সংখ্যক 1: 0*(10*10*)*. শেষটি যাচাই করতে লক্ষ করুন, প্রতিটি 10*10* ব্লক ঠিক দুটি 1 আনে।
একটি ভাষা কোনো regular expression-এর ভাষা ⇔ কোনো NFA-র ভাষা ⇔ কোনো DFA-র ভাষা। অর্থাৎ তিনটি বর্ণনা ঠিক একই শ্রেণি দেয়: regular language.
NFA ⇒ DFA হলো উপপাদ্য ৪.১, আর DFA নিজেই একটি NFA. Regex ⇒ NFA নিচের নির্মাণ দেখায়। NFA ⇒ regex-এর প্রমাণ এখানে বাদ রাখছি: Kleene-এর মূল পদ্ধতিতে Rij(k) = “i থেকে j-তে, মাঝে শুধু 1..k নম্বর state ব্যবহার করে” পথের regex, k এক এক করে বাড়িয়ে গোনা হয়, ঠিক Warshall-এর algorithm-এর কাঠামো (পর্ব ৫৪)। এর পূর্ণ রূপ পরিকল্পিত Theory of Computation কোর্সের বিষয়; এই পর্ব discrete math-এর দিক থেকে পরিচয় মাত্র।
Regex থেকে NFA: Thompson-এর নির্মাণ
Ken Thompson ১৯৬৮ সালের প্রবন্ধে regex-কে সরাসরি NFA-তে রূপ দেওয়ার পদ্ধতি দেন। প্রতিটি টুকরার NFA-র ঠিক একটি শুরু আর একটি শেষ (accepting) state থাকে; regex-এর গঠনের ওপর structural induction:
| Regex | NFA কীভাবে | State-সংখ্যা |
|---|---|---|
| a বা ε | শুরু → শেষ, তীরে a বা ε | 2 |
| R | S | নতুন শুরু থেকে ε-তে দুই NFA-র শুরু; দুই শেষ থেকে ε-তে নতুন শেষ | |R| + |S| + 2 |
| RS | R-এর শেষ থেকে ε-তে S-এর শুরু | |R| + |S| |
| R* | নতুন শুরু ও শেষ; শুরু থেকে ε-তে R-এর শুরু আর নতুন শেষ; R-এর শেষ থেকে ε-তে R-এর শুরু আর নতুন শেষ | |R| + 2 |
প্রতিটি নিয়মে নতুন NFA-র ভাষা ঠিক সংজ্ঞা ৫.১-এর ভাষা (যেমন R*-এ পথটি R-এর NFA-কে শূন্য বা তার বেশি বার পার হয়)। প্রতিটি চিহ্ন বা operator বড়জোর ২টি state যোগ করে, তাই regex-এর দৈর্ঘ্য m হলে NFA-তে বড়জোর 2m state. যেমন (0 | 1)*1: 2 + 2 + 2 = 6, star-এ 8, শেষে “1”-এর 2 জুড়ে মোট 10 (scratch-এ গোনা)।
বাস্তবের regex
Python-এর re module-এ (Python কোর্সের পর্ব ৩০) \d*25, [A-Z]{2}\d{3}-এর মতো সংক্ষেপ আছে; এগুলো ওপরের তিনটি operation-এর সংক্ষিপ্ত রূপ মাত্র, যেমন \d = (0 | ⋯ | 9). কিন্তু দুটি সতর্কতা। প্রথমত, backreference (\1 = “আগে যা মিলেছে, হুবহু আবার”) গাণিতিক অর্থে regular নয়; §৭-এর ব্লকে দেখবেন (a*)b\1 ভাষা anban চেনে, যা regular নয়। দ্বিতীয়ত, অনেক engine NFA-র পথগুলো একটি একটি করে চেষ্টা করে (backtracking); কিছু pattern-এ, যেমন (a+)+b, মিল না পেলে চেষ্টার সংখ্যা ইনপুটের দৈর্ঘ্যের সঙ্গে ঘাতের হারে বাড়তে পারে বলে বহুল নথিভুক্ত (catastrophic backtracking). Subset construction-এর মতো একসঙ্গে সব state রাখার পদ্ধতি এই বিস্ফোরণ এড়ায়।
৬ · Closure property — product construction আর complement
Regular language-এর শ্রেণি কোন operation-এ বদ্ধ? Union, concatenation আর star-এ বদ্ধতা Kleene থেকে সরাসরি আসে। Intersection আর complement-এর জন্য সুন্দর সরাসরি নির্মাণ আছে।
M1 = (Q1, Σ, δ1, s1, F1) আর M2 = (Q2, Σ, δ2, s2, F2) দুটি DFA. M = (Q1 × Q2, Σ, δ, (s1, s2), F1 × F2), যেখানে δ((p, q), a) = (δ1(p, a), δ2(q, a)). তাহলে L(M) = L(M1) ∩ L(M2). F = (F1 × Q2) ∪ (Q1 × F2) নিলে পাওয়া যায় union.
- Lemma: সব w-এর জন্য δ*((p, q), w) = (δ1*(p, w), δ2*(q, w)). w = ε-তে দুই দিকই (p, q). — base
- w = va হলে δ*((p, q), va) = δ((δ1*(p, v), δ2*(q, v)), a) = (δ1*(p, va), δ2*(q, va)). — hypothesis, δ-এর সংজ্ঞা
- অতএব w ∈ L(M) ⇔ (δ1*(s1, w), δ2*(s2, w)) ∈ F1 × F2 ⇔ দুই যন্ত্রই w গ্রহণ করে ⇔ w ∈ L(M1) ∩ L(M2). ∎ — পর্ব ১৪-এর Cartesian product
দুই যন্ত্র পাশাপাশি চলছে, যেন দুজন পরীক্ষক একই খাতা একসঙ্গে পড়ছেন আর নিজের নিজের নম্বর রাখছেন। State-সংখ্যা |Q1| · |Q2|, গুণের নিয়ম।
বাজারঘরের উদাহরণ: ধরুন একটি বিশেষ অফারের কোড হতে হবে “শেষে 25” এবং সংখ্যাটি ৩ দিয়ে বিভাজ্য। প্রথম শর্তের DFA-তে ৩টি state (§১), দ্বিতীয়টির δ(q, d) = (q + d) mod 3-এও ৩টি, তাই product-এ 3 × 3 = 9টি state, আর accepting শুধু (2, 0): “25 মিলেছে, ভাগশেষ 0”. তিন অঙ্কের কোডের মধ্যে এমন কোড 225, 525 আর 825: d25-এর অঙ্কের যোগফল d + 7, যা ৩ দিয়ে বিভাজ্য ঠিক d = 2, 5, 8-এ (scratch-এ মেলানো)। আলাদা আলাদা দুটি ছোট যন্ত্র লিখে product নেওয়া একটি বড় যন্ত্র সরাসরি আঁকার চেয়ে অনেক কম ভুলপ্রবণ।
M = (Q, Σ, δ, q0, F) একটি DFA (δ সব জোড়ায় সংজ্ঞায়িত) হলে M′ = (Q, Σ, δ, q0, Q ∖ F)-এর ভাষা Σ* ∖ L(M).
প্রমাণ: δ বদলায়নি, তাই δ*(q0, w) দুই যন্ত্রে একই state. w ∈ L(M′) ⇔ সেই state ∈ Q ∖ F ⇔ সেটি F-এ নেই ⇔ w ∉ L(M). ∎ এখানে দুটি শর্ত জরুরি: যন্ত্রটি deterministic, আর প্রতিটি ঘরে একটি transition আছে (না থাকলে একটি “ডাস্টবিন” state যোগ করে পূরণ করতে হয়)। NFA-তে accepting state উল্টালে কাজ হয় না (অনুশীলন ৪)। ফলাফল: L1 ∖ L2 = L1 ∩ (Σ* ∖ L2)-ও regular, আর “দুটি DFA-র ভাষা সমান কি না” প্রশ্নটি বোঝা যায়: symmetric difference খালি কি না দেখলেই হয়।
৭ · Pumping lemma — যা DFA পারে না
anbn (n ≥ 0) চিনতে কয়টি a পড়া হলো তা গুনে রাখতে হয়, আর n যত খুশি বড় হতে পারে। সসীম state দিয়ে এটি অসম্ভব মনে হয়; সেই অনুভূতিকে প্রমাণে বদলানোর হাতিয়ার pumping lemma, যার প্রথম প্রকাশ সাধারণত Bar-Hillel, Perles আর Shamir-এর ১৯৬১ সালের প্রবন্ধকে ধরা হয়।
L regular হলে একটি পূর্ণসংখ্যা p ≥ 1 আছে (pumping length), যাতে প্রতিটি s ∈ L, |s| ≥ p-কে s = xyz লেখা যায়, যেখানে (ক) |xy| ≤ p, (খ) |y| ≥ 1, আর (গ) সব i ≥ 0-এর জন্য xyiz ∈ L.
- L-এর একটি DFA M নিন, আর p = M-এর state-সংখ্যা। — regular-এর সংজ্ঞা
- s = s1s2⋯sn ∈ L, n ≥ p. M-এর পথ r0 = q0, rk = δ(rk−1, sk); rn ∈ F, কারণ s ∈ L. — δ*
- প্রথম p + 1টি state r0, …, rp; কিন্তু state মাত্র p-টি, তাই কোনো j < k ≤ p-তে rj = rk. — পর্ব ৩২-এর pigeonhole
- x = s1⋯sj, y = sj+1⋯sk, z = sk+1⋯sn. তাহলে |xy| = k ≤ p আর |y| = k − j ≥ 1. — (ক), (খ)
- δ*(rj, y) = rk = rj: y পড়ে যন্ত্র একই state-এ ফেরে, তাই i ≥ 0 সংখ্যক y পড়লেও rj-তে থাকে (i-এর ওপর induction). ফলে δ*(q0, xyiz) = δ*(rj, z) = rn ∈ F. ∎ — (গ)
Lemma-টি regular হওয়ার প্রয়োজনীয় শর্ত, যথেষ্ট নয়: কিছু non-regular ভাষাও এটি মানে। তাই এটি দিয়ে শুধু “regular নয়” প্রমাণ করা যায়, contradiction দিয়ে। আর quantifier-এর ক্রম (পর্ব ৫) মনে রাখুন: ∃p ∀s ∃(x, y, z) ∀i. প্রমাণকারী s বাছেন, কিন্তু বিভাজন বাছে প্রতিপক্ষ, তাই প্রতিটি বিভাজনকে হারাতে হয়।
1assume L regular, p2choose s = a^p b^p3any split: |xy|≤p, |y|≥14so y = a^k, 1 ≤ k ≤ p5pump i = 0: x z6a^(p−k) b^p ∉ L ⇒ ⊥
| s | |
| x · y · z | |
| xy^i z |
- ধরে নেওয়া। বিরোধের জন্য ধরি L = {anbn : n ≥ 0} regular. তাহলে উপপাদ্য ৭.১-এর একটি p আছে। প্রমাণ যেকোনো p-তে চলে; ছবির জন্য p = 4 ধরছি।
- s বাছাই। s = apbp = aaaabbbb. s ∈ L, আর |s| = 2p ≥ p, তাই lemma প্রযোজ্য। এই বাছাই আমাদের হাতে।
- প্রতিপক্ষের বিভাজন। Lemma শুধু বলে কোনো বিভাজন s = xyz আছে, |xy| ≤ p, |y| ≥ 1. উদাহরণ: x = a, y = aa, z = abbbb. কিন্তু যুক্তি সব বিভাজনের জন্য হতে হবে।
- মূল পর্যবেক্ষণ। s-এর প্রথম p-টি চিহ্নই a, আর xy সেই অংশের ভেতরে। তাই যেকোনো বিভাজনে y = ak, যেখানে 1 ≤ k ≤ p. উদাহরণে k = 2.
- Pump করা। i = 0 নিন: xy0z = xz = ap−kbp. উদাহরণে aabbbb. (i = 2 নিলেও চলে: ap+kbp.)
- বিরোধ। k ≥ 1, তাই p − k ≠ p: a-এর সংখ্যা b-এর চেয়ে কম, xz ∉ L. অথচ lemma বলে xz ∈ L. তাই অনুমান ভুল, L regular নয়। ∎
নিচের ব্লক একই যুক্তি ছোট p-তে পূর্ণ গণনায় দেখে: apbp-এর প্রতিটি বৈধ বিভাজন চেষ্টা করে, i = 0..3-এ সব xyiz ভাষায় থাকে এমন “ভালো” বিভাজন খোঁজে। তুলনার জন্য জোড়-1-এর ভাষায় (DFA-তে ২ state, তাই p = 2) প্রতিটি সদস্যের ভালো বিভাজন মেলে কি না দেখে, আর Python-এর backreference পরীক্ষা করে।
import re
from itertools import product
def in_anbn(s):
k = len(s) // 2
return s == "a" * k + "b" * k
def even_ones(s):
return s.count("1") % 2 == 0
def good_split(s, p, member):
# a split s = xyz with |xy| <= p, |y| >= 1 and xy^i z in L for i = 0..3
for j in range(1, p + 1): # j = |xy|
for k in range(1, j + 1): # k = |y|
x, y, z = s[:j - k], s[j - k:j], s[j:]
if all(member(x + y * i + z) for i in range(4)):
return (x, y, z)
return None
for p in range(1, 8):
s = "a" * p + "b" * p
print("p =", p, s, "-> good split:", good_split(s, p, in_anbn))
tested = [w for n in range(2, 11)
for w in map("".join, product("01", repeat=n)) if even_ones(w)]
print("even 1s, p = 2: every member of length 2..10 has a good split:",
all(good_split(w, 2, even_ones) for w in tested), len(tested))
print("example:", good_split("1001", 2, even_ones))
pat = re.compile(r"(a*)b\1") # backreference \1 = "the same a's again"
print("re with backreference accepts:",
[w for w in ("aba", "aabaa", "aaba", "b", "abaa") if pat.fullmatch(w)])আসল আউটপুট (ব্রাউজারের Python):
p = 1 ab -> good split: None
p = 2 aabb -> good split: None
p = 3 aaabbb -> good split: None
p = 4 aaaabbbb -> good split: None
p = 5 aaaaabbbbb -> good split: None
p = 6 aaaaaabbbbbb -> good split: None
p = 7 aaaaaaabbbbbbb -> good split: None
even 1s, p = 2: every member of length 2..10 has a good split: True 1022
example: ('1', '0', '01')
re with backreference accepts: ['aba', 'aabaa', 'b']
p = 1 থেকে 7-এ apbp-এর কোনো বিভাজন টেকেনি: প্রতিটিতে i = 0 বা 2-এ string ভাষার বাইরে যায়, ঠিক stepper-এর যুক্তি। এটি ছোট p-এর উদাহরণ মাত্র; সব p-এর জন্য প্রমাণ ওপরের যুক্তি। জোড়-1-এর ভাষায় দৈর্ঘ্য 2 থেকে 10-এর ১০২২টি সদস্যের প্রতিটির ভালো বিভাজন আছে, যেমন 1001-এ y = 0 যত বার খুশি পুনরাবৃত্তি করলেও 1-এর সংখ্যা 2. এখানে i শুধু 0..3 পরীক্ষা হয়েছে; সব i-এর নিশ্চয়তা উপপাদ্য ৭.১ দেয়। শেষ লাইনে (a*)b\1 গ্রহণ করেছে aba, aabaa আর b, অর্থাৎ anban; একই pumping যুক্তি (s = apbap) দেখায় এটি regular নয়। তাই backreference-ওয়ালা “regex” গণিতের regular expression নয়।
৮ · Myhill–Nerode, minimisation আর output-ওয়ালা যন্ত্র
একই ভাষার অনেক DFA হয়। সবচেয়ে ছোটটি কত বড়, আর সেটি কি অনন্য? উত্তর আসে পর্ব ৫৫-এর equivalence relation থেকে। ভাষা L-এর জন্য Σ*-এ সংজ্ঞায়িত করুন: x ≡L y ⇔ সব z ∈ Σ*-এর জন্য (xz ∈ L ⇔ yz ∈ L). অর্থাৎ x আর y-কে কোনো ভবিষ্যৎ দিয়ে আলাদা করা যায় না (§৪-এর distinguishable-এর উল্টো)। এটি reflexive, symmetric আর transitive, তাই Σ*-কে equivalence class-এ ভাগ করে।
L regular ⇔ ≡L-এর class-সংখ্যা (index) সসীম। তখন L-এর সবচেয়ে ছোট DFA-র state-সংখ্যা ঠিক এই index, আর state-এর নাম বদলানো ছাড়া সবচেয়ে ছোট DFA অনন্য।
এক দিক সহজ: DFA-তে একই state-এ পৌঁছানো দুটি string ≡L-এ সম্পর্কিত, তাই class-সংখ্যা ≤ state-সংখ্যা; উপপাদ্য ৪.২-এর যুক্তি ঠিক এটাই। anbn-এর জন্য a, aa, aaa, … পরস্পর distinguishable (ai আর aj-কে z = bi আলাদা করে), তাই অসীম class: pumping ছাড়াই দ্বিতীয় একটি প্রমাণ। Myhill (১৯৫৭) আর Nerode (১৯৫৮)-এর নামে উপপাদ্যটি; বিস্তারিত পরিকল্পিত Theory of Computation কোর্সে।
DFA minimisation: partition refinement
একটি DFA দেওয়া থাকলে সমতুল্য state-গুলো জোড়া লাগিয়ে সবচেয়ে ছোট DFA পাওয়া যায়। শুরুর ভাগ: {accepting}, {non-accepting}. প্রতি round-এ একটি ব্লকের দুটি state-কে আলাদা করুন, যদি কোনো চিহ্নে তারা এখনকার ভাগের আলাদা ব্লকে যায়। ভাগ আর না বদলালে থামুন; প্রতিটি ব্লক একটি নতুন state. প্রতি round-এ ব্লক বাড়ে বা algorithm থামে, তাই বড়জোর |Q| round. আলাদা ব্লকের state-গুলো সত্যিই distinguishable (কোন চিহ্নে ভাগ হলো, সেটি ধরে উল্টো দিকে একটি আলাদা-করা z বানানো যায়), আর শেষ ভাগের একই ব্লকের state-গুলো কোনো z-তে আলাদা হয় না; তাই ফলাফল উপপাদ্য ৮.১-এর অনন্য DFA.
উদাহরণ: “binary মান mod 6” DFA, state 0..5, δ(q, b) = (2q + b) mod 6, accepting {0, 3} (৩ দিয়ে বিভাজ্য)। শুরুর ভাগ {0, 3} | {1, 2, 4, 5}. State 1 আর 4 দুটিই 1 পড়লে accepting ব্লকে যায় (3), কিন্তু 2 আর 5 যায় না (5): ভাগ {0, 3} | {1, 4} | {2, 5}. পরের round-এ কিছু বদলায় না। তিনটি ব্লক ঠিক mod-3-এর তিনটি ভাগশেষ: q আর q + 3 একই। নিচের ব্লক product construction আর এই minimisation দুটিই চালিয়ে পূর্ণ গণনায় মেলায়।
from itertools import product
def run(delta, q, w):
for a in w:
q = delta[q, a]
return q
EVEN = {("E", "0"): "E", ("E", "1"): "O", ("O", "0"): "O", ("O", "1"): "E"}
MOD3 = {(q, b): (2 * q + int(b)) % 3 for q in range(3) for b in "01"}
# product construction: run both machines side by side on pairs of states
PROD = {((p, q), a): (EVEN[p, a], MOD3[q, a]) for p in "EO" for q in range(3) for a in "01"}
words = [w for n in range(11) for w in map("".join, product("01", repeat=n))]
bad = sum((run(PROD, ("E", 0), w) == ("E", 0)) !=
(w.count("1") % 2 == 0 and (int(w, 2) if w else 0) % 3 == 0) for w in words)
print("product DFA:", len({k[0] for k in PROD}), "states; disagreements on", len(words), "strings:", bad)
# minimisation: DFA for (value mod 6), accepting 0 and 3 ("divisible by 3")
M6 = {(q, b): (2 * q + int(b)) % 6 for q in range(6) for b in "01"}
P = [{0, 3}, {1, 2, 4, 5}] # round 0: accepting | non-accepting
while True:
where = {x: i for i, B in enumerate(P) for x in B}
newP = []
for B in P: # split B by (block after 0, block after 1)
groups = {}
for x in sorted(B):
groups.setdefault((where[M6[x, "0"]], where[M6[x, "1"]]), set()).add(x)
newP += groups.values()
print("blocks:", [sorted(B) for B in newP])
if len(newP) == len(P):
break
P = newP
where = {x: i for i, B in enumerate(P) for x in B}
MIN = {(i, a): where[M6[min(B), a]] for i, B in enumerate(P) for a in "01"}
same = all((run(M6, 0, w) in (0, 3)) == (run(MIN, where[0], w) == where[0]) for w in words)
print("minimal DFA has", len(P), "states; same language up to length 10:", same)আসল আউটপুট (ব্রাউজারের Python):
product DFA: 6 states; disagreements on 2047 strings: 0
blocks: [[0, 3], [1, 4], [2, 5]]
blocks: [[0, 3], [1, 4], [2, 5]]
minimal DFA has 3 states; same language up to length 10: True
Product DFA-তে 2 × 3 = 6টি state, আর “জোড় সংখ্যক 1 এবং ৩ দিয়ে বিভাজ্য” ভাষাটি ২০৪৭টি string-এ সরাসরি সংজ্ঞার সঙ্গে মিলেছে (উপপাদ্য ৬.১)। Minimisation-এর প্রথম round-এই ভাগ {0, 3}, {1, 4}, {2, 5}; দ্বিতীয় round-এ একই ভাগ, তাই থামা। ব্লক থেকে বানানো ৩-state DFA আর মূল ৬-state DFA একই ভাষা দেয়। ৩ state-এর কমে হবে না: ε, 1 আর 10 (মান 0, 1, 2) পরস্পর distinguishable, যেমন z = 1 জুড়লে 1 থেকে 11 = 3 গৃহীত, কিন্তু ε থেকে 1 নয়।
Output-ওয়ালা যন্ত্র: বাজারঘরের checkout
DFA শুধু হ্যাঁ/না বলে। বাস্তবের অনেক যন্ত্র প্রতিটি ধাপে কিছু করে: পর্দা দেখায়, বার্তা পাঠায়। Moore machine-এ output শুধু state-এর ওপর নির্ভর করে, Mealy machine-এ state আর input দুটির ওপর। বাজারঘরের checkout (কাল্পনিক নকশা):
| State (Moore output = পর্দা) | Input → পরের state | Mealy output (তীরে) |
|---|---|---|
| কার্ট (“কার্টের তালিকা”) | next → ঠিকানা | — |
| ঠিকানা (“ঠিকানার ফর্ম”) | next → পেমেন্ট; back → কার্ট | — |
| পেমেন্ট (“পেমেন্ট পাতা”) | ok → নিশ্চিত; fail → পেমেন্ট; back → ঠিকানা | ok/“SMS: অর্ডার নিশ্চিত”, fail/“আবার চেষ্টা করুন” |
| নিশ্চিত (“ধন্যবাদ”) | যেকোনো input → নিশ্চিত | — |
“নিশ্চিত” থেকে কোনো তীর বের হয় না, তাই একটি অর্ডার দুবার নিশ্চিত হতে পারে না: এমন নিরাপত্তা-দাবি state-এর সসীম গ্রাফে reachability দেখেই যাচাই করা যায় (পর্ব ৫৯-এর path), যা model checking-এর মূল ধারণা। Hardware-এ এর রূপায়ণ Computer Architecture কোর্সের পর্ব ১৭-এ।
৯ · পরীক্ষার অঙ্ক আর একটি ভুল প্রমাণ
অঙ্ক ১: গণনা
(ক) দৈর্ঘ্য ঠিক 6-এর কয়টি binary string (শুরুতে 0 চলবে) mod-3 DFA গ্রহণ করে? (খ) দৈর্ঘ্য 5-এর কয়টি string-এ শেষ থেকে তৃতীয় চিহ্ন 1? (গ) বাজারঘরের কুপন-কোড [A-Z]{2}[0-9]{3} (দুটি বড় হাতের ইংরেজি অক্ষর, তারপর তিনটি অঙ্ক) কয়টি?
- (ক) দৈর্ঘ্য 6-এর string মানে 0 থেকে 63-এর সংখ্যা; ৩-এর গুণিতক 0, 3, …, 63, মোট 63/3 + 1 = 22.
- (খ) শেষ থেকে তৃতীয় অবস্থান 1-এ বাঁধা, বাকি ৪টি অবস্থান মুক্ত: 24 = 16.
- (গ) গুণের নিয়ম (পর্ব ৩১): 262 × 103 = 676,000. (তিনটিই scratch-এ গোনা।)
অঙ্ক ২: subset construction
Σ = {a, b}-এ NFA: state 0 (শুরু), 1, 2 (accepting); 0-তে a, b-এ নিজের কাছে ফেরা, 0 থেকে a-তে 1, 1 থেকে b-তে 2. ভাষা: “ab দিয়ে শেষ”। DFA বানান।
- {0}: a → {0, 1}, b → {0}.
- {0, 1}: a → {0, 1}, b → {0, 2}.
- {0, 2}: a → {0, 1}, b → {0}. নতুন কিছু নেই: ৩টি state, accepting শুধু {0, 2}. সম্ভাব্য 23 = 8টির মধ্যে মাত্র ৩টিতে পৌঁছানো যায়।
অঙ্ক ৩: সবচেয়ে ছোট DFA
L5 (শেষ থেকে পঞ্চম চিহ্ন 1)-এর সবচেয়ে ছোট DFA-তে কয়টি state? উত্তর: উপপাদ্য ৪.২ অনুযায়ী অন্তত 25, আর subset construction ঠিক 32টি দেয় (§৪-এর কোড), তাই 32. Thompson-এর নিয়মে (0 | 1)*1-এর NFA-তে 10টি state (§৫)।
অঙ্ক ৪: DFA দিয়ে গোনা
দৈর্ঘ্য n-এর কয়টি binary string-এ পরপর দুটি 1 নেই (যেমন বাজারঘরের একটি কাল্পনিক নিয়ম: পরপর দুই দিন ছুটির দিন নয়)? DFA: A = “শেষ চিহ্ন 1 নয়” (শুরুও এখানে), B = “শেষ চিহ্ন 1”, আর dead state D. A-তে 0 → A, 1 → B; B-তে 0 → A, 1 → D. A আর B accepting.
- an, bn = দৈর্ঘ্য n-এর যেসব string যথাক্রমে A আর B-তে শেষ হয়। A-তে আসা যায় A বা B থেকে 0 পড়ে, B-তে শুধু A থেকে 1 পড়ে: an+1 = an + bn, bn+1 = an. — যোগের নিয়ম
- শুরু a0 = 1, b0 = 0. মোট tn = an + bn = an+1, আর an+2 = an+1 + an: Fibonacci-র recurrence (পর্ব ৪৫)।
- t3 = 5, t4 = 8, t5 = 13, t10 = 144 (scratch-এ সব string গুনে মেলানো)।
সাধারণ নীতি: DFA-কে একটি directed graph ভাবলে দৈর্ঘ্য n-এর গৃহীত string-এর সংখ্যা মানে শুরু থেকে accepting state-এ দৈর্ঘ্য n-এর walk-এর সংখ্যা, যা transition matrix-এর n-তম ঘাত থেকে আসে (পর্ব ১৯)। তাই প্রতিটি regular language-এর গণনা একটি linear recurrence মেনে চলে।
প্রমাণের ভুল ধরুন: “(01)* regular নয়”
একজন লিখলেন: “ধরি (01)* regular, pumping length p. s = (01)p নিই। y = 0 নিই, x = ε. তাহলে i = 2-এ xy2z = 0 0 1 (01)p−1 = 001…, যা (01)*-এ নেই। বিরোধ, তাই regular নয়। ∎”
ভুল কোথায়: বিভাজন বাছার অধিকার প্রমাণকারীর নয়; lemma শুধু বলে কোনো একটি বিভাজন pump করা যায়। প্রমাণকারীকে দেখাতে হয় প্রতিটি বৈধ বিভাজন ব্যর্থ। এখানে x = ε, y = 01 নিলে (p ≥ 2-এ |xy| = 2 ≤ p) xyiz = (01)p−1+i, সব i-তে ভাষায়। সত্যিই (01)* regular: তার regex তো দেওয়াই আছে, আর ৩ state-এর DFA-ও সহজ। এই ভুল আসে পর্ব ৫-এর ∃ আর ∀-এর ক্রম গুলিয়ে ফেলা থেকে।
আরও গভীরে: Hopcroft ১৯৭১ সালে partition refinement-কে O(n log n) সময়ে নামানোর algorithm দেন, যা আজও মানদণ্ড বলে গণ্য। Brzozowski ১৯৬৪ সালে regex-এর “derivative” দিয়ে সরাসরি DFA বানানোর পদ্ধতি দেন, যা আধুনিক কিছু regex engine-এ ফিরে এসেছে বলে বর্ণিত। Regular language-এর তত্ত্ব finite semigroup-এর বীজগণিতের সঙ্গে গভীরভাবে যুক্ত (syntactic monoid), আর অসীম string-এর জন্য Büchi automaton, model checking-এর ভিত্তি।
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:
- ৫ দিয়ে বিভাজ্য binary সংখ্যার DFA? ৫টি state, δ(q, b) = (2q + b) mod 5, accepting 0.
- n state-এর NFA থেকে DFA-তে সর্বোচ্চ কয়টি state? 2n; “শেষ থেকে n-তম চিহ্ন 1” দেখায় এটি এড়ানো যায় না।
- দুটি DFA-র intersection? Product construction, |Q1| · |Q2| state, accepting F1 × F2.
- anbn regular নয় কেন? s = apbp; y-তে শুধু a, pump করলে a আর b-এর সংখ্যা অসমান।
- Complement-এর জন্য accepting state উল্টানো কখন বৈধ? শুধু সম্পূর্ণ DFA-তে, NFA-তে নয়।
১০ · AI-এর যুগে — automaton-এর দাবি যাচাই
AI সহকারী DFA আঁকে আর pumping-এর প্রমাণ লেখে আত্মবিশ্বাসের সঙ্গে, কিন্তু প্রায়ই সূক্ষ্ম ভুল থাকে। কয়েকটি পরিচিত ভুল আর ধরার উপায়:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| DFA-তে কোনো ঘরের transition বাদ, বা ε-এর মতো edge case (ফাঁকা string) ভুল | দৈর্ঘ্য 0 থেকে 10-এর সব string-এ যন্ত্র চালিয়ে সরাসরি সংজ্ঞার সঙ্গে মেলান |
| Pumping-এর প্রমাণে নিজে বিভাজন বেছে নেওয়া, বা s এত ছোট নেওয়া যে |s| ≥ p নয় | Quantifier-এর ক্রম ∃p ∀s ∃xyz ∀i লিখে প্রতিটি ধাপ মেলান |
| Regular ভাষাকে “non-regular” প্রমাণ করে ফেলা (circular বা ভুল contradiction) | ছোট DFA বা regex খুঁজুন; পেলে প্রমাণ নিশ্চিত ভুল |
| NFA-তে accepting state উল্টে complement দাবি; minimisation-এ distinguishable state জোড়া লাগানো | দুই যন্ত্রের ভাষা পূর্ণ গণনায় মেলান; state-গণনা Myhill–Nerode-এর class-এর সঙ্গে মেলান |
| Backreference-ওয়ালা pattern-কে regular বলা, বা সাল ও নামের ইতিহাস বানানো | সংজ্ঞা ৫.১-এর তিনটি operation-এ লেখা যায় কি না দেখুন; ইতিহাস মূল প্রবন্ধে মেলান |
AI-এর কাছে automaton-এর প্রশ্ন করলে এমন কাঠামো দিন, যাতে প্রতিটি দাবি আলাদা করে যাচাই করা যায়:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
Automaton যাচাই করা সহজ, কারণ যন্ত্র ছোট আর string গোনা যায়: দৈর্ঘ্য 10 পর্যন্ত মাত্র ২০৪৭টি binary string. কিন্তু মনে রাখুন, সসীম গণনা উদাহরণ দেয়, প্রমাণ নয়; প্রমাণ আসে invariant আর induction থেকে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১স্নায়ুকোষের একটি গাণিতিক মডেল থেকে regular expression-এর জন্ম হলো কীভাবে?
১৯৪৩ সালে Warren McCulloch আর Walter Pitts “A Logical Calculus of the Ideas Immanent in Nervous Activity” প্রবন্ধে স্নায়ুকোষকে একটি সরল যন্ত্র হিসেবে দেখান: প্রতিটি কোষ হয় সক্রিয়, নয় নিষ্ক্রিয়, আর ইনপুটের ওজনের যোগফল একটি সীমা পেরোলে সক্রিয় হয়। এমন কোষের একটি সসীম জাল প্রতিটি সময়-ধাপে একটি নির্দিষ্ট অবস্থায় থাকে, আর ইনপুট অনুযায়ী পরের অবস্থায় যায়। অর্থাৎ পুরো জালটি একটি finite automaton, যদিও সেই নাম তখনো আসেনি।
Stephen Kleene প্রশ্ন করলেন: এমন জাল ঠিক কোন ধরনের “ঘটনা” (ইনপুটের ধারা) চিনতে পারে? তাঁর উত্তর একটি RAND প্রতিবেদনে ১৯৫১ সালে লেখা হয়, আর ১৯৫৬ সালে “Automata Studies” সংকলনে “Representation of Events in Nerve Nets and Finite Automata” নামে প্রকাশিত হয় বলে জানা যায়। Kleene দেখান, এই জাল যে ঘটনাগুলো চেনে, সেগুলো তিনটি operation দিয়ে গড়া যায়: union, concatenation আর একটি পুনরাবৃত্তির operation, যাকে আজ Kleene star বলি। তিনি এদের নাম দেন “regular events”, আর সেখান থেকেই “regular expression” আর “regular language”. নামটি কিছুটা আকস্মিক; Kleene নিজেও ভালো নাম খুঁজছিলেন বলে উল্লেখ আছে।
এই ইতিহাসের শিক্ষা হলো, একটি গাণিতিক মডেল প্রায়ই তার মূল প্রেরণার বাইরে টিকে থাকে। McCulloch–Pitts-এর মডেল আজকের স্নায়ুবিজ্ঞানে খুব সরল বলে গণ্য, আর আধুনিক artificial neural network-ও অনেক আলাদা। কিন্তু Kleene-এর উপপাদ্য, অর্থাৎ যন্ত্র আর সূত্রের সমতা, compiler, text editor, network filter আর DNA-ক্রমের খোঁজে প্রতিদিন ব্যবহৃত হয়। আর এই সমতার প্রমাণে যে কৌশল (state-এর মধ্য দিয়ে পথের regex ধাপে ধাপে বড় করা), তা পরে Warshall আর Floyd-এর algorithm-এ, অর্থাৎ পর্ব ৫৪-এ, অন্য চেহারায় ফিরে আসে।
মূল উপলব্ধি: Regular expression জন্মেছিল স্নায়ুর জাল কী চিনতে পারে সেই প্রশ্ন থেকে; Kleene-এর উত্তর, যে সসীম যন্ত্র ঠিক তিনটি operation-এ গড়া ভাষাই চেনে, মূল প্রেরণা ছাড়িয়ে কম্পিউটার বিজ্ঞানের স্থায়ী ভিত হয়ে গেছে।
প্র ০২যে যন্ত্র বাস্তবে বানানো যায় না, সেই “অনুমান করা” যন্ত্রের ধারণা এত গুরুত্বপূর্ণ কেন?
NFA “সঠিক পথ অনুমান করে” বলে মনে হয়, কিন্তু কোনো বাস্তব যন্ত্র অনুমান করতে পারে না। তবু Michael Rabin আর Dana Scott-এর ১৯৫৯ সালের প্রবন্ধ “Finite Automata and Their Decision Problems”, যেখানে nondeterministic automaton আর subset construction আসে, এতটাই প্রভাবশালী যে এর জন্য দুজন ১৯৭৬ সালের Turing Award পান বলে উল্লেখ করা হয়। কেন?
প্রথম কারণ বর্ণনার সহজতা। §৩-এর NFA-তে চারটি state আর একটি স্পষ্ট ধারণা (“এই 1-টিই সেই”), অথচ সমতুল্য DFA-তে আটটি state, আর n বাড়লে 2n. Thompson-এর নির্মাণ, union-এর প্রমাণ, concatenation আর star-এর প্রমাণ, সবই NFA দিয়ে কয়েক লাইনে হয়; DFA দিয়ে সরাসরি করলে জটিল। অর্থাৎ nondeterminism প্রমাণ আর নকশার একটি ভাষা, আর subset construction নিশ্চয়তা দেয় যে এই ভাষায় লেখা যেকোনো কিছু শেষ পর্যন্ত একটি বাস্তব DFA-তে রূপ নেবে।
দ্বিতীয় কারণ গভীর। সসীম যন্ত্রে nondeterminism শক্তি বাড়ায় না, শুধু আকার কমায়, আর সেই আকারের পার্থক্য কখনো কখনো ঘাতের হারে (উপপাদ্য ৪.২)। বড় যন্ত্রে প্রশ্নটি ভিন্ন রূপ নেয়। Turing machine-এর জন্য nondeterminism-ও গণনার শক্তি বাড়ায় না (পর্ব ৭৫ “Turing machine-এর পরিচয় — computability আর halting problem”), কিন্তু সময়ের হিসাবে nondeterministic polynomial time-ই NP শ্রেণি। “অনুমান করে polynomial সময়ে যাচাই” আর “নিজে polynomial সময়ে খোঁজা” কি সমান? এটাই P বনাম NP, যা DSA কোর্সের পর্ব ৬৪-এ দেখেছেন। সসীম যন্ত্রে উত্তর জানা (সমান, কিন্তু ঘাতের দামে), আর বড় যন্ত্রে উত্তরটি গণিতের সবচেয়ে বিখ্যাত খোলা প্রশ্নগুলোর একটি।
বাস্তবেও ধারণাটি কাজের: regex engine প্রায়ই NFA-র সম্ভাব্য state-এর সেট সরাসরি চালায়, দরকার হলে DFA-র state একটি একটি করে বানিয়ে cache-এ রাখে, যাতে পুরো 2n বানাতে না হয়।
মূল উপলব্ধি: Nondeterminism একটি বর্ণনা ও প্রমাণের হাতিয়ার: সসীম যন্ত্রে এটি শক্তি বাড়ায় না কিন্তু আকার ঘাতের হারে কমাতে পারে, আর একই ধারণা সময়ের হিসাবে P বনাম NP প্রশ্নের জন্ম দেয়।
প্র ০৩একটি নিরীহ-দেখতে regex কীভাবে একটি সার্ভারকে আটকে দিতে পারে, আর গণিত এর কী সমাধান দেয়?
Ken Thompson ১৯৬৮ সালে “Regular Expression Search Algorithm” প্রবন্ধে regex থেকে NFA বানিয়ে সব সম্ভাব্য state একসঙ্গে চালানোর পদ্ধতি দেন; text editor আর পরে grep-এর মতো Unix tool-এ এই ধারণা ব্যবহৃত হয় বলে জানা যায়। এই পদ্ধতিতে দৈর্ঘ্য n-এর লেখায় প্রতিটি চিহ্নে বড়জোর NFA-র সব state একবার দেখা হয়, তাই সময় মোটামুটি n × (state-সংখ্যা), কখনো বিস্ফোরিত হয় না।
কিন্তু পরে অনেক জনপ্রিয় regex engine অন্য পথ নেয়: backtracking. NFA-র একটি পথ ধরে এগোয়, আটকে গেলে পিছিয়ে অন্য পথ চেষ্টা করে, পর্ব ৩৬-এর backtracking-এর মতো। এর সুবিধা হলো backreference, lookaround-এর মতো বাড়তি সুবিধা সহজে যোগ করা যায়। অসুবিধা হলো পথের সংখ্যা। (a+)+b pattern-এ aaaa…a (শেষে b নেই) দিলে n-টি a-কে ভেতরের আর বাইরের + মিলে ভাগ করার উপায় 2n−1টি (n-এর composition, পর্ব ৩৫), আর মিল না পাওয়া পর্যন্ত engine এদের অনেকগুলো চেষ্টা করতে পারে। ৩০টি a-তেই প্রায় ৫৪ কোটি ভাগ। এমন আচরণকে catastrophic backtracking বলে, আর ইচ্ছাকৃতভাবে এমন ইনপুট পাঠিয়ে সার্ভার আটকানোকে ReDoS (regular expression denial of service) বলা হয়; এমন ঘটনায় বড় ওয়েবসাইট সাময়িক বন্ধ হওয়ার কথা নথিভুক্ত আছে।
গণিতের সমাধান দুই স্তরের। প্রথমত, যে pattern সত্যিই regular (backreference নেই), তাকে automaton দিয়ে চালালে সময় ইনপুটের দৈর্ঘ্যে রৈখিক থাকে; কিছু আধুনিক regex library এই নীতিতে বানানো এবং ইচ্ছা করে backreference সমর্থন করে না বলে বর্ণিত। দ্বিতীয়ত, backreference-সহ মিল খোঁজার সাধারণ সমস্যাটি NP-complete বলে জানা যায় (১৯৯০ সালের দিকের Aho-র একটি সমীক্ষায় উল্লেখিত), তাই সেখানে সব ইনপুটে দ্রুত হওয়ার আশা করা কঠিন। বাস্তব পরামর্শ: ব্যবহারকারীর ইনপুটে চলা pattern-এ nested quantifier এড়ান, সময়সীমা দিন, আর সম্ভব হলে automaton-ভিত্তিক engine ব্যবহার করুন।
মূল উপলব্ধি: Regex-এর গাণিতিক অর্থ আর engine-এর বাস্তবায়ন আলাদা জিনিস; automaton-ভিত্তিক বাস্তবায়ন রৈখিক সময়ের নিশ্চয়তা দেয়, আর backtracking-এর সুবিধার দাম হতে পারে ঘাতের হারে সময়।
প্র ০৪সবচেয়ে ছোট DFA অনন্য, এই তথ্য বাস্তবে কী কাজে লাগে?
Myhill–Nerode উপপাদ্য বলে একটি regular language-এর সবচেয়ে ছোট DFA state-এর নাম বদলানো ছাড়া একটিই। এটি একটি canonical form, পর্ব ৫৫-এর partition-এর ভাষায়: প্রতিটি state একটি equivalence class, আর DFA-টি ভাষা থেকেই নির্ধারিত, কে কীভাবে আঁকল তার ওপর নয়। John Myhill (১৯৫৭) আর Anil Nerode (১৯৫৮) এই ফলাফলের নামধারী।
প্রথম ব্যবহার: সমতা যাচাই। দুটি regex বা দুটি DFA একই ভাষা দেয় কি না জানতে দুটিকেই minimise করুন; ফলাফল দুটি একই গ্রাফ (state-এর নাম মিলিয়ে) হলে ভাষা এক, না হলে আলাদা। কোনো string-এর অসীম তালিকা দেখার দরকার নেই। এটি বিরল সৌভাগ্য: grammar বা প্রোগ্রামের ক্ষেত্রে “দুটি সমান কি না” প্রশ্নটি সাধারণভাবে algorithm দিয়ে সমাধানযোগ্য নয়, যা পর্ব ৭৫-এর undecidability-র বিষয়।
দ্বিতীয় ব্যবহার: খরচ কমানো। Compiler-এর lexer (যে অংশ source code-কে শব্দে ভাগ করে) সাধারণত সব token-এর regex মিলিয়ে একটি বড় DFA বানায়, আর minimisation তার টেবিল ছোট করে। Hardware-এ প্রতিটি state-এর জন্য flip-flop লাগে, তাই state কমানো মানে circuit ছোট। John Hopcroft ১৯৭১ সালে partition refinement-কে O(n log n) সময়ে করার উপায় দেন, যা বড় যন্ত্রেও minimisation-কে ব্যবহারযোগ্য রাখে।
তৃতীয় ব্যবহার তাত্ত্বিক: নিচের সীমা প্রমাণ। একটি ভাষায় k-টি পরস্পর distinguishable string দেখাতে পারলেই প্রমাণ হয় যেকোনো DFA-তে অন্তত k-টি state লাগে, যেমন উপপাদ্য ৪.২-এ। অসীম সংখ্যক দেখাতে পারলে ভাষাটি regular নয়। Pumping lemma-র চেয়ে এই পদ্ধতি অনেক সময় পরিষ্কার, আর এটি “যদি ও কেবল যদি”, তাই কখনো ব্যর্থ হয় না।
মূল উপলব্ধি: Myhill–Nerode ভাষাকে একটি equivalence relation-এর class দিয়ে চেনায়; তাতে সবচেয়ে ছোট DFA অনন্য হয়, ভাষার সমতা যাচাই করা যায়, আর state-সংখ্যার নিচের সীমা নিখুঁতভাবে প্রমাণ করা যায়।
প্র ০৫Finite automaton যদি এত সীমিত হয়, তবে বাস্তবের এত জায়গায় এটি কেন?
Pumping lemma দেখায় finite automaton গুনতে পারে না: anbn, সুষম বন্ধনী, বা “দুই অংশ সমান” চিনতে পারে না। তাহলে এই দুর্বল যন্ত্র সর্বত্র কেন? কারণ সীমাবদ্ধতাই এর শক্তি। সসীম স্মৃতি মানে নির্দিষ্ট, ছোট খরচ: প্রতিটি চিহ্নে একটি টেবিল দেখা, ব্যস। ইনপুট যত লম্বাই হোক, স্মৃতি বাড়ে না। আর সসীম state মানে সব আচরণ পুরোপুরি বিশ্লেষণ করা যায়: সমতা, খালি কি না, কোনো খারাপ state-এ পৌঁছানো যায় কি না, সবই algorithm দিয়ে নিশ্চিতভাবে জানা যায়।
উদাহরণ অনেক। Hardware-এ প্রতিটি controller একটি finite state machine; George Mealy (১৯৫৫) আর Edward Moore (১৯৫৬)-এর নামের যন্ত্র দুটি এই কাজেই এসেছিল। Network protocol-এর মানদণ্ড প্রায়ই state diagram দিয়ে লেখা হয়, যেমন TCP-র সংযোগ খোলা-বন্ধের অবস্থা। Compiler-এর প্রথম ধাপ lexer, যা regex-কে DFA বানিয়ে চলে; ১৯৭০-এর দশকে Lex-এর মতো tool এটি স্বয়ংক্রিয় করে বলে জানা যায়। ভিডিও গেমের চরিত্র, লিফটের নিয়ন্ত্রণ, ATM-এর পর্দা, এমনকি বাজারঘরের checkout, সবই state machine হিসেবে নকশা করলে পরীক্ষা করা সহজ হয়।
যেখানে গোনা দরকার, সেখানে যন্ত্রকে বাড়তি স্মৃতি দিতে হয়। একটি stack যোগ করলে pushdown automaton, যা context-free grammar-এর ভাষা চেনে (পর্ব ৭৩-এর Chomsky-র শ্রেণিবিন্যাসের পরের ধাপ); অসীম tape দিলে Turing machine (পর্ব ৭৫)। প্রতিটি ধাপে শক্তি বাড়ে, কিন্তু বিশ্লেষণ কঠিন হয়, শেষ ধাপে কিছু প্রশ্ন অসমাধানযোগ্য। তাই প্রকৌশলের একটি ভালো নীতি: সমস্যাটি যতটা কম শক্তিশালী মডেলে প্রকাশ করা যায়, ততটায় করুন। ইনপুট যাচাইয়ে regular ভাষা যথেষ্ট হলে তাই ব্যবহার করুন; তাতে নিরাপত্তা আর গতি দুটোই নিশ্চিত।
মূল উপলব্ধি: Finite automaton-এর সীমিত স্মৃতি তাকে দ্রুত, সস্তা আর পুরোপুরি বিশ্লেষণযোগ্য করে; যেখানে গোনা লাগে না সেখানে এটিই সেরা হাতিয়ার, আর যেখানে লাগে সেখানে stack বা tape যোগ করে পরের মডেলে যেতে হয়।
অনুশীলন
আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।
-
প্রমাণ: Σ = {a, b}-এর ওপর সব palindrome-এর ভাষা PAL = {w : w উল্টালে w} regular নয়, pumping lemma দিয়ে প্রমাণ করুন।
- বিরোধের জন্য ধরুন PAL regular, pumping length p. — উপপাদ্য ৭.১
- s = apbap নিন। s palindrome, |s| = 2p + 1 ≥ p. — s বাছাই আমাদের
- যেকোনো বিভাজনে |xy| ≤ p, তাই x আর y প্রথম p-টি a-এর ভেতরে: y = ak, k ≥ 1. — (ক), (খ)
- i = 2: xy2z = ap+kbap. b-এর বাঁয়ে p + k, ডানে p-টি a; উল্টালে apbap+k, যা আলাদা। তাই এটি palindrome নয়, বিরোধ। ∎
s = apap নিলে প্রমাণ ব্যর্থ হতো: y = aa pump করলেও জোড় দৈর্ঘ্যের a-string palindrome থাকে। ঠিক s বাছাই প্রমাণের অর্ধেক কাজ।
-
গণনা: (ক) ৩ আর ৪ state-এর দুটি DFA-র product-এ কয়টি state? (খ) দৈর্ঘ্য 8-এর কয়টি binary string-এর মান ৩ দিয়ে বিভাজ্য? (গ) দৈর্ঘ্য 5-এর কয়টি {a, b}-string “ab” দিয়ে শেষ হয়?
(ক) 3 × 4 = 12 (সবগুলোতে পৌঁছানো না-ও যেতে পারে, তাই এটি সর্বোচ্চ)। (খ) মান 0 থেকে 255; ৩-এর গুণিতক ⌊255/3⌋ + 1 = 86. (গ) শেষ দুটি চিহ্ন বাঁধা, প্রথম তিনটি মুক্ত: 23 = 8. তিনটিই scratch-এ পূর্ণ গণনায় মেলানো।
-
আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। তারপর যেকোনো Python-এ চালিয়ে মেলান।
d = {(q, b): (2*q + int(b)) % 3 for q in range(3) for b in "01"} for w in ("110", "1001", "111"): q = 0 for b in w: q = d[q, b] print(w, q)110 0 1001 0 111 1উপপাদ্য ২.১ অনুযায়ী শেষ state = মান mod 3: 110 = 6 → 0, 1001 = 9 → 0, 111 = 7 → 1. হাতে চালালে 111-এর পথ 0 → 1 → 0 → 1 (CPython 3.14-এ চালিয়ে মেলানো)।
-
প্রমাণের ভুল ধরুন: দাবি: “NFA N-এর accepting আর non-accepting state অদলবদল করলে L(N)-এর complement পাওয়া যায়।” যুক্তি: “DFA-তে এটি উপপাদ্য ৬.২, আর NFA-ও একই রকম যন্ত্র। ∎” ভুল কোথায়? একটি পাল্টা-উদাহরণ দিন।
- ভুল ধাপ: উপপাদ্য ৬.২-এর প্রমাণ “w পড়ে যন্ত্র ঠিক একটি state-এ পৌঁছায়” তথ্যের ওপর দাঁড়ানো। NFA-তে একাধিক পথ; গ্রহণ মানে “কোনো পথ F-এ”, আর অদলবদলের পর গ্রহণ মানে “কোনো পথ F-এর বাইরে”। দুটি একসঙ্গে সত্য হতে পারে।
- পাল্টা-উদাহরণ: Σ = {a}, state p (শুরু), q (accepting); p থেকে a-তে p আর q দুটিই। L(N) = a+ (অন্তত একটি a). অদলবদলে accepting {p}: প্রতিটি string পড়ে p-তে থাকা যায়, তাই নতুন ভাষা a*, অথচ complement {ε}. যেমন “a” দুটি যন্ত্রই গ্রহণ করে (scratch-এ মেলানো)।
- সঠিক পথ: আগে subset construction দিয়ে DFA বানান, প্রয়োজনে ডাস্টবিন state যোগ করুন, তারপর উল্টান।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রম“anbn regular নয়”, এই প্রমাণের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- বিরোধের জন্য ধরুন ভাষাটি regular, আর pumping length p
- s = apbp নিন, যার দৈর্ঘ্য অন্তত p
- যেকোনো বিভাজনে |xy| ≤ p, তাই y শুধু a দিয়ে গড়া
- i = 0 নিলে a-এর সংখ্যা b-এর চেয়ে কম হয়
- xz ভাষার বাইরে, যা lemma-র বিরোধী, তাই ভাষাটি regular নয়
DFA-র state 0, 1, 2; শুরু 0; δ(q, b) = (2q + b) mod 3. Binary string 1101 পড়ার পর যন্ত্র কোন state-এ থাকে?
- 0
- 1
- 2
- বলা যায় না, একাধিক state সম্ভব
n state-এর একটি NFA থেকে subset construction-এ পাওয়া DFA-তে সর্বোচ্চ কয়টি state থাকতে পারে?
- n
- n²
- 2n
- n!
Σ = {0, 1} বা {a, b}-এর ওপর নিচের কোন ভাষাটি regular নয়?
- (01)*
- জোড় সংখ্যক 1 আছে এমন binary string
- মান ৩ দিয়ে বিভাজ্য এমন binary string
- {anbn : n ≥ 0}
Pumping lemma দিয়ে কোনো ভাষা non-regular প্রমাণ করতে গেলে s = xyz বিভাজনটি কে বাছে?
- কেউ না নির্দিষ্টভাবে: প্রতিটি বৈধ বিভাজন ব্যর্থ দেখাতে হয়
- প্রমাণকারী নিজের সুবিধামতো
- প্রমাণকারী, p-এর সঙ্গে
- s-কে সমান তিন ভাগ করে নিতে হয়
একটি সম্পূর্ণ DFA (প্রতিটি state আর চিহ্নে একটি transition) M-এর accepting আর non-accepting state অদলবদল করে M′ বানানো হলো। L(M′) কী?
- L(M)-এর প্রতিটি string উল্টানো
- Σ* ∖ L(M)
- ∅
- L(M)
Regular expression (0 | 1)*1(0 | 1)(0 | 1)-এর সঙ্গে দৈর্ঘ্য ঠিক 5-এর কয়টি binary string মেলে?
- 8
- 32
- 16
- 4
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- DFA: (Q, Σ, δ, q0, F); δ*(q, ε) = q, δ*(q, wa) = δ(δ*(q, w), a); সঠিকতার প্রমাণ = state-এর invariant আর induction.
- NFA: state-এর সেটে চলে; subset construction সমতুল্য DFA দেয়, বড়জোর 2n state, আর Ln-এ এটি এড়ানো যায় না।
- Kleene: regex ⇔ NFA ⇔ DFA; Thompson regex থেকে NFA বানায়; regular language union, intersection (product) আর complement-এ বদ্ধ।
- Pumping lemma: pigeonhole থেকে আসে; non-regular প্রমাণে s আপনি বাছেন, বিভাজন প্রতিপক্ষ বাছে; anbn regular নয়।
- Minimisation: ≡L-এর class-সংখ্যা = সবচেয়ে ছোট DFA-র state; partition refinement তা খুঁজে দেয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৭৫ · Turing machine-এর পরিচয় — computability আর halting problemপরবর্তী পর্বসসীম যন্ত্র থেকে অসীম tape-এর যন্ত্রে, আর গণনার সীমায়।
- পর্ব ৩২ · Pigeonhole principle — সহজ থেকে generalizedপুনরালোচনাPumping lemma আর 2n-এর সীমার মূল যুক্তি।
- পর্ব ৫৫ · Equivalence relation আর partitionপুনরালোচনাMyhill–Nerode আর minimisation-এর ভাষা।
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল।
- Python · পর্ব ৩০ · Regular expressionকোডে প্রয়োগre module দিয়ে খোঁজা, যাচাই আর বদলানো।
- Computer Architecture · পর্ব ১৭ · Finite state machineপাশের বিষয়Moore, Mealy আর state diagram থেকে circuit.