কনটেক্সট-ফ্রি গ্রামার — ফরমাল ডেফিনিশন ও ডেরিভেশন
এই পাঠে যা শিখবেন
- CFG-এর ফরমাল ৪-টাপল সংজ্ঞা — V, Σ, R, S এবং প্রতিটির নির্দিষ্ট অর্থ
- ডেরিভেশন রিলেশন $\Rightarrow$ ও তার reflexive-transitive closure $\Rightarrow^*$-এর নির্ভুল সংজ্ঞা
- গ্রামারের ভাষা $L(G)$ এবং "কনটেক্সট-ফ্রি ভাষা"-র সংজ্ঞা — DFA-এর সমান্তরাল কাঠামো
- Python দিয়ে একটি সত্যিকারের leftmost-derivation-search ও exhaustive language-generation অ্যালগরিদম
১ · CFG-এর ফরমাল সংজ্ঞা
Programming Languages & Compiler Design কোর্সের M3/L13 ইতিমধ্যে CFG ও ডেরিভেশন ব্যবহারিক পার্সিং-গ্রাউন্ডওয়ার্কের কোণ থেকে কভার করেছে। এই পাঠ সেই একই ধারণাগুলো ফরমালি পুনরায় বলছে — M4-M6-এর তাত্ত্বিক ফলাফল (নর্মাল ফর্ম, পাম্পিং লেমা, PDA ইকুইভ্যালেন্স)-এর ভিত্তি হিসেবে।
একটি কনটেক্সট-ফ্রি গ্রামারContext-Free Grammar (CFG)ফরমালি একটি ৪-টাপল $G=(V,\Sigma,R,S)$ যা টার্মিনাল স্ট্রিং তৈরির নিয়ম বর্ণনা করে। ফরমালি একটি ৪-টাপল —
$$G = (V, \Sigma, R, S)$$
একটি ফাইনাইট সেট — ভেরিয়েবলনন-টার্মিনাল সিম্বল, প্রতিটি একটি "বিমূর্ত ক্যাটেগরি" প্রতিনিধিত্ব করে (নন-টার্মিনাল)।
টার্মিনাল অ্যালফাবেট — চূড়ান্ত স্ট্রিং-এ যে প্রকৃত সিম্বল থাকবে, V থেকে ডিসজয়েন্ট।
একটি ফাইনাইট সেট প্রোডাকশন রুলের, প্রতিটি $A \to \alpha$ আকারে, যেখানে $A \in V$ এবং $\alpha \in (V \cup \Sigma)^*$।
স্টার্ট ভেরিয়েবল, $S \in V$ — প্রতিটি ডেরিভেশন এখান থেকেই শুরু হয়।
লক্ষ্য করুন $\alpha \in (V \cup \Sigma)^*$ — একটি রুলের ডানপাশে ভেরিয়েবল ও টার্মিনাল যেকোনো মিশ্রণে, যেকোনো ক্রমে, যেকোনো সংখ্যক বার (এমনকি শূন্যবার — ε-প্রোডাকশন) থাকতে পারে। এই নমনীয়তাই CFG-কে রেগুলার ভাষার (L06-L11) চেয়ে বেশি শক্তিশালী করে তোলে — একটি DFA-এর ট্রানজিশন ফাংশন কখনো "মনে রাখতে" পারে না কতগুলো ওপেনিং প্যারেন এখনো বন্ধ হয়নি, কিন্তু একটি রিকার্সিভ CFG রুল সেটা স্বাভাবিকভাবেই করতে পারে (নিচের উদাহরণে দেখুন)।
২ · ডেরিভেশন — এক ধাপ ও বহু ধাপ
L03-এর স্ট্রাকচারাল ইনডাকশন ফ্রেমিং সরাসরি পুনরায় ব্যবহার করে, ডেরিভেশন সংজ্ঞায়িত হয় একটি এক-ধাপ ডেরিভেশন রিলেশন$u \Rightarrow v$ — u থেকে v-তে ঠিক একটি রুল প্রয়োগ করে পৌঁছানো যায় দিয়ে —
$$u \Rightarrow v \quad \text{যদি} \quad u = xAy,\ v = x\alpha y,\ \text{এবং}\ A \to \alpha \in R$$
অর্থাৎ $u$-এর কোথাও একটি ভেরিয়েবল $A$ আছে, এবং $R$-এ $A$-এর কোনো একটি রুল প্রয়োগ করে তাকে $\alpha$ দিয়ে প্রতিস্থাপন করলে $v$ পাওয়া যায় — বাকি সব ($x$ ও $y$) অপরিবর্তিত থাকে। এরপর $\Rightarrow^*$ ("u derives v") হলো এই রিলেশনের reflexive-transitive closure — শূন্য বা তার বেশি একক ধাপ পরপর প্রয়োগ করা (রিফ্লেক্সিভ অংশ মানে $u \Rightarrow^* u$ সবসময় সত্য, শূন্য ধাপেই)।
৩ · গ্রামারের ভাষা $L(G)$
একটি গ্রামার $G$-এর ভাষা হলো স্টার্ট সিম্বল থেকে ডেরাইভ-করা-যায় এমন সব টার্মিনাল স্ট্রিং —
$$L(G) = \{w \in \Sigma^* : S \Rightarrow^* w\}$$
একটি ভাষা কনটেক্সট-ফ্রি যদি কোনো CFG সেই ভাষা জেনারেট করে — L06-এর "একটি ভাষা রেগুলার যদি কোনো DFA সেটি অ্যাকসেপ্ট করে"-এর সাথে সম্পূর্ণ সমান্তরাল একটি সংজ্ঞা, শুধু চমস্কি হায়ারার্কির (L04) এক ধাপ ওপরে — DFA-এর বদলে গ্রামার, "অ্যাকসেপ্ট" করার বদলে "জেনারেট" করা।
৪ · Worked example — ব্যালেন্সড বন্ধনী গ্রামার
একটি ছোট্ট, ক্লাসিক CFG দেখা যাক — ব্যালেন্সড বন্ধনীর ভাষা, $V=\{S\}$, $\Sigma=\{(,\ )\}$, এবং একটিমাত্র ভেরিয়েবলের দুটি রুল —
$$S \to (S)S \mid \varepsilon$$
এই গ্রামারটি পরের কয়েকটি পাঠে (M4-এর CNF রূপান্তর, M5-এর PDA-CFG ইকুইভ্যালেন্স, M6-এর CYK পার্সিং) বারবার পুনরায় ব্যবহার হবে — তাই এখানেই এটির derivation ও ভাষা কোড দিয়ে যাচাই করে নেওয়া দরকার।
# CFG-এর ফরমাল সংজ্ঞা কোডে -- একটি real Grammar ক্লাস ও leftmost-derivation-search
from collections import deque
class Grammar:
def __init__(self, variables, terminals, rules, start):
self.V = set(variables) # V -- ভেরিয়েবল (নন-টার্মিনাল)
self.T = set(terminals) # Sigma -- টার্মিনাল অ্যালফাবেট
self.rules = rules # R -- dict: variable -> RHS-সিকোয়েন্সের লিস্ট
self.S = start # S -- স্টার্ট ভেরিয়েবল
def derive_leftmost(grammar, start, target, max_steps=200):
"""সবচেয়ে বামের ভেরিয়েবল বারবার এক্সপ্যান্ড করে (BFS সহ) S =>* target -- সত্যিকারের derivation খোঁজে"""
init = (start,)
queue = deque([(init, [init])])
while queue:
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:
return history
continue
prefix = ''.join(form[:idx])
if not target.startswith(prefix): # ফাইনালাইজড 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) + 5: # গ্রোথ বাউন্ড -- অতিরিক্ত বড় ব্রাঞ্চ prune
continue
queue.append((new_form, history + [new_form]))
return None
def generate_language_up_to_length(grammar, start, max_length):
"""S থেকে সব সম্ভাব্য derivation BFS করে L(G)-কে length bound পর্যন্ত exhaustively জেনারেট করে"""
result = set()
queue = deque([(start,)])
seen = {(start,)}
while queue:
form = queue.popleft()
if sum(1 for s in form if s not in grammar.V) > max_length:
continue
idx = next((i for i, sym in enumerate(form) if sym in grammar.V), None)
if idx is None:
if len(form) <= max_length:
result.add(''.join(form))
continue
var = form[idx]
for rhs in grammar.rules.get(var, []):
new_form = form[:idx] + rhs + form[idx + 1:]
if sum(1 for s in new_form if s not in grammar.V) > max_length:
continue
if new_form in seen:
continue
seen.add(new_form)
queue.append(new_form)
return result
# ব্যালেন্সড বন্ধনী গ্রামার -- G = (V, Sigma, R, S)
V, T = {'S'}, {'(', ')'}
rules = {'S': [('(', 'S', ')', 'S'), ()]} # S -> (S)S | epsilon
G = Grammar(V, T, rules, 'S')
target = "(())()"
history = derive_leftmost(G, 'S', target)
print(f"টার্গেট স্ট্রিং: {target!r}\n")
print("খুঁজে পাওয়া leftmost derivation:")
for i, form in enumerate(history):
shown = ''.join(form) if form else 'ε'
print(f" ধাপ {i}: {shown}")
print()
lang = generate_language_up_to_length(G, 'S', 6)
print(f"L(G), length <= 6 পর্যন্ত ({len(lang)}টি স্ট্রিং):")
print(sorted(lang, key=lambda w: (len(w), w)))
derive_leftmost-এর দুটি prune শর্ত লক্ষ্য করুন — (১) ফাইনালাইজড prefix টার্গেটের সাথে মিলছে কি
না (leftmost derivation-এ ভেরিয়েবলের বাম পাশের সব সিম্বল ইতিমধ্যে টার্মিনাল, তাই সেগুলো পরিবর্তন হবে না),
এবং (২) ফর্মের length টার্গেটের length-এর বেশি বেড়ে গেছে কি না। এই দুটো prune ছাড়া $S \to (S)S$-এর
রিকার্শন তাত্ত্বিকভাবে অসীম সার্চ স্পেস তৈরি করতে পারত।
$G=(V,\Sigma,R,S)$ চারটি উপাদান দিয়ে একটি গ্রামার সম্পূর্ণভাবে বর্ণনা করে; $\Rightarrow$ ও $\Rightarrow^*$ সেই গ্রামার থেকে স্ট্রিং তৈরির প্রক্রিয়া নির্ভুলভাবে সংজ্ঞায়িত করে; আর $L(G)$ সেই প্রক্রিয়ায় তৈরিযোগ্য সব স্ট্রিং-এর সম্পূর্ণ সেট। M18-M21 এই একই ভিত্তির ওপর দাঁড়িয়ে অ্যাম্বিগুইটি, সিমপ্লিফিকেশন ও নর্মাল ফর্ম নিয়ে আলোচনা করবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ একটি রুলের ডানপাশ $\alpha \in (V \cup \Sigma)^*$ — এখানে ভেরিয়েবল ও টার্মিনালের মিশ্রণ অনুমোদিত কেন এটি এত গুরুত্বপূর্ণ?
কারণ এই নমনীয়তাই CFG-কে DFA/NFA (M2)-এর চেয়ে বেশি শক্তিশালী করে। একটি DFA-এর ট্রানজিশন ফাংশন $\delta(q,a) \to q$ শুধু একটি ফিক্সড, ফাইনাইট সংখ্যক অবস্থার মধ্যে থাকতে পারে — তাই সে "কতগুলো ওপেনিং বন্ধনী বাকি আছে" গণনা করতে পারে না (সংখ্যাটি অসীম হতে পারে)। কিন্তু $S \to (S)S$-এর মতো একটি রিকার্সিভ রুল, যেখানে RHS-এ ভেরিয়েবল নিজেই ফিরে আসে, স্বাভাবিকভাবেই "নেস্টিং" ট্র্যাক করতে পারে — এটাই M4-M6 জুড়ে CFL-কে রেগুলার ভাষার চেয়ে বেশি শক্তিশালী করে তোলে (M22-এর PDA-এর স্ট্যাক দিয়ে এই একই ক্ষমতা মেশিন-স্তরে বাস্তবায়িত হবে)।
প্র ০২ $\Rightarrow^*$-এর সংজ্ঞায় "reflexive" অংশ ($u \Rightarrow^* u$ শূন্য ধাপেই সত্য) কেন দরকার?
কারণ এটি $L(G)$-এর সংজ্ঞাকে সঠিক রাখে। যদি $S$ নিজেই টার্মিনাল হতো (একটি কৃত্রিম উদাহরণে), অথবা কোনো গ্রামারে সরাসরি $S \to \varepsilon$ থাকে (যেমন এই পাঠের গ্রামারে), তাহলে "শূন্য ধাপে" $S \Rightarrow^* w$ সত্য হওয়ার সুযোগ থাকা দরকার — নাহলে ε-এর মতো স্ট্রিং কখনো ভাষার সদস্য হিসেবে গণ্য হতো না, এমনকি একটি সরাসরি $S \to \varepsilon$ রুল থাকা সত্ত্বেও (যেহেতু সেটি একটি বৈধ এক-ধাপ ডেরিভেশন, শূন্য-ধাপ নয়, কিন্তু reflexive closure-এর সাধারণ সংজ্ঞা যেকোনো সংখ্যক ধাপ — শূন্য সহ — কভার করে বলেই এই পুরো প্রক্রিয়াটি গাণিতিকভাবে সুসংগত থাকে)।
প্র ০৩
কোড সেলের generate_language_up_to_length কেন length bound ছাড়া চালানো যাবে না?
কারণ $L(G)$ এখানে অসীম — ব্যালেন্সড বন্ধনীর যেকোনো length-এর জন্য (২, ৪, ৬, ...) নতুন নতুন বৈধ স্ট্রিং
আছে, এবং $S \to (S)S$ রুল যতবার খুশি রিকার্সিভলি প্রয়োগ করা যায়। একটি অসীম সেট সম্পূর্ণভাবে "প্রিন্ট"
করা অসম্ভব — তাই max_length প্যারামিটার BFS-কে একটি নির্দিষ্ট বিন্দুতে থামতে বাধ্য করে,
একটি ফাইনাইট, পরীক্ষাযোগ্য উপসেট তৈরি করে। এটি L02-এর মূল পর্যবেক্ষণেরই প্রতিফলন — $\Sigma^*$ (এবং তাই
অনেক ভাষাই) কাউন্টেবলি ইনফাইনাইট, তাই কম্পিউটেশনালি শুধু একটি বাউন্ডেড উপসেট নিয়েই কাজ করা সম্ভব।
অনুশীলন
-
হাতে-কলমে করুন: $S \to (S)S \mid \varepsilon$ গ্রামার ব্যবহার করে
"()()"-এর একটি সম্পূর্ণ leftmost derivation নিজে হাতে লিখুন, প্রতিটি ধাপে প্রয়োগ করা রুল উল্লেখ করে।$S \Rightarrow (S)S$ $\Rightarrow ()S$ (প্রথম $S \to \varepsilon$) $\Rightarrow ()(S)S$ (দ্বিতীয় $S \to (S)S$) $\Rightarrow ()()S$ ($S \to \varepsilon$) $\Rightarrow ()()$ ($S \to \varepsilon$) — মোট ৪টি derivation ধাপ, চূড়ান্ত ফলাফল
"()()"। -
পরীক্ষা করুন: কোড সেলে
target = "(())()"-কেtarget = "((()))"করে Run চেপে দেখুন — কতটি derivation ধাপ লাগে, এবং সেটি কি আপনার প্রত্যাশার সাথে মেলে (একটি সম্পূর্ণ নেস্টেড গঠন, তিনটি বন্ধনী-জোড়া)?সার্চ সফল হবে এবং একটি ৭-ধাপের derivation পাওয়া যাবে (একই সংখ্যক ধাপ যত এই পাঠের মূল উদাহরণে, যেহেতু দুটি স্ট্রিং-এই তিনটি বন্ধনী-জোড়া আছে, শুধু নেস্টিং প্যাটার্ন ভিন্ন) — প্রতিটি $S \to (S)S$ প্রয়োগ একটি নতুন নেস্টিং লেয়ার তৈরি করে, ভেতরের-প্রথম-এর বদলে সবসময় সবচেয়ে বামের $S$ এক্সপ্যান্ড হয় বলে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — পার্স ট্রি ও অ্যাম্বিগুইটি — এই একই derivation প্রক্রিয়াকে ট্রি হিসেবে দেখাবে এবং inherent ambiguity-র গভীর প্রশ্নে যাবে।
- পাঠ ১৬ · DFA মিনিমাইজেশন ও ডিসিশন প্রপার্টি পূর্ববর্তী পাঠ M3-এর শেষ পাঠ — রেগুলার ভাষার জন্য যে ডিসিশন প্রপার্টি ছিল, M6-এ CFL-এর জন্য তার সমতুল্য প্রশ্ন কতটা কঠিন হয় তা দেখবেন।
- PLC কোর্সের L13 · CFG ও ডেরিভেশন সঙ্গী কোর্স এই একই ধারণাগুলো ব্যবহারিক পার্সিং-গ্রাউন্ডওয়ার্কের কোণ থেকে সেই পাঠে কভার করা হয়েছে।
- সব 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 — সব এক জায়গায়।