रूबी मैजिक के दूसरे संस्करण में आपका स्वागत है! एक साल पहले, हमने रूबी के Enumerable
. के बारे में जाना मॉड्यूल, जो आपके द्वारा उपयोग की जाने वाली विधियाँ प्रदान करता है, जैसे कि सरणियों, श्रेणियों और हैश जैसी गणना योग्य वस्तुओं के साथ काम करते समय।
उस समय, हमने एक LinkedLIst
बनाया था यह दिखाने के लिए कि #each
. को लागू करके किसी वस्तु को गणना योग्य कैसे बनाया जाए उस पर विधि। Enumerable
. को शामिल करके मॉड्यूल, हम #count
. जैसी विधियों को कॉल करने में सक्षम थे , #map
और #select
किसी भी लिंक की गई सूची पर उन्हें स्वयं लागू किए बिना।
हमने सीखा है कि एन्यूमरेबल्स का उपयोग कैसे किया जाता है, लेकिन वे कैसे काम करते हैं? रूबी में एन्यूमरेबल्स में जादू का एक हिस्सा उनके आंतरिक कार्यान्वयन से आता है, जो सभी एकल #each
पर आधारित है। विधि, और यहां तक कि गणकों को श्रृंखलित करने की अनुमति देता है।
आज, हम सीखेंगे कि 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
मॉड्यूल गणना विधियों के साथ आता है जैसे #map
, #count
, और #select
. #each
. को लागू करके विधि और Enumerable
. सहित हमारी कक्षा में मॉड्यूल, हम सीधे हमारी लिंक्ड सूचियों पर उन विधियों का उपयोग करने में सक्षम होंगे।
इसके बजाय, हम DIYEnumerable
. लागू करेंगे और रूबी के संस्करण के बजाय आयात करें। यह ऐसा कुछ नहीं है जो आप आम तौर पर करते हैं, लेकिन यह हमें एक स्पष्ट अंतर्दृष्टि देगा कि गणना आंतरिक रूप से कैसे काम करती है।
आइए #count
से शुरू करते हैं . Enumerable
. में प्रत्येक आयात करने योग्य विधि वर्ग #each
. का उपयोग करता है हमारे LinkedLIst
. में लागू किया गया तरीका अपने परिणामों की गणना करने के लिए ऑब्जेक्ट पर क्लास टू लूप करें।
module DIYEnumerable
def count
result = 0
each { |element| result += 1 }
result
end
end
इस उदाहरण में, हमने #count
. लागू किया है एक नए DIYEnumerable
. पर विधि मॉड्यूल जिसे हम अपनी लिंक्ड सूची में शामिल करेंगे। यह शून्य पर एक काउंटर शुरू करता है और #each
. को कॉल करता है प्रत्येक लूप के लिए काउंटर में एक जोड़ने की विधि। सभी तत्वों पर लूप करने के बाद, विधि परिणामी काउंटर लौटाती है।
module DIYEnumerable
# ...
def map
result = LinkedList.new
each { |element| result = result << yield(element) }
result
end
end
#map
विधि समान रूप से लागू की जाती है। काउंटर रखने के बजाय, यह एक संचयक का उपयोग करता है, जो एक खाली सूची के रूप में शुरू होता है। हम सूची में सभी तत्वों पर लूप करेंगे, और प्रत्येक तत्व पर पारित ब्लॉक प्राप्त करेंगे। प्रत्येक उपज का परिणाम संचायक सूची में जोड़ा जाता है।
इनपुट सूची में सभी तत्वों को लूप करने के बाद यह विधि संचायक लौटाती है।
class LinkedList
include DIYEnumerable
#...
end
DIYEnumerable
. को शामिल करने के बाद हमारे LinkedList
. में , हम अपने नए जोड़े गए #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
अब, हम उपयोग करेंगे #reverse_each
हमारे #map
. में विधि, यह सुनिश्चित करने के लिए कि यह सही क्रम में लौटा है।
irb> list = LinkedList.new(73, 12, 42)
=> [73, [12, [42]]]
irb> list.map { |element| element * 10 }
=> [730, [120, [420]]]
यह काम करता हैं! जब भी हम अपने #map
. को कॉल करते हैं किसी लिंक की गई सूची पर विधि, हम मूल के समान क्रम में एक नई सूची वापस प्राप्त करेंगे।
गणनाओं के साथ गणना करना
#each
. के माध्यम से हमारे लिंक्ड सूची वर्ग और शामिल DIYEnumerator
. पर लागू की गई विधि , अब हम दोनों तरह से लूप कर सकते हैं और लिंक्ड सूचियों पर मैप कर सकते हैं।
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
कक्षा इसमें मदद कर सकती है।
पिछली बार, हमने Kernel#to_enum
. पर कॉल करना सुनिश्चित किया था अगर LinkedList#each
विधि को बिना ब्लॉक के बुलाया गया था। यह एक 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
, हमारा एन्यूमरेटर वर्ग किसी वस्तु पर एक विधि के चारों ओर एक आवरण है। लपेटी हुई वस्तु तक बुलबुला करके, हम गणना विधियों को श्रृंखलाबद्ध कर सकते हैं।
यह काम करता है क्योंकि एक DIYEnumerator
उदाहरण अपने आप में गणनीय है। यह #each
. को लागू करता है लिपटे हुए ऑब्जेक्ट को कॉल करके, और इसमें DIYEnumerable
. शामिल है मॉड्यूल ताकि सभी गणना योग्य विधियों को उस पर बुलाया जा सके।
हम अपने DIYEnumerator
. का एक उदाहरण वापस करेंगे क्लास अगर LinkedList#each
. को कोई ब्लॉक पास नहीं किया गया है विधि।
class LinkedList
# ...
def each(&block)
if block_given?
yield @head
@tail.each(&block) if @tail
else
DIYEnumerator.new(self, :each)
end
end
end
अपने स्वयं के एन्यूमरेटर का उपयोग करके, अब हम #reverse_each
को एक खाली ब्लॉक पास किए बिना मूल क्रम में परिणाम प्राप्त करने के लिए श्रृंखला गणना कर सकते हैं। विधि कॉल।
irb> list = LinkedList.new(73, 12, 42)
=> [73, [12, [42]]]
irb> list.map { |element| element * 10 }
=> [420, [120, [730]]]
उत्सुक और आलसी गणना
यह Enumerable
. के कार्यान्वयन में हमारी झलक को समाप्त करता है मॉड्यूल और Enumerator
अभी के लिए कक्षा। हमने सीखा है कि कुछ गणना करने योग्य तरीके कैसे काम करते हैं और कैसे एक एन्यूमरेटर एक एन्यूमरेटर ऑब्जेक्ट को लपेटकर एन्यूमरेशन को चेन करने में मदद करता है।
हालाँकि, हमारे दृष्टिकोण के साथ कुछ मुद्दे हैं। इसकी प्रकृति से, गणना उत्सुक . है , जिसका अर्थ है कि जैसे ही इस पर गणना करने योग्य विधियों में से एक को कॉल किया जाता है, यह सूची में लूप हो जाता है। हालांकि ज्यादातर मामलों में यह ठीक है, किसी सूची को रिवर्स में मैप करना सूची को दो बार उलट देता है, जो अनावश्यक होना चाहिए।
लूपों की संख्या कम करने के लिए, हम Enumerator::Lazy
. को नियोजित कर सकते हैं लूपिंग को अंतिम क्षण तक विलंबित करने के लिए, और डुप्लीकेट सूची को उलटने के लिए स्वयं को रद्द कर दें।
हालांकि, हमें इसे भविष्य के एपिसोड के लिए सहेजना होगा। रूबी के जादुई आंतरिक कामकाज में इसे और आगे के अभियानों को याद नहीं करना चाहते हैं? नए लेख प्रकाशित होते ही आपके इनबॉक्स में वितरित करने के लिए, रूबी मैजिक ई-मेल न्यूज़लेटर की सदस्यता लें।