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

रूबी के Enumerable मॉड्यूल में महारत: Enumerable क्लासेस बनाना

रूबी मैजिक के एक और संस्करण में आपका स्वागत है! एक साल पहले, हमने रूबी के Enumerable मॉड्यूल के बारे में सीखा, जो उन विधियों को प्रदान करता है जिनका उपयोग आप सरणियों, रेंज और हैश जैसे गणनीय ऑब्जेक्ट्स के साथ काम करते समय करते हैं।

तब हमने एक LinkedList क्लास बनाई थी ताकि यह दिखाया जा सके कि किसी ऑब्जेक्ट को #each विधि को लागू करके कैसे गणनीय बनाया जाए। Enumerable मॉड्यूल को शामिल करके, हम किसी भी लिंक्ड लिस्ट पर #count, #map और #select जैसी विधियों को कॉल कर सकते थे, बिना उन्हें स्वयं लागू किए।

हमने सीखा कि enumerables का उपयोग कैसे करें, लेकिन वे कैसे काम करते हैं? रूबी में enumerables का एक हिस्सा उनके आंतरिक कार्यान्वयन से आता है, जो पूरी तरह से एकल #each विधि पर आधारित है, और यहां तक कि enumerators को चेन करने की भी अनुमति देता है।

आज, हम सीखेंगे कि Enumerable क्लास में विधियों को कैसे लागू किया जाता है और Enumerator ऑब्जेक्ट्स कैसे गणना विधियों को चेन करने की अनुमति देते हैं।

जैसा कि आप आदी हो गए हैं, हम अपने स्वयं के Enumerable मॉड्यूल और Enumerator क्लास के संस्करणों को लागू करके गहराई से गोता लगाएंगे। तो, अपना ओवर-इंजीनियरिंग हेलमेट पहनें और चलते हैं!

लिंक्ड लिस्ट्स

शुरू करने से पहले, आइए उस लिंक्ड लिस्ट क्लास के एक नए संस्करण से शुरू करें जिसे हमने पहले लिखा था।

class LinkedList def initialize(head = nil, *rest) @head = head if rest.first.is_a?(LinkedList) @tail = rest.first elsif rest.any? @tail = LinkedList.new(*rest) end end def <<(head) @head ? LinkedList.new(head, self) : LinkedList.new(head) end def inspect [@head, @tail].compact end def each(&block) yield @head if @head @tail.each(&block) if @tail end end

पिछले संस्करण के विपरीत, यह कार्यान्वयन खाली लिस्ट बनाने के साथ-साथ दो से अधिक आइटम वाली लिस्ट बनाने की अनुमति देता है। यह संस्करण किसी अन्य को इनिशियलाइज़ करते समय लिंक्ड लिस्ट को टेल के रूप में पास करने की भी अनुमति देता है।

irb> LinkedList.new => [] irb> LinkedList.new(1) => [1] irb> LinkedList.new(1, 2) => [1,[2]] irb> LinkedList.new(1, 2, 3) => [1,[2,[3]]] irb> LinkedList.new(1, LinkedList.new(2, 3)) => [1,[2,[3]]] irb> LinkedList.new(1, 2, LinkedList.new(3)) => [1,[2,[3]]] 

पहले, हमारे LinkedList क्लास में Enumerable मॉड्यूल शामिल था। Enumerable की किसी विधि का उपयोग करके किसी ऑब्जेक्ट पर मैपिंग करते समय, परिणाम एक सरणी में संग्रहीत होते हैं। इस बार, हम अपना स्वयं का संस्करण लागू करेंगे ताकि हमारी विधियां सरणियों के बजाय नई लिंक्ड लिस्ट लौटाएं।

Enumerable विधियाँ

रूबी का Enumerable मॉड्यूल #map, #count, और #select जैसी गणना विधियों के साथ आता है। #each विधि को लागू करके और Enumerable मॉड्यूल को अपनी क्लास में शामिल करके, हम उन विधियों को सीधे अपनी लिंक्ड लिस्ट पर उपयोग कर सकते थे।

इसके बजाय, हम DIYEnumerable लागू करेंगे और रूबी के संस्करण के बजाय इसे आयात करेंगे। यह कुछ ऐसा नहीं है जो आप आमतौर पर करेंगे, लेकिन यह हमें गणना के आंतरिक कामकाज में स्पष्ट अंतर्दृष्टि देगा।

आइए #count से शुरू करें। Enumerable क्लास में आयात योग्य प्रत्येक विधि, परिणामों की गणना करने के लिए हमारी LinkedList क्लास में लागू की गई #each विधि का उपयोग करके ऑब्जेक्ट पर लूप करती है।

module DIYEnumerable def count result = 0 each { |element| result += 1 } result end end

इस उदाहरण में, हमने एक नए DIYEnumerable मॉड्यूल पर #count विधि को लागू किया है जिसे हम अपनी लिंक्ड लिस्ट में शामिल करेंगे। यह एक काउंटर को शून्य पर शुरू करता है और प्रत्येक लूप के लिए काउंटर में एक जोड़ने के लिए #each विधि को कॉल करता है। सभी तत्वों पर लूप करने के बाद, विधि परिणामी काउंटर को लौटाती है।

module DIYEnumerable # ... def map result = LinkedList.new each { |element| result = result << yield(element) } result end end

#map विधि को इसी तरह लागू किया गया है। एक काउंटर रखने के बजाय, यह एक एक्यूमुलेटर का उपयोग करता है, जो एक खाली लिस्ट के रूप में शुरू होता है। हम लिस्ट में सभी तत्वों पर लूप करेंगे, और प्रत्येक तत्व पर पास किए गए ब्लॉक को यील्ड करेंगे। प्रत्येक यील्ड का परिणाम एक्यूमुलेटर लिस्ट में जोड़ा जाता है।

इनपुट लिस्ट के सभी तत्वों पर लूप करने के बाद विधि एक्यूमुलेटर को लौटाती है।

class LinkedList include DIYEnumerable #... end

अपने LinkedList में DIYEnumerable को शामिल करने के बाद, हम अपने नए जोड़े गए #count और #map विधियों का परीक्षण कर सकते हैं।

irb> list = LinkedList.new(73, 12, 42) => [73, [12, [42]]] irb> list.count => 3 irb> list.map { |element| element * 10 } => [420, [120, [730]]] 

दोनों विधियाँ काम करती हैं! #count विधि लिस्ट में आइटम्स को सही ढंग से गिनती है, और #map विधि प्रत्येक आइटम के लिए एक ब्लॉक चलाती है और एक अपडेटेड लिस्ट लौटाती है।

उल्टी लिस्ट्स

हालांकि, #map विधि ने लिस्ट को उल्टा कर दिया लगता है। यह समझ में आता है, क्योंकि हमारी लिंक्ड लिस्ट क्लास पर #<< विधि आइटम्स को लिस्ट के अंत में जोड़ने के बजाय शुरुआत में जोड़ती है, जो लिंक्ड लिस्ट की रिकर्सिव प्रकृति की एक विशेषता है।

उन स्थितियों के लिए जहां लिस्ट के क्रम को बनाए रखना आवश्यक है, हमें मैपिंग करते समय लिस्ट को उल्टा करने का एक तरीका चाहिए। रूबी Enumerable#reverse_each लागू करती है, जो किसी ऑब्जेक्ट पर उल्टे क्रम में लूप करती है। यह हमारी समस्या का एक उत्कृष्ट समाधान लगता है। दुर्भाग्य से, हम ऐसा दृष्टिकोण नहीं अपना सकते क्योंकि हमारी लिस्ट नेस्टेड है। जब तक हम पूरी लिस्ट पर लूप नहीं करते, हमें नहीं पता कि लिस्ट कितनी लंबी है।

लिस्ट पर ब्लॉक को उल्टे क्रम में चलाने के बजाय, हम #reverse_each का एक संस्करण जोड़ेंगे जो यह दो चरणों में करता है। यह पहले लिस्ट पर लूप करके एक नई लिस्ट बनाकर उसे उल्टा करता है। उसके बाद, यह उल्टी लिस्ट पर ब्लॉक चलाता है।

module DIYEnumerable # ... def reverse_each(&block) list = LinkedList.new each { |element| list = list << element } list.each(&block) end def map result = LinkedList.new reverse_each { |element| result = result << yield(element) } result end end

अब, हम अपने #map विधि में #reverse_each का उपयोग करेंगे, ताकि यह सुनिश्चित हो सके कि यह सही क्रम में लौटाई जाती है।

irb> list = LinkedList.new(73, 12, 42) => [73, [12, [42]]] irb> list.map { |element| element * 10 } => [730, [120, [420]]] 

यह काम करता है! जब भी हम लिंक्ड लिस्ट पर अपनी #map विधि को कॉल करते हैं, तो हमें मूल लिस्ट के समान क्रम में एक नई लिस्ट वापस मिलेगी।

Enumerators के साथ गणना को चेन करना

हमारी लिंक्ड लिस्ट क्लास पर लागू #each विधि और शामिल DIYEnumerable के माध्यम से, हम अब दोनों दिशाओं में लूप कर सकते हैं और लिंक्ड लिस्ट पर मैप कर सकते हैं।

irb> list.each { |x| p x } 73 12 42 irb> list.reverse_each { |x| p x } 42 12 73 irb> list.reverse_each.map { |x| x * 10 } => [730, [120, [420]]] => [420, [120, [730]]] 

हालांकि, क्या होगा अगर हमें किसी लिस्ट पर उल्टे क्रम में मैप करने की आवश्यकता है? चूंकि हम अब मैपिंग से पहले लिस्ट को उल्टा करते हैं, यह हमेशा मूल लिस्ट के समान क्रम में लौटती है। हमने पहले ही #reverse_each और #map दोनों को लागू कर दिया है, इसलिए हमें उन्हें एक साथ चेन करने में सक्षम होना चाहिए ताकि पीछे की ओर मैप किया जा सके। सौभाग्य से, रूबी का Enumerator क्लास इसमें मदद कर सकता है।

पिछली बार, हमने सुनिश्चित किया था कि यदि LinkedList#each विधि को ब्लॉक के बिना कॉल किया जाता है तो Kernel#to_enum को कॉल करें। इसने एक Enumerator ऑब्जेक्ट लौटाकर गणना विधियों को चेन करने की अनुमति दी। Enumerator क्लास कैसे काम करती है, यह जानने के लिए, हम अपना स्वयं का संस्करण लागू करेंगे।

class DIYEnumerator include DIYEnumerable def initialize(object, method) @object = object @method = method end def each(&block) @object.send(@method, &block) end end

रूबी के Enumerator की तरह, हमारा enumerator क्लास किसी ऑब्जेक्ट पर एक विधि के चारों ओर एक रैपर है। रैप्ड ऑब्जेक्ट पर बबलिंग करके, हम गणना विधियों को चेन कर सकते हैं।

यह इसलिए काम करता है क्योंकि एक DIYEnumerator इंस्टेंस स्वयं गणनीय है। यह रैप्ड ऑब्जेक्ट को कॉल करके #each को लागू करता है, और DIYEnumerable मॉड्यूल को शामिल करता है ताकि सभी गणनीय विधियों को इस पर कॉल किया जा सके।

यदि LinkedList#each विधि को कोई ब्लॉक पास नहीं किया जाता है तो हम अपने DIYEnumerator क्लास का एक इंस्टेंस लौटाएंगे।

class LinkedList # ... def each(&block) if block_given? yield @head @tail.each(&block) if @tail else DIYEnumerator.new(self, :each) end end end

अपने स्वयं के enumerator का उपयोग करके, हम अब गणना को चेन कर सकते हैं ताकि मूल क्रम में परिणाम प्राप्त करने के लिए #reverse_each विधि कॉल को एक खाली ब्लॉक पास किए बिना किया जा सके।

irb> list = LinkedList.new(73, 12, 42) => [73, [12, [42]]] irb> list.map { |element| element * 10 } => [420, [120, [730]]] 

ईगर और लेज़ी गणना

यह Enumerable मॉड्यूल और Enumerator क्लास के कार्यान्वयन में हमारी झलक को समाप्त करता है। हमने सीखा कि कुछ गणनीय विधियाँ कैसे काम करती हैं और कैसे एक enumerator एक गणनीय ऑब्जेक्ट को रैप करके गणना को चेन करने में मदद करता है।

हालांकि, हमारे दृष्टिकोण में कुछ समस्याएं हैं। अपनी प्रकृति से, गणना ईगर है, जिसका अर्थ है कि यह लिस्ट पर तभी लूप करती है जब उस पर गणनीय विधियों में से एक को कॉल किया जाता है। जबकि यह ज्यादातर मामलों में ठीक है, उल्टे क्रम में किसी लिस्ट पर मैपिंग करने से लिस्ट दो बार उल्टी हो जाती है, जो अनावश्यक होना चाहिए।

लूप की संख्या कम करने के लिए, हम Enumerator::Lazy को नियोजित कर सकते हैं ताकि लूपिंग को अंतिम क्षण तक विलंबित किया जा सके, और डुप्लिकेट लिस्ट उलटने को कैंसिल आउट होने दिया जाए।

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

  1. Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक

    ब्लॉक Ruby का इतना महत्वपूर्ण हिस्सा हैं, उनके बिना भाषा की कल्पना करना कठिन है। लेकिन लैम्ब्डा? कौन लैम्ब्डा से प्यार करता है? आप वर्षों तक बिना एक का उपयोग किए जा सकते हैं। वे लगभग एक बीते युग के अवशेष लगते हैं। ...लेकिन यह पूरी तरह सच नहीं है। लैम्ब्डा की आस्तीन में कुछ दिलचस्प तरकीबें हैं एक बार

  1. रूबी के साथ इंसर्शन सॉर्ट में महारत: एक चरण-दर-चरण गाइड रूबी के साथ इंसर्शन सॉर्ट में महारत: एक चरण-दर-चरण गाइड

    नोट: यह रूबी के साथ विभिन्न सॉर्टिंग एल्गोरिदम को लागू करने पर एक श्रृंखला का भाग 4 है। भाग 1 ने बबल सॉर्ट का पता लगाया, भाग 2 ने सिलेक्शन सॉर्ट का पता लगाया, और भाग 3 ने मर्ज सॉर्ट का पता लगाया। जैसा कि हम डेटा को सॉर्ट करने के लिए विभिन्न पद्धतियों का पता लगाना जारी रखते हैं, हम इंसर्शन सॉर्ट की

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

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