2025, Oct 19 08:31
Python/PCRE2 में रोमन अंक निकालते समय खाली матч से बचें
case-insensitive regex के साथ रोमन अंक निकालते समय होने वाले शून्य-लंबाई मैच की समस्या का कारण और समाधान जानें. Python/PCRE2 के लिए परखा पैटर्न.
सतत पाठ से रोमन अंक निकालना आसान प्रतीत होता है, जब तक कि कोई रेगुलर एक्सप्रेशन चुपचाप शून्य-लंबाई वाले मैच न देने लगे। ऐसे खाली कैप्चर परिणामों को दूषित करते हैं और आगे की प्रोसेसिंग को उलझा देते हैं—खासकर जब Python या PCRE2 में case-insensitive मोड में एक ही regex से काम चलाना हो। यहाँ एक सटीक उपाय है, जो खाली मैचों को खत्म करता है और इच्छित व्यवहार भी बनाए रखता है।
समस्या की रूपरेखा
साधारण वाक्यों और अलग‑थलग पंक्तियों के भीतर रोमन अंकों का मिलान परखने के लिए इस्तेमाल किया गया यह नमूना पाठ देखें:
Charles I was a bad king, I was not.
Charles X was a good one.
Who was Louis XVI?
The year is MCMXCIX, the month is June.
Do you need an X-ray, do you think?
My friends Cil and Cleo met me for coffee.
MCMLXIX
मानक रोमन अंकों के लिए एक मज़बूत पैटर्न यह है:
(?=\b[MCDXLVI]+\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})(?!-)\b
Case-insensitive ढंग से चलाने पर यह सही रोमन अंक ढूंढ लेता है, लेकिन साथ ही दो खाली मैच भी देता है: X‑ray में X से ठीक पहले, और Cil से ठीक पहले। लुकअहेड उन स्थानों को स्वीकार कर लेता है क्योंकि आगे आने वाले शब्द अक्षर सब रोमन अक्षर हैं; फिर भी मुख्य अंक वाला उपपैटर्न कानूनी तौर पर कुछ भी न खा कर भी मेल खा सकता है, इसलिए इंजन उन स्थानों पर शून्य‑लंबाई का मैच रिपोर्ट कर देता है।
खाली मैच क्यों बनते हैं
शुरुआती एसेर्शन सिर्फ इतना परखता है कि आगे रोमन अक्षर आ रहे हैं; यह नहीं कि पैटर्न उन्हें वाकई खपत करता है। अंक का मुख्य भाग वैकल्पिक टुकड़ों से बना है, जैसे M{0,4}, D?C{0,3} आदि—जिनमें से हर एक खाली स्ट्रिंग से भी मेल खा सकता है। जब अंतिम सीमा इसकी इजाज़त दे, पूरा पैटर्न पाठ में स्थिति बढ़ाए बिना सफल हो सकता है। यही X‑ray में X से पहले और Cil से पहले होता है: लुकअहेड वैध अक्षर “देख” लेता है, बॉडी सारे खाली विकल्प चुन लेती है, और अंतिम सीमा भी पास हो जाती है—नतीजा, शून्य‑लंबाई का मैच।
समाधान
कुंजी यह है कि मैच के अंत वाली शर्त यह साबित करे कि शुरू में दिखे रोमन अक्षर सचमुच खपत हुए। सीमा और डैश की जाँच की जगह ऐसी एसेर्शन लगाएँ जो आगे आने वाले अल्फ़ान्यूमेरिक और डैश को मना करे। इससे मैच का अंत उन अक्षरों से दूर टिकता है जिन्हें लुकअहेड ने पहचाना था।
(?!-)\b को बदलकर (?![\w-]) कर दें। यह होने पर शुरुआती लुकअहेड सरल किया जा सकता है, क्योंकि अब बस यह सुनिश्चित करना है कि हम किसी शब्द अक्षर से पहले वाली शब्द‑सीमा पर हैं।
संशोधित पैटर्न:
\b(?=\w)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})(?![\w-])
अब इंजन तब तक मैच नहीं बताएगा जब तक रोमन अंक वाले घटक आगे दिखाई दिए शब्द अक्षरों को खपत न कर लें—इस तरह शून्य‑लंबाई वाले परिणाम हट जाते हैं।
वैकल्पिक सुधार
घटाव वाले युग्मों की आल्टर्नेशन बिना व्यवहार बदले और सघन लिखी जा सकती है। CM|CD को C[MD], XC|XL को X[CL], और IX|IV को I[XV] किया जा सकता है। यदि आप यह शैली पसंद करते हैं, तो संक्षिप्त समकक्ष यह है:
\b(?=\w)M{0,4}(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3})(?![\w-])
यह क्यों मायने रखता है
शून्य‑लंबाई वाले मैच पेचीदा होते हैं। ये गिनती बढ़ा देते हैं, आगे की प्रोसेसिंग को जटिल बनाते हैं, और स्कैनिंग लूप में अनपेक्षित व्यवहार शुरू कर सकते हैं। जब आप गद्य से रोमन अंक निकालने के लिए एक ही रेगुलर एक्सप्रेशन पर निर्भर हों, तो यह सुनिश्चित करना कि पैटर्न पाठ खपत किए बिना सफल न हो सके, मुश्किल से दिखने वाली बगों से बचाता है।
निष्कर्ष
यदि कोई regex एक उदार लुकअहेड से शुरू होता है और उसकी बॉडी में वैकल्पिक या खाली‑स्वीकृत हिस्से हैं, तो वह इनपुट खपत किए बिना भी सफल हो सकता है। शुरुआत और अंत की एसेर्शनों को ऐसे जोड़ें कि अंत स्पष्ट रूप से आगे जारी अल्फ़ान्यूमेरिक (और यहाँ, हाइफ़न) को नकार दे—ताकि बॉडी को वही खपत करनी पड़े जो शुरुआत ने देखा था। Python या PCRE2 में इस रोमन अंक वाले मामले के लिए, case‑insensitive मोड में सामने \b(?=\w) और अंत में (?![\w-]) लगाने से खाली मैच साफ़ हट जाते हैं, जबकि सामान्य पाठ के भीतर इच्छित हिट्स मिलते रहते हैं।