रूबी 2.0 द्वारा 2013 में शिप की गई कई नई सुविधाओं में से, जिस पर मैंने कम से कम ध्यान दिया वह था नया रेगुलर एक्सप्रेशन इंजन, ओनिगमो। आखिरकार, रेगुलर एक्सप्रेशन तो रेगुलर एक्सप्रेशन हैं - मुझे इसकी परवाह क्यों करनी चाहिए कि रूबी उन्हें कैसे लागू करता है?
जैसा कि यह पता चला है, ओनिगमो रेगेक्स इंजन की आस्तीन में कुछ साफ-सुथरी तरकीबें हैं जिनमें आपके रेगुलर एक्सप्रेशन के अंदर सशर्त अभिव्यक्ति का उपयोग करने की क्षमता शामिल है।
इस पोस्ट में हम रेगेक्स कंडीशनल में गोता लगाएंगे, रूबी के उनके कार्यान्वयन की विचित्रताओं के बारे में जानेंगे, और रूबी की सीमाओं को दूर करने के लिए कुछ ट्रिक्स पर चर्चा करेंगे। चलिए शुरू करते हैं!
समूह और कैप्चरिंग
रेगुलर एक्सप्रेशन में कंडीशनल को समझने के लिए, आपको पहले समूह और कैप्चरिंग को समझने की आवश्यकता है।
कल्पना करें कि आपके पास यूएस शहरों की एक सूची है:
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 से शुरू न हो।
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 से मिलान करने की कोशिश करता है और विफल होता है यदि टेक्स्ट "USD" मौजूद है:
"100USD".match(/(USD)(?(1)\d+)/) # nil
पर्ल और कुछ अन्य भाषाओं में, आप अपने कंडीशनल में एक लुक-अहेड स्टेटमेंट जोड़ सकते हैं। यह आपको स्ट्रिंग में कहीं भी टेक्स्ट के आधार पर कंडीशनल को ट्रिगर करने देता है। लेकिन रूबी में यह नहीं है, इसलिए हमें थोड़ा रचनात्मक होना होगा।
लुक-अराउंड
सौभाग्य से, हम लुक-अराउंड अभिव्यक्ति का दुरुपयोग करके रूबी के रेगेक्स कंडीशनल की सीमाओं को दूर कर सकते हैं।
लुक-अराउंड क्या है?
आम तौर पर, रेगुलर एक्सप्रेशन पार्सर आपके स्ट्रिंग के माध्यम से शुरुआत से अंत तक मैच की तलाश में कदम रखता है। यह वर्ड प्रोसेसर में कर्सर को बाएं से दाएं ले जाने जैसा है।
लुक-अहेड और लुक-बिहाइंड अभिव्यक्तियां थोड़ी अलग तरीके से काम करती हैं। वे आपको बिना किसी वर्ण का उपभोग किए स्ट्रिंग का निरीक्षण करने देती हैं। जब वे समाप्त हो जाते हैं, तो कर्सर ठीक उसी स्थान पर छोड़ दिया जाता है जहां यह शुरुआत में था।
लुक-अराउंड में महारत हासिल करने के लिए Rexegg की गाइड देखें।
सिंटैक्स इस तरह दिखता है:
| Type | Syntax | Example |
|---|---|---|
| Look Ahead | (?=query) |
\d+(?= dollars) matches 100 in "100 dollars" |
| Negative Look Ahead | (?!query) |
\d+(?! dollars) matches 100 if it is NOT followed by the word "dollars" |
| Look Behind | (?<=query) |
(?<=lucky )\d matches 7 in "lucky 7" |
| Negative Look Behind | (?<!query) |
(?<!furious )\d matches 7 in "lucky 7" |
कंडीशनल बढ़ाने के लिए लुक-अराउंड का दुरुपयोग
हमारे कंडीशनल में, हम केवल उन समूहों के अस्तित्व को क्वेरी कर सकते हैं जो पहले से सेट हैं। आम तौर पर, इसका मतलब है कि समूह की सामग्री का उपभोग किया गया है और कंडीशनल के लिए उपलब्ध नहीं है।
लेकिन आप एक लुक-अहेड का उपयोग करके एक समूह को बिना किसी वर्ण का उपभोग किए सेट कर सकते हैं! क्या आपका दिमाग उड़ गया?
याद रखें यह कोड जो काम नहीं कर रहा था?
"100USD".match(/(USD)(?(1)\d+)/) # nil
अगर हम इसे लुक-अहेड में समूह को कैप्चर करने के लिए संशोधित करते हैं, तो यह अचानक ठीक काम करता है:
"100USD".match(/(?=.*(USD))(?(1)\d+)/) => #<MatchData "100" 1:"USD">
आइए उस क्वेरी को तोड़ें और देखें कि क्या हो रहा है:
(?=.*(USD))लुक-अहेड का उपयोग करके, "USD" के लिए टेक्स्ट स्कैन करें और इसे समूह 1 में कैप्चर करें(?(1)यदि समूह 1 मौजूद है\d+तो एक या अधिक नंबर से मिलान करें
बहुत साफ, है ना?