Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

रूबी के छिपे हुए रत्न की खोज: शक्तिशाली टेक्स्ट पार्सिंग के लिए StringScanner

रूबी न केवल एक मजेदार भाषा है, यह एक उत्कृष्ट स्टैंडर्ड लाइब्रेरी के साथ भी आती है। जिनमें से कुछ उतने ज्ञात नहीं हैं, और लगभग छिपे हुए रत्न हैं। आज अतिथि लेखक माइकल कोहल एक पसंदीदा पर प्रकाश डालते हैं: StringScanner।

रूबी के छिपे हुए रत्न: StringScanner

डेटा संरचनाओं जैसे OpenStruct और Set से लेकर CSV पार्सिंग और बेंचमार्किंग तक, तीसरे पक्ष के जेम्स को इंस्टॉल किए बिना काफी दूर तक पहुंचा जा सकता है। हालांकि, रूबी के स्टैंडर्ड इंस्टॉलेशन में कुछ कम-ज्ञात लाइब्रेरीज़ उपलब्ध हैं जो बहुत उपयोगी हो सकती हैं, जिनमें से एक है StringScanner जो दस्तावेज़ीकरण के अनुसार "एक स्ट्रिंग पर लेक्सिकल स्कैनिंग ऑपरेशन प्रदान करता है"।

स्कैनिंग और पार्सिंग

तो "लेक्सिकल स्कैनिंग" का वास्तव में क्या मतलब है? अनिवार्य रूप से यह एक इनपुट स्ट्रिंग लेने और निश्चित नियमों का पालन करके उससे जानकारी के सार्थक टुकड़ों को निकालने की प्रक्रिया का वर्णन करता है। उदाहरण के लिए, यह एक कंपाइलर के पहले चरण में देखा जा सकता है जो 2 + 1 जैसे एक्सप्रेशन को इनपुट के रूप में लेता है और इसे टोकन के निम्नलिखित अनुक्रम में बदल देता है:

[{ number: "1" }, {operator: "+"}, { number: "1"}] 

लेक्सिकल स्कैनर्स आमतौर पर फाइनाइट-स्टेट ऑटोमेटा के रूप में लागू किए जाते हैं और कई प्रसिद्ध उपकरण उपलब्ध हैं जो उन्हें हमारे लिए जेनरेट कर सकते हैं (उदाहरण के लिए ANTLR या Ragel)।

हालांकि, कभी-कभी हमारी पार्सिंग ज़रूरतें इतनी विस्तृत नहीं होती हैं, और रेगुलर एक्सप्रेशन आधारित StringScanner जैसी सरल लाइब्रेरी ऐसी स्थितियों में बहुत काम आ सकती है। यह एक तथाकथित स्कैन पॉइंटर के स्थान को याद करके काम करता है जो स्ट्रिंग में एक इंडेक्स से ज्यादा कुछ नहीं है। स्कैनिंग प्रक्रिया तब स्कैन पॉइंटर के ठीक बाद के कोड को प्रदान किए गए एक्सप्रेशन से मिलाने की कोशिश करती है। मिलान ऑपरेशन्स के अलावा, StringScanner स्कैन पॉइंटर को मूव करने (स्ट्रिंग के माध्यम से आगे या पीछे बढ़ने), आगे देखने (स्कैन पॉइंटर को संशोधित किए बिना आगे क्या है यह देखना) के साथ-साथ यह पता लगाने के लिए विधियाँ प्रदान करता है कि हम वर्तमान में स्ट्रिंग में कहां हैं (क्या यह लाइन/पूरी स्ट्रिंग की शुरुआत या अंत है आदि)।

रेल्स लॉग्स पार्स करना

काफी सिद्धांत, आइए StringScanner को क्रिया में देखें। निम्नलिखित उदाहरण नीचे दिए गए रेल्स के लॉग एंट्री की तरह एक लॉग एंट्री लेगा,

log_entry = <<EOS Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 Processing by HomeController#index as HTML Rendered text template within layouts/application (0.0ms) Rendered layouts/_assets.html.erb (2.0ms) Rendered layouts/_top.html.erb (2.6ms) Rendered layouts/_about.html.erb (0.3ms) Rendered layouts/_google_analytics.html.erb (0.4ms) Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) EOS

और इसे निम्नलिखित हैश में पार्स करेगा:

{ method: "GET", path: "/" ip: "127.0.0.1", timestamp: "2017-08-20 20:53:10 +0900", success: true, response_code: "200", duration: "79ms", }

नोट: जबकि यह StringScanner के लिए एक अच्छा उदाहरण बनाता है, एक वास्तविक एप्लिकेशन Lograge और इसके JSON लॉग फॉर्मेटर का उपयोग करके बेहतर होगा।

StringScanner का उपयोग करने के लिए हमें पहले इसकी आवश्यकता है:

require 'strscan'

इसके बाद हम लॉग एंट्री को कंस्ट्रक्टर के आर्गुमेंट के रूप में पास करके एक नया इंस्टेंस इनिशियलाइज़ कर सकते हैं। साथ ही हम अपने पार्सिंग प्रयासों के परिणाम को रखने के लिए एक खाली हैश भी परिभाषित करेंगे:

scanner = StringScanner.new(log_entry) log = {}

अब हम स्कैनर के pos मेथड का उपयोग करके अपने स्कैन पॉइंटर के वर्तमान स्थान को प्राप्त कर सकते हैं। अपेक्षित रूप से, परिणाम 0 है, स्ट्रिंग का पहला वर्ण:

scanner.pos #=> 0

आइए इसकी कल्पना करें ताकि प्रक्रिया का अनुसरण करना आसान हो:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ^ ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) 

स्कैनर की स्थिति के आगे के आत्मनिरीक्षण के लिए हम beginning_of_line? और eos? का उपयोग यह पुष्टि करने के लिए कर सकते हैं कि स्कैन पॉइंटर वर्तमान में एक लाइन की शुरुआत में है और हमने अभी तक अपने इनपुट को पूरी तरह से उपभोग नहीं किया है:

scanner.beginning_of_line? #=> true scanner.eos? #=> false

हम जो पहली जानकारी निकालना चाहते हैं वह HTTP अनुरोध विधि है, जो "Started" शब्द के बाद एक स्पेस के बाद पाई जा सकती है। हम स्कैन पॉइंटर को आगे बढ़ाने के लिए स्कैनर के उचित नाम वाले skip मेथड का उपयोग कर सकते हैं, जो अनदेखे वर्णों की संख्या लौटाएगा, जो हमारे मामले में 8 है। इसके अतिरिक्त हम matched? का उपयोग यह पुष्टि करने के लिए कर सकते हैं कि सब कुछ अपेक्षा के अनुरूप काम करता है:

scanner.skip(/Started /) #=> 8 scanner.matched? #=> true

स्कैन पॉइंटर अब अनुरोध विधि के ठीक पहले है:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ^ ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) 

अब हम वास्तविक मान निकालने के लिए scan_until का उपयोग कर सकते हैं, जो संपूर्ण रेगुलर एक्सप्रेशन मैच को लौटाता है। चूंकि अनुरोध विधि सभी बड़े अक्षरों में है, हम एक साधारण कैरेक्टर क्लास और + ऑपरेटर का उपयोग कर सकते हैं जो एक या अधिक वर्णों से मेल खाता है:

log[:method] = scanner.scan_until(/[A-Z]+/) #=> "GET"

इस ऑपरेशन के बाद स्कैन पॉइंटर "GET" शब्द के अंतिम "T" पर होगा।

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ^ ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) 

अनुरोधित पथ निकालने के लिए, हमें इसलिए एक स्पेस को छोड़ना होगा और फिर डबल कोट्स में संलग्न सब कुछ निकालना होगा। इसे प्राप्त करने के कई तरीके हैं, उनमें से एक कैप्चर ग्रुप (रेगुलर एक्सप्रेशन का वह भाग जो कोष्ठक में शामिल है, यानी (.+)) के माध्यम से है जो किसी भी वर्ण के एक या अधिक से मेल खाता है:

scanner.scan(/\s"(.+)"/) #=> " \"/\""

हालांकि, हम इस scan ऑपरेशन के रिटर्न वैल्यू का सीधे उपयोग नहीं करेंगे, बल्कि पहले कैप्चर ग्रुप का मान प्राप्त करने के लिए captures का उपयोग करेंगे:

log[:path] = scanner.captures.first #=> "/"

हमने सफलतापूर्वक पथ निकाला और स्कैन पॉइंटर अब क्लोजिंग डबल कोट पर है:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ^ ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) 

लॉग से IP एड्रेस पार्स करने के लिए, हम एक बार फिर skip का उपयोग "for" स्ट्रिंग को स्पेस से घिरे हुए अनदेखा करने के लिए करते हैं और फिर एक या अधिक नॉन-व्हाइटस्पेस वर्णों (\s व्हाइटस्पेस को दर्शाने वाला कैरेक्टर क्लास है और [^\s] इसका निषेध है) से मेल खाने के लिए scan_until का उपयोग करते हैं:

scanner.skip(/ for /) #=> 5 log[:ip] = scanner.scan_until(/[^\s]+/) #=> "127.0.0.1"

क्या आप बता सकते हैं कि स्कैन पॉइंटर अब कहां होगा? इसके बारे में एक पल के लिए सोचें और फिर अपने उत्तर की तुलना समाधान से करें:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ^ ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) 

टाइमस्टैम्प पार्स करना अब तक बहुत परिचित महसूस होना चाहिए। पहले हम भरोसेमंद पुराने skip का उपयोग शाब्दिक स्ट्रिंग " at " को अनदेखा करने के लिए करते हैं और फिर वर्तमान लाइन के अंत तक पढ़ने के लिए scan_until का उपयोग करते हैं, जिसे रेगुलर एक्सप्रेशन में $ द्वारा दर्शाया जाता है:

scanner.skip(/ at /) #=> 4 log[:timestamp] = scanner.scan_until(/$/) #=> "2017-08-20 20:53:10 +0900"

हमारी रुचि का अगला टुकड़ा अंतिम लाइन पर HTTP स्टेटस कोड है, इसलिए हम skip_until का उपयोग "Completed" शब्द के बाद स्पेस तक सभी तरह से जाने के लिए करेंगे।

scanner.skip_until(/Completed /) #=> 296

जैसा कि नाम से पता चलता है यह scan_until के समान काम करता है लेकिन मिलान स्ट्रिंग को लौटाने के बजाय यह छोड़े गए वर्णों की संख्या लौटाता है। यह स्कैन पॉइंटर को ठीक उस HTTP स्टेटस कोड के सामने रखता है जिसमें हम रुचि रखते हैं।

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) ^ 

अब इससे पहले कि हम वास्तविक HTTP रिस्पॉन्स कोड को स्कैन करें, क्या यह अच्छा नहीं होगा यदि हम बता सकें कि HTTP रिस्पॉन्स कोड सफलता को दर्शाता है (इस उदाहरण के लिए 2xx रेंज में कोई भी कोड) या विफलता (अन्य सभी रेंज)? इसे प्राप्त करने के लिए हम अगले वर्ण को देखे बिना वास्तव में स्कैन पॉइंटर को मूव किए बिना peek का उपयोग करेंगे।

log[:success] = scanner.peek(1) == "2" #=> true

अब हम अगले तीन वर्णों को पढ़ने के लिए scan का उपयोग कर सकते हैं, जिसे रेगुलर एक्सप्रेशन /\d{3}/ द्वारा दर्शाया जाता है:

log[:response_code] = scanner.scan(/\d{3}/) #=> "200"

एक बार फिर स्कैन पॉइंटर पहले से मिलान किए गए रेगुलर एक्सप्रेशन के अंत में ठीक होगा:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 ... Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) ^ 

हम अपने लॉग एंट्री से निकालना चाहते हैं अंतिम जानकारी मिलीसेकंड में निष्पादन समय है, जिसे " OK in " स्ट्रिंग को skip करके और फिर शाब्दिक स्ट्रिंग "ms" तक और सहित सब कुछ पढ़कर प्राप्त किया जा सकता है।

scanner.skip(/ OK in /) #=> 7 log[:duration] = scanner.scan_until(/ms/) #=> "79ms"

और उस अंतिम बिट के साथ, हमारे पास वह हैश है जो हम चाहते थे।

{ method: "GET", path: "/" ip: "127.0.0.1", timestamp: "2017-08-20 20:53:10 +0900", success: true, response_code: "200", duration: "79ms", }

सारांश

रूबी का StringScanner सरल रेगुलर एक्सप्रेशन और पूर्ण विकसित लेक्सर के बीच एक अच्छा मध्य मैदान घेरता है। यह जटिल स्कैनिंग और पार्सिंग आवश्यकताओं के लिए सबसे अच्छा विकल्प नहीं है। लेकिन इसकी सीधी प्रकृति इसे बुनियादी रेगुलर एक्सप्रेशन ज्ञान वाले सभी के लिए इनपुट स्ट्रिंग्स से जानकारी निकालना आसान बनाती है और मैंने अतीत में प्रोडक्शन कोड में उन लोगों का सफलतापूर्वक उपयोग किया है। हम आशा करते हैं कि आप इस छिपे हुए रत्न की खोज करेंगे।

PS: हमें बताएं कि आपको क्या लगता है कि वे छिपे हुए रत्न हैं जिन्हें हमें आगे हाइलाइट करना चाहिए!

  1. 7 शक्तिशाली Ruby Gems जिनके बारे में आपने शायद नहीं सुना होगा 7 शक्तिशाली Ruby Gems जिनके बारे में आपने शायद नहीं सुना होगा

    आपके Rails प्रोजेक्ट्स में उपयोग करने के लिए सबसे अच्छे Ruby gems क्या हैं? यही आप इस आर्टिकल में खोजेंगे! मैं आपको 7 gems देने जा रहा हूँ, लेकिन वही पुराने gems नहीं जिन्हें आपने लाखों बार देखा है, मैं आपके साथ कुछ ऐसे gems साझा करने जा रहा हूँ जो बहुत उपयोगी हैं, लेकिन कम ज्ञात हैं। लेकिन इससे पहल

  1. Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण

    Ruby का एक नया संस्करण नई सुविधाओं और प्रदर्शन सुधारों के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज (Endless Ranges) Ruby 2.5 और पुराने संस्करण पहले से ही अंतहीन रेंज का एक रूप (Float::INFINITY के साथ) का समर्थन करते हैं, लेकिन Ruby 2.6 इसे अगले स्त

  1. - रूबी 2.1 में अपग्रेड करने से 15-20% तेज़ सर्वर प्रतिक्रिया और निकट-शून्य GC समय मिलता है

    मैं इसका उत्तर एक स्क्रीनशॉट के साथ दूंगा: {% img img-responsive /images/posts/ruby-2-1-improvement.png 645 259 मैं इसे एक बड़ा हां कहूंगा %} हमने लगभग एक हफ्ते पहले प्रोडक्शन में रूबी 1.9 से 2.1 में अपग्रेड किया, और ये वही है जो हमने देखा। नीचे ग्रे सेक्शन हमारा गार्बेज कलेक्शन समय है जो लगभग शून