পার্স ট্রি ও অ্যাম্বিগুইটি (থিওরি)
এই পাঠে যা শিখবেন
- অ্যাম্বিগুয়াস গ্রামার-এর পুনরুক্তি — একটি নির্দিষ্ট গ্রামারের সমস্যা
- ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষা — একটি ভাষার নিজেরই মৌলিক সীমাবদ্ধতা
- $a^ib^jc^k : i=j \text{ অথবা } j=k$ ক্লাসিক উদাহরণ, এবং কেন এটি ইনহেরেন্টলি অ্যাম্বিগুয়াস
- Python দিয়ে সত্যিকারের ডাবল-ডেরিভেশন সার্চ চালিয়ে একটি নির্দিষ্ট স্ট্রিং-এ অ্যাম্বিগুইটি দেখানো
১ · অ্যাম্বিগুয়াস গ্রামার — পুনরুক্তি
Programming Languages & Compiler Design কোর্সের M3/L14 ইতিমধ্যে পার্স ট্রি ও অ্যাম্বিগুইটি ব্যবহারিক কোণ থেকে কভার করেছে — একটি গ্রামার পুনর্লিখে অ্যাম্বিগুইটি কীভাবে দূর করা যায় (কম্পাইলারের জন্য)। এই পাঠ সেই একই সমস্যার গভীরতর, তাত্ত্বিক প্রশ্ন-এ যায় — যা সেই কোর্স কভার করেনি।
অ্যাম্বিগুয়াস গ্রামারAmbiguous Grammarএমন একটি নির্দিষ্ট গ্রামার যেখানে কোনো এক স্ট্রিং-এর একাধিক ভিন্ন leftmost derivation আছে। হলো এমন একটি গ্রামার যেখানে L17-এর ডেরিভেশন প্রক্রিয়ায় কোনো এক টার্মিনাল স্ট্রিং-এর জন্য একাধিক ভিন্ন leftmost derivation (সমতুল্যভাবে, একাধিক ভিন্ন পার্স ট্রি) পাওয়া যায়। PLC কোর্সের L14-এ দেখানো হয়েছে — এটি একটি নির্দিষ্ট গ্রামারের সমস্যা, এবং প্রায়ই গ্রামারটি সাবধানে পুনর্লিখে (যেমন প্রিসিডেন্স/ অ্যাসোসিয়েটিভিটি এনকোড করে) সমাধান করা যায়।
২ · ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষা — নতুন, গভীরতর প্রশ্ন
কিন্তু একটি সত্যিই গুরুত্বপূর্ণ, মাঝে মাঝে বিস্ময়কর তথ্য আছে — ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষাInherently Ambiguous Languageএমন একটি কনটেক্সট-ফ্রি ভাষা যার জন্য প্রতিটি সম্ভাব্য CFG-ই অ্যাম্বিগুয়াস — কোনো unambiguous গ্রামার আদৌ অস্তিত্বে নেই। হলো এমন একটি কনটেক্সট-ফ্রি ভাষা যার জন্য সেটি জেনারেট করে এমন প্রতিটি সম্ভাব্য CFG-ই অ্যাম্বিগুয়াস। PLC কোর্সে যেভাবে নির্দিষ্ট গ্রামার পুনর্লিখে অ্যাম্বিগুইটি দূর করা গিয়েছিল, ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষার ক্ষেত্রে তা কখনোই সম্ভব নয় — যতই গ্রামার পুনর্লিখুন না কেন, কোনো এক unambiguous গ্রামার আদৌ অস্তিত্বে নেই যা সেই ভাষা জেনারেট করে।
৩ · ক্লাসিক উদাহরণ — $a^ib^jc^k : i=j$ অথবা $j=k$
একটি প্রকৃত, স্ট্যান্ডার্ড উদাহরণ —
$$L = \{a^ib^jc^k : i=j \text{ অথবা } j=k\}$$
এটি ইনহেরেন্টলি অ্যাম্বিগুয়াস। ইনফরমালি ব্যাখ্যা করলে — যে স্ট্রিং-এ উভয় শর্তই সত্য (অর্থাৎ $i=j=k$, আকার $a^nb^nc^n$), সেগুলোকে বৈধভাবে দুই উপায়ে "জাস্টিফাই" করা যায় — হয় $i=j$ নিয়মের মাধ্যমে, নয়তো $j=k$ নিয়মের মাধ্যমে। যেকোনো গ্রামার যা পুরো $L$ জেনারেট করার চেষ্টা করে, শেষ পর্যন্ত ঠিক এই $a^nb^nc^n$-আকৃতির স্ট্রিংগুলোতে গিয়ে অ্যাম্বিগুয়াস হয়ে পড়ে — এটি একটি প্রমাণিত ফলাফল (পূর্ণ ফরমাল প্রমাণ যথেষ্ট অ্যাডভান্সড, এখানে আবশ্যক নয়) — নিচের কোড সেলে আমরা এটি একটি নির্দিষ্ট স্ট্রিং-এ concretely দেখাব।
$a^ib^jc^k$-এ যেখানে $i=j$ (k স্বাধীন) — একা এই ভাষা unambiguous।
$a^ib^jc^k$-এ যেখানে $j=k$ (i স্বাধীন) — একা এই ভাষাও unambiguous।
দুটো শাখা একসাথে জুড়লে ($i=j$ অথবা $j=k$), $a^nb^nc^n$-আকৃতির স্ট্রিং উভয় শাখা দিয়েই বৈধ — ইনহেরেন্ট অ্যাম্বিগুইটি।
৪ · কোডে অ্যাম্বিগুইটি concretely দেখানো
প্রথমে দুটো আলাদা গ্রামার লেখা যাক — একটি শুধু $i=j$ (নিজে থেকেই unambiguous), আরেকটি শুধু $j=k$ (নিজে থেকেই
unambiguous)। তারপর দুটো গ্রামারকে একটি শেয়ার্ড স্টার্ট সিম্বলের নিচে জুড়ে একটি combined গ্রামার বানানো হবে
— এবং L17-এর derivation-search পুনরায় ব্যবহার করে "aabbcc" ($i=j=k=2$, তাই উভয় শর্তই সত্য)-এর
জন্য সত্যিই দুটো ভিন্ন leftmost derivation খোঁজা হবে।
# ইনহেরেন্ট অ্যাম্বিগুইটি concretely দেখানো -- একটি combined গ্রামারে দুটো ভিন্ন derivation খোঁজা
from collections import deque
class Grammar:
def __init__(self, variables, terminals, rules, start):
self.V = set(variables)
self.T = set(terminals)
self.rules = rules
self.S = start
def derive_leftmost_all(grammar, start, target, max_steps=60, max_solutions=5):
"""L17-এর derive_leftmost-এর সম্প্রসারণ -- প্রথমটিতে না থেমে একাধিক ভিন্ন derivation খোঁজে"""
queue = deque([((start,), [(start,)])])
solutions = []
while queue and len(solutions) < max_solutions:
form, history = queue.popleft()
if len(history) - 1 > max_steps:
continue
idx = next((i for i, sym in enumerate(form) if sym in grammar.V), None)
if idx is None:
if ''.join(form) == target:
solutions.append(history)
continue
prefix = ''.join(form[:idx])
if not target.startswith(prefix):
continue
var = form[idx]
for rhs in grammar.rules.get(var, []):
new_form = form[:idx] + rhs + form[idx + 1:]
terms = []
for s in new_form:
if s in grammar.V:
break
terms.append(s)
if not target.startswith(''.join(terms)):
continue
if len(new_form) > len(target) + 6:
continue
queue.append((new_form, history + [new_form]))
return solutions
# শাখা ১: A1 C1 -- A1 প্রোডিউস করে a^i b^i (i=j), C1 প্রোডিউস করে c^k (k স্বাধীন)
# শাখা ২: A2 B2 -- A2 প্রোডিউস করে a^i (i স্বাধীন), B2 প্রোডিউস করে b^j c^j (j=k)
V = {'S', 'S1', 'A1', 'C1', 'S2', 'A2', 'B2'}
T = {'a', 'b', 'c'}
rules = {
'S': [('S1',), ('S2',)], # S -> S1 | S2 (দুই শাখার মধ্যে নন-ডিটারমিনিস্টিক বাছাই)
'S1': [('A1', 'C1')],
'A1': [('a', 'A1', 'b'), ()], # A1 -> a A1 b | epsilon (i=j নিশ্চিত করে)
'C1': [('c', 'C1'), ()], # C1 -> c C1 | epsilon (k স্বাধীন)
'S2': [('A2', 'B2')],
'A2': [('a', 'A2'), ()], # A2 -> a A2 | epsilon (i স্বাধীন)
'B2': [('b', 'B2', 'c'), ()], # B2 -> b B2 c | epsilon (j=k নিশ্চিত করে)
}
G = Grammar(V, T, rules, 'S')
target = "aabbcc" # i=j=k=2 -- উভয় শর্তই সত্য
solutions = derive_leftmost_all(G, 'S', target, max_steps=40, max_solutions=5)
print(f"'{target}' -- {len(solutions)}টি ভিন্ন leftmost derivation পাওয়া গেছে\n")
for n, hist in enumerate(solutions, 1):
print(f"--- derivation {n} ---")
for form in hist:
print(" ⇒", ''.join(form) if form else 'ε')
print()
print("সিদ্ধান্ত:", "AMBIGUOUS on this string" if len(solutions) > 1 else "unambiguous on this string")
"aabbcc"-তে পৌঁছায়, কিন্তু সম্পূর্ণ ভিন্ন পথে — প্রথমটি $S \to S1$ দিয়ে শুরু
করে $i=j$ শাখা ব্যবহার করে (প্রথম "aabb" $A1$ দিয়ে, তারপর "cc" $C1$ দিয়ে), দ্বিতীয়টি $S \to S2$ দিয়ে শুরু
করে $j=k$ শাখা ব্যবহার করে (প্রথম "aa" $A2$ দিয়ে, তারপর "bbcc" $B2$ দিয়ে)। এই দুটো derivation genuinely
ভিন্ন পার্স ট্রি প্রতিনিধিত্ব করে — এটাই অ্যাম্বিগুইটির সংজ্ঞা অনুযায়ী প্রমাণ।
অ্যাম্বিগুয়াস গ্রামার প্রায়ই পুনর্লিখে ঠিক করা যায়; কিন্তু ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষা-র ক্ষেত্রে কোনো পুনর্লিখনই সাহায্য করে না — সমস্যাটি ভাষার নিজের মধ্যেই, কোনো নির্দিষ্ট গ্রামারের ডিজাইনে নয়। $a^ib^jc^k : i=j$ অথবা $j=k$ এই সত্যতার একটি ক্লাসিক, concretely-verifiable উদাহরণ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "অ্যাম্বিগুয়াস গ্রামার" ও "ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষা" — এই দুটো ধারণার মধ্যে সবচেয়ে গুরুত্বপূর্ণ পার্থক্য কী?
একটি অ্যাম্বিগুয়াস গ্রামার একটি নির্দিষ্ট গ্রামারের বৈশিষ্ট্য — সেই একই ভাষার জন্য হয়তো একটি ভিন্ন, unambiguous গ্রামার লেখা সম্ভব (PLC কোর্সের L14-এ যেমন দেখানো হয়েছে)। কিন্তু একটি ইনহেরেন্টলি অ্যাম্বিগুয়াস ভাষা একটি ভাষার নিজের বৈশিষ্ট্য — সেই ভাষা জেনারেট করে এমন প্রতিটি সম্ভাব্য গ্রামারই অ্যাম্বিগুয়াস, তাই কোনো পুনর্লিখনই সমাধান নয়। প্রথমটি একটি "ইঞ্জিনিয়ারিং সমস্যা" (গ্রামার ঠিক করুন), দ্বিতীয়টি একটি "গাণিতিক সত্যতা" (কোনো সমাধান নেই)।
প্র ০২ কোড সেলে $A1$ ও $A2$ আলাদা ভেরিয়েবল কেন — দুটোই তো "a" শুধু পুনরাবৃত্তি করে (বা কিছুই না)?
যদিও $A1$ ও $A2$ একই ধরনের স্ট্রিং প্রোডিউস করে (শূন্য বা তার বেশি "a"), তারা গ্রামারে ভিন্ন ভূমিকা পালন করে — $A1$ সরাসরি "b" এর সাথে মিলে যায় (রিকার্সিভ রুল $A1 \to aA1b$-এ, প্রতিটি "a"-এর জন্য একটি "b" গ্যারান্টিড, তাই $i=j$ নিশ্চিত হয়), কিন্তু $A2$ স্বাধীনভাবে যেকোনো সংখ্যক "a" জেনারেট করে ($A2 \to aA2 \mid \varepsilon$, কোনো "b"-এর সাথে সংযোগ ছাড়া)। এই কাঠামোগত পার্থক্যই দুটো শাখার ভিন্ন গাণিতিক শর্ত ($i=j$ বনাম $j=k$) বাস্তবায়ন করে — তাই আলাদা ভেরিয়েবল প্রয়োজন, দুটোকে একটিতে একত্র করলে শর্তগুলো ভেঙে পড়বে।
প্র ০৩
"abc" ($i=j=k=1$) স্ট্রিং-এও কি কোড সেলে দুটো derivation পাওয়া যাবে?
হ্যাঁ — "abc"-এও $i=j$ (উভয়ই ১) এবং $j=k$ (উভয়ই ১) — তাই এটিও $a^nb^nc^n$-আকৃতির (n=1),
এবং একইভাবে দুটো ভিন্ন derivation পাওয়া উচিত: একটি $S1$ শাখা দিয়ে, একটি $S2$ শাখা দিয়ে। কিন্তু
"aabc" ($i=2, j=1, k=1$)-এর মতো একটি স্ট্রিং-এ শুধু $j=k$ সত্য ($i \neq j$), তাই সেখানে
শুধুমাত্র $S2$ শাখা দিয়েই derivation সম্ভব — একটিমাত্র derivation, অ্যাম্বিগুইটি নেই।
অনুশীলন
-
চিন্তা করুন:
"aaabbbccc"($i=j=k=3$) স্ট্রিং-এর জন্য কোড সেল কতগুলো ভিন্ন derivation খুঁজে পাবে বলে আপনার ধারণা? আগে অনুমান করুন, তারপর কোড সেলেtargetপরিবর্তন করে যাচাই করুন।ঠিক আগের উদাহরণের মতোই দুটো ($n=2$-এর ক্ষেত্রে যেমন দুটো পাওয়া গিয়েছিল) — যেকোনো $a^nb^nc^n$-আকৃতির স্ট্রিং-এর জন্য উভয় শর্তই ($i=j$ এবং $j=k$) সত্য থাকে, তাই $S1$ ও $S2$ উভয় শাখাই সফল হবে, ঠিক দুটো ভিন্ন leftmost derivation দিয়ে — n-এর মান যাই হোক না কেন।
-
পরীক্ষা করুন: কোড সেলে
target = "aabc"($i=2, j=1, k=1$, শুধু $j=k$ সত্য) দিয়ে Run চেপে দেখুন কতগুলো derivation পাওয়া যায়।শুধু একটি derivation পাওয়া যাবে ($S \to S2$ শাখা দিয়ে, যেহেতু $j=k=1$)। $S \to S1$ শাখা ব্যর্থ হবে কারণ $A1 \to aA1b$ সবসময় সমান সংখ্যক "a" ও "b" প্রোডিউস করে ($i=j$ নিশ্চিত করে), আর এখানে $i=2 \neq j=1$ — তাই সেই শাখায় কোনো বৈধ derivation নেই, এবং স্ট্রিংটি এই নির্দিষ্ট স্ট্রিং-এ অ্যাম্বিগুয়াস নয় (যদিও পুরো ভাষা $L$ ইনহেরেন্টলি অ্যাম্বিগুয়াস — অ্যাম্বিগুইটি সব স্ট্রিং-এ নয়, শুধু $a^nb^nc^n$-আকৃতির স্ট্রিংগুলোতেই ঘটে)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — CFG সিমপ্লিফিকেশন — অপ্রয়োজনীয় সিম্বল ও প্রোডাকশন সরানোর সঠিক অ্যালগরিদম শেখাবে, নর্মাল ফর্মের প্রি-কন্ডিশন হিসেবে।
- পাঠ ১৭ · CFG — ফরমাল ডেফিনিশন ও ডেরিভেশন পূর্ববর্তী পাঠ এই পাঠের derivation-search কোড L17-এর ভিত্তির ওপর সরাসরি তৈরি — সেই পাঠ আগে পড়ুন যদি না পড়ে থাকেন।
- PLC কোর্সের L14 · পার্স ট্রি ও অ্যাম্বিগুইটি সঙ্গী কোর্স নির্দিষ্ট গ্রামারের অ্যাম্বিগুইটি পুনর্লিখে কীভাবে সমাধান করা হয় — কম্পাইলার-নির্মাণের ব্যবহারিক কোণ সেই পাঠে দেখুন।
- সব 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 — সব এক জায়গায়।