কনটেক্সট-ফ্রি ল্যাঙ্গুয়েজের ডিসিশন প্রপার্টি
এই পাঠে যা শিখবেন
- CFL-এর জন্য কোন প্রশ্ন ডিসাইডেবল — মেম্বারশিপ, এম্পটিনেস, ফাইনাইটনেস — ও প্রতিটির অ্যালগরিদমিক আইডিয়া
- CYK অ্যালগরিদম — একটি real dynamic-programming টেবিল-ফিলিং মেম্বারশিপ-চেকার, কোড থেকে
- কেন CFG ইকুইভ্যালেন্স আনডিসাইডেবল — M3/L16-এর রেগুলার-ইকুইভ্যালেন্সের সাথে বৈসাদৃশ্য
- CYK-এর ফলাফল একটি স্বাধীন ডেরিভেশন-সার্চ মেম্বারশিপ-চেকের সাথে ক্রস-ভেরিফাই করা
১ · CFL-এর জন্য যা ডিসাইডেবল
M3/L16-এর মতোই — DFA-এর জন্য এম্পটিনেস, ফাইনাইটনেস ও ইকুইভ্যালেন্স সবই ডিসাইডেবল ছিল — এখন প্রশ্ন হলো CFG/CFL-এর জন্য একই ধরনের ডিসিশন প্রপার্টিDecision Propertyএকটি ভাষা-শ্রেণি সম্পর্কে একটি নির্দিষ্ট প্রশ্নের উত্তর দেওয়ার জন্য কোনো অ্যালগরিদম (যা সবসময় থামে) আদৌ আছে কি না। কতটা টিকে থাকে।
$w \in L(G)$? — CYK অ্যালগরিদম দিয়ে, CNF গ্রামারে পলিনমিয়াল টাইমে (§২)।
$L(G)=\emptyset$? — S জেনারেটিং সিম্বল কি না চেক করলেই হয় (L19-এর অ্যালগরিদম, §৩)।
$L(G)$ অসীম কি না? — ভেরিয়েবল-রেফারেন্স গ্রাফে সাইকেল আছে কি না চেক করে (L16-এর DFA-সাইকেল-ডিটেকশনের অ্যানালগ)।
দুটো CFG কি একই ভাষা জেনারেট করে? — সাধারণভাবে কোনো অ্যালগরিদম নেই (§৪)।
মেম্বারশিপের অ্যালগরিদমিক আইডিয়া নিচে বিস্তারিত (§২)। এম্পটিনেস ঠিক L19-এর জেনারেটিং সিম্বল কম্পিউটেশন পুনরায় ব্যবহার করে — $L(G) = \emptyset$ হবে ঠিক তখনই যখন স্টার্ট সিম্বল $S$ নিজেই জেনারেটিং না হয়। ফাইনাইটনেস চেক করা হয় গ্রামারের ভেরিয়েবল-রেফারেন্স গ্রাফে (কোন ভেরিয়েবল কোন ভেরিয়েবলকে সরাসরি রেফার করে) একটি "useful" সাইকেল (এমন একটি সাইকেল যা S থেকে পৌঁছানো যায় এবং একটি টার্মিনাল স্ট্রিং-এও পৌঁছাতে পারে) আছে কি না পরীক্ষা করে — M3/L16-এর DFA-সাইকেল-ডিটেকশনের সরাসরি অ্যানালগ।
২ · CYK অ্যালগরিদম — মেম্বারশিপ ডিসাইডেবল কেন
CYK অ্যালগরিদমCocke-Younger-Kasami Algorithmএকটি ক্লাসিক ডায়নামিক-প্রোগ্রামিং অ্যালগরিদম যা CNF-আকৃতির গ্রামারের ওপর মেম্বারশিপ পলিনমিয়াল টাইমে ডিসাইড করে। (Cocke-Younger-Kasami) হলো L20-এর চমস্কি নর্মাল ফর্মের সরাসরি পেঅফ — CNF-এর কঠোর, ইউনিফর্ম রুল-আকৃতি ($A\to BC$ অথবা $A\to a$) ঠিক এই ডায়নামিক-প্রোগ্রামিং টেবিল-ফিলিং অ্যালগরিদমকে সম্ভব করে।
অ্যালগরিদমের আইডিয়া (state precisely): একটি টেবিল $T$ বানানো হয় যেখানে সেল $(i, l)$ ধরে রাখে সেই সব ভেরিয়েবলের সেট যারা ইনপুট স্ট্রিং $w$-এর পজিশন $i$ থেকে শুরু হওয়া দৈর্ঘ্য-$l$ সাবস্ট্রিং জেনারেট করতে পারে। বেস কেস ($l=1$): $w_i$ টার্মিনাল-এর সাথে মিলে যাওয়া সব $A \to w_i$ রুলের $A$। ইনডাক্টিভ কেস ($l>1$): সাবস্ট্রিং-কে দুই ভাগে ভাগ করে (সব সম্ভাব্য split পয়েন্টে), যদি বাম অংশ ভেরিয়েবল $B$ দিয়ে ও ডান অংশ ভেরিয়েবল $C$ দিয়ে জেনারেট হয়, এবং $A \to BC$ একটি রুল হয়, তাহলে $A$ পুরো সাবস্ট্রিংটি জেনারেট করতে পারে। $w \in L(G)$ ঠিক তখনই যখন স্টার্ট সিম্বল $S$ পুরো স্ট্রিং-এর সেলে ($i=0, l=n$) থাকে। এই বটম-আপ টেবিল-ফিলিং $O(n^3)$ টাইমে চলে (n = স্ট্রিং-এর দৈর্ঘ্য) — একটি genuinely পলিনমিয়াল-টাইম অ্যালগরিদম, direct foreshadowing of M10's complexity-class framing।
৩ · কোড: CYK ইমপ্লিমেন্টেশন ও ক্রস-ভেরিফিকেশন
নিচের কোডে একটি সঠিকভাবে কনভার্ট করা CNF গ্রামার (ব্যালেন্সড বন্ধনীর জন্য, $S \to (S)S \mid \varepsilon$ থেকে) নিয়ে একটি real CYK টেবিল-ফিলিং মেম্বারশিপ-চেকার লেখা হয়েছে, এবং একটি স্বাধীন derivation-search মেম্বারশিপ-চেকের সাথে ফলাফল ক্রস-ভেরিফাই করা হয়েছে — দুটো সম্পূর্ণ ভিন্ন অ্যালগরিদম একই উত্তর দিচ্ছে কি না তা যাচাই করাই আসল প্রমাণ।
# CNF গ্রামার: ব্যালেন্সড বন্ধনী, S -> (S)S | eps থেকে সঠিকভাবে CNF-এ কনভার্ট করা
# ধাপ ১: নতুন স্টার্ট S0 -> S | eps (S0 কখনো কোনো রুলের ডানদিকে থাকে না)
# ধাপ ২: S-এর নিজস্ব eps-প্রোডাকশন সাবস্টিটিউশন দিয়ে বাদ দেওয়া: S -> (S)S | ()S | (S) | ()
# ধাপ ৩: টার্মিনাল আইসোলেট করা ও লম্বা রুল চেইনে ভাঙা (L20-এর ধাপ)
from collections import deque
_S_RULES = [('T_open', 'X1'), ('T_open', 'Y1'), ('T_open', 'Z1'), ('T_open', 'T_close')]
CNF = {
'S0': _S_RULES + [()], # S0: S-এর ইউনিট-প্রোডাকশন বাদ দিয়ে সরাসরি + eps
'S': list(_S_RULES), # S নিজে কখনো eps জেনারেট করে না (eps শুধু S0-তে)
'X1': [('S', 'X2')],
'X2': [('T_close', 'S')],
'Y1': [('T_close', 'S')],
'Z1': [('S', 'T_close')],
'T_open': [('(',)],
'T_close': [(')',)],
}
START = 'S0'
def is_variable(sym, grammar):
return sym in grammar
def is_cnf(grammar, start):
"""যাচাই করে প্রতিটি রুল A->BC অথবা A->a আকৃতির, শুধু start-এর eps ছাড়া,
আর start কখনো কোনো রুলের ডানদিকে নেই।"""
for var, prods in grammar.items():
for prod in prods:
if len(prod) == 0:
if var != start:
return False
elif len(prod) == 1:
if is_variable(prod[0], grammar):
return False
elif len(prod) == 2:
if not (is_variable(prod[0], grammar) and is_variable(prod[1], grammar)):
return False
else:
return False
return True
print("is_cnf(CNF):", is_cnf(CNF, START))
def cyk_membership(grammar, start, w):
"""আসল CYK ডায়নামিক-প্রোগ্রামিং টেবিল-ফিলিং অ্যালগরিদম -- table[i][l] =
পজিশন i থেকে শুরু, দৈর্ঘ্য l সাবস্ট্রিং জেনারেট করতে পারে এমন ভেরিয়েবলের সেট।"""
n = len(w)
if n == 0:
return () in grammar.get(start, [])
table = [[set() for _ in range(n + 1)] for _ in range(n)]
for i in range(n): # বেস কেস: দৈর্ঘ্য ১
for var, prods in grammar.items():
for prod in prods:
if len(prod) == 1 and prod[0] == w[i]:
table[i][1].add(var)
for length in range(2, n + 1): # ইনডাক্টিভ কেস: দৈর্ঘ্য ২..n
for i in range(0, n - length + 1):
for split in range(1, length):
left = table[i][split]
right = table[i + split][length - split]
if not left or not right:
continue
for var, prods in grammar.items():
for prod in prods:
if len(prod) == 2 and prod[0] in left and prod[1] in right:
table[i][length].add(var)
return start in table[0][n]
def derive_leftmost_search(grammar, start, target, max_steps=50000):
"""একটি স্বাধীন, সম্পূর্ণ ভিন্ন কৌশল -- BFS ডেরিভেশন-সার্চ -- CYK-এর ক্রস-ভেরিফিকেশনের জন্য।"""
queue = deque([(start,)])
seen = {(start,)}
steps = 0
while queue and steps < max_steps:
steps += 1
form = queue.popleft()
idx = next((i for i, s in enumerate(form) if is_variable(s, grammar)), None)
if idx is None:
if ''.join(form) == target:
return True
continue
if sum(1 for s in form if not is_variable(s, grammar)) > len(target):
continue
for production in grammar[form[idx]]:
new_form = form[:idx] + tuple(production) + form[idx + 1:]
if new_form not in seen:
seen.add(new_form)
queue.append(new_form)
return False
test_strings = ["", "()", "(())", "()()", "(()())", "(", ")(", "(()", "((()))", "(()))("]
print("\nস্ট্রিং | CYK | derivation-search | মিলেছে?")
all_agree = True
for s in test_strings:
cyk_result = cyk_membership(CNF, START, s)
deriv_result = derive_leftmost_search(CNF, START, s)
agree = cyk_result == deriv_result
all_agree = all_agree and agree
print(f"{s!r:14s} | {str(cyk_result):5s} | {str(deriv_result):18s} | {agree}")
print("\nসবগুলো মিলেছে:", all_agree)
cyk_membership কোনোভাবেই ডেরিভেশন সার্চ করে না — শুধু নিচ থেকে ওপরে (bottom-up) সাবস্ট্রিং-দৈর্ঘ্য
বাড়িয়ে একটি টেবিল ভরে $O(n^3)$ সময়ে সিদ্ধান্তে পৌঁছায়। অথচ derive_leftmost_search সম্পূর্ণ ভিন্নভাবে
কাজ করে — সেন্টেনশিয়াল ফর্মের BFS। দুটো সম্পূর্ণ ভিন্ন অ্যালগরিদম একই উত্তর দিচ্ছে — এটাই CYK-এর সঠিকতার একটি
কনক্রিট, কোড-ভেরিফায়েড প্রমাণ এই উদাহরণের জন্য।
৪ · কেন ইকুইভ্যালেন্স আনডিসাইডেবল
এখানেই CFL, M3/L16-এর রেগুলার ল্যাঙ্গুয়েজ থেকে striking ভাবে আলাদা হয়ে যায়। M3/L16-এ দেখানো হয়েছিল দুটো DFA একই ভাষা চেনে কি না তা ডিসাইড করা সম্ভব — সিমেট্রিক ডিফারেন্স বানিয়ে এম্পটিনেস চেক করে। কিন্তু দুটো CFG $G_1, G_2$ ঠিক একই ভাষা জেনারেট করে কি না — সেই প্রশ্নের জন্য কোনো সাধারণ অ্যালগরিদম আদৌ নেই — এই ফলাফল formally প্রমাণিত হয় M9-এর টেকনিক (রিডাকশন) ব্যবহার করে, যা এখনও এই কোর্সে আসেনি — এখানে শুধু ফলাফলটি একটি গুরুত্বপূর্ণ, কনক্রিট প্রথম উদাহরণ হিসেবে স্টেট করা হচ্ছে।
চমস্কি হায়ারার্কিতে এক ধাপ ওপরে ওঠা (রেগুলার → context-free) নতুন ভাষা চেনার ক্ষমতা দেয়, কিন্তু একই সাথে একটি algorithmic প্রশ্ন (ইকুইভ্যালেন্স) যা আগে ডিসাইডেবল ছিল তা আনডিসাইডেবল হয়ে যায়। এটাই ঠিক L04-এ ফ্ল্যাগ করা "power vs practicality" টেনশনের প্রথম concrete দৃষ্টান্ত — এবং M9-এ (ডিসাইডেবিলিটি মডিউল) এই থিম আরও অনেক গভীরভাবে ফিরে আসবে।
CFL-এর জন্য মেম্বারশিপ (CYK, পলিনমিয়াল টাইম), এম্পটিনেস ও ফাইনাইটনেস ডিসাইডেবল — কিন্তু দুটো CFG-এর ইকুইভ্যালেন্স আনডিসাইডেবল, রেগুলার ল্যাঙ্গুয়েজের সাথে একটি স্পষ্ট বৈসাদৃশ্য। এটাই এই কোর্সের প্রথম উদাহরণ যেখানে "বেশি গণনাশক্তি" সরাসরি "কম algorithmic ট্র্যাক্টেবিলিটি"-র সাথে সংযুক্ত হয়ে যায়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ CYK অ্যালগরিদম CNF গ্রামার প্রয়োজন কেন — সাধারণ (non-CNF) একটি CFG-এর ওপর সরাসরি চালানো যায় না কেন?
CYK-এর টেবিল-ফিলিং লজিক নির্ভর করে প্রতিটি রুল ঠিক দুটো ভেরিয়েবলে (বাইনারি split) অথবা ঠিক একটি টার্মিনালে ভাঙা যায় এই কঠোর, ইউনিফর্ম আকৃতির ওপর — তবেই একটি সাবস্ট্রিং-কে দুই ভাগে ভাগ করে প্রতিটি ভাগ আলাদাভাবে দেখার (এবং ছোট সাবপ্রবলেম থেকে টেবিল বড় করার) কৌশলটি কাজ করে। যদি রুলের ডানদিকে তিন বা তার বেশি সিম্বল থাকতো (যেমন $A \to aBcD$), অথবা ইউনিট প্রোডাকশন থাকতো ($A \to B$), তাহলে split পয়েন্ট গণনার নিয়মিততা ভেঙে যেত — তাই L20-এর CNF কনভার্শন CYK-এর একটি hard precondition।
প্র ০২ "এম্পটিনেস ডিসাইডেবল" আর "মেম্বারশিপ ডিসাইডেবল" — এই দুটো ভিন্ন প্রশ্ন কেন গুলিয়ে ফেলা উচিত নয়?
মেম্বারশিপ জিজ্ঞেস করে একটি নির্দিষ্ট স্ট্রিং $w$ ভাষায় আছে কি না ($w \in L(G)$?), যেখানে এম্পটিনেস জিজ্ঞেস করে পুরো ভাষাটাই খালি কি না ($L(G) = \emptyset$?, অর্থাৎ কোনো স্ট্রিং-ই ভাষায় নেই কি না)। দুটোই ডিসাইডেবল, কিন্তু সম্পূর্ণ ভিন্ন অ্যালগরিদম দিয়ে — মেম্বারশিপ CYK-এর মতো একটি নির্দিষ্ট স্ট্রিং প্রসেস করে, এম্পটিনেস L19-এর জেনারেটিং-সিম্বল কম্পিউটেশন দিয়ে পুরো গ্রামার বিশ্লেষণ করে, কোনো নির্দিষ্ট স্ট্রিং ছাড়াই।
প্র ০৩ যদি কেউ দাবি করে সে দুটো CFG-এর ইকুইভ্যালেন্স চেক করার একটি অ্যালগরিদম বানিয়েছে যা সবসময় সঠিক উত্তর দেয়, সেই দাবি নিয়ে আপনার সন্দেহের কারণ কী হবে?
কারণ এই পাঠ ঠিক এটাই প্রতিষ্ঠিত করেছে — CFG ইকুইভ্যালেন্স সাধারণভাবে আনডিসাইডেবল, অর্থাৎ কোনো অ্যালগরিদম প্রতিটি সম্ভাব্য CFG জোড়ার জন্য সঠিক, সবসময়-থামা উত্তর দিতে পারে না — এটি একটি formally প্রমাণিত, স্থায়ী সীমাবদ্ধতা (M9-এর কৌশলে প্রমাণিত)। এমন একটি "সবসময়-সঠিক" অ্যালগরিদমের দাবি হয় ভুল (কিছু ইনপুটে ভুল উত্তর দেবে), অথবা কিছু ইনপুটে কখনো থামবে না (তাই "সবসময় উত্তর দেয়" দাবিটাই মিথ্যা)।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোডে
test_strings-এ নিজের কিছু স্ট্রিং যোগ করুন (যেমন"(((())))"বা"())(") এবং Run চেপে দেখুন CYK ও derivation-search উভয়ই একমত কি না।"(((())))"একটি বৈধ ব্যালেন্সড বন্ধনী স্ট্রিং (৪টি খোলা, ৪টি বন্ধ, সঠিক নেস্টিং) — উভয় পদ্ধতিইTrueদেবে।"())("ব্যালেন্সড নয় (তৃতীয় ক্যারেক্টারে বন্ধনী আগেই বেশি বন্ধ হয়ে গেছে, শেষেরটাও unclosed) — উভয় পদ্ধতিইFalseদেবে। দুটো সম্পূর্ণ ভিন্ন অ্যালগরিদমের মিলে যাওয়াই CYK-এর সঠিকতার প্রমাণ। -
চিন্তা করুন: ধরুন দুটো CFG আছে যাদের রুলগুলো দেখতে সম্পূর্ণ ভিন্ন, কিন্তু আপনি সন্দেহ করছেন
তারা একই ভাষা জেনারেট করে। আপনি কী করতে পারেন (এই পাঠের সীমাবদ্ধতা মাথায় রেখে) — এবং কী করতে পারবেন না?
আপনি একটি length bound পর্যন্ত উভয় গ্রামার থেকে ভাষা জেনারেট করে (L27-স্টাইলের BFS দিয়ে) তুলনা করতে পারেন — যদি কোনো bound-এ পার্থক্য পান, নিশ্চিতভাবে বলতে পারবেন তারা ভিন্ন ভাষা। কিন্তু যদি সব bound পর্যন্ত মিলে যায়, আপনি কখনো নিশ্চিতভাবে বলতে পারবেন না তারা সত্যিই সমান — কারণ ঠিক এই সাধারণ প্রশ্নের জন্যই কোনো ডিসাইডিং অ্যালগরিদম নেই (§৪)। এটাই আনডিসাইডেবিলিটির ব্যবহারিক পরিণতি — একটি ব্যাপক সার্চ কনফার্মেশন দিতে পারে না, শুধু কাউন্টার-এক্সাম্পল খুঁজে পেলে refutation দিতে পারে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ: কনটেক্সট-সেনসিটিভ গ্রামার ও LBA পাঠ ২৯ M7 শুরু — চমস্কি হায়ারার্কির পরবর্তী স্তর, Type 1, ও তার recognizing automaton।
- পূর্ববর্তী: চমস্কি নর্মাল ফর্ম পাঠ ২০ CYK-এর জন্য দরকারি CNF কনভার্শন কীভাবে কাজ করে তা বিস্তারিত সেই পাঠে।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M9-এ (ডিসাইডেবিলিটি) হল্টিং প্রবলেম ও আনডিসাইডেবিলিটির formal প্রমাণ কৌশল আসছে।