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

रूबी में गणना के अंदर

रूबी मैजिक के दूसरे संस्करण में आपका स्वागत है! एक साल पहले, हमने रूबी के 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 . को नियोजित कर सकते हैं लूपिंग को अंतिम क्षण तक विलंबित करने के लिए, और डुप्लीकेट सूची को उलटने के लिए स्वयं को रद्द कर दें।

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


  1. रूबी में लैम्ब्डा का उपयोग करना

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

  1. रूबी में इंसर्शन सॉर्ट को समझना

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

  1. रूबी में 9 नई सुविधाएँ 2.6

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