পাঠ ১৫ · ৫৬-এর মধ্যে · মডিউল ৩

মাইহিল-নেরোড থিওরেম

Myhill-Nerode theorem
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ডিস্টিংগুইশেবিলিটি ও ইকুইভ্যালেন্স — স্ট্রিং-এর উপর একটি নতুন ধরনের রিলেশন
  • মাইহিল-নেরোড থিওরেমের ফরমাল স্টেটমেন্ট এবং এর দুটো দিক — regularity এবং state-count
  • এটি কীভাবে L14-এর পাম্পিং লেমার একটি বিকল্প, প্রায়ই বেশি সহজ, নন-রেগুলারিটি-প্রমাণ কৌশল দেয়
  • L16-এর DFA মিনিমাইজেশনের সাথে সরাসরি সংযোগ — কেন এই থিওরেমই মিনিমাইজেশনের গাণিতিক ন্যায্যতা
  • Python-এ real are_equivalent ও count_equivalence_classes — ইকুইভ্যালেন্স ক্লাস কোডে গণনা

১ · ডিস্টিংগুইশেবিলিটি — একটি নতুন লেন্স

DFA বা regex দিয়ে রেগুলারিটি দেখার বদলে, মাইহিল-নেরোড থিওরেম সরাসরি স্ট্রিং-এর দিকে তাকায়। দুটো স্ট্রিং $x, y \in \Sigma^*$ ভাষা $L$-এর সাপেক্ষে ডিস্টিংগুইশেবলDistinguishableদুটো স্ট্রিং, যদি কোনো একটি suffix থাকে যা যোগ করলে একটি ফলাফল $L$-এ পড়ে আর অন্যটি পড়ে না। যদি একটি suffix $z \in \Sigma^*$ থাকে যাতে —

$$xz \in L \iff yz \notin L$$

অর্থাৎ, একই $z$ জুড়ে দিলে একটির ফলাফল $L$-এ পড়ে, অন্যটির পড়ে না। যদি এমন কোনো $z$-ই না থাকে (সব suffix-এর জন্যই $xz \in L \iff yz \in L$), তাহলে $x, y$ ইকুইভ্যালেন্ট — লেখা হয় $x \equiv_L y$। এটি একটি বৈধ ইকুইভ্যালেন্স রিলেশন (রিফ্লেক্সিভ, সিমেট্রিক, ট্রানজিটিভ — সহজেই যাচাইযোগ্য), তাই এটি $\Sigma^*$-কে ডিসজয়েন্ট ইকুইভ্যালেন্স ক্লাসে ভাগ করে।

২ · মাইহিল-নেরোড থিওরেম

মাইহিল-নেরোড থিওরেম

একটি ভাষা $L$ রেগুলার হয় যদি এবং কেবল যদি $\equiv_L$ রিলেশনের ইকুইভ্যালেন্স ক্লাসের সংখ্যা ফাইনাইট হয়। যখন $L$ রেগুলার, এই ক্লাস-সংখ্যা ঠিক $L$-এর মিনিমাল DFA-র স্টেট-সংখ্যার সমান — প্রতিটি ইকুইভ্যালেন্স ক্লাস মিনিমাল DFA-র একটি স্টেটের সাথে এক-এক (bijective) মিলে যায়।

এই থিওরেমের payoff দুইদিকেই শক্তিশালী — (১) এটি L14-এর পাম্পিং লেমার একটি বিকল্প নন-রেগুলারিটি-প্রমাণ কৌশল দেয় — যদি দেখানো যায় যে $\equiv_L$-এর অসীম সংখ্যক ইকুইভ্যালেন্স ক্লাস আছে (যেমন সব $0^n$ একে অপর থেকে ডিস্টিংগুইশেবল, ভিন্ন $n$-এর জন্য), তাহলে $L$ নন-রেগুলার, পাম্পিং লেমার ধাপে ধাপে "সব split যাচাই" ছাড়াই। (২) এটি একটি লোয়ার বাউন্ড কৌশল দেয় — কোনো DFA-র জন্য অন্তত কতগুলো স্টেট লাগবেই তা প্রমাণ করা যায়।

$x \equiv_L y$ রিলেশন suffix দিয়ে ডিস্টিংগুইশেবিলিটি ইকুইভ্যালেন্স ক্লাস $\Sigma^*$-এর একটি পার্টিশন মিনিমাল DFA প্রতিটি ক্লাস = একটি স্টেট ক্লাস ফাইনাইট হলে রেগুলার, অসীম হলে নন-রেগুলার
ডিস্টিংগুইশেবিলিটি রিলেশনের ইকুইভ্যালেন্স ক্লাসগুলোই আসলে মিনিমাল DFA-র স্টেট — L16-এ এটি মিনিমাইজেশন অ্যালগরিদমের ভিত্তি হবে।

৩ · কোডে যাচাই — even-1s ভাষায় ২টি ক্লাস

নিচের কোড সেলে are_equivalent(x, y, in_L, test_suffixes) একটি প্রতিনিধিত্বশীল suffix-সেট দিয়ে $x \equiv_L y$ পরীক্ষা করে, এবং count_equivalence_classes একটি টেস্ট-স্ট্রিং-ব্যাচকে ক্লাসে ভাগ করে। "even number of 1s" ভাষায় দেখানো হচ্ছে "0" ও "00" ইকুইভ্যালেন্ট (দুটোরই even parity), কিন্তু "0" ও "1" নয় (খালি suffix $z=\varepsilon$-ই যথেষ্ট — কারণ "0"-এর 1-সংখ্যা even (0টি), তাই "0" $\in L$, কিন্তু "1"-এর 1-সংখ্যা odd, তাই "1" $\notin L$) — এবং গণনা করা ক্লাস-সংখ্যা পরিচিত মিনিমাল DFA-র স্টেট সংখ্যা ২-এর সাথে মেলে কি না তা যাচাই করা হচ্ছে।

Python
def even_ones_language(s):
    return s.count('1') % 2 == 0   # L06-এর "even number of 1s" ভাষা


def are_equivalent(x, y, in_L, test_suffixes):
    # প্রতিনিধিত্বশীল suffix-সেট দিয়ে x =_L y পরীক্ষা -- একটি distinguishing suffix পেলেই non-equivalent
    for z in test_suffixes:
        if in_L(x + z) != in_L(y + z):
            return False, z
    return True, None


def count_equivalence_classes(in_L, test_strings, test_suffixes):
    classes = []   # প্রতিটি এন্ট্রি একটি ইকুইভ্যালেন্স ক্লাসের স্ট্রিং-লিস্ট
    for s in test_strings:
        placed = False
        for cls in classes:
            equiv, _ = are_equivalent(s, cls[0], in_L, test_suffixes)
            if equiv:
                cls.append(s)
                placed = True
                break
        if not placed:
            classes.append([s])
    return classes


test_suffixes = ["", "0", "1", "00", "01", "10", "11", "010", "101", "111"]

eq1, w1 = are_equivalent("0", "00", even_ones_language, test_suffixes)
print(f"'0' equiv_L '00' ? {eq1}  (উভয়েরই 1-সংখ্যা even, তাই যেকোনো suffix উভয়কেই একইভাবে প্রভাবিত করে)")

eq2, w2 = are_equivalent("0", "1", even_ones_language, test_suffixes)
print(f"'0' equiv_L '1' ? {eq2}  distinguishing suffix z={w2!r}")
print(f"    L('0'+z) = {even_ones_language('0'+w2)},  L('1'+z) = {even_ones_language('1'+w2)}")

test_strings = ["", "0", "1", "00", "01", "10", "11", "000", "001", "010", "011",
                 "100", "101", "110", "111", "0000", "1111", "0101010"]

classes = count_equivalence_classes(even_ones_language, test_strings, test_suffixes)
print()
print(f"{len(test_strings)}টি টেস্ট স্ট্রিং, {len(classes)}টি ইকুইভ্যালেন্স ক্লাসে ভাগ হলো:")
for i, cls in enumerate(classes):
    print(f"  ক্লাস {i}: {cls}")

known_minimal_dfa_size = 2   # L06-এর "even number of 1s" DFA-র পরিচিত, ন্যূনতম স্টেট সংখ্যা
print()
print(f"পরিচিত মিনিমাল DFA-র স্টেট সংখ্যা: {known_minimal_dfa_size}")
print(f"গণনা করা ক্লাস-সংখ্যা মিলছে কি না: {len(classes) == known_minimal_dfa_size}")

    
লক্ষ্য করুন count_equivalence_classes একটি ফাইনাইট suffix-সেট আর ফাইনাইট স্ট্রিং-ব্যাচ ব্যবহার করছে — এটি একটি practical approximation, ফরমাল প্রমাণ নয় (ফরমাল প্রমাণে সব সম্ভাব্য অসীম suffix/স্ট্রিং বিবেচনা করতে হয়)। কিন্তু এই ভাষায় (even-1s) দুটো ক্লাসই যথেষ্ট প্রতিনিধিত্বশীল suffix দিয়ে সহজেই ধরা পড়ে যায় — বাস্তবে সহজ ভাষার জন্য এই কৌশলটি একটি চমৎকার concrete sanity-check।
L16-এর সাথে সরাসরি সংযোগ

এই থিওরেমটাই কেন DFA মিনিমাইজেশন (L16) কাজ করে তার গাণিতিক ন্যায্যতা — যেহেতু ইকুইভ্যালেন্স ক্লাসগুলো নিজেরাই মিনিমাল DFA-র স্টেট, মিনিমাইজেশন অ্যালগরিদম মূলত এই ক্লাসগুলোই খুঁজে বের করে (যে DFA স্টেটগুলো একে অপরের সাথে "সমতুল্য আচরণ" করে, তাদের মার্জ করে দেয়)। আর যেহেতু ইকুইভ্যালেন্স রিলেশন একটি নির্দিষ্ট, ভাষা-নির্ভর পার্টিশন, মিনিমাল DFA অনন্য (up to state renaming) — এই অনন্যতার প্রমাণও এই থিওরেম থেকেই আসে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ $0^n1^n$-এর জন্য কেন সব $0^i$ ($i=0,1,2,...$) একে অপর থেকে ডিস্টিংগুইশেবল, এবং এটি নন-রেগুলারিটির প্রমাণ কীভাবে দেয়?

দুটো ভিন্ন $i \neq j$-এর জন্য $0^i$ ও $0^j$ নিন। suffix $z = 1^i$ ব্যবহার করুন — $0^i \cdot 1^i = 0^i1^i \in L$ (সমান সংখ্যক 0, 1), কিন্তু $0^j \cdot 1^i = 0^j1^i \notin L$ (যেহেতু $j \neq i$, সংখ্যা অসমান)। তাই $z=1^i$ এই দুটোকে ডিস্টিংগুইশ করে — এটি সব $i \neq j$ জোড়ার জন্যই সত্য, তাই $\{0^0, 0^1, 0^2, \ldots\}$-এর প্রতিটি ভিন্ন সদস্য একটি ভিন্ন ইকুইভ্যালেন্স ক্লাসে পড়ে — অর্থাৎ অসীম সংখ্যক ক্লাস। মাইহিল-নেরোড থিওরেম অনুযায়ী, অসীম ক্লাস মানে সরাসরি নন-রেগুলার — কোনো পাম্পিং-স্প্লিট বিশ্লেষণ ছাড়াই।

প্র ০২ ডিস্টিংগুইশেবিলিটি রিলেশন কেন একটি বৈধ ইকুইভ্যালেন্স রিলেশন (রিফ্লেক্সিভ, সিমেট্রিক, ট্রানজিটিভ)?

রিফ্লেক্সিভ: $x \equiv_L x$ — যেকোনো $z$-এর জন্য $xz \in L \iff xz \in L$, তুচ্ছভাবে সত্য। সিমেট্রিক: যদি $x \equiv_L y$ (কোনো suffix distinguisher করে না), তাহলে একই কারণে $y \equiv_L x$ — সংজ্ঞা নিজেই symmetric ("সব suffix-এ একই ফলাফল")। ট্রানজিটিভ: যদি $x \equiv_L y$ এবং $y \equiv_L z$ (এখানে $z$ একটি স্ট্রিং, suffix নয়), তাহলে যেকোনো suffix $w$-এর জন্য $xw \in L \iff yw \in L$ (প্রথম থেকে) এবং $yw \in L \iff zw \in L$ (দ্বিতীয় থেকে) — চেইন করলে $xw \in L \iff zw \in L$, অর্থাৎ $x \equiv_L z$। তিনটি শর্তই সন্তুষ্ট, তাই এটি একটি বৈধ ইকুইভ্যালেন্স রিলেশন, ফলে $\Sigma^*$-কে ডিসজয়েন্ট ক্লাসে ভাগ করে।

প্র ০৩ উপরের কোডে যদি test_suffixes-এ শুধু [""] রাখা হতো, ফলাফল কি এখনও নির্ভরযোগ্য হতো?

না — শুধু খালি suffix দিয়ে চেক করলে are_equivalent আসলে শুধু "$x \in L \iff y \in L$" পরীক্ষা করত, যা সব suffix-এর জন্য সমতা পরীক্ষা করার চেয়ে অনেক দুর্বল শর্ত। উদাহরণ: "0" ও "01" — উভয়েরই... আসলে even-1s ভাষায় "0" $\in L$ (0টি 1) কিন্তু "01" $\notin L$ (1টি 1), তাই এই নির্দিষ্ট জোড়া তখনও ধরা পড়ত। কিন্তু সাধারণভাবে, শুধু খালি suffix দিয়ে দুটো স্ট্রিং "$x\in L \iff y\in L$" মিলে গেলেও ভবিষ্যতে কোনো suffix জুড়লে তারা ভিন্ন আচরণ করতে পারে — তাই ফরমালি সঠিক ফলাফলের জন্য যথেষ্ট প্রতিনিধিত্বশীল suffix-সেট দরকার, শুধু একটি নয়।

অনুশীলন

  1. চিন্তা করুন: "contains substring 01" ভাষায় (L07-এর উদাহরণ) কতগুলো ইকুইভ্যালেন্স ক্লাস থাকা উচিত বলে আপনার ধারণা? (ইঙ্গিত: L07-এর NFA থেকে সাবসেট কনস্ট্রাকশনে পাওয়া DFA-র স্টেট সংখ্যা চিন্তা করুন)

    ৩টি ক্লাস — L12/L13-এর কোডে ব্যবহৃত contains01 DFA-টির ৩টি স্টেট ($q_0$: এখনো "0" দেখা যায়নি বা "01" মেলেনি, $q_1$: শেষ ক্যারেক্টার "0" ছিল কিন্তু "01" এখনও মেলেনি, $q_2$: "01" ইতিমধ্যে মিলে গেছে, চিরকাল accepting) — এবং এই DFA-টি আসলে ইতিমধ্যেই মিনিমাল (কোনো দুটো স্টেট একে অপরের সমতুল্য নয়, প্রতিটির আচরণ ভিন্ন কোনো না কোনো suffix-এ)। তাই মাইহিল-নেরোড অনুযায়ী ইকুইভ্যালেন্স ক্লাসও ৩টি হওয়া উচিত।

  2. পরীক্ষা করুন: উপরের কোড সেলে even_ones_language-এর বদলে একটি নতুন ফাংশন lambda s: len(s) % 3 == 0 ("দৈর্ঘ্য ৩-এর গুণিতক") বসিয়ে চালান — প্রত্যাশিত ক্লাস-সংখ্যা কত হওয়া উচিত, এবং কোড কি সেই সংখ্যাই দেয়?

    ৩টি ক্লাস হওয়া উচিত — দৈর্ঘ্য mod 3 এর মান 0, 1, বা 2 অনুযায়ী, কারণ যেকোনো suffix $z$ যোগ করলে নতুন দৈর্ঘ্য $|x|+|z|$, এবং mod 3 ফলাফল শুধু $|x| \bmod 3$-এর উপর নির্ভর করে। কোড এই তিনটি ক্লাসই সঠিকভাবে শনাক্ত করবে (যদি test_strings-এ যথেষ্ট বৈচিত্র্যময় দৈর্ঘ্যের স্ট্রিং থাকে), যা একটি স্বাধীন উদাহরণে থিওরেমের সাধারণতা আরেকবার নিশ্চিত করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
L14 · একটি ল্যাঙ্গুয়েজ রেগুলার নয় তা প্রমাণ করা