مقدمة في علوم الحاسوب النظرية › المجالات اللانهائية والآلات والتعبيرات النمطية 8 / 26 السابق التالي

دوال ذات مداخل لا نهائية، وآلات ذاتية، وتعبيرات نمطية { #chapinfinite }

دوال ذات مداخل لا نهائية، وآلات ذاتية، وتعبيرات نمطية

  • تعريف دوال على مداخل ذات طول غير محدود، لا يمكن وصفها بجدول متناهٍ الحجم من المداخل والمخارج.
  • التكافؤ مع مهمة اتخاذ قرار الانتماء إلى لغة ما.
  • الآلات الذاتية المحدودة الحتمية (اختياري): مثال بسيط لنموذج للحوسبة غير المحدودة.
  • التكافؤ مع التعبيرات النمطية.

"الخوارزمية جواب متناهٍ على عدد لا نهائي من الأسئلة."، منسوب إلى ستيفن كلين (Stephen Kleene).

لنموذج الدوائر البوليانية (أو بما يكافئه، لغة البرمجة NAND-CIRC) عيب مهم واحد: فالدائرة البوليانية لا تستطيع إلا حساب دالة متنهية ff. وبتحديد، بما أن كل بوابة لها مُدخلان، فإن دائرة ذات حجم ss يمكنها الحساب على مدخل طوله على الأكثر 2s2s. وعليه فإن هذا النموذج لا يجسّد تصورنا الحدسي للخوارزمية بوصفها وصفة واحدة (single recipe) لحساب دالة قد تكون لا نهائية. على سبيل المثال، خوارزمية الضرب المعيارية في المدرسة الابتدائية هي خوارزمية واحدة تضرب الأعداد من جميع الأطوال. لكننا لا نستطيع التعبير عن هذه الخوارزمية بدائرة واحدة، وإنما نحتاج إلى دائرة مختلفة (أو بما يكافئها، برنامج NAND-CIRC) لكل طول مدخل (انظر الشكل التوضيحي).

/images/introtcs/lec_05_infinite-1.webp

في هذا الفصل، نوسّع تعريفنا للمهام الحاسوبية (computational tasks) لنأخذ في الحسبان دوالًا مجالها غير محدود هو {0,1}∗\{0,1\}^*. نركّز على مسألة تعريف ماذا هي المهام التي نريد حسابها، تاركين في الغالب مسألة كيف نحسبها إلى فصول لاحقة، حيث سنرى آلات تورينج (Turing machines) ونماذج حوسبة أخرى للحساب على مداخل غير محدودة الطول. لكننا سنرى هنا مثالًا واحدًا لنموذج حساب بسيط ومقيَّد، وهو الآلات الذاتية المحدودة الحتمية (deterministic finite automata, DFAs).

في هذا الفصل، نناقش دوال تأخذ كمدخلات سلاسل بطول اعتباطي. وسنركّز في كثير من الأحيان على الحالة الخاصة من الدوال البوليانية، حيث يكون الخرج بتًّا واحدًا. وما زالت هذه دوال لا نهائية، لأن مداخلها ذات طول غير محدود، ومن ثمّ لا يمكن حساب مثل هذه الدالة بأي دائرة بوليانية واحدة.

في النصف الثاني من هذا الفصل، نناقش الآلات المحدودة (finite automata)، وهو نموذج حوسبة يستطيع حساب دوال بطول غير محدود. الآلات المحدودة ليست قوية بقدر Python أو غيرها من لغات البرمجة العامة الغرض، لكنها يمكن أن تكون مدخلًا (introduction) إلى هذه النماذج الأكثر عمومية. كما نبيّن أيضًا نتيجة جميلة: الدوال التي يمكن حسابها بالآلات المحدودة هي بالضبط الدوال التي تقابل التعبيرات النمطية (regular expressions). لكن يجوز للقارئ كذلك أن يتخطّى الآلات الذاتيّة ويمضي مباشرةً إلى مناقشتنا لـ_آلات تورينج_ في الحلقات وما لا نهاية.

دوال ذات مداخل بطول غير محدود

حتى الآن، كنا نعتبر المهمة الحاسوبية تحويل سلسلة ما من الطول nn إلى سلسلة من الطول mm. لكن المهام الحاسوبية، في العموم، قد تتضمّن مداخل ذات طول غير محدود، بمعنى أنه لا سقف لطولها. على سبيل المثال، الدالة التالية المكتوبة بلغة Python تحسب الدالة XOR:{0,1}∗→{0,1}XOR:\{0,1\}^* \rightarrow \{0,1\}، حيث XOR(x)XOR(x) تساوي 11 إذا وفقط إذا كان عدد 11 في xx فرديًا. (بعبارة أخرى، XOR(x)=∑i=0∣x∣−1ximod  2XOR(x) = \sum_{i=0}^{|x|-1} x_i \mod 2 لكل x∈{0,1}∗x\in \{0,1\}^*.) وعلى بساطته، لا يمكن حساب دالة XORXOR بدائرة بوليانية. بدلًا من ذلك، لكل nn يمكننا حساب XORnXOR_n (تقييد XORXOR على {0,1}n\{0,1\}^n) باستخدام دائرة مختلفة (انظر مثلًا الشكل التوضيحي).

def XOR(X):
    '''Takes list X of 0's and 1's
       Outputs 1 if the number of 1's is odd and outputs 0 otherwise'''
    result = 0
    for i in range(len(X)):
        result = (result + X[i]) % 2
    return result

/images/introtcs/lec_05_infinite-2.webp

لقد درسنا في هذا الكتاب من قبل حساب دوال متنهية f:{0,1}n→{0,1}mf:\{0,1\}^n \rightarrow \{0,1\}^m. ويمكن دائمًا وصف مثل هذه الدالة ff بسرد كل قيمها الـ 2n2^n التي تأخذها على المداخل x∈{0,1}nx\in \{0,1\}^n. في هذا الفصل، نأخذ في الحسبان دوال مثل XORXOR التي تقبل مداخل بحجم غير محدود. ورغم أننا نستطيع وصف XORXOR باستخدام عدد متناهٍ من الرموز (بل لقد فعلنا للتو ذلك أعلاه)، إلا أنها تقبل عددًا لا نهائيًا من المداخل الممكنة، ومن ثمّ لا نستطيع ببساطة أن نكتب كل قيمها. الحال نفسها تنطبق على كثير من الدوال الأخرى التي تلتقط مهام حاسوبية مهمة، بما فيها الجمع، والضرب، والترتيب، وإيجاد المسارات في الرسوم البيانية، وملاءمة المنحنيات للنقاط، وهكذا. وللتناقض مع الحالة المتناهية، سنسمّي أحيانًا الدالة F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} (أو F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^*) بـ_لا نهائية_. لكن هذا لا يعني أن FF تأخذ كمدخل سلاسل ذات طول لا نهائي! بل يعني فقط أن FF تستطيع أن تأخذ كمدخل سلسلة قد يكون طولها اعتباطيًا، ومن ثمّ لا نستطيع ببساطة أن نكتب جدولًا لكل مخارج FF على المداخل المختلفة.

دالة F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^* تحدّد المهمة الحاسوبية التي تحوّل مدخلًا x∈{0,1}∗x\in \{0,1\}^* إلى الخرج F(x)F(x).

وكما رأينا من قبل، فإن قصر انتباهنا على الدوال التي تستخدم سلاسل ثنائية كمداخل ومخارج لا ينتقص من عموميتنا، إذ يمكن ترميز كائنات أخرى، بما فيها الأعداد والقوائم والمصفوفات والصور والفيديوهات وغيرها، كسلاسل ثنائية.

وكما من قبل، من الضروري التمييز بين المواصفة (specification) و_التنفيذ_ (implementation). على سبيل المثال، تأمّل الدالة التالية:

TWINP(x)={1∃p∈N s.t.p,p+2 are primes and p>∣x∣0otherwise TWINP(x) = \begin{cases} 1 & \exists_{p \in \N} \text{ s.t.} p,p+2 \text{ are primes and } p>|x| \\ 0 & \text{otherwise} \end{cases}

هذه دالة معرّفة جيدًا رياضيًا. فلكل xx، تملك TWINP(x)TWINP(x) قيمة فريدة إمّا 00 أو 11. لكن، حتى اللحظة، لا يعرف أحد برنامجًا في لغة Python يحسب هذه الدالة. تفترض حدس الأعداد الأولية التوأمة (Twin prime conjecture) أنه لكل nn يوجد p>np>n بحيث يكون pp وp+2p+2 أوليين معًا. وإن كان هذا الحدس صادقًا، فإن حساب TT سهل حقًا - فبرنامج def T(x): return 1 سيفي بالغرض. غير أن الرياضيين حاولوا منذ عام 1849 إثبات هذا الحدس دون نجاح. ومع ذلك، سواء كنا نعرف كيف نُنفّذ الدالة TWINPTWINP أم لا، فإن التعريف أعلاه يوفّر مواصفتها.

مداخل ومخارج متغيّرة

كثير من الدوال التي تهمّنا تأخذ أكثر من مُدخل واحد. على سبيل المثال، الدالة

MULT(x,y)=x⋅y MULT(x,y) = x \cdot y

تأخذ التمثيل الثنائي لزوج من الأعداد الصحيحة x,y∈Nx,y \in \N، وتخرج التمثيل الثنائي حاصل ضربهما x⋅yx \cdot y. لكن، بما أننا نستطيع تمثيل زوج من السلاسل كسلسلة واحدة، فسنعتبر دوالًا مثل MULT على أنها تحوّل {0,1}∗\{0,1\}^* إلى {0,1}∗\{0,1\}^*. ولن نُعنى عادةً بالتفاصيل منخفضة المستوى مثل الطريقة الدقيقة لتمثيل زوج من الأعداد الصحيحة كسلسلة، إذ إن كل الخيارات تقريبًا ستكون متكافئة لأغراضنا.

وهذا مثال آخر على دالة نريد حسابها، وهي

PALINDROME(x)={1∀i∈[∣x∣]xi=x∣x∣−i0otherwise PALINDROME(x) = \begin{cases} 1 & \forall_{i \in [|x|]} x_i = x_{|x|-i} \\ 0 & \text{otherwise} \end{cases}

يكون خرج PALINDROMEPALINDROME بتًّا واحدًا. وتُعرف الدوال ذات الخرج البتّي الواحد بأنها دوال بوليانية (Boolean functions). والدوال البوليانية محورية في نظرية الحوسبة، وسنتناولها كثيرًا في هذا الكتاب. لاحظ أنه رغم أن الدوال البوليانية خرجها بت واحد، فإن مدخلها قد يكون بطول اعتباطي. إذن فهي لا تزال دوال لا نهائية لا يمكن وصفها بجدول متناهٍ من القيم.

«تبولانية» الدوال. أحيانًا قد يكون من الملائم الحصول على صيغة بوليانية (Boolean variant) لدالة غير بوليانية. على سبيل المثال، ما يلي هو صيغة بوليانية لـMULTMULT.

BMULT(x,y,i)={ith bit of x⋅yi<∣x⋅y∣0otherwise BMULT(x,y,i) = \begin{cases} i^{th} \text{ bit of } x\cdot y & i <|x \cdot y| \\ 0 & \text{otherwise} \end{cases}

إذا استطعنا حساب BMULTBMULT بأي لغة برمجة مثل Python أو C أو Java وغيرها، أمكنا حينها حساب MULTMULT أيضًا، والعكس صحيح.

تمرين محلول — تبولانية الدوال العامة

بيّن أنه لكل دالة F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^*، توجد دالة بوليانية BF:{0,1}∗→{0,1}BF:\{0,1\}^* \rightarrow \{0,1\} بحيث إن برنامج Python لحساب BFBF يمكن تحويله إلى برنامج لحساب FF، والعكس صحيح.

الحل

لكل F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^*، يمكننا تعريف

BF(x,i,b)={F(x)ii<∣F(x)∣,b=01i<∣F(x)∣,b=10i≥∣F(x)∣ BF(x,i,b) = \begin{cases} F(x)_i & i<|F(x)|, b=0 \\ 1 & i<|F(x)|, b=1 \\ 0 & i \geq |F(x)| \end{cases}

لتكون الدالة التي، على مدخل x∈{0,1}∗,i∈N,b∈{0,1}x \in \{0,1\}^*, i \in \N, b\in \{0,1\}، تخرج البت ithi^{th} من F(x)F(x) إذا كان b=0b=0 وi<∣F(x)∣i<|F(x)|. وإذا كان b=1b=1، فإن BF(x,i,b)BF(x,i,b) تخرج 11 إذا وفقط إذا كان i<∣F(x)∣i<|F(x)|، ومن ثمّ يتيح هذا حساب طول F(x)F(x).

وحساب BFBF من FF مباشر. وفي الاتجاه المقابل، إذا أعطينا دالة Python باسم BF تحسب BFBF، يمكننا حساب FF كما يلي:

def F(x):
    res = []
    i = 0
    while BF(x,i,1):
        res.append(BF(x,i,0))
        i += 1
    return res

اللغات الشكليّة

لكل دالة بوليانية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}، يمكننا تعريف المجموعة LF={x∣F(x)=1}L_F = \{ x | F(x) = 1 \} من السلاسل التي تخرج عليها FF القيمة 11. وتُعرف هذه المجموعات بأنها لغات (languages). وهذا الاسم متجذّر في نظرية اللغات الشكليّة التي سارها لغويون مثل نوام تشومسكي (Noam Chomsky). واللغة الشكلية (formal language) هي مجموعة جزئية L⊆{0,1}∗L \subseteq \{0,1\}^* (أو على نحو أعمّ L⊆Σ∗L \subseteq \Sigma^* لبعض أبجدية متناهية Σ\Sigma). أما مسألة الانتماء (membership) أو_قرار_ (decision) (decision) الخاصة بلغة LL، فهي مهمة تحديد، معطى x∈{0,1}∗x\in \{0,1\}^*، ما إذا كان x∈Lx\in L أم لا. وإذا استطعنا حساب الدالة FF، أمكنا عندها اتخاذ قرار الانتماء إلى اللغة LFL_F، والعكس صحيح. ولذلك فإن كثيرًا من الكتب مثل [@SipserBook] تشير إلى مهمة حساب دالة بوليانية بأنها «اتخاذ قرار بشأن لغة». في هذا الكتاب، نصف المهام الحاسوبية غالبًا باستخدام تدوين الدالة، فهو أسهل في التعميم على الحساب الذي ينتج أكثر من بت واحدًا. لكن، بما أن مصطلح اللغة شائع جدًا في الأدبيات، فسنشير إليه أحيانًا.

تقييدات الدوال

إذا كانت F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} دالة بوليانية وn∈Nn\in \N، فإن تقييد (restriction) FF على المداخل ذات الطول nn، ويُرمز إليه بـFnF_n، هو الدالة المتناهية f:{0,1}n→{0,1}f:\{0,1\}^n \rightarrow \{0,1\} بحيث f(x)=F(x)f(x) = F(x) لكل x∈{0,1}nx\in \{0,1\}^n. بمعنى آخر، FnF_n هي الدالة المتناهية المعرَّفة على المداخل ${0,1}n\$ \{0,1\}^n فقط، لكنها تتفق مع FF على تلك المداخل. ولأن FnF_n دالة متناهية، فهي يمكن حسابها بدائرة بوليانية، وهو ما يقود إلى المبرهنة التالية:

مبرهنة — مجموعة دوائر لكل دالة لا نهائية

ليكن F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}. إذًا توجد مجموعة {Cn}n∈{1,2,…}\{ C_n \}_{n\in \{1,2,\ldots\}} من الدوائر بحيث إن لكل n>0n>0، تحسب CnC_n تقييد FnF_n لـFF على المداخل ذات الطول nn.

برهان

هذا نتيجة فورية لعمومية الدوائر البوليانية. بالفعل، بما أن FnF_n تحوّل {0,1}n\{0,1\}^n إلى {0,1}\{0,1\}، فإن مبرهنة — شمولية دوائر NAND، بحدّ محسّن يقتضي وجود دائرة بوليانية CnC_n تحسبها. بل إن حجم هذه الدائرة هو على الأكثر c⋅2n/nc \cdot 2^n / n بوابة لبعض الثابت c≤10c \leq 10.

وبالتحديد، فإن مبرهنة — مجموعة دوائر لكل دالة لا نهائية يقتضي وجود مثل هذه المجموعة من الدوائر {Cn}\{ C_n \} حتى لدالة TWINPTWINP التي وصفناها من قبل، رغم أننا لا نعرف أي برنامج لحسابها. وبالفعل، ليس هذا مثيرًا للدهشة إلى حد كبير: فلكل n∈Nn\in \N بعينه، تكون TWINPnTWINP_n إمّا دالة الصفر الثابتة أو دالة الواحد الثابتة، وكلاهما يمكن حسابه بدوائر بوليانية بسيطة جدًا. ومن ثمّ هناك بالتأكيد مجموعة دوائر {Cn}\{ C_n \} تحسب TWINPTWINP. والصعوبة في حساب TWINPTWINP باستخدام Python أو أي لغة برمجة أخرى تنشأ من أننا لا نعرف، لكل nn بعينه، ما هي الدائرة CnC_n في هذه المجموعة.

الآلات الذاتية المحدودة الحتمية (اختياري)

كل نماذج الحوسبة التي تطرّقناها حتى الآن - الدوائر البوليانية والبرامج المستقيمة (straight-line programs) - لم تنطبق إلا على الدوال المتنهية.

سنقدّم في الحلقات وما لا نهاية آلات تورينج (Turing machines)، وهي النماذج المركزية للحوسبة على الدوال ذات المداخل غير المحدودة الطول. لكنّنا في هذا القسم نقدّم النموذج الأبسط، وهو الآلات الذاتية المحدودة الحتمية (deterministic finite automata, DFAs). يمكن للآلات الذاتية أن تكون جسراً تمهيدياً جيّدًا نحو آلات تورينج، وإن كانت لن تُستخدَم كثيرًا في أجزاء هذا الكتاب اللاحقة، ولذلك بإمكان القارئ أن يتخطّى إلى الحلقات وما لا نهاية مباشرةً. تبيّن أنّ الآلات الذاتية المحدودة الحتمية متكافئة في قوتها مع التعبيرات النمطية (regular expressions): وهي آلية قويّة لتحديد الأنماط، تُستخدَم على نطاق واسع في الممارسة العملية. معالجتنا للآلات الذاتية مختصرة نسبيًا. وهناك موارد كثيرة تساعدك على أن تصبح أكثر اطّلاعًا بالآلات الذاتية المحدودة الحتمية. وبشكل خاص، فإنّ الفصل الأول من كتاب سيبسر [@SipserBook] يحتوي على عرضٍ ممتاز لهذه المادة. كما وهناك مواقع كثيرة على الإنترنت تحوي محاكيات للآلات الذاتية، إضافةً إلى مترجِمات من التعبيرات النمطية إلى الآلات الذاتية والعكس (انظر مثلًا هنا وهنا).

على مستوى عالٍ، الخوارزمية (algorithm) هي وصفة لحساب مُخرَج انطلاقًا من مُدخل، عبر مزيج من الخطوات التالية:

  1. اقرأ بتًّا من المدخل
  2. حدِّث الحالة (state) أي الذاكرة العاملة
  3. توقّف وأنتج مُخرَجًا

على سبيل المثال، تذكّر برنامج Python الذي يحسب دالة XORXOR:

def XOR(X):
    '''Takes list X of 0's and 1's
       Outputs 1 if the number of 1's is odd and outputs 0 otherwise'''
    result = 0
    for i in range(len(X)):
        result = (result + X[i]) % 2
    return result

في كل خطوة يقرأ هذا البرنامج بتًّا واحدًا X[i] ويحدّث حالته result بناءً على ذلك البت (فيقلب result إذا كان X[i] يساوي 11 ويُبقيه على حاله فيما عدا ذلك). وحين ينتهي من اجتياز المدخل يُخرج البرنامج result. في علم الحاسوب، يُسمّى برنامجٌ من هذا النوع خوارزمية مرورٍ واحد بذاكرة ثابتة (single-pass constant-memory algorithm) لأنّه يجري مرورًا واحدًا فوق المدخل وذاكرته العاملة متناهية. (وفي هذه الحالة، لا يكون result إلّا 00 أو 11.) وتُسمّى هذه الخوارزمية أيضًا آلة ذاتية محدودة حتمية (Deterministic Finite Automaton) أو DFA (واسمٌ آخر للآلات الذاتية المحدودة الحتمية هو آلة الحالات المتناهية (finite state machine)). يمكننا التفكير في هذه الخوارزمية بوصفها «آلة» (machine) تستطيع أن تكون في إحدى CC حالة، حيث CC ثابت. تبدأ الآلة في حالةٍ ابتدائية ما، ثم تقرأ مدخلها x∈{0,1}∗x\in \{0,1\}^* بتًّا بت. وحينما تقرأ الآلة بتًّا σ∈{0,1}\sigma \in \{0,1\} تنتقل إلى حالة جديدة تعتمد على σ\sigma وعلى حالتها السابقة. ويعتمد مُخرَج الآلة على الحالة النهائية. وكل خوارزمية مرورٍ واحد بذاكرة ثابتة تقابل آلةً كهذه. فإذا استخدمت خوارزمية cc بتًّا من الذاكرة، أمكن تمثيل محتوى ذاكرتها كسلسلة من الطول cc. ولذلك يمكن لهذه الخوارزمية أن تكون في واحدة من 2c2^c حالة على الأكثر في أي لحظة أثناء التنفيذ.

يمكننا تحديد آلة ذاتية محدودة حتمية ذات CC حالة عبر قائمة من C⋅2C \cdot 2 قاعدة. وستكون كل قاعدة على الشكل «إذا كانت الآلة في الحالة vv وكان البت المقروء من المدخل هو σ\sigma فإنّ الحالة الجديدة هي v′v'». وفي نهاية الحساب، لدينا أيضًا قاعدة على الشكل «إذا كانت الحالة النهائية إحدى الحالات التالية ... فإنّ الخرج 11، وإلا فإنّ الخرج 00». على سبيل المثال، يمكن تمثيل برنامج Python أعلاه بآلة ذاتية ذات حالتين تحسب XORXOR على الشكل التالي:

  • ابدأ في الحالة 00.
  • لكل حالة s∈{0,1}s \in \{0,1\} ولكل بتّ مدخل σ\sigma يُقرأ، إذا كان σ=1\sigma =1 فانتقل إلى الحالة 1−s1-s، وإلا فابقَ في الحالة ss.
  • في النهاية أخرج 11 إذا وفقط إذا كان s=1s=1.

يمكننا أيضًا وصف آلة ذاتية محدودة حتمية ذات CC حالة بوصفها رسماً بيانيًا (graph) مُعنونًا من CC رأس. فلكل حالة ss ولكل بتّ σ\sigma نضيف حافةً موجّهة مُعنونة بـσ\sigma بين ss والحالة s′s' بحيث إن كانت الآلة في الحالة ss وقرأت σ\sigma فإنّها تنتقل إلى s′s'. (وإذا بقيت الحالة كما هي فإنّ هذه الحافة ستكون حلقةً ذاتية (self-loop)، وكذلك، إذا انتقلت ss إلى s′s' في حالتي σ=0\sigma=0 وσ=1\sigma=1 معًا فإنّ الرسم البياني سيحتوي حافتين متوازيتين.) كما نعنّن المجموعة S\mathcal{S} من الحالات التي تُخرج الآلة الذاتية 11 في نهاية الحساب. وتُعرف هذه المجموعة بأنها مجموعة حالات القبول (accepting states). انظر الشكل التوضيحي للتمثيل البياني لآلة XORXOR الذاتية.

/images/introtcs/lec_05_infinite-3.webp

رسميًا، تُحدَّد الآلة الذاتية المحدودة الحتمية بـ (1) جدول القواعد البالغ C⋅2C \cdot 2، ويمكن تمثيله بـ_دالة انتقال_ (transition function) TT تُسقط حالة s∈[C]s \in [C] وبتًّا σ∈{0,1}\sigma \in \{0,1\} على الحالة s′∈[C]s' \in [C] التي تنتقل إليها الآلة من الحالة ss عند المدخل σ\sigma، وبـ (2) مجموعة S\mathcal{S} من حالات القبول. ويؤدي هذا إلى التعريف التالي.

تعريف — الآلة الذاتية المحدودة الحتمية

الآلة الذاتية المحدودة الحتمية (DFA) ذات CC حالات فوق {0,1}\{0,1\} هي زوج (T,S)(T,\mathcal{S}) مع T:[C]×{0,1}→[C]T:[C]\times \{0,1\} \rightarrow [C] وS⊆[C]\mathcal{S} \subseteq [C]. الدالة المتناهية TT تُعرف بـ_دالة الانتقال_ (transition function) الخاصة بالآلة الذاتية المحدودة الحتمية. والمجموعة S\mathcal{S} تُعرف بأنها مجموعة حالات القبول.

لتكن F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} دالةً بوليانية ذات المجال اللانهائي {0,1}∗\{0,1\}^*. نقول إنّ (T,S)(T,\mathcal{S}) تحسب دالةً F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} إذا كان لكل n∈Nn\in\N ولكل x∈{0,1}nx\in \{0,1\}^n، إذا عرّفنا s0=0s_0=0 وsi+1=T(si,xi)s_{i+1} = T(s_i,x_i) لكل i∈[n]i\in [n]، فإنّ sn∈S⇔F(x)=1 s_n \in \mathcal{S} \Leftrightarrow F(x)=1

توقّف وتأمّل

احرص على عدم الخلط بين دالة الانتقال (transition function) الخاصة بالآلة الذاتية (TT في تعريف — الآلة الذاتية المحدودة الحتمية)، وهي دالة متناهية تحدّد جدول «القواعد» الذي تتبعه الآلة، وبين الدالة التي تحسبها الآلة الذاتية (FF في تعريف — الآلة الذاتية المحدودة الحتمية) وهي دالة لا نهائية.

ملاحظة — التعريفات في النصوص الأخرى

يمكن تعريف الآلات الذاتية المحدودة الحتمية بعدّة طرائق متكافئة. وبشكل خاص، يعرّف سيبسر [@SipserBook] الآلة الذاتية المحدودة الحتمية بوصفها خماسية (Q,Σ,δ,q0,F)(Q,\Sigma,\delta,q_0,F) حيث QQ هي مجموعة الحالات، وΣ\Sigma هي الأبجدية، وδ\delta هي دالة الانتقال، وq0q_0 هي الحالة الابتدائية، وFF هي مجموعة حالات القبول. في هذا الكتاب تكون مجموعة الحالات دائمًا على الشكل Q={0,…,C−1}Q=\{0,\ldots,C-1 \} وتكون الحالة الابتدائية دائمًا q0=0q_0 = 0، لكنّ هذا لا يغيّر شيئًا في القوة الحسابية لهذه النماذج. كذلك فإنّنا نقتصر على الحالة التي تكون فيها الأبجدية Σ\Sigma مساويةً لـ {0,1}\{0,1\}.

تمرين محلول — آلة ذاتية محدودة حتمية لـ (010)∗(010)^*

برهن أنّه توجد آلة ذاتية محدودة حتمية تحسب الدالة التالية FF:

F(x)={13 divides ∣x∣ and ∀i∈[∣x∣/3]x3ix3i+1x3i+2=0100otherwiseF(x) = \begin{cases} 1 & 3 \text{ divides } |x| \text{ and } \forall_{i\in [|x|/3]} x_{3i} x_{3i+1} x_{3i+2} = 010 \\ 0 & \text{otherwise} \end{cases}

الحل

عند طلب إنشاء آلة ذاتية محدودة حتمية، من المفيد غالبًا أن نبدأ بإنشاء خوارزمية مرورٍ واحد بذاكرة ثابتة باستخدام صياغة أعمّ (مثلًا باستخدام شيفرة شبه برمجية (pseudocode) أو برنامج Python). وبعد أن تتوفّر لدينا هذه الخوارزمية، يمكننا ترجمتها آليًا إلى آلة ذاتية محدودة حتمية. إليك برنامج Python بسيطًا يحسب FF:

def F(X):
    '''Return 1 iff X is a concatenation of zero/more copies of [0,1,0]'''
    if len(X) % 3 != 0:
        return False
    ultimate = 0
    penultimate = 1
    antepenultimate = 0
    for idx, b in enumerate(X):
        antepenultimate = penultimate
        penultimate = ultimate
        ultimate = b
        if idx % 3 == 2 and ((antepenultimate, penultimate, ultimate) != (0,1,0)):
            return False
    return True

بما أننا نحتفظ بثلاثة متغيّرات بوليانية، يمكن أن تكون الذاكرة العاملة في واحدة من 23=82^3 = 8 تشكيلة، ولذلك يمكن ترجمة البرنامج أعلاه مباشرةً إلى آلة ذاتية محدودة حتمية ذات 88 حالات. وفي حين أنّ ذلك ليس ضروريًا لحلّ السؤال، فإنّنا بفحص الآلة الناتجة نرى أنّه بإمكاننا دمج بعض الحالات و الحصول على آلة ذاتية ذات 44 حالات، موصوفة في الشكل التوضيحي. انظر أيضًا الشكل التوضيحي الذي يصوّر تنفيذ هذه الآلة على مدخلٍ بعينه.

/images/introtcs/lec_05_infinite-4.webp

تشريح الآلة الذاتية (متنهية مقابل غير محدودة)

الآن وقد بدأنا ننظر في مهام حاسوبية ذات أحجام مداخل غير محدودة، من الضروري التمييز بين مكوّنات خوارزمتنا ذات الطول الثابت وتلك التي تنمو مع حجم المدخل. وفي حالة الآلات الذاتية المحدودة الحتمية فإنّ هذه المكوّنات هي التالية:

مكوّنات ذات حجم ثابت: بمعلومية آلة ذاتية محدودة حتمية AA، فإنّ الكميات التالية ثابتة ومستقلة عن حجم المدخل:

  • عدد الحالات CC في AA.

  • دالة الانتقال TT (ولها 2C2C مُدخلًا، ولذلك يمكن تحديدها بجدول من 2C2C صفًا، كلّ خانةٍ منه عددٌ في [C][C]).

  • المجموعة S⊆[C]\mathcal{S} \subseteq [C] من حالات القبول. ويمكن وصف هذه المجموعة بسلسلة في {0,1}C\{0,1\}^C تحدّد أيّ الحالات تنتمي إلى S\mathcal{S} وأيّها لا تنتمي.

ومعنى ما سبق مجتمعةً أنّه يمكننا وصف الآلة الذاتية وصفًا تامًّا باستخدام عددٍ متناهٍ من الرموز. وهذه خاصيةٌ نطلبها من أيّ مفهومٍ لـ«الخوارزمية»: ينبغي أن نتمكّن من كتابة مواصفةٍ تامّة لكيفية إنتاج مُخرَج انطلاقًا من مُدخل.

مكوّنات ذات حجم غير محدود: الكميات التالية المتعلقة بالآلة الذاتية المحدودة الحتمية ليست محدودة بأيّ ثابت. ونشدّد على أنّها تبقى متنهية بالنسبة لأيّ مدخلٍ بعينه.

  • طول المدخل x∈{0,1}∗x\in \{0,1\}^* الذي تُعطى له الآلة الذاتية المحدودة الحتمية. فطول المدخل متناهٍ دائمًا، لكنّه غير محدود مسبقًا.

  • عدد الخطوات التي تتّخذها الآلة الذاتية المحدودة الحتمية يمكن أن ينمو مع طول المدخل. بالفعل، تجري الآلة مرورًا واحدًا على المدخل، ولذلك تتّخذ بالضبط ∣x∣|x| خطوة على مدخل x∈{0,1}∗x\in \{0,1\}^*.

تنفيذ الآلة الذاتية المحدودة الحتمية في dfazeroonefig. عدد الحالات وحجم دالة الانتقال محدودان، لكن قد يكون المدخل بأيّ طول. وإذا كانت الآلة الذاتية المحدودة الحتمية في الحالة  وراقبت القيمة  فتنتقل إلى الحالة . وفي نهاية التنفيذ تقبل الآلة إذا توفّرت الحالة النهائية في  وحسب.

الدوال القابلة للحساب بآلة ذاتية محدودة حتمية

نقول إنّ دالةً F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} هي قابلة للحساب بآلة ذاتية محدودة حتمية (DFA computable) إذا وُجدت آلة ذاتية محدودة حتمية DFADFA تحسب FF. لقد رأينا في السكر النحوي، وحساب كل دالة أنّ كل دالة متناهية يمكن حسابها بواسطة دائرة بوليانية. وعليه، فقد تتوقّع الآن أنّ كل دالة لا نهائية يمكن حسابها بواسطة بعض الآلة الذاتية المحدودة الحتمية. لكنّ هذا غير صحيح البتّة. سنرى بعد قليل أمثلةً بسيطة على دوالٍ لا نهائية لا يمكن حسابها بآلات ذاتية محدودة حتمية، لكنّنا سنبدأ بإثبات أنّ مثل هذه الدوال موجودة.

مبرهنة — الدوال القابلة للحساب بآلة ذاتية محدودة حتمية هي عدِّيّة

لتكن DFACOMPDFACOMP مجموعة كلّ الدوال البوليانية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} بحيث توجد آلة ذاتية محدودة حتمية تحسب FF. إذًا DFACOMPDFACOMP مجموعة عدِّيّة.

يمكن وصف كلّ آلة ذاتية محدودة حتمية بسلسلة من طول متناهٍ، وهذا يُنتج دالة تغطية (surjection) من {0,1}∗\{0,1\}^* إلى DFACOMPDFACOMP: وهي الدالة التي تُسقط كلّ سلسلةٍ تصف آلةً ذاتية AA على الدالة التي تحسبها.

برهان

يمكن وصف كلّ آلة ذاتية محدودة حتمية بسلسلة متناهية تمثّل دالة الانتقال TT ومجموعة حالات القبول، وكلّ آلة ذاتية محدودة حتمية AA تحسب دالةً ما F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}. وعليه يمكننا تعريف الدالة التالية StDC:{0,1}∗→DFACOMPStDC:\{0,1\}^* \rightarrow DFACOMP:

StDC(a)={Fa represents automaton A and F is the function A computes ONEotherwise StDC(a) = \begin{cases} F & a \text{ represents automaton } A \text{ and } F \text{ is the function } A \text{ computes } \\ ONE & \text{otherwise} \end{cases}

حيث ONE:{0,1}∗→{0,1}ONE:\{0,1\}^* \rightarrow \{0,1\} هي الدالة الثابتة التي تُخرج 11 على كلّ المداخل (وهي عنصرٌ في DFACOMPDFACOMP). وبما أنّه بالتعريف كلّ دالة FF في DFACOMPDFACOMP يحسبها بعضُ الآلة الذاتية، فإنّ StDCStDC دالة تغطية من {0,1}∗\{0,1\}^* إلى DFACOMPDFACOMP، وهذا يعني أنّ DFACOMPDFACOMP مجموعة عدِّيّة (انظر الشروط المكافئة لقابلية العدّ).

وبما أنّ مجموعة كلّ الدوال البوليانية غير عدِّيّة، نحصل على النتيجة التالية:

مبرهنة — وجود دوال لا تحسبها الآلات الذاتية المحدودة الحتمية

توجد دالة بوليانية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} لا يمكن حسابها بواسطة أيّ آلة ذاتية محدودة حتمية.

برهان

إذا كانت كلّ دالة بوليانية FF قابلة للحساب بواسطة بعض الآلة الذاتية المحدودة الحتمية، فإنّ DFACOMPDFACOMP تساوي المجموعة ALLALL من كلّ الدوال البوليانية، لكن، وحسب مبرهنة — الدوال المنطقية غير قابلة للعدّ، فإنّ هذه المجموعة غير عدِّيّة، وهذا يناقض مبرهنة — الدوال القابلة للحساب بآلة ذاتية محدودة حتمية هي عدِّيّة.

التعبيرات النمطية

البحث (searching) عن جزءٍ من النص مهمةٌ شائعة في الحوسبة. وفي جوهرها، فإنّ مسألة البحث (search problem) بسيطةٌ إلى حدٍّ كبير. لدينا مجموعة X={x0,…,xk}X = \{ x_0, \ldots, x_k \} من السلاسل (مثلًا: ملفاتٌ على قرصٍ صلب، أو سجلات طلاب في قاعدة بيانات)، ويريد المستخدم أن يعرف المجموعة الجزئية من كلّ x∈Xx \in X التي يطابقها (matched) بعضُ النمط (مثلًا: كلّ الملفات التي تنتهي أسماؤها بالسلسلة .txt). في العموم، يمكننا السماح للمستخدم بتحديد النمط عبر تحديد دالةٍ (function) قابلة للحساب F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}، حيث يمثّل F(x)=1F(x)=1 تطابقَ النمط مع xx. أي إنّ المستخدم يقدّم برنامجًا (program) PP بلغة برمجة مثل Python، والنظام يُعيد كلّ x∈Xx \in X بحيث P(x)=1P(x)=1. على سبيل المثال، يمكن البحث عن كلّ ملفات النص التي تحتوي السلسلة important document، أو ربما (بأن يكون PP مُصنِّفًا قائمًا على شبكة عصبية) عن كلّ الصور التي تحتوي قطة. لكنّنا لا نريد لنظامنا أن يقع في حلقةٍ لا نهائية لمجرّد محاولته تقييم البرنامج PP! ولهذا السبب، فإنّ الأنظمة النموذجية للبحث في الملفات أو قواعد البيانات لا تسمح للمستخدم بتحديد الأنماط بلغة برمجة كاملة. بدلًا من ذلك، تستخدم هذه الأنظمة نماذج حوسبة مقيّدة (restricted computational models) تكون من جهةٍ غنية بما يكفي لالتقاط كثيرٍ من الاستعلامات المطلوبة في الممارسة العملية (مثل كلّ أسماء الملفات المنتهية بـ .txt، أو كلّ أرقام الهواتف من الشكل (617) xxx-xxxx)، ومن جهةٍ أخرى مقيّدة بما يكفي بحيث يمكن تقييم الاستعلامات بكفاءةٍ عالية جدًّا على ملفاتٍ ضخمة، ولا يمكن أن تؤدي على الإطلاق إلى حلقةٍ لا نهائية.

من أشهر نماذج الحوسبة هذه التعبيرات النمطية. فإذا كنت قد استخدمت محرّرَ نصوصٍ متقدّمًا، أو صدفةَ سطرِ أوامر (command-line shell)، أو قم بأيّ نوعٍ من التلاعب بملفات النص، فمن المرجّح أنّك صادفت التعبيرات النمطية.

التعبير النمطي (regular expression) فوق أبجديةٍ ما Σ\Sigma يُنشأ من خلال تركيب عناصر Σ\Sigma مع عملية الدمج (concatenation)، وكذلك مع ∣| (وهي المقابلة لـ_أو_) و∗* (وهي المقابلة للتكرار صفر مرّات أو أكثر). (وغالبًا ما تتضمن التطبيقات الشائعة للتعبيرات النمطية في لغات البرمجة والأصداف بعض العمليات الإضافية فوق ∣| و∗*، لكنّ هذه العمليات يمكن تنفيذها بوصفها «سكرًا نحويًا» (syntactic sugar) باستخدام المشغّلَيْن ∣| و∗*.) على سبيل المثال، يقابل التعبير النمطي التالي فوق الأبجدية {0,1}\{0,1\} مجموعة كلّ السلاسل x∈{0,1}∗x\in \{0,1\}^* التي يتكرّر فيها كلّ رقمٍ مرّتين على الأقل: (00(0∗)∣11(1∗))∗  . (00(0^*)|11(1^*))^* \;.

يقابل التعبير النمطي التالي فوق الأبجدية {a,…,z,0,…,9}\{ a,\ldots,z,0,\ldots,9 \} مجموعة كلّ السلاسل التي تتكوّن من تسلسلٍ من حرفٍ واحدٍ أو أكثر من الحروف aa-dd يليه تسلسلٌ من رقمٍ أو أكثر (دون صفرٍ بادئ):

(a∣b∣c∣d)(a∣b∣c∣d)∗(1∣2∣3∣4∣5∣6∣7∣8∣9)(0∣1∣2∣3∣4∣5∣6∣7∣8∣9)∗  . (a|b|c|d)(a|b|c|d)^*(1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)^* \;.

رسميًا، تُعرَّف التعبيرات النمطية بالتعريف العودي التالي:

تعريف — التعبير النمطي

التعبير النمطي (regular expression) ee فوق أبجدية Σ\Sigma هو سلسلة فوق Σ∪{(,),∣,∗,∅,""}\Sigma \cup \{ (,),|,*,\emptyset, "" \} تأخذ أحد الأشكال التالية:

  1. e=σe = \sigma حيث σ∈Σ\sigma \in \Sigma

  2. e=(e′∣e′′)e = (e' | e'') حيث e′,e′′e', e'' تعبيران نمطيان.

  3. e=(e′)(e′′)e = (e')(e'') حيث e′,e′′e',e'' تعبيران نمطيان. (وعادة ما نحذف الأقواس عندما لا يكون هناك خطرُ التباس، ونكتب حينئذٍ e′  e′′e' \; e''.)

  4. e=(e′)∗e = (e')^* حيث e′e' تعبير نمطي.

وأخيرًا فإنّنا نسمح أيضًا بحالاتِ «الحدّ» (edge cases) التالية: e=∅e = \emptyset وe=""e = "". وهذه هي التعبيرات النمطية المقابلة، على الترتيب، لقبولِ لا سلاسل، وقبولِ السلسلة الفارغة فقط.

سنحذف الأقواس عندما يمكن استنتاجها من السياق. كما نستخدم العُرف بأنّ «أو» والدمج يُجمَّعان من جهة اليسار، وأنّنا نعطي الأولوية العليا لـ∗*، ثم للدمج، ثم لـ«أو». ولذلك نكتب مثلًا 00∗∣1100^*|11 بدلًا من ((0)(0∗))∣((1)(1))((0)(0^*))|((1)(1)).

كلّ تعبير نمطي ee يقابل دالةً Φe:Σ∗→{0,1}\Phi_{e}:\Sigma^* \rightarrow \{0,1\} حيث Φe(x)=1\Phi_{e}(x)=1 إذا كان xx يطابق (matches) التعبير النمطي. على سبيل المثال، إذا كان e=(00∣11)∗e = (00|11)^* فإنّ Φe(110011)=1\Phi_e(110011)=1 لكنّ Φe(101)=0\Phi_e(101)=0 (هل ترى لماذا؟).

توقّف وتأمّل

التعريف الرسمي لـΦe\Phi_{e} هو من جملة التعريفات التي يكون كتابتها أعرق من إدراكها. ولذلك قد يكون من الأسهل لك أن تستنتج التعريف بنفسك أولًا، ثم تتحقّق من مطابقته لما هو مكتوب أدناه.

تعريف — مطابقة تعبير نمطي

لتكن ee تعبيرًا نمطيًا فوق الأبجدية Σ\Sigma. تُعرَّف الدالة Φe:Σ∗→{0,1}\Phi_{e}:\Sigma^* \rightarrow \{0,1\} على النحو التالي:

  1. إذا كان e=σe = \sigma فإنّ Φe(x)=1\Phi_{e}(x)=1 إذا وفقط إذا كان x=σx=\sigma.

  2. إذا كان e=(e′∣e′′)e = (e' | e'') فإنّ Φe(x)=Φe′(x)∨Φe′′(x)\Phi_{e}(x) = \Phi_{e'}(x) \vee \Phi_{e''}(x) حيث ∨\vee هو مشغّل «أو».

  3. إذا كان e=(e′)(e′′)e = (e')(e'') فإنّ Φe(x)=1\Phi_{e}(x) = 1 إذا وفقط إذا وُجد x′,x′′∈Σ∗x',x'' \in \Sigma^* بحيث إنّ xx هو دمج x′x' وx′′x'' وأنّ Φe′(x′)=Φe′′(x′′)=1\Phi_{e'}(x')=\Phi_{e''}(x'')=1.

  4. إذا كان e=(e′)∗e= (e')* فإنّ Φe(x)=1\Phi_{e}(x)=1 إذا وفقط إذا وُجد k∈Nk\in \N وx0,…,xk−1∈Σ∗x_0,\ldots,x_{k-1} \in \Sigma^* بحيث إنّ xx هو الدمج x0⋯xk−1x_0 \cdots x_{k-1} وأنّ Φe′(xi)=1\Phi_{e'}(x_i)=1 لكلّ i∈[k]i\in [k].

  5. وأخيرًا، بالنسبة إلى حالتَي «الحدّ» فإنّ Φ∅\Phi_{\emptyset} هي الدالة الثابتة صفر، وΦ""\Phi_{""} هي الدالة التي لا تُخرج إلّا 11 على السلسلة الفارغة """".

نقول إنّ تعبيرًا نمطيًا ee فوق Σ\Sigma يطابق (matches) سلسلةً x∈Σ∗x \in \Sigma^* إذا كان Φe(x)=1\Phi_{e}(x)=1.

توقّف وتأمّل

التعريفات السابقة ليست صعبة بذاتها، لكنّها ثقيلة قليلًا. لذا ينبغي أن تتوقّف هنا وتُعيد قراءتها حتى تفهم لماذا تقابل تصوّرنا البديهي للتعبيرات النمطية. وهذا مهمّ ليس من أجل فهم التعبيرات النمطية نفسها فحسب (وهي تُستخدَم مرارًا وتكرارًا في عددٍ كبيرٍ جدًّا من التطبيقات)، بل أيضًا من أجل أن تصبح أفضل في فهم التعريفات العودية على نحو عام.

تُسمّى الدالة البوليانية «منتظمة» (regular) إذا كانت تُخرج 11 على المجموعة بالضبط من السلاسل التي يطابقها بعضُ التعبير النمطي. أي إنّ

تعريف — الدوال المنتظمة / اللغات المنتظمة

لتكن Σ\Sigma مجموعة متناهية، لتكن F:Σ∗→{0,1}F:\Sigma^* \rightarrow \{0,1\} دالةً بوليانية. نقول إنّ FF منتظمة (regular) إذا كان F=ΦeF=\Phi_{e} لبعض التعبير النمطي ee.

وكذلك، لكل لغةٍ شكلية L⊆Σ∗L \subseteq \Sigma^* نقول إنّ LL منتظمة إذا وفقط إذا وُجد تعبيرٌ نمطي ee بحيث يكون x∈Lx\in L إذا وفقط إذا كان ee يطابق xx.

مثال — دالة منتظمة

لتكن Σ={a,b,c,d,0,1,2,3,4,5,6,7,8,9}\Sigma=\{ a,b,c,d,0,1,2,3,4,5,6,7,8,9 \}، لتكن F:Σ∗→{0,1}F:\Sigma^* \rightarrow \{0,1\} الدالة التي يكون فيها F(x)=1F(x)=1 إذا وفقط إذا كان xx يتكوّن من حرفٍ واحدٍ أو أكثر من الحروف aa-dd يليه تسلسلٌ من رقمٍ أو أكثر (دون صفرٍ بادئ). إذًا FF دالةٌ منتظمة، لأنّ F=ΦeF=\Phi_e حيث e=(a∣b∣c∣d)(a∣b∣c∣d)∗(1∣2∣3∣4∣5∣6∣7∣8∣9)(0∣1∣2∣3∣4∣5∣6∣7∣8∣9)∗e = (a|b|c|d)(a|b|c|d)^*(1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)^* وهو التعبير الذي رأيناه في المعادلة.

فإذا أردنا مثلًا التحقّق من أنّ Φe(abc12078)=1\Phi_e(abc12078)=1، يمكننا فعل ذلك بالملاحظة على أنّ التعبير (a∣b∣c∣d)(a|b|c|d) يطابق السلسلة aa، وأنّ التعبير (a∣b∣c∣d)∗(a|b|c|d)^* يطابق bcbc، وأنّ (1∣2∣3∣4∣5∣6∣7∣8∣9)(1|2|3|4|5|6|7|8|9) يطابق السلسلة 11، وأنّ التعبير (0∣1∣2∣3∣4∣5∣6∣7∣8∣9)∗(0|1|2|3|4|5|6|7|8|9)^* يطابق السلسلة 20782078. وكلٌّ من هذه يفضي إلى تعبيرٍ أبسط. على سبيل المثال، التعبير (a∣b∣c∣d)∗(a|b|c|d)^* يطابق السلسلة bcbc لأنّ سلسلتي الحرف الواحد bb وcc كلتيهما يطابقها التعبير a∣b∣c∣da|b|c|d.

يمكن تعريف التعبير النمطي فوق أيّ أبجدية متناهية Σ\Sigma، لكنّنا، كالمعتاد، سنركّز في معظم الوقت على الحالة الثنائية (binary case) حيث Σ={0,1}\Sigma = \{0,1\}. وكلّ الرؤى العامة النظرية والعملية عن التعبيرات النمطية (أو كلّها تقريبًا) يمكن استخلاصها من دراسة الحالة الثنائية.

خوارزميات مطابقة التعبيرات النمطية

لن تكون التعبيرات النمطية نافعةً جدًّا في البحث إذا لم نتمكّن من تقييم ما إذا كانت سلسلة xx يطابقها تعبير نمطي ee معيّن، أم لا. ولحسن الحظّ، هناك خوارزمية تفعل ذلك. وبالتحديد، هناك خوارزمية (فكّر فيها بوصفها «برنامج Python»، مع أنّنا سنضبط لاحقًا مفهوم الخوارزميات باستخدام آلات تورينج) تُخرج على مدخل تعبير نمطي ee فوق الأبجدية {0,1}\{0,1\} وسلسلة x∈{0,1}∗x\in \{0,1\}^* القيمة 11 إذا وفقط إذا كان ee يطابق xx (أي أنّها تُخرج Φe(x)\Phi_e(x)).

بالفعل، فإنّ تعريف — مطابقة تعبير نمطي يحدّد في الواقع خوارزميةً عودية لحساب Φe\Phi_{e}. وبالتحديد، يمكن التفكير في كلّ عملية من عملياتنا -الدمج، و«أو»، والنجمة- على أنّها تُقلّص مهمة التحقّق ممّا إذا كان تعبير ee يطابق سلسلة xx إلى التحقّق ممّا إذا كانت بعضُ التعبيرات الجزئية لـee تطابق سلاسلَ جزئية من xx. وبما أنّ هذه التعبيرات الجزئية أقصر دائمًا من التعبير الأصلي، فإنّ هذا يعطي خوارزميةً عودية للتحقّق ممّا إذا كان ee يطابق xx، وفي نهاية المطاف ستتوقّف عند الحالات الأساس للتعبيرات التي تقابل رمزًا واحدًا أو السلسلة الفارغة.

خوارزمية — Regular expression matching

INPUT: Regular expression $e$ over $\Sigma^*$, $x\in \Sigma^*$

OUTPUT:  $\Phi_e(x)$

procedure{Match}{$e$,$x$}
lIf {$e=\emptyset$} return $0$ lendif
lIf {$x=""$} return $\CALL{MatchEmpty}{e}$lendif
lIf {$e \in \Sigma$} return $1$ iff $x=e$ lendif
lIf {$e = (e' | e'')$} return {$Match(e',x)$ or $Match(e'',x)$} lendif
If {$e= (e')(e'')$}
   For {$i \in [|x|]$}
      lIf {$Match(e',x_0 \cdots x_{i-1})$ and  $Match(e'',x_i \cdots x_{|x|-1})$} return $1$ lendif
   Endfor
Endif
If {$e = (e')^*$}
    lIf {$e'=""$} return $Match("",x)$ lendif 
    # $("")^*$ is the same as $""$
    For {$i \in [|x|]$}
      # $x_0 \cdots x_{i-1}$ is shorter than $x$
      lIf {$Match(e,x_0 \cdots x_{i-1})$ and  $Match(e',x_i \cdots x_{|x|-1})$} return $1$ lendif
   Endfor
Endif
return $0$
endprocedure

نفترض أعلاه أنّ لدينا إجراءً MatchEmpty\text{\textsc{MatchEmpty}} يُخرج على مدخل تعبير نمطي ee القيمة 11 إذا وفقط إذا كان ee يطابق السلسلة الفارغة """".

الملاحظة المفتاحية هي أنّه في تعريفنا العودي للتعبيرات النمطية، متى ما كان ee مكوّنًا من تعبير واحدٍ أو تعبيرين e′,e′′e',e''، فإنّ هذين التعبيرين النمطيين أصغر من ee. وفي نهاية المطاف (حين يبلغ حجمهما 11) فإنّهما لا بدّ أن يقابلا الحالة غير العودية الخاصة برمزٍ واحدٍ من الأبجدية. وبالتوازي، فإنّ الاستدعاءات العودية التي تجري في خوارزمية — Regular expression matching تقابل دائمًا تعبيرًا أقصر أو (في حالة تعبيرٍ من الشكل (e′)∗(e')^*) سلسلةَ مُدخل أقصر. وعليه، يمكننا إثبات صحّة خوارزمية — Regular expression matching على المداخل من الشكل (e,x)(e,x) بالحثّ على min⁡{∣e∣,∣x∣}\min \{ |e|, |x| \}. الحالة الأساس هي أن يكون x=""x="" أو أن يكون ee رمزًا واحدًا من الأبجدية، أو """" أو ∅\emptyset. وفي حالة التعبير عن الشكل e=(e′∣e′′)e=(e'|e'') أو e=(e′)(e′′)e=(e')(e'')، فإنّنا نجري استدعاءاتٍ عودية بالتعبيرين الأقصر e′,e′′e',e''. وفي حالة التعبير عن الشكل e=(e′)∗e=(e')^*، فإنّنا نجري استدعاءاتٍ عودية إمّا بسلسلة xx أقصر ومع التعبير ذاته، وإمّا بالتعبير الأقصر e′e' ومع سلسلة x′x' تساوي xx في الطول أو تكون أقصر منها.

تمرين محلول — مطابقة السلسلة الفارغة

قدّم خوارزمية تُخرج على مدخل تعبير نمطي ee القيمة 11 إذا وفقط إذا كان Φe("")=1\Phi_e("")=1.

الحل

يمكننا الحصول على خوارزميةٍ عودية من هذا النوع عبر الملاحظات التالية:

  1. التعبير عن الشكل """" أو (e′)∗(e')^* يطابق السلسلة الفارغة دائمًا.

  2. التعبير عن الشكل σ\sigma، حيث σ∈Σ\sigma \in \Sigma رمزٌ من رموز الأبجدية، لا يطابق السلسلة الفارغة أبدًا.

  3. التعبير النمطي ∅\emptyset لا يطابق السلسلة الفارغة.

  4. التعبير عن الشكل e′∣e′′e'|e'' يطابق السلسلة الفارغة إذا وفقط إذا طابق أحدهما e′e' أو e′′e'' السلسلة الفارغة.

  5. التعبير عن الشكل (e′)(e′′)(e')(e'') يطابق السلسلة الفارغة إذا وفقط إذا طابق كلٌّ منهما e′e' وe′′e'' السلسلة الفارغة.

وبالنظر إلى الملاحظات أعلاه، نرى أنّ الخوارزمية التالية تتحقّق ممّا إذا كان ee يطابق السلسلة الفارغة:

خوارزمية — Check for empty string

INPUT: Regular expression $e$ over $\Sigma^*$, $x\in \Sigma^*$

OUTPUT:  $1$ iff $e$ matches the emptry string.
procedure{MatchEmpty}{$e$}
lIf {$e=""$} return $1$ lendif
lIf {$e=\emptyset$ or $e \in \Sigma$} return $0$ lendif
lIf {$e=(e'|e'')$} return $MatchEmpty(e')$ or $MatchEmpty(e'')$ lendif
LIf {$e=(e')(e'')$} return $MatchEmpty(e')$ and $MatchEmpty(e'')$ lendif
lIf {$e=(e')^*$} return $1$ lendif
endprocedure

المطابقة الكفؤة للتعبيرات النمطية (اختياري)

خوارزمية — Regular expression matching ليس كفؤًا إلى حدٍّ كبير. فعلى سبيل المثال، إذا أعطينا تعبيرًا يتضمّن الدمج أو عملية «النجمة» وسلسلةً من الطول nn، فإنّه يمكن أن يُجري nn استدعاءً عوديًا، وعليه يمكن إثبات أنّ خوارزمية — Regular expression matching في أسوأ الحالات قد يستغرق زمنًا أسّيًّا في طول سلسلة المُدخل xx. ولحسن الحظّ، تبيّن أنّ هناك خوارزمية أكثر كفاءة بكثير تستطيع مطابقة التعبيرات النمطية في زمنٍ خطّي (أي O(n)O(n)). وبما أنّنا لم نغطِّ بعد موضوعي تعقيد الزمن وتعقيد المكان، فإنّنا نصف هذه الخوارزمية بصياغةٍ عامّة دون ضبط النموذج الحسابي بدقّة. وبدلًا من ذلك سنستخدم المفهوم الاستعماليّ لزمن التشغيل O(n)O(n) كما هو مستعمَل في مقرّرات مقدّمة إلى البرمجة وفي مقابلات البرمجة على السبورة. وسترى تعريفًا رسميًا لتعقيد الزمن في نمذجة زمن التنفيذ.

مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي

لتكن ee تعبيرًا نمطيًا. إذًا توجد خوارزمية بزمن O(n)O(n) تحسب Φe\Phi_{e}.

الثابت الضمني في حدّ O(n)O(n) في مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يعتمد على التعبير ee. وعليه، فإنّ طريقة أخرى لقول مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي هي أنّه لكل تعبير ee يوجد ثابت cc وخوارزمية AA تحسب Φe\Phi_e على مداخل بطول nn بت باستخدام c⋅nc\cdot n خطوة على الأكثر. وهذا يبدو معقولًا، إذ إنّنا في الممارسة العملية نرغب غالبًا في حساب Φe(x)\Phi_e(x) لتعبيرٍ نمطي صغير ee ووثيقةٍ كبيرة xx. ويخبرنا مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي أنّه يمكننا فعل ذلك بزمن تشغيل يتناسب تناسبًا خطّيًّا مع حجم الوثيقة، حتى لو كان اعتمادُه على حجم التعبير النمطي (على الأرجح) أسوأ.

نُثبت مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي عبر الحصول على خوارزميةٍ عودية أكثر كفاءة، تحدّد ممّا إذا كان ee يطابق سلسلة x∈{0,1}nx\in \{0,1\}^n عبر تقليص هذه المهمة إلى تحديد ممّا إذا كان تعبيرٌ ذو صلة e′e' يطابق x0,…,xn−2x_0,\ldots,x_{n-2}. وهذا سيؤدّي إلى معادلةٍ لزمن التشغيل من الشكل T(n)=T(n−1)+O(1)T(n) = T(n-1) + O(1)، ويُحلّ بها أنّ T(n)=O(n)T(n)=O(n).

تقييدات التعبيرات النمطية. المفهوم المركزي في الخوارزمية الكامن خلف مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي هو مفهوم التقييد (restriction) للتعبير النمطي. الفكرة أنّه لكل تعبير نمطي ee ولكل رمز σ\sigma في أبجديته، من الممكن تعريف تعبير نمطي e[σ]e[\sigma] بحيث يطابق e[σ]e[\sigma] سلسلة xx إذا وفقط إذا طابق ee السلسلة xσx\sigma. على سبيل المثال، إذا كان ee هو التعبير النمطي (01)∗(01)(01)^*(01) (أي ورود واحد أو أكثر لـ0101) فإنّ e[1]e[1] يساوي (01)∗0(01)^*0 وسيكون e[0]e[0] هو ∅\emptyset. (هل ترى لماذا؟)

خوارزمية — Restricting regular expression يحسب التقييد e[σ]e[\sigma] بمعلومي تعبير نمطي ee ورمزٍ من رموز الأبجدية σ\sigma. وهو يتوقّف دائمًا، إذ إنّ الاستدعاءات العودية التي يجريها تكون دائمًا على تعبيرات أصغر من تعبير المُدخل. ويمكن إثبات صحّته بالحثّ على طول التعبير النمطي ee، وتكون الحالات الأساس هي أن يكون ee هو """" أو ∅\emptyset، أو رمزًا واحدًا من رموز الأبجدية τ\tau.

خوارزمية — Restricting regular expression

INPUT: Regular expression  $e$ over $\Sigma$, symbol $\sigma \in \Sigma$

OUTPUT: Regular expression $e'=e[\sigma]$ such that $\Phi_{e'}(x) = \Phi_e(x \sigma)$ -for every $x\in \Sigma^*$

procedure{Restrict}{$e$,$\sigma$}
lIf {$e=""$ or $e=\emptyset$} return $\emptyset$ lendif
lIf {$e=\tau$ -for $\tau \in \Sigma$} return $""$ -if $\tau=\sigma$ and return $\emptyset$ otherwise lendif
lIf {$e=(e'|e'')$} return $(Restrict(e',\sigma) | Restrict(e'',\sigma))$ lendif
lIf {$e=(e')^*$} return $(e')^* (Restrict(e',\sigma))$ lendif
lIf {$e= (e')(e'')$ and $\Phi_{e''}("")=0$} return $(e')(Restrict(e'',\sigma))$ lendif
lIf {$e= (e')(e'')$ and $\Phi_{e''}("")=1$} return $(e' Restrict(e'',\sigma)) \; | \; Restrict(e',\sigma)$ lendif
endprocedure

باستخدام هذا المفهوم للتقييد، يمكننا تعريف خوارزمية المطابقة العودية التالية للتعبيرات النمطية:

خوارزمية — Regular expression matching in linear time

INPUT: Regular expression $e$ over $\Sigma^*$, $x\in \Sigma^n$ where $n\in\N$

OUTPUT:  $\Phi_e(x)$

procedure{FMatch}{$e$,$x$}
lIf {$x=""$} return $\CALL{MatchEmpty}{e}$ lendif
Let $e' \leftarrow \CALL{Restrict}{e,x_{n-1}}$
return $FMatch(e',x_0 \cdots x_{n-2})$
endprocedure

بحسب تعريف التقييد، فإنّ لكل σ∈Σ\sigma\in \Sigma ولكل x′∈Σ∗x'\in \Sigma^*، يطابق التعبير ee السلسلة x′σx'\sigma إذا وفقط إذا طابق e[σ]e[\sigma] السلسلة x′x'. وعليه، فإنّ لكل ee ولكل x∈Σnx\in \Sigma^n، لدينا Φe[xn−1](x0⋯xn−2)=Φe(x)\Phi_{e[x_{n-1}]}(x_0\cdots x_{n-2}) = \Phi_e(x)، وبعدها يُعيد خوارزمية — Regular expression matching in linear time الإجابة الصحيحة فعلًا. والمهمة المتبقّية الوحيدة هي تحليل زمن تشغيله. لاحظ أنّ خوارزمية — Regular expression matching in linear time يستخدم إجراء MatchEmpty\text{\textsc{MatchEmpty}} الخاص بـتمرين محلول — مطابقة السلسلة الفارغة في الحالة الأساس التي يكون فيها x=""x="". لكنّ هذا مقبول لأنّ زمن تشغيل هذا الإجراء يعتمد فقط على ee ومستقلّ عن طول المُدخل الأصلي.

للبساطة، فلنقصر انتباهنا على الحالة التي تكون فيها الأبجدية Σ\Sigma مساويةً لـ {0,1}\{0,1\}. لنعرّف C(ℓ)C(\ell) بأنّها أقصى عدد عمليات تتّخذه خوارزمية — Restricting regular expression حين يُعطى كمدخل تعبير نمطي ee فوق {0,1}\{0,1\} من ℓ\ell رمزًا على الأكثر. ويمكن إثبات أنّ القيمة C(ℓ)C(\ell) متعدّدة الحدود في ℓ\ell، لكنّ هذا ليس مهمًّا لهذه المبرهنة، إذ إنّنا نهتمّ فقط باعتماد زمن حساب Φe(x)\Phi_e(x) على طول xx وليس باعتماد هذا الزمن على طول ee.

خوارزمية — Regular expression matching in linear time خوارزمية عودية تُعطى تعبيرًا ee وسلسلة x∈{0,1}nx\in \{0,1\}^n، فتُجري حسابًا من C(∣e∣)C(|e|) خطوة على الأكثر ثمّ تستدعي نفسها بتعبيرٍ ما e′e' وسلسلة x′x' من الطول n−1n-1. وستتوقّف بعد nn خطوة عند وصولها إلى سلسلةٍ من الطول 00. وعليه، فإنّ زمن التشغيل T(e,n)T(e,n) الذي يحتاجه خوارزمية — Regular expression matching in linear time لحساب Φe\Phi_e على مداخل من الطول nn يحقق المعادلة العودية:

T(e,n)=max⁡{T(e[0],n−1),T(e[1],n−1)}+C(∣e∣)T(e,n) = \max \{ T(e[0],n-1) , T(e[1],n-1) \} + C(|e|)

(وفي الحالة الأساس n=0n=0، يساوي T(e,0)T(e,0) ثابتًا ما يعتمد فقط على ee.) ولكي نحصل على بعض الحدس تجاه التعبير المعادلة، فلنفكّ التكرار درجةً واحدة، فنكتب T(e,n)T(e,n) على النحو

T(e,n)=max⁡{T(e[0][0],n−2)+C(∣e[0]∣),T(e[0][1],n−2)+C(∣e[0]∣),T(e[1][0],n−2)+C(∣e[1]∣),T(e[1][1],n−2)+C(∣e[1]∣)}+C(∣e∣)  .\begin{aligned}T(e,n) &= \max \{ T(e[0][0],n-2) + C(|e[0]|), \\ &T(e[0][1],n-2) + C(|e[0]|), \\ &T(e[1][0],n-2) + C(|e[1]|), \\ &T(e[1][1],n-2) + C(|e[1]|) \} + C(|e|)\;.\end{aligned}

بمتابعة ذلك على هذا النحو، يمكننا أن نرى أنّ T(e,n)≤n⋅C(L)+O(1)T(e,n) \leq n \cdot C(L) + O(1) حيث LL هو أطول طولٍ لأيّ تعبير e′e' نصادفه في الطريق. وعليه، فإنّ الدعوى التالية تكفي لإثبات أنّ خوارزمية — Regular expression matching in linear time يعمل بزمن O(n)O(n):

الدعوى: لتكن ee تعبيرًا نمطيًا فوق {0,1}\{0,1\}، إذًا يوجد عدد L(e)∈NL(e) \in \N بحيث إنّ لكل تسلسل من الرموز α0,…,αn−1\alpha_0,\ldots,\alpha_{n-1}، إذا عرّفنا e′=e[α0][α1]⋯[αn−1]e' = e[\alpha_0][\alpha_1]\cdots [\alpha_{n-1}] (أي تقييد ee إلى α0\alpha_0، ثمّ α1\alpha_1 وهكذا على التوالي)، فإنّ ∣e′∣≤L(e)|e'| \leq L(e).

اقتباس

إثبات الدعوى: لتعبيرٍ نمطي ee فوق {0,1}\{0,1\} وα∈{0,1}m\alpha\in \{0,1\}^m، نرمز بـe[α]e[\alpha] إلى التعبير e[α0][α1]⋯[αm−1]e[\alpha_0][\alpha_1]\cdots [\alpha_{m-1}] الناتج عن تقييد ee إلى α0\alpha_0 ثمّ إلى α1\alpha_1 وهكذا. ولنضع S(e)={e[α]∣α∈{0,1}∗}S(e) = \{ e[\alpha] | \alpha \in \{0,1\}^* \}. وسنُثبت الدعوى بأنّنا نبيّن أنّه لكل ee تكون المجموعة S(e)S(e) متناهية، وبالتالي فإنّ العدد L(e)L(e) أيضًا متناهٍ، وهو أطول طولٍ لـe′e' حيث e′∈S(e)e'\in S(e).

نُثبت ذلك بالحثّ على بنية ee. إذا كان ee رمزًا أو السلسلة الفارغة أو المجموعة الخالية، فإنّ هذا واضحٌ لإثباته، إذ إنّ أكثر ما يمكن أن تحتويه المجموعة S(e)S(e) هو التعبير ذاته و"""" و∅\emptyset. وإلا فإنّنا نقسم إلى الحالتين (i) e=e′∗e = e'^* و (ii) e=e′e′′e = e'e''، حيث e′,e′′e',e'' تعبيران أصغر (وبالتالي، بحسب فرضية الحثّ، فإنّ S(e′)S(e') وS(e′′)S(e'') متناهيتان). في الحالة (i)، إذا كان e=(e′)∗e = (e')^* فإنّ e[α]e[\alpha] إمّا يساوي (e′)∗e′[α](e')^* e'[\alpha] أو يكون ببساطة المجموعة الخالية إذا كان e′[α]=∅e'[\alpha]=\emptyset. وبما أنّ e′[α]e'[\alpha] ينتمي إلى المجموعة S(e′)S(e')، فإنّ عدد التعبيرات المتمايزة في S(e)S(e) هو ∣S(e′)∣+1|S(e')|+1 على الأكثر. وفي الحالة (ii)، إذا كان e=e′e′′e = e' e'' فإنّ كلّ تقييدات ee إلى السلاسل α\alpha إمّا أن تأخذ الشكل e′e′′[α]e' e''[\alpha] أو تأخذ الشكل e′e′′[α]∣e′[α′]e' e''[\alpha] | e'[\alpha'] حيث α′\alpha' سلسلةٌ ما بحيث α=α′α′′\alpha = \alpha' \alpha'' ويطابق e′′[α′′]e''[\alpha''] السلسلة الفارغة. وبما أنّ e′′[α]∈S(e′′)e''[\alpha] \in S(e'') وe′[α′]∈S(e′)e'[\alpha'] \in S(e')، فإنّ عدد التعبيرات المتمايزة المحتملة من الشكل e[α]e[\alpha] هو ∣S(e′′)∣+∣S(e′′)∣⋅∣S(e′)∣|S(e'')| + |S(e'')|\cdot |S(e')| على الأكثر. وبهذا يكتمل إثبات الدعوى.

الخلاصة أنّه أثناء تشغيل خوارزمية — Regular expression matching in linear time على تعبير نمطي ee، فإنّ جميع التعبيرات التي نصادفها تنتمي إلى المجموعة المتناهية S(e)S(e)، مهما كان حجم المُدخل xx كبيرًا، ولذلك فإنّ زمن تشغيل خوارزمية — Regular expression matching in linear time يحقق المعادلة T(n)=T(n−1)+C′T(n) = T(n-1) + C' حيث C′C' ثابتٌ يعتمد على ee. ويُحلّ ذلك بنتيجة O(n)O(n) حيث إنّ الثابت الضمني في رمز OO يمكن (وسيكون) أن يعتمد على ee، لكنّه — وهذا هو المهمّ — لا يعتمد على طول المُدخل xx.

مطابقة التعبيرات النمطية باستخدام الآلات الذاتية المحدودة الحتمية

مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي مثيرٌ للإعجاب إلى حدٍّ كبير، لكنّنا نستطيع فعل أفضل من ذلك. وبالتحديد، مهما كانت السلسلة xx طويلة، يمكننا حساب Φe(x)\Phi_e(x) مع الاحتفاظ بمقدارٍ ثابتٍ فقط من الذاكرة، بل وبإجراء مرورٍ واحد (single pass) فوق xx. أي أنّ الخوارزمية ستفحص المُدخل xx مرةً واحدة من البداية إلى النهاية، ثمّ تحدّد ما إذا كانت xx يطابقها التعبير ee أم لا. وهذا مهمٌّ في الحالة الشائعة التي نحاول فيها مطابقة تعبير نمطي قصير على ملفٍ أو وثيقةٍ ضخمة قد لا تتّسع حتى في ذاكرة حاسوبنا. وبالطبع، كما رأينا من قبل، فإنّ خوارزمية المرور الواحد بذاكرة ثابتة هي ببساطة آلة ذاتية محدودة حتمية. وكما سنرى في مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي، فإنّ الدالة يمكن حسابها بتعبيرٍ نمطي إذا وفقط إذا أمكن حسابها بآلة ذاتية محدودة حتمية. ونبدأ بإثبات اتجاه «فقط إذا»:

مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية

لتكن ee تعبيرًا نمطيًا. إذًا توجد خوارزمية تحسب Φe(x)\Phi_e(x) على مُدخل x∈{0,1}∗x\in \{0,1\}^* مع إجراء مرورٍ واحدٍ فوق xx مع الاحتفاظ بمقدارٍ ثابتٍ من الذاكرة.

خوارزمية المرور الواحد بذاكرة ثابتة للتحقّق ممّا إذا كانت سلسلةٌ ما تطابق تعبيرًا نمطيًا معروضة في خوارزمية — Regular expression matching by a DFA. والفكرة هي استبدال الخوارزمية العودية في خوارزمية — Regular expression matching in linear time ببرنامجٍ ديناميكي (dynamic program)، باستخدام تقنية [الاسترجاع] (memoization). فإذا لم تدرس بعد مقرّرًا في الخوارزميات، فقد لا تكون على علم بهذه التقنيات. وهذا مقبول؛ فبينما هذه الخوارزمية الأكثر كفاءة حاسمةٌ للتطبيقات العملية العديدة للتعبيرات النمطية، فإنّها ليست ذات أهميةٍ بالغةٍ لهذا الكتاب.

خوارزمية — Regular expression matching by a DFA

INPUT: Regular expression $e$ over $\Sigma^*$, $x\in \Sigma^n$ where $n\in\N$

OUTPUT:  $\Phi_e(x)$

procedure{DFAMatch}{$e$,$x$}
Let $S \leftarrow S(e)$ be the set $\{ e[\alpha] | \alpha\in \Sigma^* \}$ as defined in the proof of the linear-time matching theorem.
For {$e' \in S$}
    Let $v_{e'} \leftarrow 1$ -if $\Phi_{e'}("")=1$ and $v_{e'} \leftarrow 0$ otherwise
endfor
For {$i \in [n]$}
    Let $last_{e'} \leftarrow v_{e'}$ -for all $e' \in S$
    Let $v_{e'} \leftarrow last_{e'[x_i]}$ -for all $e' \in S$
endfor
return $v_e$
endprocedure

برهان

يتحقّق خوارزمية — Regular expression matching by a DFA ممّا إذا كانت السلسلة x∈Σ∗x\in \Sigma^* المعطاة يطابقها التعبير النمطي ee. فلكل تعبير نمطي ee، تملك هذه الخوارزمية عددًا ثابتًا من المتغيّرات البوليانية (على وجه التحديد، متغيّر ve′v_{e'} لكل e′∈S(e)e' \in S(e) ومتغيّر laste′last_{e'} لكل e′e' في S(e)S(e)، مستخدمةً الحقيقة أنّ e′[xi]∈S(e)e'[x_i] \in S(e) لكل e′∈S(e)e'\in S(e)). وهي تجري مرورًا واحدًا فوق سلسلة المُدخل. وعليه فإنّها تقابل آلة ذاتية محدودة حتمية. ونُثبت صحّتها بالحثّ على طول المُدخل nn. وبشكل أدقّ، سنُثبت أنّه قبل قراءة xix_i، تكون المتغيّرات ve′v_{e'} مساوية لـΦe′(x0⋯xi−1)\Phi_{e'}(x_0 \cdots x_{i-1}) لكل e′∈S(e)e' \in S(e). ففي الحالة i=0i=0 فهذا صحيح لأنّنا نهيّئ ve′=Φe′("")v_{e'} = \Phi_{e'}("") لكل e′∈S(e)e' \in S(e). وأما لـi>0i>0 فهذا صحيح بحكم الحثّ، إذ إنّ فرضية الحثّ تنص على أنّ laste′=Φe′(x0⋯xi−2)last_{e'} = \Phi_{e'}(x_0 \cdots x_{i-2}) لكل e′∈S(e)e' \in S(e)، وبتعريف المجموعة S(e′)S(e')، فإنّه لكل e′∈S(e)e' \in S(e) ولكل xi−1∈Σx_{i-1} \in \Sigma، يكون e′′=e′[xi−1]e'' = e'[x_{i-1}] في S(e)S(e)، وأنّ Φe′(x0⋯xi−1)=Φe′′(x0⋯xi)\Phi_{e'}(x_0 \cdots x_{i-1}) = \Phi_{e''}(x_0 \cdots x_i).

تكافؤ التعبيرات النمطية والآلات الذاتية

تذكّر أنّ الدالة البوليانية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} تُعرَّف بأنّها منتظمة إذا كانت مساويةً لـΦe\Phi_e لبعض التعبير النمطي ee. (بالمثل، تُعرَّف اللغة L⊆{0,1}∗L \subseteq \{0,1\}^* بأنّها منتظمة إذا وُجد تعبير نمطي ee بحيث يطابق ee السلسلة xx إذا وفقط إذا كان x∈Lx\in L.) والمبرهنة التالية هي النتيجة المركزية في نظرية الآلات الذاتية:

مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي

لتكن F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}. إذًا FF منتظمة إذا وفقط إذا وُجدت آلة ذاتية محدودة حتمية (T,S)(T,\mathcal{S}) تحسب FF.

أحد الاتجاهين يتبع من مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية، الذي يُظهر أنّه لكل تعبير نمطي ee يمكن حساب الدالة Φe\Phi_e بآلة ذاتية محدودة حتمية (انظر مثلًا الشكل التوضيحي). أمّا الاتجاه الآخر، فنُبيّن أنّه بمعلومية آلة ذاتية محدودة حتمية (T,S)(T,\mathcal{S})، يمكننا لكل v,w∈[C]v,w \in [C] أن نجد تعبيرًا نمطيًا يطابق x∈{0,1}∗x\in \{0,1\}^* إذا وفقط إذا كانت الآلة الذاتية المحدودة الحتمية، وهي تبدأ من الحالة vv، تنتهي في الحالة ww بعد قراءة xx.

/images/introtcs/lec_05_infinite-5.webp

بإعطائنا آلةً ذاتيةً محدودةً حتميةً بحالة ، نُعرِّف لكلّ  وعدد  الدالة  التي تُخرج واحدًا على المدخل  إذاً وفقط إذاً، حين تُشغَّل الآلة من الحالة  وتُعطى المدخل ، بلغت الحالة  وهي لا تمرّ إلاّ بالحالات الوسيطة .

برهان

بما أنّ مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية يُثبت اتجاه «فقط إذا»، فإنّنا لا نحتاج إلّا إلى إثبات اتجاه «إذا». لتكن A=(T,S)A=(T,\mathcal{S}) آلة ذاتية محدودة حتمية ذات CC حالات تحسب الدالة FF. نحتاج إلى إثبات أنّ FF منتظمة.

لكل v,w∈[C]v,w \in [C]، نترك Fv,w:{0,1}∗→{0,1}F_{v,w}:\{0,1\}^* \rightarrow \{0,1\} تكون الدالة التي تُسقط x∈{0,1}∗x\in \{0,1\}^* على 11 إذا وفقط إذا كانت الآلة الذاتية AA، وهي تبدأ من الحالة vv، تصل إلى الحالة ww إذا قرأت المُدخل xx. وسنُثبت أنّ Fv,wF_{v,w} منتظمة لكل v,wv,w. وهذا سيُثبت المبرهنة، إذ إنّ بحسب تعريف — الآلة الذاتية المحدودة الحتمية فإنّ F(x)F(x) تساوي ناتج «أو» (OR) للدوال F0,w(x)F_{0,w}(x) لكل w∈Sw\in \mathcal{S}. وعليه، إذا كانت لدينا تعبيرات نمطية لكل دالةٍ من الشكل Fv,wF_{v,w}، فإنّنا (باستخدام عملية ∣|) يمكننا الحصول على تعبير نمطي لـFF أيضًا.

لتقديم تعبيرات نمطية للدوال Fv,wF_{v,w}، نبدأ بتعريف الدوال التالية Fv,wtF_{v,w}^t: فلكل v,w∈[C]v,w \in [C] ولكل 0≤t≤C0 \leq t \leq C، يكون Fv,wt(x)=1F_{v,w}^t(x)=1 إذا وفقط إذا كانت الآلة الذاتية، وهي تبدأ من vv وتلاحظ xx، تصل إلى ww مع كون كلّ الحالات الوسيطة في المجموعة [t]={0,…,t−1}[t]=\{0,\ldots, t-1\} (انظر الشكل التوضيحي). أي إنّ v,wv,w نفسيهما قد يكونان خارج [t][t]، لكنّ Fv,wt(x)=1F_{v,w}^t(x)=1 إذا وفقط إذا لم تدخل الآلة الذاتية أبدًا، طوال تنفيذها على المُدخل xx ( حين تبدأ عند vv)، أيّ حالةٍ من الحالات خارج [t][t]، وانتهت مع ذلك عند ww. وإذا كان t=0t=0 فإنّ [t][t] هي المجموعة الخالية، ومن ثمّ يكون Fv,w0(x)=1F^0_{v,w}(x)=1 إذا وفقط إذا وصلت الآلة الذاتية إلى ww من vv مباشرةً على xx، دون أيّ حالة وسيطة. وإذا كان t=Ct=C فإنّ كلّ الحالات في [t][t]، ومن ثمّ Fv,wt=Fv,wF_{v,w}^t= F_{v,w}.

سنُثبت المبرهنة بالحثّ على tt، بأنّنا نبيّن أنّ Fv,wtF^t_{v,w} منتظمة لكل v,wv,w وtt. ففي الحالة الأساس t=0t=0، تكون Fv,w0F^0_{v,w} منتظمة لكل v,wv,w لأنّها يمكن وصفها بإحدى التعبيرات """" أو ∅\emptyset أو 00 أو 11 أو 0∣10|1. وبشكل أدقّ، إذا كان v=wv=w فإنّ Fv,w0(x)=1F^0_{v,w}(x)=1 إذا وفقط إذا كانت xx هي السلسلة الفارغة. وإذا كان v≠wv\neq w فإنّ Fv,w0(x)=1F^0_{v,w}(x)=1 إذا وفقط إذا كانت xx تتكوّن من رمزٍ واحد σ∈{0,1}\sigma \in \{0,1\} وكان T(v,σ)=wT(v,\sigma)=w. وعليه، فإنّ Fv,w0F^0_{v,w} في هذه الحالة يقابل أحد التعبيرات النمطية الأربعة 0∣10|1 أو 00 أو 11 أو ∅\emptyset، بحسب ما إذا كانت AA تنتقل إلى ww من vv حين تقرأ 00 أو 11 (أو كليهما)، أو أحد الرمزين فقط، أو لا شيء منهما.

خطوة الحثّ: والآن بعد أن رأينا الحالة الأساس، فلنُثبت الحالة العامة بالحثّ. ولنفترض، بحسب فرضية الحثّ، أنّه لكل v′,w′∈[C]v',w' \in [C] لدينا تعبير نمطي Rv′,w′tR_{v',w'}^t يحسب Fv′,w′tF_{v',w'}^t. نحتاج إلى إثبات أنّ Fv,wt+1F_{v,w}^{t+1} منتظمة لكل v,wv,w. إذا وصلت الآلة الذاتية من vv إلى ww باستخدام الحالات الوسيطة [t+1][t+1]، فإنّها تزور الحالة tt-ية صفر مرّات أو أكثر. وإذا كان المسار الذي يحمله الوسم xx يجعل الآلة الذاتية تنتقل من vv إلى ww دون زيارة الحالة tt-ية إطلاقًا، فإنّ xx يطابقه التعبير النمطي Rv,wtR_{v,w}^t. وإذا كان المسار الذي يحمله الوسم xx يجعل الآلة الذاتية تنتقل من vv إلى ww مع زيارة الحالة tt-ية k>0k>0 مرّات، فإنّنا نستطيع التفكير في هذا المسار على النحو التالي:

  • ينتقل أوّلًا من vv إلى tt باستخدام الحالات الوسيطة في [t−1][t-1] فقط.

  • ثمّ يذهب من tt إلى نفسه k−1k-1 مرّة باستخدام الحالات الوسيطة في [t−1][t-1] فقط

  • ثمّ يذهب من tt إلى ww باستخدام الحالات الوسيطة في [t−1][t-1] فقط.

وعليه، فإنّ السلسلة xx في هذه الحالة يطابقها التعبير النمطي Rv,tt(Rt,tt)∗Rt,wtR_{v,t}^t(R_{t,t}^t)^* R_{t,w}^t. (وانظر أيضًا الشكل التوضيحي.)

وعليه أيضًا، يمكننا حساب Fv,wt+1F_{v,w}^{t+1} باستخدام التعبير النمطي

Rv,wt  ∣  Rv,tt(Rt,tt)∗Rt,wt  .R_{v,w}^t \;|\; R_{v,t}^t(R_{t,t}^t)^* R_{t,w}^t\;.

وبهذا يكتمل إثبات خطوة الحثّ، وبالتالي إثبات المبرهنة.

إن كانت لدينا تعبيراتٌ منتظمة  تقابل  لكلّ ، فيمكننا الحصول على تعبيرٍ منتظم  يقابل . والملاحظة الجوهرية أنّ المسار من  إلى  المستعمل لـ إمّا أن لا يلمس  إطلاقًا (وفي هذه الحالة يُلتقط بالتعبير )، وإمّا أن يذهب من  إلى ، ثمّ يعود إلى  صفرًا أو أكثر من المرّات، ثمّ يذهب من  إلى  (وفي هذه الحالة يُلتقط بالتعبير ).

خصائص الإغلاق للتعبيرات النمطية

إذا كانت FF وGG دالتين منتظمتين يحسبهما التعبيران ee وff على الترتيب، فإنّ التعبير e∣fe|f يحسب الدالة H=F∨GH = F \vee G المعرَّفة بأنّ H(x)=F(x)∨G(x)H(x) = F(x) \vee G(x). وطريقةٌ أخرى لقول ذلك هي أنّ مجموعة الدوال المنتظمة مغلقة تحت عملية «أو». أي إنّ F∨GF \vee G منتظمة إن كانت FF وGG منتظمتين. ومن النتائج المهمّة المشتقّة من مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي أنّ هذه المجموعة مغلقة أيضًا تحت عملية النفي:

لمّة — التعبيرات النمطية مغلقة تحت التكملة

إذا كانت F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} منتظمة، فإنّ الدالة F‾\overline{F} منتظمة أيضًا، حيث F‾(x)=1−F(x)\overline{F}(x) = 1 - F(x) لكل x∈{0,1}∗x\in \{0,1\}^*.

برهان

إذا كانت FF منتظمة، فإنّها بحسب مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يمكن حسابها بآلة ذاتية محدودة حتمية AA. لكنّنا نستطيع عندها إنشاء آلة ذاتية محدودة حتمية A‾\overline{A} تؤدّي الحساب نفسه مع قلب مجموعة الحالات المقبولة. وستحسب الآلة الذاتية المحدودة الحتمية A‾\overline{A} الدالة F‾\overline{F}. وبحسب مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي فإنّ هذا يعني أنّ F‾\overline{F} منتظمة أيضًا.

بما أنّ a∧b=a‾∨b‾‾a \wedge b = \overline{\overline{a} \vee \overline{b}}، فإنّ لمّة — التعبيرات النمطية مغلقة تحت التكملة ينص على أنّ مجموعة الدوال المنتظمة مغلقة تحت عملية «وال» (AND) أيضًا. وعلاوةً على ذلك، بما أنّ «أو» والنفي و«وال» تكوّن أساسًا كونيًّا، فإنّ هذه المجموعة مغلقة أيضًا تحت NAND وXOR وأيّ دالةٍ متناهية أخرى. أي إنّ لدينا النتيجة التالية:

مبرهنة — إغلاق التعبيرات النمطية

لتكن f:{0,1}k→{0,1}f:\{0,1\}^k \rightarrow \{0,1\} أيّ دالة بوليانية متناهية، ولتكن F0,…,Fk−1:{0,1}∗→{0,1}F_0,\ldots,F_{k-1} : \{0,1\}^* \rightarrow \{0,1\} دوالًا منتظمة. إذًا الدالة G(x)=f(F0(x),F1(x),…,Fk−1(x))G(x) = f(F_0(x),F_1(x),\ldots,F_{k-1}(x)) منتظمة.

برهان

هذا نتيجةٌ مباشرة لانغلاق الدوال المنتظمة تحت «أو» والنفي (وبالتالي تحت «وال»)، مقترنةً بـمبرهنة — شمولية الدوائر المنطقية الذي ينص على أنّ كلّ ff يمكن حسابه بدائرة بوليانية (وهي مجرّد مزيج من عمليات «وال» و«أو» والنفي).

قيود التعبيرات النمطية ومبرهنة الضخّ

كفاية مطابقة التعبيرات النمطية تجعلها مفيدةً جدًّا. ولهذا فإنّ أنظمة التشغيل ومحرّرات النصوص تقيّد واجهة البحث فيها غالبًا على التعبيرات النمطية، ولا تسمح بالبحث عبر تحديد دالةٍ اعتباطية. لكنّ هذه الكفاءة تأتي بثمن. وكما رأينا، لا يمكن للتعبيرات النمطية حساب كلّ دالة. وفي الواقع، هناك بعض الدوال البسيطة جدًّا (والمفيدة!) التي لا يمكنها حسابها. وهنا مثالٌ على ذلك:

لمّة — مطابقة الأقواس

لتكن Σ={⟨,⟩}\Sigma = \{\langle ,\rangle \}، ولتكن MATCHPAREN:Σ∗→{0,1}MATCHPAREN:\Sigma^* \rightarrow \{0,1\} الدالة التي، عند إعطائها سلسلة أقواس، تُخرج 11 إذا وفقط إذا كان كلّ قوس فتح مطابقًا بقوس إغلاق مقابل. إذًا لا يوجد تعبير نمطي فوق Σ\Sigma يحسب MATCHPARENMATCHPAREN.

لمّة — مطابقة الأقواس نتيجةٌ للنتيجة التالية، المعروفة بـ_مبرهنة الضخّ_ (pumping lemma):

مبرهنة — مبرهنة الضخّ

لتكن ee تعبيرًا نمطيًا فوق أبجديةٍ ما Σ\Sigma. إذًا يوجد عددٌ ما n0n_0 بحيث إنّ لكل w∈Σ∗w\in \Sigma^* ذات ∣w∣>n0|w|>n_0 وΦe(w)=1\Phi_{e}(w)=1، يمكننا كتابة w=xyzw=xyz لسلاسل x,y,z∈Σ∗x,y,z \in \Sigma^* تحقق الشروط التالية:

  1. ∣y∣≥1|y| \geq 1.

  2. ∣xy∣≤n0|xy| \leq n_0.

  3. Φe(xykz)=1\Phi_{e}(xy^kz)=1 لكل k∈Nk\in \N.

/images/introtcs/lec_05_infinite-6.webp

الفكرة وراء الإثبات هي التالية. لتكن n0n_0 ضعف عدد الرموز المستخدَمة في التعبير ee، فإنّ الطريقة الوحيدة التي يمكن أن يوجد فيها ww ذات ∣w∣>n0|w|>n_0 وΦe(w)=1\Phi_{e}(w)=1 هي أن يحتوي ee على المشغّل ∗* (أي النجمة)، وأنّ هناك سلسلةً جزئية غير فارغة yy من ww طابقها (e′)∗(e')^* لبعض تعبيرٍ جزئي e′e' من ee. يمكننا الآن تكرار yy أيّ عددٍ من المرّات وما زلنا نُنتج سلسلةً مطابقة. وانظر أيضًا الشكل التوضيحي.

توقّف وتأمّل

إنّ مبرهنة الضخّ ثقيلة قليلًا في صياغتها، لكنّ إحدى طرائق تذكّرها هي أنّها ببساطة تقول ما يلي: «إذا كانت سلسلةٌ تطابق تعبيرًا نمطيًا وطولها كافٍ، فلا بدّ أن يطابق أحدُ سلاسلها الفرعية باستخدام المشغّل ∗*».

برهان

لإثبات المبرهنة رسميًا، نستعمل الحثّ على طول التعبير. وكما في كلّ براهين الحثّ، سيكون هذا طويلًا بعض الشيء، لكنّ الأمر في نهاية المطاف يعود مباشرةً إلى البديهة أعلاه بأنّنا في موضعٍ ما كان علينا استعمال عملية النجمة. وقراءة هذا الإثبات، وفهم كيفية ارتباط الإثبات الرسمي أدناه بالبديهة أعلاه، هي طريقةٌ جيّدة جدًا كي نتعوّد على براهين الحثّ من هذا النوع.

فرضية الحثّ لدينا هي أنّه لتعبيرٍ من الطول nn، يحقّق n0=2nn_0=2n شروط المبرهنة. أما الحالة الأساس (base case) فهي أن يكون التعبير رمزًا واحدًا σ∈Σ\sigma \in \Sigma أو أن يكون التعبير ∅\emptyset أو """". في كلّ هذه الحالات تتحقّق شروط المبرهنة لمجرّد أنّ n0=2n_0=2، ولا توجد سلسلة xx من طولٍ أكبر من n0n_0 يطابقها التعبير.

ونُثبت الآن خطوة الحثّ (inductive step). لتكن ee تعبيرًا نمطيًا من n>1n>1 رمزًا. نضع n0=2nn_0=2n ولتكن w∈Σ∗w\in \Sigma^* سلسلةً تحقق ∣w∣>n0|w|>n_0. وبما أنّ ee يتضمّن أكثر من رمز واحد، فإنّه يأخذ أحد الأشكال (a) e′∣e′′e' | e''، أو (b) (e′)(e′′)(e')(e'')، أو (c) (e′)∗(e')^*، حيث في كلّ هذه الحالات يكون للتعبيرين الجزئيين e′e' وe′′e'' عددٌ من الرموز أقلّ من ee، ومن ثمّ يحقّقان فرضية الحثّ.

في الحالة (a)، فإنّ كلّ سلسلة ww يطابقها ee يجب أن يطابقها e′e' أو e′′e''. وإذا كان e′e' يطابق ww، فإنّه بما أنّ ∣w∣>2∣e′∣|w|>2|e'|، وبحسب فرضية الحثّ، توجد x,y,zx,y,z مع ∣y∣≥1|y| \geq 1 و∣xy∣≤2∣e′∣<n0|xy| \leq 2|e'| <n_0 بحيث يطابق e′e' (وبالتالي أيضًا e=e′∣e′′e=e'|e'') السلسلة xykzxy^kz لكلّ kk. والحجّة ذاتها تعمل في الحالة التي يكون فيها e′′e'' يطابق ww.

في الحالة (b)، إذا كان ww يطابقه (e′)(e′′)(e')(e'')، يمكننا كتابة w=w′w′′w=w'w'' بحيث يطابق e′e' السلسلة w′w' ويطابق e′′e'' السلسلة w′′w''. ونقسم إلى حالاتٍ فرعية. فإذا كان ∣w′∣>2∣e′∣|w'|>2|e'|، فإنّه بحسب فرضية الحثّ توجد x,y,z′x,y,z' مع ∣y∣≥1|y| \geq 1 و∣xy∣≤2∣e′∣<n0|xy| \leq 2|e'| < n_0 بحيث w′=xyz′w'=xyz' ويطابق e′e' السلسلة xykz′xy^kz' لكلّ k∈Nk\in \N. وهذا يُكمل الإثبات، إذ إنّنا إذا وضعنا z=z′w′′z=z'w'' رأينا أنّ w=w′w′′=xyzw=w'w''=xyz وأنّ e=(e′)(e′′)e=(e')(e'') يطابق xykzxy^kz لكلّ k∈Nk\in \N. وإلّا، إذا كان ∣w′∣≤2∣e′∣|w'| \leq 2|e'|، فإنّه بما أنّ ∣w∣=∣w′∣+∣w′′∣>n0=2(∣e′∣+∣e′′∣)|w|=|w'|+|w''|>n_0=2(|e'|+|e''|)، لا بدّ من أنّ ∣w′′∣>2∣e′′∣|w''|>2|e''|. وعليه، بحسب فرضية الحثّ، توجد x′,y,zx',y,z بحيث ∣y∣≥1|y| \geq 1 و∣x′y∣≤2∣e′′∣|x'y| \leq 2|e''| ويطابق e′′e'' السلسلة x′ykzx'y^kz لكلّ k∈Nk\in \N. لكنّنا الآن إذا وضعنا x=w′x′x=w'x' رأينا أنّ ∣xy∣=∣w′∣+∣x′y∣≤2∣e′∣+2∣e′′∣=n0|xy| = |w'| + |x'y| \leq 2|e'| + 2|e''| =n_0، ومن جهة أخرى فإنّ التعبير e=(e′)(e′′)e=(e')(e'') يطابق xykz=w′x′ykzxy^kz = w'x'y^kz لكلّ k∈Nk\in \N.

في الحالة (c)، إذا كان ww يطابقه (e′)∗(e')^*، فإنّ w=w0⋯wtw= w_0\cdots w_t حيث لكلّ i∈[t]i\in [t] تكون wiw_i سلسلةً غير فارغة يطابقها e′e'. وإذا كان ∣w0∣>2∣e′∣|w_0|>2|e'|، فيمكننا استعمال الأسلوب نفسه المستخدَم أعلاه في حالة الدمج. وإلّا، فإنّنا نلاحظ مجرّدًا أنّه إذا كانت xx هي السلسلة الفارغة، وy=w0y=w_0، وz=w1⋯wtz=w_1\cdots w_t، فإنّ ∣xy∣≤n0|xy| \leq n_0 وتُطابَق xykzxy^kz بواسطة (e′)∗(e')^* لكلّ k∈Nk\in \N.

ملاحظة — التعريفات العودية وبراهين الحثّ

عندما يكون كائنٌ ما معرَّفًا بشكلٍ عودي (كما في حالة التعبيرات النمطية)، فإنّه من الطبيعي أن نُثبت خصائص هذا النوع من الكائنات بالحثّ. أي إنّنا إذا أردنا إثبات أنّ جميع كائنات هذا النوع تملك الخاصية PP، فإنّ من الطبيعي أن نستعمل خطوةَ حثٍّ تقول إنّه إذا كان لدى o′,o′′,o′′′o',o'',o''' إلخ الخاصية PP، فإنّ الكائن oo الناتج عن تركيبها يملكها أيضًا.

باستعمال مبرهنة الضخّ، يمكننا بسهولة إثبات لمّة — مطابقة الأقواس (أي إثبات أنّ دالة «مطابقة الأقواس» غير منتظمة):

برهان

لنفترض، بغرض التناقض، أنّه يوجد تعبير ee بحيث Φe=MATCHPAREN\Phi_{e}= MATCHPAREN. ولتكن n0n_0 العددَ الناتج من مبرهنة — مبرهنة الضخّ، ولتكن w=⟨n0⟩n0w =\langle^{n_0}\rangle^{n_0} (أي n0n_0 قوس فتح يليه n0n_0 قوس إغلاق). نرى عندها أنّه إذا كتبنا w=xyzw=xyz كما في مبرهنة — مبرهنة الضخّ، فإنّ الشرط ∣xy∣≤n0|xy| \leq n_0 يعني أنّ yy تتكوّن من أقواس الفتح فقط. ومن ثمّ فإنّ السلسلة xy2zxy^2z ستحتوي أقواس فتح أكثر من أقواس الإغلاق. وعليه فإنّ MATCHPAREN(xy2z)=0MATCHPAREN(xy^2z)=0، لكنّ مبرهنة الضخّ تقول Φe(xy2z)=1\Phi_{e}(xy^2z)=1، وهذا يناقض افتراضنا أنّ Φe=MATCHPAREN\Phi_{e}=MATCHPAREN.

إنّ مبرهنة الضخّ أداةٌ مفيدة جدًّا لإظهار أنّ دوالّ معيّنة لا يمكن حسابها بتعبيرٍ نمطي. لكنّها ليست شرطًا «إذا وفقط إذا» للانظام: فهناك دوالّ غير منتظمة تحقّق شروط مبرهنة الضخّ رغم ذلك. ولفهم مبرهنة الضخّ، من الضروري اتّباع ترتيب المكمّيات في مبرهنة — مبرهنة الضخّ. وبشكل خاص، فإنّ العدد n0n_0 في نصّ مبرهنة — مبرهنة الضخّ يعتمد على التعبير النمطي (في الإثبات اخترنا n0n_0 يساوي ضعف عدد الرموز في التعبير). وعليه، إذا أردنا استعمال مبرهنة الضخّ لاستبعاد وجود تعبيرٍ نمطي ee يحسب دالةً ما FF، فيجب أن نتمكّن من اختيار مُدخلٍ مناسب w∈{0,1}∗w\in \{0,1\}^* يمكن أن يكون كبيرًا اعتباطيًا ويحقّق F(w)=1F(w)=1. وهذا يبدو معقولًا إذا فكّرت في البديهة وراء مبرهنة الضخّ: فنحن نحتاج ww كبيرًا بما يكفي لإجبار استعمال مشغّل النجمة.

/images/introtcs/lec_05_infinite-7.webp

تمرين محلول — المتناظرات ليست منتظمة

برهن أنّ الدالة التالية فوق الأبجدية {0,1,;}\{0,1,; \} ليست منتظمة: PAL(w)=1PAL(w)=1 إذا وفقط إذا كان w=u;uRw = u;u^R حيث u∈{0,1}∗u \in \{0,1\}^* وuRu^R تدلّ على uu «معكوسة»: أي السلسلة u∣u∣−1⋯u0u_{|u|-1}\cdots u_0. (غالبًا ما تُعرَّف دالةُ المتناظر (Palindrome) دون رمز فاصل صريح ;;، لكنّ النسخة التي تحتوي فاصلًا من هذا النوع أنظف قليلًا، ولذلك نستعملها هنا. وهذا لا يصنع فرقًا كبيرًا، إذ يمكن بسهولة ترميز الفاصل كسلسلةٍ ثنائية خاصة بدلًا من ذلك.)

الحل

نستعمل مبرهنة الضخّ. ولنفترض، بغرض التناقض، أنّه يوجد تعبيرٌ نمطي ee يحسب PALPAL، ولتكن n0n_0 العددَ الذي تعطيه مبرهنة الضخّ (مبرهنة — مبرهنة الضخّ). نأخذ بعين الاعتبار السلسلة w=0n0;0n0w = 0^{n_0};0^{n_0}. وبما أنّ معكوس سلسلةِ أصفار هي سلسلةُ أصفار، فإنّ PAL(w)=1PAL(w)=1. والآن، بمبرهنة الضخّ، إذا كانت PALPAL محسوبةً بـee، يمكننا كتابة w=xyzw=xyz بحيث ∣xy∣≤n0|xy| \leq n_0 و∣y∣≥1|y|\geq 1 وPAL(xykz)=1PAL(xy^kz)=1 لكلّ k∈Nk\in \N. وبخاصةٍ، لا بدّ أن يتحقّق PAL(xz)=1PAL(xz)=1، لكنّ هذا تناقض، إذ إنّ xz=0n0−∣y∣;0n0xz=0^{n_0-|y|};0^{n_0} ومن ثمّ فإنّ جزأيه ليسا بالطول نفسه، وهما بالتالي ليس أحدهما معكوس الآخر.

وللمثالٍ آخر على إثباتٍ يعتمد على مبرهنة الضخّ، انظر الشكل التوضيحي الذي يوضّح رسمًا تخطيطيًّا لإثبات عدم انتظام الدالة F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} المعرَّفة بأنّ F(x)=1F(x)=1 إذا وفقط إذا كان x=0n1nx=0^n1^n لبعض n∈Nn\in \N (أي أنّ xx تتكوّن من سلسلةِ أصفار متتالية، يليها سلسلةُ آحاد متتالية من الطول نفسه).

الإجابة عن الأسئلة الدلالية حول التعبيرات النمطية

للتعبيرات النمطية تطبيقات تتجاوز البحث. فعلى سبيل المثال، تُستخدَم التعبيرات النمطية كثيرًا لتحديد الرموز المُجزِّأة (tokens) (مثل ما يُعدّ معرّف متغيّر صالحًا أو كلمةً مفتاحية) في تصميم المحلِّلات النحوية (parsers) و_المصرِّفات_ (compilers) و_المفسِّرات_ (interpreters) الخاصة بلغات البرمجة. وللتعبيرات النمطية تطبيقات أخرى أيضًا: فعلى سبيل المثال، انتقل عالم الشبكات في السنوات الأخيرة من الطوبولوجيات الثابتة إلى «الشبكات المُعرَّفة برمجيًّا». وتُوجَّه هذه الشبكات بمفاتيحٍ قابلة للبرمجة تستطيع تنفيذ سياسات (policies) مثل «إذا كانت الحزمة مؤمَّنة بـSSL فوجّهها إلى A، وإلا فوجّهها إلى B». ولتمثيل هذه السياسات نحتاج لغةً تكون من جهةٍ معبِّرةً بما يكفي لالتقاط السياسات التي نريد تنفيذها، ومن جهةٍ أخرى مقيّدةً بما يكفي حتى نتمكّن من تنفيذها بسرعة على مستوى الشبكة، وأيضًا حتّى نتمكّن من الإجابة عن أسئلةٍ مثل «هل يستطيع C رؤية الحزم المنقولة من A إلى B؟». وتستعمل لغة برمجة الشبكات NetKAT (NetKAT network programming language) صيغةً من التعبيرات النمطية لتحقيق ذلك بالضبط. ولهذا التطبيق، من المهمّ ألّا نتمكّن فحسب من الإجابة عمّا إذا كان تعبير ee يطابق سلسلة xx، بل أن نتمكّن أيضًا من الإجابة عن أسئلةٍ دلالية (semantic questions) حول التعبيرات النمطية مثل «هل يحسب التعبيران ee وe′e' الدالة نفسها؟» و«هل توجد سلسلة xx يطابقها التعبير ee؟». وتُظهر المبرهنة التالية أنّه يمكننا الإجابة عن السؤال الأخير:

مبرهنة — إمكان حساب خِواء اللغات المنتظمة

توجد خوارزمية تُعطى تعبيرًا نمطيًا ee فتُخرج 11 إذا وفقط إذا كانت Φe\Phi_{e} هي الدالة الثابتة صفر.

الفكرة أنّه يمكننا ملاحظة ذلك مباشرةً من بنية التعبير. فالطريقة الوحيدة التي يحسب بها تعبيرٌ نمطي ee الدالة الثابتة صفر هي أن يكون ee على الشكل ∅\emptyset أو أن ينتج عن دمج ∅\emptyset مع تعبيراتٍ أخرى.

برهان

نعرّف التعبير النمطي بأنّه «خاوٍ» (empty) إذا كان يحسب الدالة الثابتة صفر. وبمعلومية تعبيرٍ نمطي ee، يمكننا تحديد ما إذا كان ee خاويًا باستخدام القواعد التالية:

  • إذا كان ee على الشكل σ\sigma أو """" فليس خاويًا.

  • إذا كان ee ليس خاويًا فإنّ e∣e′e|e' ليس خاويًا لكلّ e′e'.

  • إذا كان ee ليس خاويًا فإنّ e∗e^* ليس خاويًا.

  • إذا كان ee وe′e' كلاهما ليس خاويًا فإنّ e  e′e\; e' ليس خاويًا.

  • ∅\emptyset خاوٍ.

وباستعمال هذه القواعد، من السهل إيجاد خوارزميةٍ عودية لتحديد الخِواء.

باستعمال مبرهنة — إمكان حساب خِواء اللغات المنتظمة، يمكننا الحصول على خوارزمية تحدّد ما إذا كان تعبيران نمطيان ee وe′e' متكافئين، بالمعنى أنّهما يحسبان الدالة نفسها.

مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية

لتكن REGEQ:{0,1}∗→{0,1}REGEQ:\{0,1\}^* \rightarrow \{0,1\} الدالة التي، على مُدخل (سلسلة تمثّل) زوجٍ من التعبيرات النمطية e,e′e,e'، يكون REGEQ(e,e′)=1REGEQ(e,e')=1 إذا وفقط إذا كان Φe=Φe′\Phi_{e} = \Phi_{e'}. إذًا توجد خوارزمية تحسب REGEQREGEQ.

الفكرة هي أن نُبيّن أنّه بمعلومية زوجٍ من التعبيرات النمطية ee وe′e' يمكننا إيجاد تعبير e′′e'' بحيث يكون Φe′′(x)=1\Phi_{e''}(x)=1 إذا وفقط إذا كان Φe(x)≠Φe′(x)\Phi_e(x) \neq \Phi_{e'} (x). وعليه فإنّ Φe′′\Phi_{e''} هي الدالة الثابتة صفر إذا وفقط إذا كان ee وe′e' متكافئين، وبذلك يمكننا اختبار خِواء e′′e'' لتحديد تكافؤ ee وe′e'.

برهان

سنُثبت مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية انطلاقًا من مبرهنة — إمكان حساب خِواء اللغات المنتظمة. (والمبرهنتان متكافئتان في الواقع: فمن السهل إثبات مبرهنة — إمكان حساب خِواء اللغات المنتظمة انطلاقًا من مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية، إذ إنّ التحقّق من الخِواء هو نفسه التحقّق من التكافؤ مع التعبير ∅\emptyset.) بمعلومية تعبيرين نمطيين ee وe′e'، سنحسب تعبيرًا e′′e'' بحيث يكون Φe′′(x)=1\Phi_{e''}(x) =1 إذا وفقط إذا كان Φe(x)≠Φe′(x)\Phi_e(x) \neq \Phi_{e'}(x). ومن الممكن ملاحظة أنّ ee يكافئ e′e' إذا وفقط إذا كان e′′e'' خاويًا.

نبدأ بالملاحظة على أنّه لكل بتّ a,b∈{0,1}a,b \in \{0,1\}، فإنّ a≠ba \neq b إذا وفقط إذا كان (a∧b‾)  ∨  (a‾∧b)  . (a \wedge \overline{b}) \; \vee \; (\overline{a} \wedge b) \;.

ولذلك نحتاج إلى إنشاء e′′e'' بحيث لكل xx،

Φe′′(x)=(Φe(x)∧Φe′(x)‾)  ∨  (Φe(x)‾∧Φe′(x))  . \Phi_{e''}(x) = (\Phi_{e}(x) \wedge \overline{\Phi_{e'}(x)}) \; \vee \; (\overline{\Phi_{e}(x)} \wedge \Phi_{e'}(x)) \;.

ولإنشاء التعبير e′′e''، سنُبيّن كيف يمكننا، بمعلومية أيّ زوجٍ من التعبيرين ee وe′e'، إنشاء تعبيرَي e∧e′e\wedge e' وe‾\overline{e} اللذين يحسبان الدالتين Φe∧Φe′\Phi_{e} \wedge \Phi_{e'} وΦe‾\overline{\Phi_{e}} على الترتيب. (وحساب التعبير الخاص بـe∨e′e \vee e' مباشر باستعمال عملية ∣| في التعبيرات النمطية.) وبشكل أدقّ، فإنّ بحسب لمّة — التعبيرات النمطية مغلقة تحت التكملة فإنّ الدوال المنتظمة مغلقة تحت النفي، ما يعني أنّه لكل تعبيرٍ نمطي ee يوجد تعبير e‾\overline{e} بحيث Φe‾(x)=1−Φe(x)\Phi_{\overline{e}}(x) = 1 - \Phi_{e}(x) لكل x∈{0,1}∗x\in \{0,1\}^*. والآن، لكل تعبيرين ee وe′e'، فإنّ التعبير e∧e′=(e‾∣e′‾)‾ e \wedge e' = \overline{(\overline{e} | \overline{e'})} يحسب «وال» بين التعبيرين. وبمعلومية هذين التحويلين، نرى أنّه لكل تعبيرين نمطيين ee وe′e' يمكننا إيجاد تعبيرٍ نمطي e′′e'' يحقق المعادلة بحيث يكون e′′e'' خاويًا إذا وفقط إذا كان ee وe′e' متكافئين.

خلاصة

  • نموذجة المهام الحاسوبية على مداخل ذات حجمٍ اعتباطي باستخدام دوال لا نهائية F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^*.
  • تأخذ هذه الدوال مُدخلًا على شكل سلسلة طويلة اعتباطيًّا (لكنها تبقى متناهية!)، ولا يمكن وصفها بجدولٍ متناهٍ من المداخل والمخارج.
  • تُسمّى الدالة ذات الخرج البتّي الواحد دالة بوليانية، وتُعدّ مهمة حسابها مكافئةً لاتخاذ قرار بشأن لغة L⊆{0,1}∗L\subseteq \{0,1\}^*.
  • الآلات الذاتية المحدودة الحتمية (DFAs) نموذجٌ بسيط لحساب الدوال البوليانية (اللا نهائية).
  • هناك بعض الدوال التي لا يمكن حسابها بآلات ذاتية محدودة حتمية.
  • مجموعة الدوال القابلة للحساب بآلات ذاتية محدودة حتمية هي نفسها مجموعة اللغات التي يمكن التعرّف عليها بالتعبيرات النمطية.

التمارين

تمرين — خصائص الإغلاق للدوال المنتظمة

لنفترض أنّ F,G:{0,1}∗→{0,1}F,G:\{0,1\}^* \rightarrow \{0,1\} منتظمتان. لكلٍّ من تعريفات الدالة HH التالية، إمّا أن تُثبت أنّ HH منتظمة دائمًا أو تعطي مثالًا مضادًا على F,GF,G المنتظمتين يجعلان HH غير منتظمة.

  1. H(x)=F(x)∨G(x)H(x) = F(x) \vee G(x).

  2. H(x)=F(x)∧G(x)H(x) = F(x) \wedge G(x)

  3. H(x)=NAND(F(x),G(x))H(x) = NAND(F(x),G(x)).

  4. H(x)=F(xR)H(x) = F(x^R) حيث xRx^R هو معكوس xx: أي xR=xn−1xn−2⋯xox^R = x_{n-1}x_{n-2} \cdots x_o مع n=∣x∣n=|x|.

  5. H(x)={1x=uv s.t. F(u)=G(v)=10otherwiseH(x) = \begin{cases}1 & x=uv \text{ s.t. } F(u)=G(v)=1 \\ 0 & \text{otherwise} \end{cases}

  6. H(x)={1x=uu s.t. F(u)=G(u)=10otherwiseH(x) = \begin{cases}1 & x=uu \text{ s.t. } F(u)=G(u)=1 \\ 0 & \text{otherwise} \end{cases}

  7. H(x)={1x=uuR s.t. F(u)=G(u)=10otherwiseH(x) = \begin{cases}1 & x=uu^R \text{ s.t. } F(u)=G(u)=1 \\ 0 & \text{otherwise} \end{cases}

تمرين

إحدى الدالتين التاليتين اللتين تُسقطان {0,1}∗\{0,1\}^* في {0,1}\{0,1\} يمكن حسابها بتعبيرٍ نمطي، والأخرى لا يمكن. فاكتب التعبير الذي يحسب الدالة القابلة للحساب، وبرهن مستعملًا مبرهنة الضخّ استحالة ذلك في حالة الدالة الأخرى.

  • F(x)=1F(x)=1 إذا كان 44 يقسم ∑i=0∣x∣−1xi\sum_{i=0}^{|x|-1} x_i، وF(x)=0F(x)=0 فيما عدا ذلك.

  • G(x)=1G(x) = 1 إذا وفقط إذا كان ∑i=0∣x∣−1xi≥∣x∣/4\sum_{i=0}^{|x|-1} x_i \geq |x|/4، وG(x)=0G(x)=0 فيما عدا ذلك.

تمرين — عدم الانتظام

  1. برهن أنّ الدالة التالية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} ليست منتظمة. فلكل x∈{0,1}∗x\in \{0,1\}^*، يكون F(x)=1F(x)=1 إذا وفقط إذا كان xx على الشكل x=13ix=1^{3^i} لبعض i>0i>0.

  2. برهن أنّ الدالة التالية F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} ليست منتظمة. فلكل x∈{0,1}∗x\in \{0,1\}^*، يكون F(x)=1F(x)=1 إذا وفقط إذا كان ∑jxj=3i\sum_j x_j = 3^i لبعض i>0i>0.

ملاحظات مرجعية

العلاقة بين التعبيرات النمطية والآلات المتناهية موضوعٌ جميل، لا نلمس منه في هذا النصّ إلّا القليل. وقد عولج بمزيدٍ من التفصيل في [@SipserBook, @hopcroft, @kozen1997automata]. وتناقش هذه النصوص أيضًا مواضيعَ مثل الآلات الذاتية غير الحتمية (non-deterministic finite automata, NFA) والعلاقة بين القواعد الخالية من السياق والآلات ذات المكدّس.

أُنشئت الآلة الذاتية في الشكل التوضيحي باستخدام محاكي آلات الحالات المتناهية (FSM simulator) لإيفان زوزاك (Ivan Zuzak) وفيدرانا يانكوفيتش (Vedrana Jankovic). وإثباتنا لـمبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يرتبط بدقّة بـمبرهنة ميهيل-نيرود (Myhill-Nerode Theorem). ويمكن صياغة أحد اتجاهَي مبرهنة ميهيل-نيرود بأنّه إذا كان ee تعبيرًا نمطيًا فإنّ هناك عددًا متناهىً على الأكثر من السلاسل z0,…,zk−1z_0,\ldots,z_{k-1} بحيث إنّ Φe[zi]≠Φe[zj]\Phi_{e[z_i]} \neq \Phi_{e[z_j]} لكلّ 0≤i≠j<k0 \leq i\neq j < k.