ماهي المحددات الإستحواذية
- المحرك الطماع: يقوم بجلب أكبر قدر من المطابقات أولاً ثم يبدأ في التخلي عن المطابقات تدريجياً
- المحرك الكسول: يقوم المحرك بمطابقة أقل قدر من المطابقات ثم يبدأ في زيادة المطابقات تدريجياً
طبعا بالرغم من أن كلاً من السلوكين يغير من طريقة المحرك في البحث لكن لايغير من حقيقة أن المحرك يقوم بعمل تتبع خلفي backtracking حتى يقوم بتجربة جميع الإحتمالات محاولاً إنجاح باقي رموز النمط، وهنا يمكن أن نمنع المحرك من تجربة جميع الإحمالات الممكنة وذلك عن طريق المحددات الاستحواذية Possessive quantifiers. والهدف منها هو زيادة كفاءة المحرك وأيضاً يمكن عن طريقها التخلي عن جزء معين من المطابقة.
كيف تعمل المحددات الإستحواذية
كما هو الحال في المحددات الطماعة Greedy quantifiers تقوم المحددات الاستحواذية بجلب أكبر قدر من المطابقات، ولكن على عكس المحددات الطماعة لاتقوم المحددات الاستحواذية بالتخلي عن أي شيء من المطابقة، فالصفقة هنا إما الكل أو لاشيء.
يمكن أن تجعل المحدد استحواذي بإضافة علامة + بعد المحدد هكذا ++ فكل من ?+ و {n,m}+ هي محددات استحواذية.
لفهم كيف تعمل المحددات الإستحواذية سنقوم أولاً باختبار النمط “[^”]*+” على النص “abc” :
- يقوم المحرك بالبحث عن “ وتحدث مطابقة
- ثم ينتقل المحرك للرمز [^”] للبحث عن أي شيء ليس “ فهو من فئات الرموز الملغية Negated Character sets
- يقوم المحرك بمطابقة كل من a,b,c بسبب وجود النجمة *
- ثم يقوم المحرك بالبحث عن “ في النهاية وتحدث مطابقة
وفي هذه الحالة تكون النتيجة واحدة سواء استخدمنا greedy quantifier أو possessive quantifiers لأن أي منهم سيقوم بمطابقة “ بعد مطابقة abc وبالتالي لاحاجة للتتبع الخلفي ولن يظهر تأثير تحسن أداء المحرك أساساً، لكن يظهر تحسن الأداء عند فشل المحرك.
قبل أن أبدا في شرح خطوات المحرك عند الفشل ، أريد أن أذكر أنه عندما قمت باختبار النمط “[^”]*+” عند الفشل لم يقم بشيء مختلف عن “[^”]*” وتبين أيضاً أن المحرك لم يقم بعمل تتبع خلفي في حالة “[^”]*” وهذا غير متوقع نهائياً، وبعد كثير من البحث طرحت سؤالي هنا وكانت الإجابة ان بعض المحركات ومنها المحرك PCRE الخاص بلغة PHP تقوم بشكل تلقائي بتحويل محددات الكميات في بعض الظروف إلى محددات كميات استحواذية، وبالتالي النمط “[^”]*” تم التعامل معه على أنه استحواذي على الرغم من أننا لم نكتبه هكذا “[^”]*+” علينا إلغاء التحويل التلقائي للمحدادات إلى محددات استحواذية، ويتم ذلك بإضافة (*NO_AUTO_POSSESS) إلى النمط هكذا (*NO_AUTO_POSSESS)”[^”]*” .
وبالتالي سيظهر الفارق بين كل من النمطين “[^”]*+” و “[^”]*” عند فشل المحرك في مطابقة النص “abcv ، حيث أنه:
في الحالة بدون محدد الكميات الإستحواذية يأخذ المحرك 12 خطوة ليكتشف أنه لايمكن مطابقة النمط لأنه يقوم بعمل تتبع خلفي محاولاً مطابقة “
أما في حالة المحدد الكميات الإستحواذي 8 خطوات فقط لأن المحرك عندما بقوم بمطابقة abc لن يتخلى عن أي منها لصالح محاولة جديدة لمطابقة “ وبالتالي تصبح المقارنة خطية Linear comparison حيث عند الفشل يقوم المحرك بالبدء من جديد من مكان آخر في النص.
وهنا تظهر أهمية المحددات الإستحواذية بأنها تجعل المحرك يصل لحالة الفشل اسرع وبالتالي تحسين في أداء المحرك والبرنامج ككل.
المحددات الاستحواذية تغير النتائج
يمكن أن يؤدي استخدام المحددات الاستحواذية إلى تغيير نتيجة المطابقة. نظرًا لعدم إجراء التتبع الخلفي، فلن يتم العثور على المطابقات التي تحتاج إلى محدد كمي طماع يقوم بتتبع خلفي.
فمثلا النمط “.*” سيقوم بمطابقة “abc” في “abc”x لكن “.*+” لن يقوم بذلك، وبالتالي عند استخدام محددات الكميات الإستحواذية، يجب عليك التأكد من أن كل ما تقوم بتطبيق محددات الكميات الإستحواذية عليه يجب ألا يكون المحدد قادرًا على مطابقة مايجب أن يطابقه الرمز الذي يليه في النمط. وهذا ما حدث في في المثال السابق، حيث أن النقطة تطابق أيضًا علامة “ بعد abc. وبالتالي استخدام المحددات الكمية هنا ليس الحل المناسب. لاحظ هذا لم يسبب مشكلة عند تطبيق النمط “[^”]*+” على النص “abcv لأن الفئة [^”] لايمكنها مطابقة “ بعد abc وبالتالي أن نجعلها استحواذية لن يسبب مشكلة في النتائج.
استخدام المجموعات الذرية بدلا من المحدادات الاستحواذية
أحياناً يكون استخدام المحددات الاستحوذية أسهل من المجموعات الذرية Atomic groups، لكن المشكلة تظهر عندما يكون المحك الذي تستخدمه لايدعم المحددات الاستحواذية، فجميع المحركات التي تدعم المحددات الاستحواذية تدعم أيضاً المجموعات الذرية ولكن ليس العكس. وبالتالي أحياناً يفرض عليك استخدام المجموعات الذرية.
فمثلا بدلاً من كتابة X*+ أكتب (?>X*) ومهم جداً الإنتباه إلى طريقة كتابة المجموعات الذرية Atomic groups فكما تلاحظ كلاً من الرمز ومحدد الكمية مكتوبان داخل المجموعة. وأيضاً (?:a|b)*+ تكافئ (?>(?:a|b)*) ولكن لاتكتبها هكذا (?>a|b)* لأن هذا لايعطي نفس النتيجة.
ربما تحصل على نمط تعبيري جاهز معتمد على المحددات الاستحواذية ولكنك تنوي استخدامه في محرك لايدعمها وبالتالي ستحتاج لتحويلها للنسخة المكافئة لها بالتجميع الذري، ولكن هناك أداة غاية في القوة والروعة يمكنها أن تساعدك في ذلك اسمها RegexBuddy.