2013 में रूबी 2.0 ने जिन कई नई विशेषताओं को वापस भेज दिया, उनमें से जिस पर मैंने कम से कम ध्यान दिया, वह था नया रेगुलर एक्सप्रेशन इंजन, ओनिग्मो। आखिरकार, रेगुलर एक्सप्रेशन रेगुलर एक्सप्रेशन हैं - मुझे इसकी परवाह क्यों करनी चाहिए कि रूबी उन्हें कैसे लागू करती है?
जैसा कि यह पता चला है, ओनिग्मो रेगेक्स इंजन की आस्तीन में कुछ साफ-सुथरी चालें हैं जिनमें आपके नियमित अभिव्यक्तियों के अंदर सशर्त उपयोग करने की क्षमता शामिल है।
इस पोस्ट में हम रेगेक्स कंडीशन्स के बारे में जानेंगे, रूबी द्वारा उनके कार्यान्वयन की विचित्रताओं के बारे में जानेंगे, और रूबी की सीमाओं के आसपास काम करने के लिए कुछ ट्रिक्स पर चर्चा करेंगे। आइए शुरू करें!
ग्रुप और कैप्चरिंग
रेगुलर एक्सप्रेशन में कंडीशनल को समझने के लिए, आपको सबसे पहले ग्रुपिंग और कैप्चरिंग को समझना होगा।
कल्पना कीजिए कि आपके पास अमेरिकी उद्धरणों की सूची है:
Fayetteville, AR
Seattle, WA
आप शहर के नाम को राज्य के संक्षिप्त नाम से अलग करना चाहेंगे। ऐसा करने का एक तरीका कई मैच करना है:
PLACE = "Fayetteville, AR"
# City: Match any char that's not a comma
PLACE.match(/[^,]+/)
# => #<MatchData "Fayetteville">
# Separator: Match a comma and optional spaces
PLACE.match(/, */)
# => #<MatchData ", ">
# State: Match a 2-letter code at the end of the string.
PLACE.match(/[A-Z]{2}$/)
# => #<MatchData "AR">
यह काम करता है, लेकिन यह बहुत वर्बोज़ है। समूहों का उपयोग करके, आप केवल एक रेगुलर एक्सप्रेशन के साथ शहर और राज्य दोनों को कैप्चर कर सकते हैं।
तो चलिए ऊपर दिए गए रेगुलर एक्सप्रेशन को जोड़ते हैं, और प्रत्येक सेक्शन को कोष्ठकों से घेरते हैं। माता-पिता हैं कि आप चीजों को नियमित अभिव्यक्तियों में कैसे समूहित करते हैं।
PLACE = "Fayetteville, AR"
m = PLACE.match(/([^,]+)(, *)([A-Z]{2})/)
# => #<MatchData "Fayetteville, AR" 1:"Fayetteville" 2:", " 3:"AR">
जैसा कि आप देख सकते हैं, उपरोक्त अभिव्यक्ति शहर और राज्य दोनों को दर्शाती है। आप MatchData
. का इलाज करके उन तक पहुंच सकते हैं एक सरणी की तरह:
m[1]
# => "Fayetteville"
m[3]
# => "AR"
समूहीकरण के साथ समस्या, जैसा कि ऊपर किया गया है, यह है कि कैप्चर किए गए डेटा को एक सरणी में डाल दिया जाता है। यदि सरणी में इसकी स्थिति बदल जाती है, तो आपको अपना कोड अपडेट करना होगा या आपने अभी-अभी एक बग पेश किया है।
उदाहरण के लिए, हम यह तय कर सकते हैं कि ", "
. को कैप्चर करना मूर्खतापूर्ण है पात्र। इसलिए हम रेगुलर एक्सप्रेशन के उस हिस्से के आसपास के माता-पिता को हटा देते हैं:
m = PLACE.match(/([^,]+), *([A-Z]{2})/)
# => #<MatchData "Fayetteville, AR" 1:"Fayetteville" 2:"AR">
m[3]
# => nil
लेकिन अब m[3]
अब राज्य - बग शहर शामिल नहीं है।
नामांकित समूह
आप रेगुलर एक्सप्रेशन समूहों को नाम देकर उन्हें अधिक अर्थपूर्ण बना सकते हैं। सिंटैक्स काफी हद तक हमारे द्वारा उपयोग किए जाने के समान है। हम रेगेक्स को माता-पिता में घेरते हैं, और इस तरह नाम निर्दिष्ट करते हैं:
/(?<groupname>regex)/
यदि हम इसे अपने शहर/राज्य रेगुलर एक्सप्रेशन पर लागू करते हैं, तो हमें यह प्राप्त होता है:
m = PLACE.match(/(?<city>[^,]+), *(?<state>[A-Z]{2})/)
# => #<MatchData "Fayetteville, AR" city:"Fayetteville" state:"AR">
और हम MatchData
. का इलाज करके कैप्चर किए गए डेटा तक पहुंच सकते हैं हैश की तरह:
m[:city]
# => "Fayetteville"
सशर्त
रेगुलर एक्सप्रेशन में कंडीशनल फॉर्म लेते हैं /(?(A)X|Y)/
. उनका उपयोग करने के कुछ मान्य तरीके यहां दिए गए हैं:
# If A is true, then evaluate the expression X, else evaluate Y
/(?(A)X|Y)/
# If A is true, then X
/(?(A)X)/
# If A is false, then Y
/(?(A)|Y)/
आपकी स्थिति के लिए सबसे आम विकल्पों में से दो, A
हैं:
- क्या कोई नामित या क्रमांकित समूह कैप्चर किया गया है?
- क्या चारों ओर देखने का मूल्यांकन सत्य होता है?
आइए देखें कि उनका उपयोग कैसे करें:
क्या कोई समूह कैप्चर किया गया है?
किसी समूह की उपस्थिति की जांच करने के लिए, ?(n)
. का उपयोग करें सिंटैक्स, जहां n एक पूर्णांक है, या समूह का नाम <>
. से घिरा हुआ है या ''
।
# Has group number 1 been captured?
/(?(1)foo|bar)/
# Has a group named "mygroup" been captured?
/(?(<mygroup>)foo|bar)/
उदाहरण
कल्पना कीजिए कि आप यूएस टेलीफ़ोन नंबर पार्स कर रहे हैं। इन नंबरों में तीन अंकों का क्षेत्र कोड होता है जो तब तक वैकल्पिक होता है जब तक कि संख्या एक से शुरू न हो।
1-800-555-1212 # Valid
800-555-1212 # Valid
555-1212 # Valid
1-555-1212 # INVALID!!
संख्या 1 से शुरू होने पर ही हम क्षेत्र कोड को एक आवश्यकता बनाने के लिए एक सशर्त का उपयोग कर सकते हैं।
# This regular expression looks complex, but it's made of simple pieces
# `^(1-)?` Does the string start with "1-"? If so, capture it as group 1
# `(?(1)` Was anything captured in group one?
# `\d{3}-` if so, do a required match of three digits and a dash (the area code)
# `|(\d{3}-)?` if not, do an optional match of three digits and a dash (area code)
# `\d{3}-\d{4}` match the rest of the phone number, which is always required.
re = /^(1-)?(?(1)\d{3}-|(\d{3}-)?)\d{3}-\d{4}/
"1-800-555-1212".match(re)
#=> #<MatchData "1-800-555-1212" 1:"1-" 2:nil>
"800-555-1212".match(re)
#=> #<MatchData "800-555-1212" 1:nil 2:"800-">
"555-1212".match(re)
#=> #<MatchData "555-1212" 1:nil 2:nil>
"1-555-1212".match(re)
=> nil
सीमाएं
समूह-आधारित सशर्तों का उपयोग करने में एक समस्या यह है कि समूह से मेल खाने वाले स्ट्रिंग में उन वर्णों का "खपत" होता है। उन वर्णों का उपयोग सशर्त द्वारा नहीं किया जा सकता है।
उदाहरण के लिए, यदि टेक्स्ट "यूएसडी" मौजूद है, तो निम्न कोड 100 से मेल खाने की कोशिश करता है और विफल रहता है:
"100USD".match(/(USD)(?(1)\d+)/) # nil
पर्ल और कुछ अन्य भाषाओं में, आप अपने कंडीशनल में एक लुक-फ़ॉरवर्ड स्टेटमेंट जोड़ सकते हैं। यह आपको स्ट्रिंग में कहीं भी टेक्स्ट के आधार पर सशर्त ट्रिगर करने देता है। लेकिन रूबी के पास यह नहीं है, इसलिए हमें थोड़ा रचनात्मक होना होगा।
चारों ओर देखें
सौभाग्य से, हम लुक-अराउंड एक्सप्रेशन का दुरुपयोग करके रूबी के रेगेक्स सशर्त में सीमाओं के आसपास काम कर सकते हैं।
आसपास क्या है?
आम तौर पर, नियमित अभिव्यक्ति पार्सर मैचों की तलाश में शुरुआत से अंत तक आपकी स्ट्रिंग के माध्यम से कदम उठाता है। यह किसी शब्द संसाधक में कर्सर को बाएँ से दाएँ घुमाने जैसा है।
आगे-आगे और पीछे-पीछे के भाव थोड़े अलग तरीके से काम करते हैं। वे आपको किसी भी वर्ण का उपभोग किए बिना स्ट्रिंग का निरीक्षण करने देते हैं। जब वे कर लिए जाते हैं, तो कर्सर ठीक उसी स्थान पर छोड़ दिया जाता है जहां वह शुरुआत में था।
चारों ओर देखने के लिए एक महान परिचय के लिए, आगे देखने और पीछे देखने के लिए रेक्सग की मार्गदर्शिका देखें
सिंटैक्स ऐसा दिखता है:
टाइप करें | <थ>वाक्यविन्यासउदाहरण | |
---|---|---|
आगे देखें | (?=query) | \d+(?= dollars) "100 डॉलर" में 100 से मेल खाता है |
नकारात्मक नजरिया | (?!query) | \d+(?! dollars) 100 से मेल खाता है यदि इसके बाद "डॉलर" शब्द नहीं आता है |
पीछे देखो | (?<=query) | (?<=lucky )\d "भाग्यशाली 7" में 7 से मेल खाता है |
पीछे नकारात्मक नजरिया | (?<!query) | (?<!furious )\d "भाग्यशाली 7" में 7 से मेल खाता है |
सशर्तों को बेहतर बनाने के लिए लुक-अराउंड का दुरुपयोग
हमारे सशर्त में, हम केवल उन समूहों के अस्तित्व को क्वेरी कर सकते हैं जो पहले ही सेट हो चुके हैं। आम तौर पर, इसका मतलब है कि समूह की सामग्री का उपभोग कर लिया गया है और सशर्त के लिए उपलब्ध नहीं है।
लेकिन आप बिना किसी वर्ण के समूह सेट करने के लिए लुक-फ़ॉरवर्ड का उपयोग कर सकते हैं! क्या आपका दिमाग अभी तक उड़ा है?
यह कोड याद रखें जो काम नहीं करता था?
"100USD".match(/(USD)(?(1)\d+)/) # nil
अगर हम समूह को आगे देखने के लिए इसे संशोधित करते हैं, तो यह अचानक ठीक काम करता है:
"100USD".match(/(?=.*(USD))(?(1)\d+)/)
=> #<MatchData "100" 1:"USD">
आइए उस क्वेरी को तोड़ दें और देखें कि क्या हो रहा है:
(?=.*(USD))
आगे की ओर देखते हुए, "USD" के लिए टेक्स्ट को स्कैन करें और इसे समूह 1 में कैप्चर करें(?(1)
अगर समूह 1 मौजूद है\d+
फिर एक या अधिक संख्याओं का मिलान करें
बहुत साफ, हुह?