টুরিং মেশিন ডিজাইন করা — worked examples
এই পাঠে যা শিখবেন
- টেপকে "স্ক্র্যাচ স্পেস" হিসেবে ব্যবহারের কৌশল — মার্ক করা/অচিহ্নিত সিম্বল-ভ্যারিয়েন্ট দিয়ে
- $\{ww\}$ ভাষার জন্য মিডপয়েন্ট খোঁজা ও দুই অর্ধেক মেলানোর সম্পূর্ণ ডিজাইন
- বাইনারি ইনক্রিমেন্ট TM — স্ক্যান-রাইট-দেন-ক্যারি-প্রোপাগেট-লেফট ডিজাইন প্যাটার্ন
- কেন টেপ "ইনপুটের দৈর্ঘ্যের চেয়ে বেশি" বাড়তে পারা এই দুটি ডিজাইনের জন্যই অপরিহার্য
১ · উদাহরণ ১ — $\{ww : w \in \{0,1\}^*\}$
এই ভাষার প্রতিটি স্ট্রিং হলো কোনো স্ট্রিং $w$-এর ঠিক দুইবার পুনরাবৃত্তি (যেমন "0101", যেখানে
$w = $ "01")। L32-এর $\{0^n1^n\}$-এর মতো সরাসরি বাম-ডান ক্রসিং-অফ এখানে কাজ করে না, কারণ
আগে থেকে জানা নেই কোথায় প্রথমার্ধ শেষ হয়ে দ্বিতীয়ার্ধ শুরু হচ্ছে — প্রথমেই সেই
মিডপয়েন্ট বের করতে হয়।
ডিজাইন আইডিয়া (দুই ধাপে): প্রথম ধাপে, বাম প্রান্ত থেকে leftmost অচিহ্নিত সিম্বলকে a0/a1
(মূল মান-সহ) দিয়ে চিহ্নিত করা হয়, আর একই সাথে ডান প্রান্ত থেকে rightmost অচিহ্নিত সিম্বলকে
b0/b1 দিয়ে চিহ্নিত করা হয় — এই দুই দিক থেকে "ক্রলিং" চালিয়ে যাওয়া হয় যতক্ষণ না
বাম দিকের সার্চ সরাসরি একটি b-চিহ্নিত সেলে ধাক্কা খায় — তখনই মিডপয়েন্ট পাওয়া যায় (দৈর্ঘ্য
জোড়; বিজোড় হলে দুই সার্চ একই অচিহ্নিত সেলে মিলে যাবে, তখন reject)। দ্বিতীয় ধাপে, position $i$-এর
a-ট্যাগ আর position $mid+i$-এর b-ট্যাগ একে একে মিলিয়ে দেখা হয়।
২ · কোড — $\{ww\}$-এর জন্য সম্পূর্ণ TM
নিচের কোডে a0/a1 (বাম-অর্ধের ট্যাগ, মূল মান-সহ), b0/b1 (ডান-অর্ধের ট্যাগ) এবং
X0/X1 (দ্বিতীয় ধাপে ইতিমধ্যে মিলে-যাওয়া সিম্বল) — এই অতিরিক্ত টেপ-সিম্বলগুলো ($\Gamma$-তে
আছে, $\Sigma=\{0,1\}$-এ নেই) দিয়ে সত্যিকারের স্ক্র্যাচ-স্পেস ব্যবহার প্রদর্শিত হয়েছে।
B = '_'
class TM:
def __init__(self, trans, start, accept, reject):
self.trans, self.start = trans, start
self.accept, self.reject = accept, reject
def run(self, s, max_steps=50000):
tape = {i: c for i, c in enumerate(s)}
head, state, steps = 0, self.start, 0
while steps < max_steps:
if state == self.accept:
return "accept", steps
if state == self.reject:
return "reject", steps
sym = tape.get(head, B)
key = (state, sym)
if key not in self.trans:
return "reject", steps
ns, w, mv = self.trans[key]
tape[head] = w
head += 1 if mv == 'R' else -1
state = ns
steps += 1
return "timeout", steps
def build_ww_tm():
t = {}
t[('find_left', B)] = ('accept', B, 'R') # খালি স্ট্রিং -- accept
# ধাপ ১: leftmost অচিহ্নিত সিম্বলকে a-ট্যাগ দাও, তারপর ডানপ্রান্তে গিয়ে
# rightmost অচিহ্নিত সিম্বলকে b-ট্যাগ দাও -- এভাবে দুই দিক থেকে এগোতে থাকো
t[('find_left', '0')] = ('seek_right', 'a0', 'R')
t[('find_left', '1')] = ('seek_right', 'a1', 'R')
t[('find_left', 'a0')] = ('find_left', 'a0', 'R')
t[('find_left', 'a1')] = ('find_left', 'a1', 'R')
t[('find_left', 'b0')] = ('phase2_start', 'b0', 'L') # a আর b মিলে গেল -- মিডপয়েন্ট!
t[('find_left', 'b1')] = ('phase2_start', 'b1', 'L')
for sym in ('0', '1', 'a0', 'a1', 'b0', 'b1'):
t[('seek_right', sym)] = ('seek_right', sym, 'R')
t[('seek_right', B)] = ('find_right', B, 'L')
t[('find_right', 'b0')] = ('find_right', 'b0', 'L')
t[('find_right', 'b1')] = ('find_right', 'b1', 'L')
t[('find_right', '0')] = ('back_to_left', 'b0', 'L')
t[('find_right', '1')] = ('back_to_left', 'b1', 'L')
t[('find_right', 'a0')] = ('reject', 'a0', 'L') # একটিমাত্র মাঝের সেল -- বিজোড় দৈর্ঘ্য
t[('find_right', 'a1')] = ('reject', 'a1', 'L')
for sym in ('0', '1', 'b0', 'b1'):
t[('back_to_left', sym)] = ('back_to_left', sym, 'L')
t[('back_to_left', 'a0')] = ('find_left', 'a0', 'R')
t[('back_to_left', 'a1')] = ('find_left', 'a1', 'R')
# ধাপ ২: বাম প্রান্তে ফিরে গিয়ে a[i] বনাম b[i] একে একে মেলাও
t[('phase2_start', 'a0')] = ('phase2_start', 'a0', 'L')
t[('phase2_start', 'a1')] = ('phase2_start', 'a1', 'L')
t[('phase2_start', B)] = ('cmp_find_a', B, 'R')
t[('cmp_find_a', 'X0')] = ('cmp_find_a', 'X0', 'R')
t[('cmp_find_a', 'X1')] = ('cmp_find_a', 'X1', 'R')
t[('cmp_find_a', 'a0')] = ('cmp_seek_b0', 'X0', 'R')
t[('cmp_find_a', 'a1')] = ('cmp_seek_b1', 'X1', 'R')
t[('cmp_find_a', B)] = ('accept', B, 'R') # সব জোড়া মিলে গেছে -- accept
for v, other in (('0', '1'), ('1', '0')):
st = 'cmp_seek_b' + v
t[(st, 'a0')] = (st, 'a0', 'R')
t[(st, 'a1')] = (st, 'a1', 'R')
t[(st, 'X0')] = (st, 'X0', 'R')
t[(st, 'X1')] = (st, 'X1', 'R')
t[(st, 'b' + v)] = ('cmp_back', 'X' + v, 'L') # মিলেছে
t[(st, 'b' + other)] = ('reject', 'b' + other, 'R') # মেলেনি
for sym in ('a0', 'a1', 'X0', 'X1', 'b0', 'b1'):
t[('cmp_back', sym)] = ('cmp_back', sym, 'L')
t[('cmp_back', B)] = ('cmp_find_a', B, 'R')
return TM(t, 'find_left', 'accept', 'reject')
tm = build_ww_tm()
tests = ["0101", "1111", "", "0110", "010"]
for s in tests:
result, steps = tm.run(s)
w_repr = s[:len(s)//2] if len(s) % 2 == 0 else "N/A"
print(f"{s!r:8s} -> {result:8s} (ধাপ {steps:3d}) | w={w_repr!r}")
"0101" ($w=$"01") ও "1111" ($w=$"11") accept
হয়; "" ($w=$"") accept হয়; "0110" জোড় দৈর্ঘ্যের হলেও প্রথমার্ধ
"01" ≠ দ্বিতীয়ার্ধ "10" বলে reject হয়; "010" বিজোড় দৈর্ঘ্যের বলে
কখনোই $ww$ আকারে লেখা যায় না, তাই reject হয় — সবগুলোই এই কোড চালিয়ে সরাসরি নিশ্চিত করা হয়েছে।
৩ · উদাহরণ ২ — বাইনারি ইনক্রিমেন্ট (TM কে "সাধারণ কম্পিউটার" হিসেবে)
দ্বিতীয় উদাহরণটি দেখায় একটি TM কীভাবে সাধারণ গাণিতিক গণনাও করতে পারে — টেপে বাইনারি সংখ্যায় ১ যোগ করা।
ডিজাইন: রাইটমোস্ট ডিজিট পর্যন্ত স্ক্যান করো, তারপর ক্যারি-সহ বামে যোগ করতে থাকো — 0+1=1 (ক্যারি
থামে), 1+1=10 (লেখো 0, ক্যারি বামে চালিয়ে যাও)। এটি L37-এর চার্চ-টুরিং থিসিসের সরাসরি
ফোরশ্যাডো — "যেকোনো কার্যকরভাবে গণনাযোগ্য ফাংশন কোনো-না-কোনো TM দিয়ে গণনা করা যায়"।
def build_increment_tm():
t = {}
t[('scan_right', '0')] = ('scan_right', '0', 'R')
t[('scan_right', '1')] = ('scan_right', '1', 'R')
t[('scan_right', B)] = ('carry', B, 'L') # রাইটমোস্ট ডিজিটের পরে ব্ল্যাংক পেলে ক্যারি শুরু
t[('carry', '0')] = ('done', '1', 'L') # 0+1=1 -- ক্যারি থেমে গেল
t[('carry', '1')] = ('carry', '0', 'L') # 1+1=10 -- 0 লেখো, ক্যারি বামে চালিয়ে যাও
t[('carry', B)] = ('done', '1', 'L') # সব ডিজিট 1 ছিল -- বাম প্রান্তে নতুন 1 বসাও (টেপ বাড়ল!)
t[('done', '0')] = ('done', '0', 'L')
t[('done', '1')] = ('done', '1', 'L')
t[('done', B)] = ('accept', B, 'R')
return TM(t, 'scan_right', 'accept', 'reject')
def read_tape(tm, s):
tape = {i: c for i, c in enumerate(s)}
head, state, steps = 0, tm.start, 0
while steps < 5000 and state != tm.accept:
sym = tape.get(head, B)
ns, w, mv = tm.trans[(state, sym)]
tape[head] = w
head += 1 if mv == 'R' else -1
state = ns
steps += 1
lo, hi = min(tape), max(tape)
return ''.join(tape.get(i, B) for i in range(lo, hi + 1)).strip(B), steps
incr = build_increment_tm()
for s in ["011", "111", "0", "1", "1011", "1111"]:
out, steps = read_tape(incr, s)
print(f"{s!r:6s} (={int(s,2):2d}) -> {out!r:6s} (={int(out,2):2d}) ধাপ={steps} "
f"টেপ {'বাড়ল' if len(out) > len(s) else 'বাড়েনি'}")
"011" (৩) → "100" (৪), এবং "111" (৭) →
"1000" (৮) — দ্বিতীয় ক্ষেত্রে টেপ ৩ সেল থেকে ৪ সেলে সত্যিকারেই বেড়ে গেছে,
কারণ সবগুলো ডিজিটই 1 ছিল বলে ক্যারি বাম প্রান্ত পর্যন্ত পৌঁছে একটি সম্পূর্ণ নতুন ডিজিট যোগ করেছে। এই আচরণ
L29-এর বাউন্ডেড-টেপ LBA-র পক্ষে কখনোই সম্ভব নয় — LBA-র টেপ ইনপুটের দৈর্ঘ্যেই আটকে থাকে, TM-এর টেপ প্রয়োজনমতো
বাড়তে পারে (L32)।
দুটি উদাহরণই দেখায় কীভাবে টেপকে শুধু "ইনপুট রাখার জায়গা" নয় বরং একটি সক্রিয় স্ক্র্যাচ স্পেস হিসেবে ব্যবহার করা যায় — মার্ক-করা সিম্বল-ভ্যারিয়েন্ট দিয়ে (ww), অথবা টেপকে বাড়িয়ে (ইনক্রিমেন্ট)। এই দুই কৌশলই M8-এর পরের পাঠগুলোতে (মাল্টি-টেপ, নন-ডিটারমিনিজম) আরও শক্তিশালী রূপে ফিরে আসবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ $\{ww\}$ TM-এ কেন সরাসরি "প্রথম অর্ধেকের দৈর্ঘ্য একটি ভ্যারিয়েবলে গুণে রাখা" যায় না?
কারণ একটি TM-এর নিজস্ব কোনো "ভ্যারিয়েবল" নেই — শুধু আছে স্টেট (finite সংখ্যক) ও টেপ। যেহেতু ইনপুট স্ট্রিং যেকোনো দৈর্ঘ্যের হতে পারে, "দৈর্ঘ্য" সংরক্ষণের একমাত্র উপায় হলো সেটিকে টেপেই কোনোভাবে এনকোড করে রাখা (যেমন এই পাঠের a/b ট্যাগিং কৌশলে, দৈর্ঘ্যের তথ্য পরোক্ষভাবে "কতগুলো সেল ট্যাগ হয়েছে" আকারে টেপেই থেকে যায়) — এটিই "finite স্টেট, কিন্তু সম্ভাব্যত অসীম টেপ" মডেলের একটি স্বাভাবিক সীমাবদ্ধতা ও শক্তি দুটোই।
প্র ০২
বাইনারি ইনক্রিমেন্ট TM-এ ('carry', B) ট্রানজিশনটি ঠিক কখন ট্রিগার হয়, এবং কেন সেটিই একমাত্র জায়গা যেখানে টেপ বাড়ে?
('carry', B) ট্রিগার হয় যখন ক্যারি বহন করতে করতে হেড বাম প্রান্তের ব্ল্যাংক পর্যন্ত পৌঁছে
যায় — অর্থাৎ ইনপুটের প্রতিটি ডিজিটই 1 ছিল (যেমন "111")। এই একমাত্র
ক্ষেত্রেই একটি সম্পূর্ণ নতুন ডিজিট (একটি নতুন 1) বসাতে হয়, কারণ বিদ্যমান কোনো ডিজিটেই ক্যারি "শোষিত"
হতে পারেনি। অন্য যেকোনো ক্ষেত্রে (কোনো 0 পাওয়া গেলে) ক্যারি সেখানেই থেমে যায় এবং টেপের দৈর্ঘ্য
অপরিবর্তিত থাকে।
প্র ০৩ $\{ww\}$-এর ধাপ ১-এ "বাম সার্চ সরাসরি b-ট্যাগে ধাক্কা খায়" এই শর্তটি ছাড়া, কীভাবে মেশিন ভুলভাবে একটি বিজোড়-দৈর্ঘ্যের স্ট্রিংকে accept করে ফেলতে পারত?
যদি এই দুটি সংঘর্ষ-সনাক্তকরণ শর্ত (বাম সার্চ সরাসরি b-তে ধাক্কা = জোড় দৈর্ঘ্য, বনাম ডান সার্চ সরাসরি
a-তে ধাক্কা = বিজোড় দৈর্ঘ্য) আলাদা করে চেক না করা হতো, মেশিন হয়তো মাঝের অতিরিক্ত সেলটিকে ভুলভাবে
অগ্রাহ্য করে দুই অর্ধেক মিলিয়ে ফেলত — যেমন "010"-এ মাঝের সেলটি (position 1) বাদ পড়ে
গিয়ে "0" বনাম "0" মিলে গেছে ভেবে ভুলভাবে accept করে ফেলতে পারত। কোডে
স্পষ্টভাবে ('find_right', 'a0'/'a1')-কে সরাসরি reject-এ পাঠিয়ে এই ভুলটি প্রতিরোধ করা
হয়েছে।
অনুশীলন
-
চিন্তা করুন:
read_tapeফাংশনেtm.rejectস্টেট চেক করা হয়নি — শুধুwhile ... state != tm.acceptলুপ চলছে। ইনক্রিমেন্ট TM-এর ট্রানজিশন টেবিল দেখে বলুন, এই TM কি কখনো reject স্টেটে যেতে পারে? কেন বা কেন নয়?না, কখনো যেতে পারে না — ট্রানজিশন টেবিলে
reject-এ যাওয়ার কোনো এন্ট্রিই নেই।scan_right,carry, ওdone— প্রতিটি স্টেটেই সম্ভাব্য প্রতিটি সিম্বলের ('0','1', ব্ল্যাংক) জন্য একটি সংজ্ঞায়িত ট্রানজিশন আছে, তাই কোনো "অসংজ্ঞায়িত ট্রানজিশন" পরিস্থিতিই ঘটে না। এটি স্বাভাবিক — ইনক্রিমেন্ট একটি টোটাল ফাংশন (যেকোনো বাইনারি সংখ্যার জন্যই সংজ্ঞায়িত), তাই এই TM-এর কখনো "reject করার" কোনো কারণই নেই। -
পরীক্ষা করুন: $\{ww\}$-এর টেস্টে
"0000"($w=$"00") যোগ করে Run চাপুন — এটি accept হবে কি? আর"0001"(জোড় দৈর্ঘ্য, কিন্তু $ww$ নয়) দিয়ে কী হয়?"0000"accept হবে, কারণ প্রথমার্ধ"00"= দ্বিতীয়ার্ধ"00"।"0001"reject হবে — যদিও দৈর্ঘ্য জোড় (৪), প্রথমার্ধ"00"≠ দ্বিতীয়ার্ধ"01", তাই ধাপ ২-এর তুলনায় প্রথম মিসম্যাচেই (position 1:0বনাম1) সরাসরি reject হয়ে যাবে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M8-এর বাকি পাঠগুলো — ভ্যারিয়েন্ট, নন-ডিটারমিনিজম, চার্চ-টুরিং থিসিস — এই মডিউলেই আসছে।
- পরবর্তী পাঠ — মাল্টি-টেপ ও মাল্টি-ট্র্যাক ভ্যারিয়েন্ট পাঠ ৩৫ এই পাঠের ww ডিজাইনের জটিলতা দেখে মনে হতে পারে — দুটি টেপ থাকলে কতটা সহজ হতো? পরের পাঠেই সেই উত্তর।
- Data Structures & Algorithms কোর্স সহোদর কোর্স টেপ-স্ক্যানিং ও ক্যারি-প্রোপাগেশনের প্যাটার্ন সরাসরি অ্যারে-ভিত্তিক অ্যালগরিদমের সাথে সম্পর্কিত।