গ্রেইবাখ নর্মাল ফর্ম
এই পাঠে যা শিখবেন
- GNF-এর ফরমাল সংজ্ঞা এবং CNF থেকে এর কাঠামোগত পার্থক্য
- কেন GNF-এ derivation length স্ট্রিং length-এর সমান — এবং এর ব্যবহারিক গুরুত্ব
- CNF থেকে GNF রূপান্তরের মূল কৌশল — left recursion elimination-এর রূপরেখা
- একটি concrete গ্রামারে হাতে-করা রূপান্তর, Python দিয়ে
is_gnfও derivation-length যাচাই
১ · GNF-এর ফরমাল সংজ্ঞা
L20-এর CNF-এর বিপরীতে, গ্রেইবাখ নর্মাল ফর্মGreibach Normal Form (GNF)একটি স্ট্যান্ডার্ডাইজড গ্রামার ফর্ম যেখানে প্রতিটি রুল ঠিক একটি টার্মিনাল দিয়ে শুরু হয়, তারপর শূন্য বা তার বেশি ভেরিয়েবল। একটি ভিন্ন স্ট্যান্ডার্ডাইজড রূপ — প্রতিটি রুল এই শেপে —
$$A \to a\alpha \qquad (a \in \Sigma,\ \alpha \in V^*)$$
অর্থাৎ প্রতিটি রুল ঠিক একটি টার্মিনাল দিয়ে শুরু হয়, তারপর শুধুমাত্র ভেরিয়েবল (শূন্যটিও হতে পারে) — CNF-এর মতোই, GNF-এও একটি ঐচ্ছিক $S \to \varepsilon$ ব্যতিক্রম আছে যদি ε ভাষার সদস্য হয়।
থিওরেম (L20-এর সমান্তরাল): প্রতিটি কনটেক্সট-ফ্রি ভাষার একটি সমতুল্য GNF গ্রামার আছে।
২ · GNF-এর genuine payoff — derivation length = string length
যেহেতু প্রতিটি রুল ঠিক একটি টার্মিনাল দিয়ে শুরু হয়, প্রতিটি ডেরিভেশন ধাপ (leftmost ভেরিয়েবল এক্সপ্যান্ড করা) ঠিক একটি নতুন টার্মিনাল ইনপুটে যোগ করে — কখনো শূন্য (unit/ε-প্রোডাকশনের মতো), কখনো একাধিক নয়। ফলাফল: length $n$-এর একটি স্ট্রিং derive করতে ঠিক $n$টি derivation ধাপ লাগে, প্রতিবার, নিশ্চিতভাবে — CNF-এ এই নিশ্চয়তা নেই ($A \to BC$ রুলে কোনো টার্মিনাল consume হয় না)।
এই বৈশিষ্ট্যটি টপ-ডাউন পার্সিং-কে left-recursion সমস্যা ছাড়াই সোজাসুজি করে তোলে — সরাসরি সম্পর্কিত Programming Languages & Compiler Design কোর্সের M5-এর left-recursion-elimination আলোচনার সাথে (একটি ভিন্ন, কিন্তু সম্পর্কিত কৌশল একই অন্তর্নিহিত সমস্যা সমাধান করছে)।
৩ · CNF থেকে GNF — রূপান্তরের রূপরেখা
সাধারণ GNF রূপান্তর অ্যালগরিদম genuinely জটিল — এটি সাধারণত CNF (L20) থেকে শুরু করে, ভেরিয়েবলদের একটি ক্রম নির্ধারণ করে, এবং সেই ক্রম অনুযায়ী left recursion দূর করে ও পরোক্ষ রেফারেন্স ধারাবাহিকভাবে "আনফোল্ড" করে (আগের ভেরিয়েবলের রুল দিয়ে প্রতিস্থাপন করে) যতক্ষণ না প্রতিটি রুল টার্মিনাল দিয়ে শুরু হয়। এখানে সম্পূর্ণ সাধারণ অ্যালগরিদম বাস্তবায়ন আবশ্যক নয় — বরং একটি নির্দিষ্ট, ছোট গ্রামারে এই কৌশলটি হাতে-কলমে প্রয়োগ করে দেখানো হবে, এবং কোড দিয়ে ফলাফল যাচাই করা হবে।
৪ · Worked example — $a^nb^n$ ($n \geq 1$)-এর CNF থেকে GNF
একটি ছোট CNF গ্রামার দিয়ে শুরু করা যাক, $L = \{a^nb^n : n \geq 1\}$-এর জন্য —
$$S \to AB \mid AC \qquad C \to SB \qquad A \to a \qquad B \to b$$
এখানে $A \to a$ ও $B \to b$ ইতিমধ্যেই GNF-শেপে (টার্মিনাল, তারপর শূন্য ভেরিয়েবল)। $S$-এর রুলে leading সিম্বল $A$ — সেটিকে তার নিজের রুল ($A \to a$) দিয়ে প্রতিস্থাপন করলে $S \to aB \mid aC$ পাওয়া যায়, যা এখন GNF-শেপে। $C \to SB$-তে leading সিম্বল $S$ — সেটিকে (ইতিমধ্যে-GNF) $S$-এর রুল দিয়ে প্রতিস্থাপন করলে $C \to aBB \mid aCB$ পাওয়া যায়, যা-ও GNF-শেপে। $A$ এখন আর কোথাও রেফারেন্স হয় না, তাই L19-এর reachability অনুযায়ী বাদ দেওয়া যায় —
$$S \to aB \mid aC \qquad B \to b \qquad C \to aBB \mid aCB$$
# হাতে-করা GNF রূপান্তর -- is_gnf checker ও derivation-length যাচাই
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 is_gnf(grammar):
"""প্রতিটি রুল A -> a-alpha শেপে কি না -- ঠিক একটি টার্মিনাল দিয়ে শুরু, তারপর শুধু ভেরিয়েবল"""
for var, rhss in grammar.rules.items():
for rhs in rhss:
if var == grammar.S and rhs == ():
continue
if len(rhs) == 0 or rhs[0] not in grammar.T:
return False, (var, rhs)
if any(sym not in grammar.V for sym in rhs[1:]):
return False, (var, rhs)
return True, None
def generate_language_up_to_length(grammar, start, max_length):
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
def derive_leftmost(grammar, start, target, max_steps=60):
queue = deque([((start,), [(start,)])])
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):
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 None
def derivation_length_matches_string_length(grammar, start, string):
"""একটি স্ট্রিং derive করতে ঠিক len(string) ধাপ লাগে কি না -- GNF-এর দাবিকৃত বৈশিষ্ট্য"""
hist = derive_leftmost(grammar, start, string, max_steps=len(string) + 2)
if hist is None:
return False, None
steps = len(hist) - 1
return steps == len(string), steps
# ---- সোর্স CNF গ্রামার, a^n b^n (n>=1) ----
rules_cnf = {
'S': [('A', 'B'), ('A', 'C')],
'A': [('a',)],
'B': [('b',)],
'C': [('S', 'B')],
}
G_cnf = Grammar({'S', 'A', 'B', 'C'}, {'a', 'b'}, rules_cnf, 'S')
# ---- হাতে-করা GNF (A প্রতিস্থাপিত, তাই আর রেফারেন্স হয় না -- L19 অনুযায়ী বাদ) ----
rules_gnf = {
'S': [('a', 'B'), ('a', 'C')],
'B': [('b',)],
'C': [('a', 'B', 'B'), ('a', 'C', 'B')],
}
G_gnf = Grammar({'S', 'B', 'C'}, {'a', 'b'}, rules_gnf, 'S')
ok, bad = is_gnf(G_gnf)
print("is_gnf(G_gnf):", ok, bad)
lang_cnf = generate_language_up_to_length(G_cnf, 'S', 8)
lang_gnf = generate_language_up_to_length(G_gnf, 'S', 8)
print("L(CNF) length<=8:", sorted(lang_cnf, key=lambda w: (len(w), w)))
print("L(GNF) length<=8:", sorted(lang_gnf, key=lambda w: (len(w), w)))
print("ভাষা অভিন্ন:", lang_cnf == lang_gnf)
print()
for s in ["ab", "aabb", "aaabbb"]:
matches, steps = derivation_length_matches_string_length(G_gnf, 'S', s)
print(f"স্ট্রিং={s!r} length={len(s)} derivation ধাপ={steps} মেলে={matches}")
"aabb" (length ৪)-এর derivation ঠিক ৪ ধাপে হয় — $S \Rightarrow aC \Rightarrow
aaBB \Rightarrow aabB \Rightarrow aabb$ — প্রতিটি ধাপ ঠিক একটি নতুন টার্মিনাল যোগ করছে। L20-এর CNF
গ্রামারে একই স্ট্রিং-এর derivation-এ এই নিশ্চয়তা নেই ($A \to BC$-এর মতো রুল কোনো টার্মিনাল consume না
করেই একটি ধাপ ব্যবহার করে)।
GNF-এ প্রতিটি রুল একটি টার্মিনাল দিয়ে শুরু হওয়ার গ্যারান্টি দেয় বলেই derivation length ও string length সবসময় সমান থাকে — এটি শুধু একটি কৌতূহলোদ্দীপক তথ্য নয়, বরং টপ-ডাউন পার্সিং-এ left-recursion সমস্যা এড়ানোর একটি গাণিতিক ভিত্তি। L20-এর CNF ও L21-এর GNF — একই কনটেক্সট-ফ্রি ভাষার দুটো ভিন্ন, প্রতিটি নিজ নিজ অ্যালগরিদমিক payoff-এর জন্য অপ্টিমাইজড, স্ট্যান্ডার্ডাইজড রূপ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ GNF-এর সংজ্ঞায় $\alpha \in V^*$ (শুধু ভেরিয়েবল) কেন — কেন RHS-এর মাঝখানে বা শেষে আরেকটি টার্মিনাল অনুমোদিত নয়?
কারণ GNF-এর পুরো পয়েন্টই হলো "প্রতিটি রুল প্রয়োগ = ঠিক একটি টার্মিনাল consume" এই নিশ্চয়তা বজায় রাখা। যদি RHS-এর মাঝখানে আরেকটি টার্মিনাল অনুমোদিত হতো (যেমন $A \to aBcD$), তাহলে সেই টার্মিনাল $c$ কোন derivation ধাপে "consume" হয়েছে তা অস্পষ্ট হয়ে যেত — leftmost derivation-এ $c$ ততক্ষণ পর্যন্ত অপেক্ষা করে যতক্ষণ না $B$ সম্পূর্ণভাবে টার্মিনাল স্ট্রিং-এ বিস্তৃত হয়, কিন্তু $c$ নিজে কোনো ভেরিয়েবল এক্সপ্যানশনের ফলাফল নয় — এই অস্পষ্টতা derivation-length = string-length নিশ্চয়তা ভেঙে দিত।
প্র ০২ এই পাঠের worked example-এ $A$-কে কেন সরিয়ে ফেলা হলো — এটি কি L19-এর কোন নির্দিষ্ট ধারণার প্রয়োগ?
হ্যাঁ, ঠিক L19-এর reachability ধারণার প্রয়োগ। রূপান্তরের সময় $S$-এর রুলে $A$-কে তার নিজের রুল
($A \to a$) দিয়ে সরাসরি প্রতিস্থাপন করা হয়েছে ($S \to AB$ থেকে $S \to aB$) — এই প্রতিস্থাপনের পর
চূড়ান্ত GNF গ্রামারে $A$-কে রেফারেন্স করার মতো আর কোনো রুল অবশিষ্ট থাকে না, তাই $A$ আর রিচেবল নয় (L19-এর
সংজ্ঞা অনুযায়ী), এবং তাকে বাদ দেওয়া নিরাপদ — ভাষা অপরিবর্তিত থাকে, কোড সেলের
lang_cnf == lang_gnf চেক এটিই নিশ্চিত করে।
প্র ০৩ যদি কোনো স্ট্রিং-এর জন্য একাধিক ভিন্ন leftmost derivation থাকত (L18-এর অ্যাম্বিগুইটি), তাহলে কি derivation length তবুও string length-এর সমান থাকত?
হ্যাঁ — GNF-এর derivation-length গ্যারান্টি প্রতিটি বৈধ leftmost derivation-এর জন্য পৃথকভাবে সত্য, এটি কোনো নির্দিষ্ট derivation-এর ওপর নির্ভর করে না। যেকোনো derivation, যেভাবেই সে ভেরিয়েবলগুলো এক্সপ্যান্ড করুক না কেন, GNF গ্রামারে প্রতিটি ধাপে ঠিক একটি টার্মিনাল consume করবে — তাই অ্যাম্বিগুয়াস স্ট্রিং-এর একাধিক ভিন্ন derivation থাকলেও, প্রতিটি derivation-এর length পৃথকভাবে ঠিক স্ট্রিং-এর length-এর সমান হবে। অ্যাম্বিগুইটি (L18) ও derivation-length (এই পাঠ) সম্পূর্ণ আলাদা, স্বাধীন বৈশিষ্ট্য।
অনুশীলন
-
হাতে-কলমে করুন: $C \to aCB$ রুল ব্যবহার করে
"aaabbb"($n=3$)-এর একটি সম্পূর্ণ leftmost derivation হাতে লিখুন, প্রতি ধাপে কতগুলো টার্মিনাল এখন পর্যন্ত consume হয়েছে গণনা করুন।$S \Rightarrow aC \Rightarrow a(aCB) = aaCB \Rightarrow aa(aBB)B = aaaBBB \Rightarrow aaabBB \Rightarrow aaabbB \Rightarrow aaabbb$ — মোট ৬ ধাপ, স্ট্রিং-এর length ৬-এর সমান। প্রতি ধাপে ঠিক একটি নতুন টার্মিনাল (a বা b) যোগ হয়েছে, ঠিক যেমন কোড সেলের
derivation_length_matches_string_lengthদাবি করে। -
পরীক্ষা করুন: কোড সেলের টেস্ট-লিস্টে
"aaaabbbb"($n=4$) যোগ করে Run চেপে দেখুন derivation length এখনো string length-এর সমান থাকে কি না।হ্যাঁ —
matches=True,steps=8(স্ট্রিং-এর length ৮-এর সমান)। এই প্যাটার্ন যেকোনো $n$-এর জন্য চলতে থাকে, যেহেতু GNF গ্রামারে প্রতিটি ডেরিভেশন ধাপ কাঠামোগতভাবেই ঠিক একটি টার্মিনাল consume করতে বাধ্য — এটি কোনো নির্দিষ্ট $n$-এর ওপর নির্ভরশীল কাকতালীয় ফলাফল নয়, বরং GNF-এর সংজ্ঞা থেকে সরাসরি অনুসৃত একটি গ্যারান্টি।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ পরবর্তী পাঠ — M5-এর শুরু — PDA-এর ফরমাল সংজ্ঞা — একটি স্ট্যাক-সহ NFA যা ঠিক এই CFG-গুলোর সমতুল্য গণনাশক্তি রাখে।
- পাঠ ২০ · চমস্কি নর্মাল ফর্ম পূর্ববর্তী পাঠ এই পাঠের worked example ঠিক সেই পাঠের CNF ধারণার ওপর ভিত্তি করে তৈরি — GNF সাধারণত CNF থেকেই শুরু হয়।
- Programming Languages & Compiler Design কোর্স সঙ্গী কোর্স সেই কোর্সের M5-এর left-recursion-elimination আলোচনা GNF-এর মূল কৌশলের সাথে সরাসরি সম্পর্কিত — একই অন্তর্নিহিত সমস্যার একটি ব্যবহারিক সমাধান।
- সব 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 — সব এক জায়গায়।