دوال ذات مداخل لا نهائية، وآلات ذاتية، وتعبيرات نمطية { #chapinfinite }
دوال ذات مداخل لا نهائية، وآلات ذاتية، وتعبيرات نمطية
- تعريف دوال على مداخل ذات طول غير محدود، لا يمكن وصفها بجدول متناهٍ الحجم من المداخل والمخارج.
- التكافؤ مع مهمة اتخاذ قرار الانتماء إلى لغة ما.
- الآلات الذاتية المحدودة الحتمية (اختياري): مثال بسيط لنموذج للحوسبة غير المحدودة.
- التكافؤ مع التعبيرات النمطية.
"الخوارزمية جواب متناهٍ على عدد لا نهائي من الأسئلة."، منسوب إلى ستيفن كلين (Stephen Kleene).
لنموذج الدوائر البوليانية (أو بما يكافئه، لغة البرمجة NAND-CIRC) عيب مهم واحد: فالدائرة البوليانية لا تستطيع إلا حساب دالة متنهية . وبتحديد، بما أن كل بوابة لها مُدخلان، فإن دائرة ذات حجم يمكنها الحساب على مدخل طوله على الأكثر . وعليه فإن هذا النموذج لا يجسّد تصورنا الحدسي للخوارزمية بوصفها وصفة واحدة (single recipe) لحساب دالة قد تكون لا نهائية. على سبيل المثال، خوارزمية الضرب المعيارية في المدرسة الابتدائية هي خوارزمية واحدة تضرب الأعداد من جميع الأطوال. لكننا لا نستطيع التعبير عن هذه الخوارزمية بدائرة واحدة، وإنما نحتاج إلى دائرة مختلفة (أو بما يكافئها، برنامج NAND-CIRC) لكل طول مدخل (انظر الشكل التوضيحي).

في هذا الفصل، نوسّع تعريفنا للمهام الحاسوبية (computational tasks) لنأخذ في الحسبان دوالًا مجالها غير محدود هو . نركّز على مسألة تعريف ماذا هي المهام التي نريد حسابها، تاركين في الغالب مسألة كيف نحسبها إلى فصول لاحقة، حيث سنرى آلات تورينج (Turing machines) ونماذج حوسبة أخرى للحساب على مداخل غير محدودة الطول. لكننا سنرى هنا مثالًا واحدًا لنموذج حساب بسيط ومقيَّد، وهو الآلات الذاتية المحدودة الحتمية (deterministic finite automata, DFAs).
في هذا الفصل، نناقش دوال تأخذ كمدخلات سلاسل بطول اعتباطي. وسنركّز في كثير من الأحيان على الحالة الخاصة من الدوال البوليانية، حيث يكون الخرج بتًّا واحدًا. وما زالت هذه دوال لا نهائية، لأن مداخلها ذات طول غير محدود، ومن ثمّ لا يمكن حساب مثل هذه الدالة بأي دائرة بوليانية واحدة.
في النصف الثاني من هذا الفصل، نناقش الآلات المحدودة (finite automata)، وهو نموذج حوسبة يستطيع حساب دوال بطول غير محدود. الآلات المحدودة ليست قوية بقدر Python أو غيرها من لغات البرمجة العامة الغرض، لكنها يمكن أن تكون مدخلًا (introduction) إلى هذه النماذج الأكثر عمومية. كما نبيّن أيضًا نتيجة جميلة: الدوال التي يمكن حسابها بالآلات المحدودة هي بالضبط الدوال التي تقابل التعبيرات النمطية (regular expressions). لكن يجوز للقارئ كذلك أن يتخطّى الآلات الذاتيّة ويمضي مباشرةً إلى مناقشتنا لـ_آلات تورينج_ في الحلقات وما لا نهاية.
دوال ذات مداخل بطول غير محدود
حتى الآن، كنا نعتبر المهمة الحاسوبية تحويل سلسلة ما من الطول إلى سلسلة من الطول . لكن المهام الحاسوبية، في العموم، قد تتضمّن مداخل ذات طول غير محدود، بمعنى أنه لا سقف لطولها. على سبيل المثال، الدالة التالية المكتوبة بلغة Python تحسب الدالة ، حيث تساوي إذا وفقط إذا كان عدد في فرديًا. (بعبارة أخرى، لكل .) وعلى بساطته، لا يمكن حساب دالة بدائرة بوليانية. بدلًا من ذلك، لكل يمكننا حساب (تقييد على ) باستخدام دائرة مختلفة (انظر مثلًا الشكل التوضيحي).
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

لقد درسنا في هذا الكتاب من قبل حساب دوال متنهية . ويمكن دائمًا وصف مثل هذه الدالة بسرد كل قيمها الـ التي تأخذها على المداخل . في هذا الفصل، نأخذ في الحسبان دوال مثل التي تقبل مداخل بحجم غير محدود. ورغم أننا نستطيع وصف باستخدام عدد متناهٍ من الرموز (بل لقد فعلنا للتو ذلك أعلاه)، إلا أنها تقبل عددًا لا نهائيًا من المداخل الممكنة، ومن ثمّ لا نستطيع ببساطة أن نكتب كل قيمها. الحال نفسها تنطبق على كثير من الدوال الأخرى التي تلتقط مهام حاسوبية مهمة، بما فيها الجمع، والضرب، والترتيب، وإيجاد المسارات في الرسوم البيانية، وملاءمة المنحنيات للنقاط، وهكذا. وللتناقض مع الحالة المتناهية، سنسمّي أحيانًا الدالة (أو ) بـ_لا نهائية_. لكن هذا لا يعني أن تأخذ كمدخل سلاسل ذات طول لا نهائي! بل يعني فقط أن تستطيع أن تأخذ كمدخل سلسلة قد يكون طولها اعتباطيًا، ومن ثمّ لا نستطيع ببساطة أن نكتب جدولًا لكل مخارج على المداخل المختلفة.
دالة تحدّد المهمة الحاسوبية التي تحوّل مدخلًا إلى الخرج .
وكما رأينا من قبل، فإن قصر انتباهنا على الدوال التي تستخدم سلاسل ثنائية كمداخل ومخارج لا ينتقص من عموميتنا، إذ يمكن ترميز كائنات أخرى، بما فيها الأعداد والقوائم والمصفوفات والصور والفيديوهات وغيرها، كسلاسل ثنائية.
وكما من قبل، من الضروري التمييز بين المواصفة (specification) و_التنفيذ_ (implementation). على سبيل المثال، تأمّل الدالة التالية:
هذه دالة معرّفة جيدًا رياضيًا. فلكل ، تملك قيمة فريدة إمّا أو .
لكن، حتى اللحظة، لا يعرف أحد برنامجًا في لغة Python يحسب هذه الدالة.
تفترض حدس الأعداد الأولية التوأمة (Twin prime conjecture) أنه لكل يوجد بحيث يكون و أوليين معًا.
وإن كان هذا الحدس صادقًا، فإن حساب سهل حقًا - فبرنامج def T(x): return 1 سيفي بالغرض.
غير أن الرياضيين حاولوا منذ عام 1849 إثبات هذا الحدس دون نجاح.
ومع ذلك، سواء كنا نعرف كيف نُنفّذ الدالة أم لا، فإن التعريف أعلاه يوفّر مواصفتها.
مداخل ومخارج متغيّرة
كثير من الدوال التي تهمّنا تأخذ أكثر من مُدخل واحد. على سبيل المثال، الدالة
تأخذ التمثيل الثنائي لزوج من الأعداد الصحيحة ، وتخرج التمثيل الثنائي حاصل ضربهما . لكن، بما أننا نستطيع تمثيل زوج من السلاسل كسلسلة واحدة، فسنعتبر دوالًا مثل MULT على أنها تحوّل إلى . ولن نُعنى عادةً بالتفاصيل منخفضة المستوى مثل الطريقة الدقيقة لتمثيل زوج من الأعداد الصحيحة كسلسلة، إذ إن كل الخيارات تقريبًا ستكون متكافئة لأغراضنا.
وهذا مثال آخر على دالة نريد حسابها، وهي
يكون خرج بتًّا واحدًا. وتُعرف الدوال ذات الخرج البتّي الواحد بأنها دوال بوليانية (Boolean functions). والدوال البوليانية محورية في نظرية الحوسبة، وسنتناولها كثيرًا في هذا الكتاب. لاحظ أنه رغم أن الدوال البوليانية خرجها بت واحد، فإن مدخلها قد يكون بطول اعتباطي. إذن فهي لا تزال دوال لا نهائية لا يمكن وصفها بجدول متناهٍ من القيم.
«تبولانية» الدوال. أحيانًا قد يكون من الملائم الحصول على صيغة بوليانية (Boolean variant) لدالة غير بوليانية. على سبيل المثال، ما يلي هو صيغة بوليانية لـ.
إذا استطعنا حساب بأي لغة برمجة مثل Python أو C أو Java وغيرها، أمكنا حينها حساب أيضًا، والعكس صحيح.
تمرين محلول — تبولانية الدوال العامة
بيّن أنه لكل دالة ، توجد دالة بوليانية بحيث إن برنامج Python لحساب يمكن تحويله إلى برنامج لحساب ، والعكس صحيح.
الحل
لكل ، يمكننا تعريف
لتكون الدالة التي، على مدخل ، تخرج البت من إذا كان و. وإذا كان ، فإن تخرج إذا وفقط إذا كان ، ومن ثمّ يتيح هذا حساب طول .
وحساب من مباشر.
وفي الاتجاه المقابل، إذا أعطينا دالة Python باسم BF تحسب ، يمكننا حساب كما يلي:
def F(x):
res = []
i = 0
while BF(x,i,1):
res.append(BF(x,i,0))
i += 1
return res
اللغات الشكليّة
لكل دالة بوليانية ، يمكننا تعريف المجموعة من السلاسل التي تخرج عليها القيمة . وتُعرف هذه المجموعات بأنها لغات (languages). وهذا الاسم متجذّر في نظرية اللغات الشكليّة التي سارها لغويون مثل نوام تشومسكي (Noam Chomsky). واللغة الشكلية (formal language) هي مجموعة جزئية (أو على نحو أعمّ لبعض أبجدية متناهية ). أما مسألة الانتماء (membership) أو_قرار_ (decision) (decision) الخاصة بلغة ، فهي مهمة تحديد، معطى ، ما إذا كان أم لا. وإذا استطعنا حساب الدالة ، أمكنا عندها اتخاذ قرار الانتماء إلى اللغة ، والعكس صحيح. ولذلك فإن كثيرًا من الكتب مثل [@SipserBook] تشير إلى مهمة حساب دالة بوليانية بأنها «اتخاذ قرار بشأن لغة». في هذا الكتاب، نصف المهام الحاسوبية غالبًا باستخدام تدوين الدالة، فهو أسهل في التعميم على الحساب الذي ينتج أكثر من بت واحدًا. لكن، بما أن مصطلح اللغة شائع جدًا في الأدبيات، فسنشير إليه أحيانًا.
تقييدات الدوال
إذا كانت دالة بوليانية و، فإن تقييد (restriction) على المداخل ذات الطول ، ويُرمز إليه بـ، هو الدالة المتناهية بحيث لكل . بمعنى آخر، هي الدالة المتناهية المعرَّفة على المداخل فقط، لكنها تتفق مع على تلك المداخل. ولأن دالة متناهية، فهي يمكن حسابها بدائرة بوليانية، وهو ما يقود إلى المبرهنة التالية:
مبرهنة — مجموعة دوائر لكل دالة لا نهائية
ليكن . إذًا توجد مجموعة من الدوائر بحيث إن لكل ، تحسب تقييد لـ على المداخل ذات الطول .
برهان
هذا نتيجة فورية لعمومية الدوائر البوليانية. بالفعل، بما أن تحوّل إلى ، فإن مبرهنة — شمولية دوائر NAND، بحدّ محسّن يقتضي وجود دائرة بوليانية تحسبها. بل إن حجم هذه الدائرة هو على الأكثر بوابة لبعض الثابت .
وبالتحديد، فإن مبرهنة — مجموعة دوائر لكل دالة لا نهائية يقتضي وجود مثل هذه المجموعة من الدوائر حتى لدالة التي وصفناها من قبل، رغم أننا لا نعرف أي برنامج لحسابها. وبالفعل، ليس هذا مثيرًا للدهشة إلى حد كبير: فلكل بعينه، تكون إمّا دالة الصفر الثابتة أو دالة الواحد الثابتة، وكلاهما يمكن حسابه بدوائر بوليانية بسيطة جدًا. ومن ثمّ هناك بالتأكيد مجموعة دوائر تحسب . والصعوبة في حساب باستخدام Python أو أي لغة برمجة أخرى تنشأ من أننا لا نعرف، لكل بعينه، ما هي الدائرة في هذه المجموعة.
الآلات الذاتية المحدودة الحتمية (اختياري)
كل نماذج الحوسبة التي تطرّقناها حتى الآن - الدوائر البوليانية والبرامج المستقيمة (straight-line programs) - لم تنطبق إلا على الدوال المتنهية.
سنقدّم في الحلقات وما لا نهاية آلات تورينج (Turing machines)، وهي النماذج المركزية للحوسبة على الدوال ذات المداخل غير المحدودة الطول. لكنّنا في هذا القسم نقدّم النموذج الأبسط، وهو الآلات الذاتية المحدودة الحتمية (deterministic finite automata, DFAs). يمكن للآلات الذاتية أن تكون جسراً تمهيدياً جيّدًا نحو آلات تورينج، وإن كانت لن تُستخدَم كثيرًا في أجزاء هذا الكتاب اللاحقة، ولذلك بإمكان القارئ أن يتخطّى إلى الحلقات وما لا نهاية مباشرةً. تبيّن أنّ الآلات الذاتية المحدودة الحتمية متكافئة في قوتها مع التعبيرات النمطية (regular expressions): وهي آلية قويّة لتحديد الأنماط، تُستخدَم على نطاق واسع في الممارسة العملية. معالجتنا للآلات الذاتية مختصرة نسبيًا. وهناك موارد كثيرة تساعدك على أن تصبح أكثر اطّلاعًا بالآلات الذاتية المحدودة الحتمية. وبشكل خاص، فإنّ الفصل الأول من كتاب سيبسر [@SipserBook] يحتوي على عرضٍ ممتاز لهذه المادة. كما وهناك مواقع كثيرة على الإنترنت تحوي محاكيات للآلات الذاتية، إضافةً إلى مترجِمات من التعبيرات النمطية إلى الآلات الذاتية والعكس (انظر مثلًا هنا وهنا).
على مستوى عالٍ، الخوارزمية (algorithm) هي وصفة لحساب مُخرَج انطلاقًا من مُدخل، عبر مزيج من الخطوات التالية:
- اقرأ بتًّا من المدخل
- حدِّث الحالة (state) أي الذاكرة العاملة
- توقّف وأنتج مُخرَجًا
على سبيل المثال، تذكّر برنامج Python الذي يحسب دالة :
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] يساوي ويُبقيه على حاله فيما عدا ذلك).
وحين ينتهي من اجتياز المدخل يُخرج البرنامج result.
في علم الحاسوب، يُسمّى برنامجٌ من هذا النوع خوارزمية مرورٍ واحد بذاكرة ثابتة (single-pass constant-memory algorithm) لأنّه يجري مرورًا واحدًا فوق المدخل وذاكرته العاملة متناهية.
(وفي هذه الحالة، لا يكون result إلّا أو .)
وتُسمّى هذه الخوارزمية أيضًا آلة ذاتية محدودة حتمية (Deterministic Finite Automaton) أو DFA (واسمٌ آخر للآلات الذاتية المحدودة الحتمية هو آلة الحالات المتناهية (finite state machine)).
يمكننا التفكير في هذه الخوارزمية بوصفها «آلة» (machine) تستطيع أن تكون في إحدى حالة، حيث ثابت.
تبدأ الآلة في حالةٍ ابتدائية ما، ثم تقرأ مدخلها بتًّا بت.
وحينما تقرأ الآلة بتًّا تنتقل إلى حالة جديدة تعتمد على وعلى حالتها السابقة.
ويعتمد مُخرَج الآلة على الحالة النهائية.
وكل خوارزمية مرورٍ واحد بذاكرة ثابتة تقابل آلةً كهذه.
فإذا استخدمت خوارزمية بتًّا من الذاكرة، أمكن تمثيل محتوى ذاكرتها كسلسلة من الطول .
ولذلك يمكن لهذه الخوارزمية أن تكون في واحدة من حالة على الأكثر في أي لحظة أثناء التنفيذ.
يمكننا تحديد آلة ذاتية محدودة حتمية ذات حالة عبر قائمة من قاعدة. وستكون كل قاعدة على الشكل «إذا كانت الآلة في الحالة وكان البت المقروء من المدخل هو فإنّ الحالة الجديدة هي ». وفي نهاية الحساب، لدينا أيضًا قاعدة على الشكل «إذا كانت الحالة النهائية إحدى الحالات التالية ... فإنّ الخرج ، وإلا فإنّ الخرج ». على سبيل المثال، يمكن تمثيل برنامج Python أعلاه بآلة ذاتية ذات حالتين تحسب على الشكل التالي:
- ابدأ في الحالة .
- لكل حالة ولكل بتّ مدخل يُقرأ، إذا كان فانتقل إلى الحالة ، وإلا فابقَ في الحالة .
- في النهاية أخرج إذا وفقط إذا كان .
يمكننا أيضًا وصف آلة ذاتية محدودة حتمية ذات حالة بوصفها رسماً بيانيًا (graph) مُعنونًا من رأس. فلكل حالة ولكل بتّ نضيف حافةً موجّهة مُعنونة بـ بين والحالة بحيث إن كانت الآلة في الحالة وقرأت فإنّها تنتقل إلى . (وإذا بقيت الحالة كما هي فإنّ هذه الحافة ستكون حلقةً ذاتية (self-loop)، وكذلك، إذا انتقلت إلى في حالتي و معًا فإنّ الرسم البياني سيحتوي حافتين متوازيتين.) كما نعنّن المجموعة من الحالات التي تُخرج الآلة الذاتية في نهاية الحساب. وتُعرف هذه المجموعة بأنها مجموعة حالات القبول (accepting states). انظر الشكل التوضيحي للتمثيل البياني لآلة الذاتية.

رسميًا، تُحدَّد الآلة الذاتية المحدودة الحتمية بـ (1) جدول القواعد البالغ ، ويمكن تمثيله بـ_دالة انتقال_ (transition function) تُسقط حالة وبتًّا على الحالة التي تنتقل إليها الآلة من الحالة عند المدخل ، وبـ (2) مجموعة من حالات القبول. ويؤدي هذا إلى التعريف التالي.
تعريف — الآلة الذاتية المحدودة الحتمية
الآلة الذاتية المحدودة الحتمية (DFA) ذات حالات فوق هي زوج مع و. الدالة المتناهية تُعرف بـ_دالة الانتقال_ (transition function) الخاصة بالآلة الذاتية المحدودة الحتمية. والمجموعة تُعرف بأنها مجموعة حالات القبول.
لتكن دالةً بوليانية ذات المجال اللانهائي . نقول إنّ تحسب دالةً إذا كان لكل ولكل ، إذا عرّفنا و لكل ، فإنّ
توقّف وتأمّل
احرص على عدم الخلط بين دالة الانتقال (transition function) الخاصة بالآلة الذاتية ( في تعريف — الآلة الذاتية المحدودة الحتمية)، وهي دالة متناهية تحدّد جدول «القواعد» الذي تتبعه الآلة، وبين الدالة التي تحسبها الآلة الذاتية ( في تعريف — الآلة الذاتية المحدودة الحتمية) وهي دالة لا نهائية.
ملاحظة — التعريفات في النصوص الأخرى
يمكن تعريف الآلات الذاتية المحدودة الحتمية بعدّة طرائق متكافئة. وبشكل خاص، يعرّف سيبسر [@SipserBook] الآلة الذاتية المحدودة الحتمية بوصفها خماسية حيث هي مجموعة الحالات، و هي الأبجدية، و هي دالة الانتقال، و هي الحالة الابتدائية، و هي مجموعة حالات القبول. في هذا الكتاب تكون مجموعة الحالات دائمًا على الشكل وتكون الحالة الابتدائية دائمًا ، لكنّ هذا لا يغيّر شيئًا في القوة الحسابية لهذه النماذج. كذلك فإنّنا نقتصر على الحالة التي تكون فيها الأبجدية مساويةً لـ .
تمرين محلول — آلة ذاتية محدودة حتمية لـ
برهن أنّه توجد آلة ذاتية محدودة حتمية تحسب الدالة التالية :
الحل
عند طلب إنشاء آلة ذاتية محدودة حتمية، من المفيد غالبًا أن نبدأ بإنشاء خوارزمية مرورٍ واحد بذاكرة ثابتة باستخدام صياغة أعمّ (مثلًا باستخدام شيفرة شبه برمجية (pseudocode) أو برنامج Python). وبعد أن تتوفّر لدينا هذه الخوارزمية، يمكننا ترجمتها آليًا إلى آلة ذاتية محدودة حتمية. إليك برنامج Python بسيطًا يحسب :
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
بما أننا نحتفظ بثلاثة متغيّرات بوليانية، يمكن أن تكون الذاكرة العاملة في واحدة من تشكيلة، ولذلك يمكن ترجمة البرنامج أعلاه مباشرةً إلى آلة ذاتية محدودة حتمية ذات حالات. وفي حين أنّ ذلك ليس ضروريًا لحلّ السؤال، فإنّنا بفحص الآلة الناتجة نرى أنّه بإمكاننا دمج بعض الحالات و الحصول على آلة ذاتية ذات حالات، موصوفة في الشكل التوضيحي. انظر أيضًا الشكل التوضيحي الذي يصوّر تنفيذ هذه الآلة على مدخلٍ بعينه.

تشريح الآلة الذاتية (متنهية مقابل غير محدودة)
الآن وقد بدأنا ننظر في مهام حاسوبية ذات أحجام مداخل غير محدودة، من الضروري التمييز بين مكوّنات خوارزمتنا ذات الطول الثابت وتلك التي تنمو مع حجم المدخل. وفي حالة الآلات الذاتية المحدودة الحتمية فإنّ هذه المكوّنات هي التالية:
مكوّنات ذات حجم ثابت: بمعلومية آلة ذاتية محدودة حتمية ، فإنّ الكميات التالية ثابتة ومستقلة عن حجم المدخل:
-
عدد الحالات في .
-
دالة الانتقال (ولها مُدخلًا، ولذلك يمكن تحديدها بجدول من صفًا، كلّ خانةٍ منه عددٌ في ).
-
المجموعة من حالات القبول. ويمكن وصف هذه المجموعة بسلسلة في تحدّد أيّ الحالات تنتمي إلى وأيّها لا تنتمي.
ومعنى ما سبق مجتمعةً أنّه يمكننا وصف الآلة الذاتية وصفًا تامًّا باستخدام عددٍ متناهٍ من الرموز. وهذه خاصيةٌ نطلبها من أيّ مفهومٍ لـ«الخوارزمية»: ينبغي أن نتمكّن من كتابة مواصفةٍ تامّة لكيفية إنتاج مُخرَج انطلاقًا من مُدخل.
مكوّنات ذات حجم غير محدود: الكميات التالية المتعلقة بالآلة الذاتية المحدودة الحتمية ليست محدودة بأيّ ثابت. ونشدّد على أنّها تبقى متنهية بالنسبة لأيّ مدخلٍ بعينه.
-
طول المدخل الذي تُعطى له الآلة الذاتية المحدودة الحتمية. فطول المدخل متناهٍ دائمًا، لكنّه غير محدود مسبقًا.
-
عدد الخطوات التي تتّخذها الآلة الذاتية المحدودة الحتمية يمكن أن ينمو مع طول المدخل. بالفعل، تجري الآلة مرورًا واحدًا على المدخل، ولذلك تتّخذ بالضبط خطوة على مدخل .

الدوال القابلة للحساب بآلة ذاتية محدودة حتمية
نقول إنّ دالةً هي قابلة للحساب بآلة ذاتية محدودة حتمية (DFA computable) إذا وُجدت آلة ذاتية محدودة حتمية تحسب . لقد رأينا في السكر النحوي، وحساب كل دالة أنّ كل دالة متناهية يمكن حسابها بواسطة دائرة بوليانية. وعليه، فقد تتوقّع الآن أنّ كل دالة لا نهائية يمكن حسابها بواسطة بعض الآلة الذاتية المحدودة الحتمية. لكنّ هذا غير صحيح البتّة. سنرى بعد قليل أمثلةً بسيطة على دوالٍ لا نهائية لا يمكن حسابها بآلات ذاتية محدودة حتمية، لكنّنا سنبدأ بإثبات أنّ مثل هذه الدوال موجودة.
مبرهنة — الدوال القابلة للحساب بآلة ذاتية محدودة حتمية هي عدِّيّة
لتكن مجموعة كلّ الدوال البوليانية بحيث توجد آلة ذاتية محدودة حتمية تحسب . إذًا مجموعة عدِّيّة.
يمكن وصف كلّ آلة ذاتية محدودة حتمية بسلسلة من طول متناهٍ، وهذا يُنتج دالة تغطية (surjection) من إلى : وهي الدالة التي تُسقط كلّ سلسلةٍ تصف آلةً ذاتية على الدالة التي تحسبها.
برهان
يمكن وصف كلّ آلة ذاتية محدودة حتمية بسلسلة متناهية تمثّل دالة الانتقال ومجموعة حالات القبول، وكلّ آلة ذاتية محدودة حتمية تحسب دالةً ما . وعليه يمكننا تعريف الدالة التالية :
حيث هي الدالة الثابتة التي تُخرج على كلّ المداخل (وهي عنصرٌ في ). وبما أنّه بالتعريف كلّ دالة في يحسبها بعضُ الآلة الذاتية، فإنّ دالة تغطية من إلى ، وهذا يعني أنّ مجموعة عدِّيّة (انظر الشروط المكافئة لقابلية العدّ).
وبما أنّ مجموعة كلّ الدوال البوليانية غير عدِّيّة، نحصل على النتيجة التالية:
مبرهنة — وجود دوال لا تحسبها الآلات الذاتية المحدودة الحتمية
توجد دالة بوليانية لا يمكن حسابها بواسطة أيّ آلة ذاتية محدودة حتمية.
برهان
إذا كانت كلّ دالة بوليانية قابلة للحساب بواسطة بعض الآلة الذاتية المحدودة الحتمية، فإنّ تساوي المجموعة من كلّ الدوال البوليانية، لكن، وحسب مبرهنة — الدوال المنطقية غير قابلة للعدّ، فإنّ هذه المجموعة غير عدِّيّة، وهذا يناقض مبرهنة — الدوال القابلة للحساب بآلة ذاتية محدودة حتمية هي عدِّيّة.
التعبيرات النمطية
البحث (searching) عن جزءٍ من النص مهمةٌ شائعة في الحوسبة.
وفي جوهرها، فإنّ مسألة البحث (search problem) بسيطةٌ إلى حدٍّ كبير.
لدينا مجموعة من السلاسل (مثلًا: ملفاتٌ على قرصٍ صلب، أو سجلات طلاب في قاعدة بيانات)، ويريد المستخدم أن يعرف المجموعة الجزئية من كلّ التي يطابقها (matched) بعضُ النمط (مثلًا: كلّ الملفات التي تنتهي أسماؤها بالسلسلة .txt).
في العموم، يمكننا السماح للمستخدم بتحديد النمط عبر تحديد دالةٍ (function) قابلة للحساب ، حيث يمثّل تطابقَ النمط مع .
أي إنّ المستخدم يقدّم برنامجًا (program) بلغة برمجة مثل Python، والنظام يُعيد كلّ بحيث .
على سبيل المثال، يمكن البحث عن كلّ ملفات النص التي تحتوي السلسلة important document، أو ربما (بأن يكون مُصنِّفًا قائمًا على شبكة عصبية) عن كلّ الصور التي تحتوي قطة.
لكنّنا لا نريد لنظامنا أن يقع في حلقةٍ لا نهائية لمجرّد محاولته تقييم البرنامج !
ولهذا السبب، فإنّ الأنظمة النموذجية للبحث في الملفات أو قواعد البيانات لا تسمح للمستخدم بتحديد الأنماط بلغة برمجة كاملة.
بدلًا من ذلك، تستخدم هذه الأنظمة نماذج حوسبة مقيّدة (restricted computational models) تكون من جهةٍ غنية بما يكفي لالتقاط كثيرٍ من الاستعلامات المطلوبة في الممارسة العملية (مثل كلّ أسماء الملفات المنتهية بـ .txt، أو كلّ أرقام الهواتف من الشكل (617) xxx-xxxx)، ومن جهةٍ أخرى مقيّدة بما يكفي بحيث يمكن تقييم الاستعلامات بكفاءةٍ عالية جدًّا على ملفاتٍ ضخمة، ولا يمكن أن تؤدي على الإطلاق إلى حلقةٍ لا نهائية.
من أشهر نماذج الحوسبة هذه التعبيرات النمطية. فإذا كنت قد استخدمت محرّرَ نصوصٍ متقدّمًا، أو صدفةَ سطرِ أوامر (command-line shell)، أو قم بأيّ نوعٍ من التلاعب بملفات النص، فمن المرجّح أنّك صادفت التعبيرات النمطية.
التعبير النمطي (regular expression) فوق أبجديةٍ ما يُنشأ من خلال تركيب عناصر مع عملية الدمج (concatenation)، وكذلك مع (وهي المقابلة لـ_أو_) و (وهي المقابلة للتكرار صفر مرّات أو أكثر). (وغالبًا ما تتضمن التطبيقات الشائعة للتعبيرات النمطية في لغات البرمجة والأصداف بعض العمليات الإضافية فوق و، لكنّ هذه العمليات يمكن تنفيذها بوصفها «سكرًا نحويًا» (syntactic sugar) باستخدام المشغّلَيْن و.) على سبيل المثال، يقابل التعبير النمطي التالي فوق الأبجدية مجموعة كلّ السلاسل التي يتكرّر فيها كلّ رقمٍ مرّتين على الأقل:
يقابل التعبير النمطي التالي فوق الأبجدية مجموعة كلّ السلاسل التي تتكوّن من تسلسلٍ من حرفٍ واحدٍ أو أكثر من الحروف - يليه تسلسلٌ من رقمٍ أو أكثر (دون صفرٍ بادئ):
رسميًا، تُعرَّف التعبيرات النمطية بالتعريف العودي التالي:
تعريف — التعبير النمطي
التعبير النمطي (regular expression) فوق أبجدية هو سلسلة فوق تأخذ أحد الأشكال التالية:
-
حيث
-
حيث تعبيران نمطيان.
-
حيث تعبيران نمطيان. (وعادة ما نحذف الأقواس عندما لا يكون هناك خطرُ التباس، ونكتب حينئذٍ .)
-
حيث تعبير نمطي.
وأخيرًا فإنّنا نسمح أيضًا بحالاتِ «الحدّ» (edge cases) التالية: و. وهذه هي التعبيرات النمطية المقابلة، على الترتيب، لقبولِ لا سلاسل، وقبولِ السلسلة الفارغة فقط.
سنحذف الأقواس عندما يمكن استنتاجها من السياق. كما نستخدم العُرف بأنّ «أو» والدمج يُجمَّعان من جهة اليسار، وأنّنا نعطي الأولوية العليا لـ، ثم للدمج، ثم لـ«أو». ولذلك نكتب مثلًا بدلًا من .
كلّ تعبير نمطي يقابل دالةً حيث إذا كان يطابق (matches) التعبير النمطي. على سبيل المثال، إذا كان فإنّ لكنّ (هل ترى لماذا؟).
توقّف وتأمّل
التعريف الرسمي لـ هو من جملة التعريفات التي يكون كتابتها أعرق من إدراكها. ولذلك قد يكون من الأسهل لك أن تستنتج التعريف بنفسك أولًا، ثم تتحقّق من مطابقته لما هو مكتوب أدناه.
تعريف — مطابقة تعبير نمطي
لتكن تعبيرًا نمطيًا فوق الأبجدية . تُعرَّف الدالة على النحو التالي:
-
إذا كان فإنّ إذا وفقط إذا كان .
-
إذا كان فإنّ حيث هو مشغّل «أو».
-
إذا كان فإنّ إذا وفقط إذا وُجد بحيث إنّ هو دمج و وأنّ .
-
إذا كان فإنّ إذا وفقط إذا وُجد و بحيث إنّ هو الدمج وأنّ لكلّ .
-
وأخيرًا، بالنسبة إلى حالتَي «الحدّ» فإنّ هي الدالة الثابتة صفر، و هي الدالة التي لا تُخرج إلّا على السلسلة الفارغة .
نقول إنّ تعبيرًا نمطيًا فوق يطابق (matches) سلسلةً إذا كان .
توقّف وتأمّل
التعريفات السابقة ليست صعبة بذاتها، لكنّها ثقيلة قليلًا. لذا ينبغي أن تتوقّف هنا وتُعيد قراءتها حتى تفهم لماذا تقابل تصوّرنا البديهي للتعبيرات النمطية. وهذا مهمّ ليس من أجل فهم التعبيرات النمطية نفسها فحسب (وهي تُستخدَم مرارًا وتكرارًا في عددٍ كبيرٍ جدًّا من التطبيقات)، بل أيضًا من أجل أن تصبح أفضل في فهم التعريفات العودية على نحو عام.
تُسمّى الدالة البوليانية «منتظمة» (regular) إذا كانت تُخرج على المجموعة بالضبط من السلاسل التي يطابقها بعضُ التعبير النمطي. أي إنّ
تعريف — الدوال المنتظمة / اللغات المنتظمة
لتكن مجموعة متناهية، لتكن دالةً بوليانية. نقول إنّ منتظمة (regular) إذا كان لبعض التعبير النمطي .
وكذلك، لكل لغةٍ شكلية نقول إنّ منتظمة إذا وفقط إذا وُجد تعبيرٌ نمطي بحيث يكون إذا وفقط إذا كان يطابق .
مثال — دالة منتظمة
لتكن ، لتكن الدالة التي يكون فيها إذا وفقط إذا كان يتكوّن من حرفٍ واحدٍ أو أكثر من الحروف - يليه تسلسلٌ من رقمٍ أو أكثر (دون صفرٍ بادئ). إذًا دالةٌ منتظمة، لأنّ حيث وهو التعبير الذي رأيناه في المعادلة.
فإذا أردنا مثلًا التحقّق من أنّ ، يمكننا فعل ذلك بالملاحظة على أنّ التعبير يطابق السلسلة ، وأنّ التعبير يطابق ، وأنّ يطابق السلسلة ، وأنّ التعبير يطابق السلسلة . وكلٌّ من هذه يفضي إلى تعبيرٍ أبسط. على سبيل المثال، التعبير يطابق السلسلة لأنّ سلسلتي الحرف الواحد و كلتيهما يطابقها التعبير .
يمكن تعريف التعبير النمطي فوق أيّ أبجدية متناهية ، لكنّنا، كالمعتاد، سنركّز في معظم الوقت على الحالة الثنائية (binary case) حيث . وكلّ الرؤى العامة النظرية والعملية عن التعبيرات النمطية (أو كلّها تقريبًا) يمكن استخلاصها من دراسة الحالة الثنائية.
خوارزميات مطابقة التعبيرات النمطية
لن تكون التعبيرات النمطية نافعةً جدًّا في البحث إذا لم نتمكّن من تقييم ما إذا كانت سلسلة يطابقها تعبير نمطي معيّن، أم لا. ولحسن الحظّ، هناك خوارزمية تفعل ذلك. وبالتحديد، هناك خوارزمية (فكّر فيها بوصفها «برنامج Python»، مع أنّنا سنضبط لاحقًا مفهوم الخوارزميات باستخدام آلات تورينج) تُخرج على مدخل تعبير نمطي فوق الأبجدية وسلسلة القيمة إذا وفقط إذا كان يطابق (أي أنّها تُخرج ).
بالفعل، فإنّ تعريف — مطابقة تعبير نمطي يحدّد في الواقع خوارزميةً عودية لحساب . وبالتحديد، يمكن التفكير في كلّ عملية من عملياتنا -الدمج، و«أو»، والنجمة- على أنّها تُقلّص مهمة التحقّق ممّا إذا كان تعبير يطابق سلسلة إلى التحقّق ممّا إذا كانت بعضُ التعبيرات الجزئية لـ تطابق سلاسلَ جزئية من . وبما أنّ هذه التعبيرات الجزئية أقصر دائمًا من التعبير الأصلي، فإنّ هذا يعطي خوارزميةً عودية للتحقّق ممّا إذا كان يطابق ، وفي نهاية المطاف ستتوقّف عند الحالات الأساس للتعبيرات التي تقابل رمزًا واحدًا أو السلسلة الفارغة.
خوارزمية — 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
نفترض أعلاه أنّ لدينا إجراءً يُخرج على مدخل تعبير نمطي القيمة إذا وفقط إذا كان يطابق السلسلة الفارغة .
الملاحظة المفتاحية هي أنّه في تعريفنا العودي للتعبيرات النمطية، متى ما كان مكوّنًا من تعبير واحدٍ أو تعبيرين ، فإنّ هذين التعبيرين النمطيين أصغر من . وفي نهاية المطاف (حين يبلغ حجمهما ) فإنّهما لا بدّ أن يقابلا الحالة غير العودية الخاصة برمزٍ واحدٍ من الأبجدية. وبالتوازي، فإنّ الاستدعاءات العودية التي تجري في خوارزمية — Regular expression matching تقابل دائمًا تعبيرًا أقصر أو (في حالة تعبيرٍ من الشكل ) سلسلةَ مُدخل أقصر. وعليه، يمكننا إثبات صحّة خوارزمية — Regular expression matching على المداخل من الشكل بالحثّ على . الحالة الأساس هي أن يكون أو أن يكون رمزًا واحدًا من الأبجدية، أو أو . وفي حالة التعبير عن الشكل أو ، فإنّنا نجري استدعاءاتٍ عودية بالتعبيرين الأقصر . وفي حالة التعبير عن الشكل ، فإنّنا نجري استدعاءاتٍ عودية إمّا بسلسلة أقصر ومع التعبير ذاته، وإمّا بالتعبير الأقصر ومع سلسلة تساوي في الطول أو تكون أقصر منها.
تمرين محلول — مطابقة السلسلة الفارغة
قدّم خوارزمية تُخرج على مدخل تعبير نمطي القيمة إذا وفقط إذا كان .
الحل
يمكننا الحصول على خوارزميةٍ عودية من هذا النوع عبر الملاحظات التالية:
-
التعبير عن الشكل أو يطابق السلسلة الفارغة دائمًا.
-
التعبير عن الشكل ، حيث رمزٌ من رموز الأبجدية، لا يطابق السلسلة الفارغة أبدًا.
-
التعبير النمطي لا يطابق السلسلة الفارغة.
-
التعبير عن الشكل يطابق السلسلة الفارغة إذا وفقط إذا طابق أحدهما أو السلسلة الفارغة.
-
التعبير عن الشكل يطابق السلسلة الفارغة إذا وفقط إذا طابق كلٌّ منهما و السلسلة الفارغة.
وبالنظر إلى الملاحظات أعلاه، نرى أنّ الخوارزمية التالية تتحقّق ممّا إذا كان يطابق السلسلة الفارغة:
خوارزمية — 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 ليس كفؤًا إلى حدٍّ كبير. فعلى سبيل المثال، إذا أعطينا تعبيرًا يتضمّن الدمج أو عملية «النجمة» وسلسلةً من الطول ، فإنّه يمكن أن يُجري استدعاءً عوديًا، وعليه يمكن إثبات أنّ خوارزمية — Regular expression matching في أسوأ الحالات قد يستغرق زمنًا أسّيًّا في طول سلسلة المُدخل . ولحسن الحظّ، تبيّن أنّ هناك خوارزمية أكثر كفاءة بكثير تستطيع مطابقة التعبيرات النمطية في زمنٍ خطّي (أي ). وبما أنّنا لم نغطِّ بعد موضوعي تعقيد الزمن وتعقيد المكان، فإنّنا نصف هذه الخوارزمية بصياغةٍ عامّة دون ضبط النموذج الحسابي بدقّة. وبدلًا من ذلك سنستخدم المفهوم الاستعماليّ لزمن التشغيل كما هو مستعمَل في مقرّرات مقدّمة إلى البرمجة وفي مقابلات البرمجة على السبورة. وسترى تعريفًا رسميًا لتعقيد الزمن في نمذجة زمن التنفيذ.
مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي
لتكن تعبيرًا نمطيًا. إذًا توجد خوارزمية بزمن تحسب .
الثابت الضمني في حدّ في مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يعتمد على التعبير . وعليه، فإنّ طريقة أخرى لقول مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي هي أنّه لكل تعبير يوجد ثابت وخوارزمية تحسب على مداخل بطول بت باستخدام خطوة على الأكثر. وهذا يبدو معقولًا، إذ إنّنا في الممارسة العملية نرغب غالبًا في حساب لتعبيرٍ نمطي صغير ووثيقةٍ كبيرة . ويخبرنا مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي أنّه يمكننا فعل ذلك بزمن تشغيل يتناسب تناسبًا خطّيًّا مع حجم الوثيقة، حتى لو كان اعتمادُه على حجم التعبير النمطي (على الأرجح) أسوأ.
نُثبت مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي عبر الحصول على خوارزميةٍ عودية أكثر كفاءة، تحدّد ممّا إذا كان يطابق سلسلة عبر تقليص هذه المهمة إلى تحديد ممّا إذا كان تعبيرٌ ذو صلة يطابق . وهذا سيؤدّي إلى معادلةٍ لزمن التشغيل من الشكل ، ويُحلّ بها أنّ .
تقييدات التعبيرات النمطية. المفهوم المركزي في الخوارزمية الكامن خلف مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي هو مفهوم التقييد (restriction) للتعبير النمطي. الفكرة أنّه لكل تعبير نمطي ولكل رمز في أبجديته، من الممكن تعريف تعبير نمطي بحيث يطابق سلسلة إذا وفقط إذا طابق السلسلة . على سبيل المثال، إذا كان هو التعبير النمطي (أي ورود واحد أو أكثر لـ) فإنّ يساوي وسيكون هو . (هل ترى لماذا؟)
خوارزمية — Restricting regular expression يحسب التقييد بمعلومي تعبير نمطي ورمزٍ من رموز الأبجدية . وهو يتوقّف دائمًا، إذ إنّ الاستدعاءات العودية التي يجريها تكون دائمًا على تعبيرات أصغر من تعبير المُدخل. ويمكن إثبات صحّته بالحثّ على طول التعبير النمطي ، وتكون الحالات الأساس هي أن يكون هو أو ، أو رمزًا واحدًا من رموز الأبجدية .
خوارزمية — 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
بحسب تعريف التقييد، فإنّ لكل ولكل ، يطابق التعبير السلسلة إذا وفقط إذا طابق السلسلة . وعليه، فإنّ لكل ولكل ، لدينا ، وبعدها يُعيد خوارزمية — Regular expression matching in linear time الإجابة الصحيحة فعلًا. والمهمة المتبقّية الوحيدة هي تحليل زمن تشغيله. لاحظ أنّ خوارزمية — Regular expression matching in linear time يستخدم إجراء الخاص بـتمرين محلول — مطابقة السلسلة الفارغة في الحالة الأساس التي يكون فيها . لكنّ هذا مقبول لأنّ زمن تشغيل هذا الإجراء يعتمد فقط على ومستقلّ عن طول المُدخل الأصلي.
للبساطة، فلنقصر انتباهنا على الحالة التي تكون فيها الأبجدية مساويةً لـ . لنعرّف بأنّها أقصى عدد عمليات تتّخذه خوارزمية — Restricting regular expression حين يُعطى كمدخل تعبير نمطي فوق من رمزًا على الأكثر. ويمكن إثبات أنّ القيمة متعدّدة الحدود في ، لكنّ هذا ليس مهمًّا لهذه المبرهنة، إذ إنّنا نهتمّ فقط باعتماد زمن حساب على طول وليس باعتماد هذا الزمن على طول .
خوارزمية — Regular expression matching in linear time خوارزمية عودية تُعطى تعبيرًا وسلسلة ، فتُجري حسابًا من خطوة على الأكثر ثمّ تستدعي نفسها بتعبيرٍ ما وسلسلة من الطول . وستتوقّف بعد خطوة عند وصولها إلى سلسلةٍ من الطول . وعليه، فإنّ زمن التشغيل الذي يحتاجه خوارزمية — Regular expression matching in linear time لحساب على مداخل من الطول يحقق المعادلة العودية:
(وفي الحالة الأساس ، يساوي ثابتًا ما يعتمد فقط على .) ولكي نحصل على بعض الحدس تجاه التعبير المعادلة، فلنفكّ التكرار درجةً واحدة، فنكتب على النحو
بمتابعة ذلك على هذا النحو، يمكننا أن نرى أنّ حيث هو أطول طولٍ لأيّ تعبير نصادفه في الطريق. وعليه، فإنّ الدعوى التالية تكفي لإثبات أنّ خوارزمية — Regular expression matching in linear time يعمل بزمن :
الدعوى: لتكن تعبيرًا نمطيًا فوق ، إذًا يوجد عدد بحيث إنّ لكل تسلسل من الرموز ، إذا عرّفنا (أي تقييد إلى ، ثمّ وهكذا على التوالي)، فإنّ .
اقتباس
إثبات الدعوى: لتعبيرٍ نمطي فوق و، نرمز بـ إلى التعبير الناتج عن تقييد إلى ثمّ إلى وهكذا. ولنضع . وسنُثبت الدعوى بأنّنا نبيّن أنّه لكل تكون المجموعة متناهية، وبالتالي فإنّ العدد أيضًا متناهٍ، وهو أطول طولٍ لـ حيث .
نُثبت ذلك بالحثّ على بنية . إذا كان رمزًا أو السلسلة الفارغة أو المجموعة الخالية، فإنّ هذا واضحٌ لإثباته، إذ إنّ أكثر ما يمكن أن تحتويه المجموعة هو التعبير ذاته و و. وإلا فإنّنا نقسم إلى الحالتين (i) و (ii) ، حيث تعبيران أصغر (وبالتالي، بحسب فرضية الحثّ، فإنّ و متناهيتان). في الحالة (i)، إذا كان فإنّ إمّا يساوي أو يكون ببساطة المجموعة الخالية إذا كان . وبما أنّ ينتمي إلى المجموعة ، فإنّ عدد التعبيرات المتمايزة في هو على الأكثر. وفي الحالة (ii)، إذا كان فإنّ كلّ تقييدات إلى السلاسل إمّا أن تأخذ الشكل أو تأخذ الشكل حيث سلسلةٌ ما بحيث ويطابق السلسلة الفارغة. وبما أنّ و، فإنّ عدد التعبيرات المتمايزة المحتملة من الشكل هو على الأكثر. وبهذا يكتمل إثبات الدعوى.
الخلاصة أنّه أثناء تشغيل خوارزمية — Regular expression matching in linear time على تعبير نمطي ، فإنّ جميع التعبيرات التي نصادفها تنتمي إلى المجموعة المتناهية ، مهما كان حجم المُدخل كبيرًا، ولذلك فإنّ زمن تشغيل خوارزمية — Regular expression matching in linear time يحقق المعادلة حيث ثابتٌ يعتمد على . ويُحلّ ذلك بنتيجة حيث إنّ الثابت الضمني في رمز يمكن (وسيكون) أن يعتمد على ، لكنّه — وهذا هو المهمّ — لا يعتمد على طول المُدخل .
مطابقة التعبيرات النمطية باستخدام الآلات الذاتية المحدودة الحتمية
مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي مثيرٌ للإعجاب إلى حدٍّ كبير، لكنّنا نستطيع فعل أفضل من ذلك. وبالتحديد، مهما كانت السلسلة طويلة، يمكننا حساب مع الاحتفاظ بمقدارٍ ثابتٍ فقط من الذاكرة، بل وبإجراء مرورٍ واحد (single pass) فوق . أي أنّ الخوارزمية ستفحص المُدخل مرةً واحدة من البداية إلى النهاية، ثمّ تحدّد ما إذا كانت يطابقها التعبير أم لا. وهذا مهمٌّ في الحالة الشائعة التي نحاول فيها مطابقة تعبير نمطي قصير على ملفٍ أو وثيقةٍ ضخمة قد لا تتّسع حتى في ذاكرة حاسوبنا. وبالطبع، كما رأينا من قبل، فإنّ خوارزمية المرور الواحد بذاكرة ثابتة هي ببساطة آلة ذاتية محدودة حتمية. وكما سنرى في مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي، فإنّ الدالة يمكن حسابها بتعبيرٍ نمطي إذا وفقط إذا أمكن حسابها بآلة ذاتية محدودة حتمية. ونبدأ بإثبات اتجاه «فقط إذا»:
مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية
لتكن تعبيرًا نمطيًا. إذًا توجد خوارزمية تحسب على مُدخل مع إجراء مرورٍ واحدٍ فوق مع الاحتفاظ بمقدارٍ ثابتٍ من الذاكرة.
خوارزمية المرور الواحد بذاكرة ثابتة للتحقّق ممّا إذا كانت سلسلةٌ ما تطابق تعبيرًا نمطيًا معروضة في خوارزمية — 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 ممّا إذا كانت السلسلة المعطاة يطابقها التعبير النمطي . فلكل تعبير نمطي ، تملك هذه الخوارزمية عددًا ثابتًا من المتغيّرات البوليانية (على وجه التحديد، متغيّر لكل ومتغيّر لكل في ، مستخدمةً الحقيقة أنّ لكل ). وهي تجري مرورًا واحدًا فوق سلسلة المُدخل. وعليه فإنّها تقابل آلة ذاتية محدودة حتمية. ونُثبت صحّتها بالحثّ على طول المُدخل . وبشكل أدقّ، سنُثبت أنّه قبل قراءة ، تكون المتغيّرات مساوية لـ لكل . ففي الحالة فهذا صحيح لأنّنا نهيّئ لكل . وأما لـ فهذا صحيح بحكم الحثّ، إذ إنّ فرضية الحثّ تنص على أنّ لكل ، وبتعريف المجموعة ، فإنّه لكل ولكل ، يكون في ، وأنّ .
تكافؤ التعبيرات النمطية والآلات الذاتية
تذكّر أنّ الدالة البوليانية تُعرَّف بأنّها منتظمة إذا كانت مساويةً لـ لبعض التعبير النمطي . (بالمثل، تُعرَّف اللغة بأنّها منتظمة إذا وُجد تعبير نمطي بحيث يطابق السلسلة إذا وفقط إذا كان .) والمبرهنة التالية هي النتيجة المركزية في نظرية الآلات الذاتية:
مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي
لتكن . إذًا منتظمة إذا وفقط إذا وُجدت آلة ذاتية محدودة حتمية تحسب .
أحد الاتجاهين يتبع من مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية، الذي يُظهر أنّه لكل تعبير نمطي يمكن حساب الدالة بآلة ذاتية محدودة حتمية (انظر مثلًا الشكل التوضيحي). أمّا الاتجاه الآخر، فنُبيّن أنّه بمعلومية آلة ذاتية محدودة حتمية ، يمكننا لكل أن نجد تعبيرًا نمطيًا يطابق إذا وفقط إذا كانت الآلة الذاتية المحدودة الحتمية، وهي تبدأ من الحالة ، تنتهي في الحالة بعد قراءة .


برهان
بما أنّ مبرهنة — آلة ذاتية محدودة حتمية لمطابقة التعبيرات النمطية يُثبت اتجاه «فقط إذا»، فإنّنا لا نحتاج إلّا إلى إثبات اتجاه «إذا». لتكن آلة ذاتية محدودة حتمية ذات حالات تحسب الدالة . نحتاج إلى إثبات أنّ منتظمة.
لكل ، نترك تكون الدالة التي تُسقط على إذا وفقط إذا كانت الآلة الذاتية ، وهي تبدأ من الحالة ، تصل إلى الحالة إذا قرأت المُدخل . وسنُثبت أنّ منتظمة لكل . وهذا سيُثبت المبرهنة، إذ إنّ بحسب تعريف — الآلة الذاتية المحدودة الحتمية فإنّ تساوي ناتج «أو» (OR) للدوال لكل . وعليه، إذا كانت لدينا تعبيرات نمطية لكل دالةٍ من الشكل ، فإنّنا (باستخدام عملية ) يمكننا الحصول على تعبير نمطي لـ أيضًا.
لتقديم تعبيرات نمطية للدوال ، نبدأ بتعريف الدوال التالية : فلكل ولكل ، يكون إذا وفقط إذا كانت الآلة الذاتية، وهي تبدأ من وتلاحظ ، تصل إلى مع كون كلّ الحالات الوسيطة في المجموعة (انظر الشكل التوضيحي). أي إنّ نفسيهما قد يكونان خارج ، لكنّ إذا وفقط إذا لم تدخل الآلة الذاتية أبدًا، طوال تنفيذها على المُدخل ( حين تبدأ عند )، أيّ حالةٍ من الحالات خارج ، وانتهت مع ذلك عند . وإذا كان فإنّ هي المجموعة الخالية، ومن ثمّ يكون إذا وفقط إذا وصلت الآلة الذاتية إلى من مباشرةً على ، دون أيّ حالة وسيطة. وإذا كان فإنّ كلّ الحالات في ، ومن ثمّ .
سنُثبت المبرهنة بالحثّ على ، بأنّنا نبيّن أنّ منتظمة لكل و. ففي الحالة الأساس ، تكون منتظمة لكل لأنّها يمكن وصفها بإحدى التعبيرات أو أو أو أو . وبشكل أدقّ، إذا كان فإنّ إذا وفقط إذا كانت هي السلسلة الفارغة. وإذا كان فإنّ إذا وفقط إذا كانت تتكوّن من رمزٍ واحد وكان . وعليه، فإنّ في هذه الحالة يقابل أحد التعبيرات النمطية الأربعة أو أو أو ، بحسب ما إذا كانت تنتقل إلى من حين تقرأ أو (أو كليهما)، أو أحد الرمزين فقط، أو لا شيء منهما.
خطوة الحثّ: والآن بعد أن رأينا الحالة الأساس، فلنُثبت الحالة العامة بالحثّ. ولنفترض، بحسب فرضية الحثّ، أنّه لكل لدينا تعبير نمطي يحسب . نحتاج إلى إثبات أنّ منتظمة لكل . إذا وصلت الآلة الذاتية من إلى باستخدام الحالات الوسيطة ، فإنّها تزور الحالة -ية صفر مرّات أو أكثر. وإذا كان المسار الذي يحمله الوسم يجعل الآلة الذاتية تنتقل من إلى دون زيارة الحالة -ية إطلاقًا، فإنّ يطابقه التعبير النمطي . وإذا كان المسار الذي يحمله الوسم يجعل الآلة الذاتية تنتقل من إلى مع زيارة الحالة -ية مرّات، فإنّنا نستطيع التفكير في هذا المسار على النحو التالي:
-
ينتقل أوّلًا من إلى باستخدام الحالات الوسيطة في فقط.
-
ثمّ يذهب من إلى نفسه مرّة باستخدام الحالات الوسيطة في فقط
-
ثمّ يذهب من إلى باستخدام الحالات الوسيطة في فقط.
وعليه، فإنّ السلسلة في هذه الحالة يطابقها التعبير النمطي . (وانظر أيضًا الشكل التوضيحي.)
وعليه أيضًا، يمكننا حساب باستخدام التعبير النمطي
وبهذا يكتمل إثبات خطوة الحثّ، وبالتالي إثبات المبرهنة.

خصائص الإغلاق للتعبيرات النمطية
إذا كانت و دالتين منتظمتين يحسبهما التعبيران و على الترتيب، فإنّ التعبير يحسب الدالة المعرَّفة بأنّ . وطريقةٌ أخرى لقول ذلك هي أنّ مجموعة الدوال المنتظمة مغلقة تحت عملية «أو». أي إنّ منتظمة إن كانت و منتظمتين. ومن النتائج المهمّة المشتقّة من مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي أنّ هذه المجموعة مغلقة أيضًا تحت عملية النفي:
لمّة — التعبيرات النمطية مغلقة تحت التكملة
إذا كانت منتظمة، فإنّ الدالة منتظمة أيضًا، حيث لكل .
برهان
إذا كانت منتظمة، فإنّها بحسب مبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يمكن حسابها بآلة ذاتية محدودة حتمية . لكنّنا نستطيع عندها إنشاء آلة ذاتية محدودة حتمية تؤدّي الحساب نفسه مع قلب مجموعة الحالات المقبولة. وستحسب الآلة الذاتية المحدودة الحتمية الدالة . وبحسب مبرهنة — تكافؤ الآلة الذاتية المحدودة الحتمية والتعبير النمطي فإنّ هذا يعني أنّ منتظمة أيضًا.
بما أنّ ، فإنّ لمّة — التعبيرات النمطية مغلقة تحت التكملة ينص على أنّ مجموعة الدوال المنتظمة مغلقة تحت عملية «وال» (AND) أيضًا. وعلاوةً على ذلك، بما أنّ «أو» والنفي و«وال» تكوّن أساسًا كونيًّا، فإنّ هذه المجموعة مغلقة أيضًا تحت NAND وXOR وأيّ دالةٍ متناهية أخرى. أي إنّ لدينا النتيجة التالية:
مبرهنة — إغلاق التعبيرات النمطية
لتكن أيّ دالة بوليانية متناهية، ولتكن دوالًا منتظمة. إذًا الدالة منتظمة.
برهان
هذا نتيجةٌ مباشرة لانغلاق الدوال المنتظمة تحت «أو» والنفي (وبالتالي تحت «وال»)، مقترنةً بـمبرهنة — شمولية الدوائر المنطقية الذي ينص على أنّ كلّ يمكن حسابه بدائرة بوليانية (وهي مجرّد مزيج من عمليات «وال» و«أو» والنفي).
قيود التعبيرات النمطية ومبرهنة الضخّ
كفاية مطابقة التعبيرات النمطية تجعلها مفيدةً جدًّا. ولهذا فإنّ أنظمة التشغيل ومحرّرات النصوص تقيّد واجهة البحث فيها غالبًا على التعبيرات النمطية، ولا تسمح بالبحث عبر تحديد دالةٍ اعتباطية. لكنّ هذه الكفاءة تأتي بثمن. وكما رأينا، لا يمكن للتعبيرات النمطية حساب كلّ دالة. وفي الواقع، هناك بعض الدوال البسيطة جدًّا (والمفيدة!) التي لا يمكنها حسابها. وهنا مثالٌ على ذلك:
لمّة — مطابقة الأقواس
لتكن ، ولتكن الدالة التي، عند إعطائها سلسلة أقواس، تُخرج إذا وفقط إذا كان كلّ قوس فتح مطابقًا بقوس إغلاق مقابل. إذًا لا يوجد تعبير نمطي فوق يحسب .
لمّة — مطابقة الأقواس نتيجةٌ للنتيجة التالية، المعروفة بـ_مبرهنة الضخّ_ (pumping lemma):
مبرهنة — مبرهنة الضخّ
لتكن تعبيرًا نمطيًا فوق أبجديةٍ ما . إذًا يوجد عددٌ ما بحيث إنّ لكل ذات و، يمكننا كتابة لسلاسل تحقق الشروط التالية:
-
.
-
.
-
لكل .

الفكرة وراء الإثبات هي التالية. لتكن ضعف عدد الرموز المستخدَمة في التعبير ، فإنّ الطريقة الوحيدة التي يمكن أن يوجد فيها ذات و هي أن يحتوي على المشغّل (أي النجمة)، وأنّ هناك سلسلةً جزئية غير فارغة من طابقها لبعض تعبيرٍ جزئي من . يمكننا الآن تكرار أيّ عددٍ من المرّات وما زلنا نُنتج سلسلةً مطابقة. وانظر أيضًا الشكل التوضيحي.
توقّف وتأمّل
إنّ مبرهنة الضخّ ثقيلة قليلًا في صياغتها، لكنّ إحدى طرائق تذكّرها هي أنّها ببساطة تقول ما يلي: «إذا كانت سلسلةٌ تطابق تعبيرًا نمطيًا وطولها كافٍ، فلا بدّ أن يطابق أحدُ سلاسلها الفرعية باستخدام المشغّل ».
برهان
لإثبات المبرهنة رسميًا، نستعمل الحثّ على طول التعبير. وكما في كلّ براهين الحثّ، سيكون هذا طويلًا بعض الشيء، لكنّ الأمر في نهاية المطاف يعود مباشرةً إلى البديهة أعلاه بأنّنا في موضعٍ ما كان علينا استعمال عملية النجمة. وقراءة هذا الإثبات، وفهم كيفية ارتباط الإثبات الرسمي أدناه بالبديهة أعلاه، هي طريقةٌ جيّدة جدًا كي نتعوّد على براهين الحثّ من هذا النوع.
فرضية الحثّ لدينا هي أنّه لتعبيرٍ من الطول ، يحقّق شروط المبرهنة. أما الحالة الأساس (base case) فهي أن يكون التعبير رمزًا واحدًا أو أن يكون التعبير أو . في كلّ هذه الحالات تتحقّق شروط المبرهنة لمجرّد أنّ ، ولا توجد سلسلة من طولٍ أكبر من يطابقها التعبير.
ونُثبت الآن خطوة الحثّ (inductive step). لتكن تعبيرًا نمطيًا من رمزًا. نضع ولتكن سلسلةً تحقق . وبما أنّ يتضمّن أكثر من رمز واحد، فإنّه يأخذ أحد الأشكال (a) ، أو (b) ، أو (c) ، حيث في كلّ هذه الحالات يكون للتعبيرين الجزئيين و عددٌ من الرموز أقلّ من ، ومن ثمّ يحقّقان فرضية الحثّ.
في الحالة (a)، فإنّ كلّ سلسلة يطابقها يجب أن يطابقها أو . وإذا كان يطابق ، فإنّه بما أنّ ، وبحسب فرضية الحثّ، توجد مع و بحيث يطابق (وبالتالي أيضًا ) السلسلة لكلّ . والحجّة ذاتها تعمل في الحالة التي يكون فيها يطابق .
في الحالة (b)، إذا كان يطابقه ، يمكننا كتابة بحيث يطابق السلسلة ويطابق السلسلة . ونقسم إلى حالاتٍ فرعية. فإذا كان ، فإنّه بحسب فرضية الحثّ توجد مع و بحيث ويطابق السلسلة لكلّ . وهذا يُكمل الإثبات، إذ إنّنا إذا وضعنا رأينا أنّ وأنّ يطابق لكلّ . وإلّا، إذا كان ، فإنّه بما أنّ ، لا بدّ من أنّ . وعليه، بحسب فرضية الحثّ، توجد بحيث و ويطابق السلسلة لكلّ . لكنّنا الآن إذا وضعنا رأينا أنّ ، ومن جهة أخرى فإنّ التعبير يطابق لكلّ .
في الحالة (c)، إذا كان يطابقه ، فإنّ حيث لكلّ تكون سلسلةً غير فارغة يطابقها . وإذا كان ، فيمكننا استعمال الأسلوب نفسه المستخدَم أعلاه في حالة الدمج. وإلّا، فإنّنا نلاحظ مجرّدًا أنّه إذا كانت هي السلسلة الفارغة، و، و، فإنّ وتُطابَق بواسطة لكلّ .
ملاحظة — التعريفات العودية وبراهين الحثّ
عندما يكون كائنٌ ما معرَّفًا بشكلٍ عودي (كما في حالة التعبيرات النمطية)، فإنّه من الطبيعي أن نُثبت خصائص هذا النوع من الكائنات بالحثّ. أي إنّنا إذا أردنا إثبات أنّ جميع كائنات هذا النوع تملك الخاصية ، فإنّ من الطبيعي أن نستعمل خطوةَ حثٍّ تقول إنّه إذا كان لدى إلخ الخاصية ، فإنّ الكائن الناتج عن تركيبها يملكها أيضًا.
باستعمال مبرهنة الضخّ، يمكننا بسهولة إثبات لمّة — مطابقة الأقواس (أي إثبات أنّ دالة «مطابقة الأقواس» غير منتظمة):
برهان
لنفترض، بغرض التناقض، أنّه يوجد تعبير بحيث . ولتكن العددَ الناتج من مبرهنة — مبرهنة الضخّ، ولتكن (أي قوس فتح يليه قوس إغلاق). نرى عندها أنّه إذا كتبنا كما في مبرهنة — مبرهنة الضخّ، فإنّ الشرط يعني أنّ تتكوّن من أقواس الفتح فقط. ومن ثمّ فإنّ السلسلة ستحتوي أقواس فتح أكثر من أقواس الإغلاق. وعليه فإنّ ، لكنّ مبرهنة الضخّ تقول ، وهذا يناقض افتراضنا أنّ .
إنّ مبرهنة الضخّ أداةٌ مفيدة جدًّا لإظهار أنّ دوالّ معيّنة لا يمكن حسابها بتعبيرٍ نمطي. لكنّها ليست شرطًا «إذا وفقط إذا» للانظام: فهناك دوالّ غير منتظمة تحقّق شروط مبرهنة الضخّ رغم ذلك. ولفهم مبرهنة الضخّ، من الضروري اتّباع ترتيب المكمّيات في مبرهنة — مبرهنة الضخّ. وبشكل خاص، فإنّ العدد في نصّ مبرهنة — مبرهنة الضخّ يعتمد على التعبير النمطي (في الإثبات اخترنا يساوي ضعف عدد الرموز في التعبير). وعليه، إذا أردنا استعمال مبرهنة الضخّ لاستبعاد وجود تعبيرٍ نمطي يحسب دالةً ما ، فيجب أن نتمكّن من اختيار مُدخلٍ مناسب يمكن أن يكون كبيرًا اعتباطيًا ويحقّق . وهذا يبدو معقولًا إذا فكّرت في البديهة وراء مبرهنة الضخّ: فنحن نحتاج كبيرًا بما يكفي لإجبار استعمال مشغّل النجمة.

تمرين محلول — المتناظرات ليست منتظمة
برهن أنّ الدالة التالية فوق الأبجدية ليست منتظمة: إذا وفقط إذا كان حيث و تدلّ على «معكوسة»: أي السلسلة . (غالبًا ما تُعرَّف دالةُ المتناظر (Palindrome) دون رمز فاصل صريح ، لكنّ النسخة التي تحتوي فاصلًا من هذا النوع أنظف قليلًا، ولذلك نستعملها هنا. وهذا لا يصنع فرقًا كبيرًا، إذ يمكن بسهولة ترميز الفاصل كسلسلةٍ ثنائية خاصة بدلًا من ذلك.)
الحل
نستعمل مبرهنة الضخّ. ولنفترض، بغرض التناقض، أنّه يوجد تعبيرٌ نمطي يحسب ، ولتكن العددَ الذي تعطيه مبرهنة الضخّ (مبرهنة — مبرهنة الضخّ). نأخذ بعين الاعتبار السلسلة . وبما أنّ معكوس سلسلةِ أصفار هي سلسلةُ أصفار، فإنّ . والآن، بمبرهنة الضخّ، إذا كانت محسوبةً بـ، يمكننا كتابة بحيث و و لكلّ . وبخاصةٍ، لا بدّ أن يتحقّق ، لكنّ هذا تناقض، إذ إنّ ومن ثمّ فإنّ جزأيه ليسا بالطول نفسه، وهما بالتالي ليس أحدهما معكوس الآخر.
وللمثالٍ آخر على إثباتٍ يعتمد على مبرهنة الضخّ، انظر الشكل التوضيحي الذي يوضّح رسمًا تخطيطيًّا لإثبات عدم انتظام الدالة المعرَّفة بأنّ إذا وفقط إذا كان لبعض (أي أنّ تتكوّن من سلسلةِ أصفار متتالية، يليها سلسلةُ آحاد متتالية من الطول نفسه).
الإجابة عن الأسئلة الدلالية حول التعبيرات النمطية
للتعبيرات النمطية تطبيقات تتجاوز البحث. فعلى سبيل المثال، تُستخدَم التعبيرات النمطية كثيرًا لتحديد الرموز المُجزِّأة (tokens) (مثل ما يُعدّ معرّف متغيّر صالحًا أو كلمةً مفتاحية) في تصميم المحلِّلات النحوية (parsers) و_المصرِّفات_ (compilers) و_المفسِّرات_ (interpreters) الخاصة بلغات البرمجة. وللتعبيرات النمطية تطبيقات أخرى أيضًا: فعلى سبيل المثال، انتقل عالم الشبكات في السنوات الأخيرة من الطوبولوجيات الثابتة إلى «الشبكات المُعرَّفة برمجيًّا». وتُوجَّه هذه الشبكات بمفاتيحٍ قابلة للبرمجة تستطيع تنفيذ سياسات (policies) مثل «إذا كانت الحزمة مؤمَّنة بـSSL فوجّهها إلى A، وإلا فوجّهها إلى B». ولتمثيل هذه السياسات نحتاج لغةً تكون من جهةٍ معبِّرةً بما يكفي لالتقاط السياسات التي نريد تنفيذها، ومن جهةٍ أخرى مقيّدةً بما يكفي حتى نتمكّن من تنفيذها بسرعة على مستوى الشبكة، وأيضًا حتّى نتمكّن من الإجابة عن أسئلةٍ مثل «هل يستطيع C رؤية الحزم المنقولة من A إلى B؟». وتستعمل لغة برمجة الشبكات NetKAT (NetKAT network programming language) صيغةً من التعبيرات النمطية لتحقيق ذلك بالضبط. ولهذا التطبيق، من المهمّ ألّا نتمكّن فحسب من الإجابة عمّا إذا كان تعبير يطابق سلسلة ، بل أن نتمكّن أيضًا من الإجابة عن أسئلةٍ دلالية (semantic questions) حول التعبيرات النمطية مثل «هل يحسب التعبيران و الدالة نفسها؟» و«هل توجد سلسلة يطابقها التعبير ؟». وتُظهر المبرهنة التالية أنّه يمكننا الإجابة عن السؤال الأخير:
مبرهنة — إمكان حساب خِواء اللغات المنتظمة
توجد خوارزمية تُعطى تعبيرًا نمطيًا فتُخرج إذا وفقط إذا كانت هي الدالة الثابتة صفر.
الفكرة أنّه يمكننا ملاحظة ذلك مباشرةً من بنية التعبير. فالطريقة الوحيدة التي يحسب بها تعبيرٌ نمطي الدالة الثابتة صفر هي أن يكون على الشكل أو أن ينتج عن دمج مع تعبيراتٍ أخرى.
برهان
نعرّف التعبير النمطي بأنّه «خاوٍ» (empty) إذا كان يحسب الدالة الثابتة صفر. وبمعلومية تعبيرٍ نمطي ، يمكننا تحديد ما إذا كان خاويًا باستخدام القواعد التالية:
-
إذا كان على الشكل أو فليس خاويًا.
-
إذا كان ليس خاويًا فإنّ ليس خاويًا لكلّ .
-
إذا كان ليس خاويًا فإنّ ليس خاويًا.
-
إذا كان و كلاهما ليس خاويًا فإنّ ليس خاويًا.
-
خاوٍ.
وباستعمال هذه القواعد، من السهل إيجاد خوارزميةٍ عودية لتحديد الخِواء.
باستعمال مبرهنة — إمكان حساب خِواء اللغات المنتظمة، يمكننا الحصول على خوارزمية تحدّد ما إذا كان تعبيران نمطيان و متكافئين، بالمعنى أنّهما يحسبان الدالة نفسها.
مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية
لتكن الدالة التي، على مُدخل (سلسلة تمثّل) زوجٍ من التعبيرات النمطية ، يكون إذا وفقط إذا كان . إذًا توجد خوارزمية تحسب .
الفكرة هي أن نُبيّن أنّه بمعلومية زوجٍ من التعبيرات النمطية و يمكننا إيجاد تعبير بحيث يكون إذا وفقط إذا كان . وعليه فإنّ هي الدالة الثابتة صفر إذا وفقط إذا كان و متكافئين، وبذلك يمكننا اختبار خِواء لتحديد تكافؤ و.
برهان
سنُثبت مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية انطلاقًا من مبرهنة — إمكان حساب خِواء اللغات المنتظمة. (والمبرهنتان متكافئتان في الواقع: فمن السهل إثبات مبرهنة — إمكان حساب خِواء اللغات المنتظمة انطلاقًا من مبرهنة — إمكان حساب تكافؤ التعبيرات النمطية، إذ إنّ التحقّق من الخِواء هو نفسه التحقّق من التكافؤ مع التعبير .) بمعلومية تعبيرين نمطيين و، سنحسب تعبيرًا بحيث يكون إذا وفقط إذا كان . ومن الممكن ملاحظة أنّ يكافئ إذا وفقط إذا كان خاويًا.
نبدأ بالملاحظة على أنّه لكل بتّ ، فإنّ إذا وفقط إذا كان
ولذلك نحتاج إلى إنشاء بحيث لكل ،
ولإنشاء التعبير ، سنُبيّن كيف يمكننا، بمعلومية أيّ زوجٍ من التعبيرين و، إنشاء تعبيرَي و اللذين يحسبان الدالتين و على الترتيب. (وحساب التعبير الخاص بـ مباشر باستعمال عملية في التعبيرات النمطية.) وبشكل أدقّ، فإنّ بحسب لمّة — التعبيرات النمطية مغلقة تحت التكملة فإنّ الدوال المنتظمة مغلقة تحت النفي، ما يعني أنّه لكل تعبيرٍ نمطي يوجد تعبير بحيث لكل . والآن، لكل تعبيرين و، فإنّ التعبير يحسب «وال» بين التعبيرين. وبمعلومية هذين التحويلين، نرى أنّه لكل تعبيرين نمطيين و يمكننا إيجاد تعبيرٍ نمطي يحقق المعادلة بحيث يكون خاويًا إذا وفقط إذا كان و متكافئين.
خلاصة
- نموذجة المهام الحاسوبية على مداخل ذات حجمٍ اعتباطي باستخدام دوال لا نهائية .
- تأخذ هذه الدوال مُدخلًا على شكل سلسلة طويلة اعتباطيًّا (لكنها تبقى متناهية!)، ولا يمكن وصفها بجدولٍ متناهٍ من المداخل والمخارج.
- تُسمّى الدالة ذات الخرج البتّي الواحد دالة بوليانية، وتُعدّ مهمة حسابها مكافئةً لاتخاذ قرار بشأن لغة .
- الآلات الذاتية المحدودة الحتمية (DFAs) نموذجٌ بسيط لحساب الدوال البوليانية (اللا نهائية).
- هناك بعض الدوال التي لا يمكن حسابها بآلات ذاتية محدودة حتمية.
- مجموعة الدوال القابلة للحساب بآلات ذاتية محدودة حتمية هي نفسها مجموعة اللغات التي يمكن التعرّف عليها بالتعبيرات النمطية.
التمارين
تمرين — خصائص الإغلاق للدوال المنتظمة
لنفترض أنّ منتظمتان. لكلٍّ من تعريفات الدالة التالية، إمّا أن تُثبت أنّ منتظمة دائمًا أو تعطي مثالًا مضادًا على المنتظمتين يجعلان غير منتظمة.
-
.
-
-
.
-
حيث هو معكوس : أي مع .
-
-
-
تمرين
إحدى الدالتين التاليتين اللتين تُسقطان في يمكن حسابها بتعبيرٍ نمطي، والأخرى لا يمكن. فاكتب التعبير الذي يحسب الدالة القابلة للحساب، وبرهن مستعملًا مبرهنة الضخّ استحالة ذلك في حالة الدالة الأخرى.
-
إذا كان يقسم ، و فيما عدا ذلك.
-
إذا وفقط إذا كان ، و فيما عدا ذلك.
تمرين — عدم الانتظام
-
برهن أنّ الدالة التالية ليست منتظمة. فلكل ، يكون إذا وفقط إذا كان على الشكل لبعض .
-
برهن أنّ الدالة التالية ليست منتظمة. فلكل ، يكون إذا وفقط إذا كان لبعض .
ملاحظات مرجعية
العلاقة بين التعبيرات النمطية والآلات المتناهية موضوعٌ جميل، لا نلمس منه في هذا النصّ إلّا القليل. وقد عولج بمزيدٍ من التفصيل في [@SipserBook, @hopcroft, @kozen1997automata]. وتناقش هذه النصوص أيضًا مواضيعَ مثل الآلات الذاتية غير الحتمية (non-deterministic finite automata, NFA) والعلاقة بين القواعد الخالية من السياق والآلات ذات المكدّس.
أُنشئت الآلة الذاتية في الشكل التوضيحي باستخدام محاكي آلات الحالات المتناهية (FSM simulator) لإيفان زوزاك (Ivan Zuzak) وفيدرانا يانكوفيتش (Vedrana Jankovic). وإثباتنا لـمبرهنة — مطابقة التعبيرات النمطية في زمن خطّي يرتبط بدقّة بـمبرهنة ميهيل-نيرود (Myhill-Nerode Theorem). ويمكن صياغة أحد اتجاهَي مبرهنة ميهيل-نيرود بأنّه إذا كان تعبيرًا نمطيًا فإنّ هناك عددًا متناهىً على الأكثر من السلاسل بحيث إنّ لكلّ .