2025, Oct 04 17:32
टेक्स्ट नॉर्मलाइज़ेशन: Python में newline रन को x→x−1 घटाने का regex
जानें कैसे Python regex और lookahead से हर newline रन को ठीक एक से घटाएं (x→x−1), बिना पैराग्राफ संरचना तोड़े। व्यावहारिक उदाहरण, सावधानियाँ और सही पैटर्न।
टेक्स्ट नॉर्मलाइज़ेशन अक्सर मामूली-सा काम लगता है, लेकिन एक छोटा नियम आते ही काम का स्वरूप बदल जाता है। आम तौर पर अपेक्षा यह होती है कि नई पंक्तियों (newline) की हर श्रृंखला को बिल्कुल एक से घटाया जाए—उन्हें पूरी तरह एक में न समेटा जाए। यह महीन-सी शर्त “x newlines को x−1 newlines में बदलो” आसानी से नजरअंदाज हो जाती है, खासकर जब हम सीधे ‘सबको एक में समेटो’ वाला तरीका चुन लेते हैं।
समस्या विवरण
किसी स्ट्रिंग में कई-कई newline के रन हों, तो लक्ष्य है हर रन से ठीक एक newline घटाना। उदाहरण के लिए, शुरुआत यह है:
"Anna lives in Latin America.\n\nShe loves the vibes from the cities\n and the good weather.\n\n\nAnna is great"
अपेक्षित परिणाम:
"Anna lives in Latin America.\nShe loves the vibes from the cities and the good weather.\n\nAnna is great"
सीधा-सा प्रयास जो अर्थ बदल देता है
कई newlines को एक में समेट देना लुभावना लगता है। लेकिन यह हर रन से ठीक एक newline घटाने के बराबर नहीं है। यह कार्यान्वयन देखें:
import re
def shrink_lines(blob):
    blob = re.sub(r'\n{2,}', '\n', blob)
    return blob
sample = "Anna lives in Latin America.\n\nShe loves the vibes from the cities\n and the good weather.\n\n\nAnna is great"
result = shrink_lines(sample)
यह देता है:
"Anna lives in Latin America.\nShe loves the vibes from the cities\n and the good weather.\nAnna is great"
जो हमारा लक्ष्य नहीं है। “Anna is great” से पहले वाली तीन लगातार newlines एक में सिमट जाती हैं—यानी जहाँ एक हटनी चाहिए थी, वहाँ दो हट गईं।
यह असंगति क्यों होती है
पैटर्न \n{2,} दो या अधिक newlines की किसी भी श्रृंखला को एक newline से बदल देता है। मतलब, रन जितना लंबा, उतनी अधिक newlines हट जाती हैं। यानी यह सामान्यीकरण करता है, घटाव नहीं। जबकि अपेक्षा है कि लगातार आने वाले हर रन—यहाँ तक कि अकेली newline भी—से केवल एक newline हटे, ताकि हर रन एक से छोटा हो जाए।
समाधान: हर रन में सिर्फ अंतिम newline हटाएँ
हर लगातार श्रृंखला से ठीक एक newline घटाने का सटीक तरीका है: ऐसे newline को मिलान करना जो अगले newline से तुरंत न जुड़ा हो, और उसे हटाना। इसके लिए lookahead बिल्कुल उपयुक्त है:
import re
def trim_one_break(segment):
    return re.sub(r'\n(?!\n)', '', segment)
text_in = "Anna lives in Latin America.\n\nShe loves the vibes from the cities\n and the good weather.\n\n\nAnna is great"
text_out = trim_one_break(text_in)
यह तरीका हर सतत समूह की आखिरी newline पकड़कर हटाता है। अकेली newline शून्य हो जाती है, डबल single बनती है, और ट्रिपल double—यानी बिल्कुल x → x−1 वाला व्यवहार।
महत्वपूर्ण सावधानी
एक से घटाव केवल उन रन पर सही काम करता है जहाँ newlines लगातार हों। newlines के बीच अगर whitespace है, तो वे एक ही रन नहीं माने जाते। उदाहरण के लिए, अनुक्रम "This is line 1\n \nThis is line 2" पर \n(?!\n) लगाने के बाद यह "This is line 1 This is line 2" बन जाता है। स्पेस adjacency तोड़ देता है, इसलिए दोनों newlines अलग-अलग समझी जाती हैं और दोनों हट जाती हैं—कुल मिलाकर दो की कमी हो जाती है।
यह क्यों मायने रखता है
टेक्स्ट पाइपलाइनों में स्पेसिंग का स्थिर व्यवहार बहुत अहम होता है। “हर newline रन को एक कम कर दो” जैसा नियम पैराग्राफ़ की आपसी संरचना को बरकरार रखते हुए पूरे दस्तावेज़ में लेआउट को समान रूप से कस देता है। इसके बजाय “सबको एक में समेटो” अपनाने से संरचना चुपचाप बदल सकती है और अर्थपूर्ण खाली जगह मिट सकती है।
मुख्य बातें
यदि उद्देश्य लगातार newline अनुक्रमों को ठीक एक से घटाना है, तो हर रन की अंतिम newline को lookahead के साथ मैच करके हटा दें। ध्यान रहे, newlines के बीच कोई भी स्पेस या अन्य वर्ण रन को तोड़ देते हैं और नतीजा बदल जाता है। अपने डेटा में “sequence” की परिभाषा के अनुरूप ही regex लिखें, और उन किनारी स्थितियों की पड़ताल करें जहाँ लाइन ब्रेक्स के बीच whitespace आ सकता है।
यह लेख StackOverflow पर एक प्रश्न, जिसे Alexis ने पूछा, और Chris Maurer के उत्तर पर आधारित है।