রুবি ম্যাজিকের নতুন সংস্করণে স্বাগতম! এক বছর আগে আমরা রুবির Enumerable মডিউল সম্পর্কে জানেছিলাম, যা অ্যারে, রেঞ্জ এবং হ্যাশের মতো enumerable অবজেক্টের সাথে কাজ করার সময় ব্যবহৃত মেথডগুলো প্রদান করে।
তখন আমরা একটি LinkedList ক্লাস তৈরি করেছিলাম যাতে দেখানো যায় কিভাবে #each মেথড ইমপ্লিমেন্ট করে একটি অবজেক্টকে enumerable বানানো যায়। Enumerable মডিউল ইনক্লুড করলে, আমরা কোনো লিংকড লিস্টে #count, #map এবং #select মেথডগুলো কল করতে পারতাম, সেগুলো নিজেদের ইমপ্লিমেন্ট না করেই।
আমরা enumerables কিভাবে ব্যবহার করব সেটা শিখেছি, কিন্তু এগুলো কাজ কিভাবে করে? রুবিতে enumerables-এর জাদুর একটি অংশ এর অভ্যন্তরীণ ইমপ্লিমেন্টেশন থেকে আসে, যা সম্পূর্ণরূপে একক #each মেথডের ওপর ভিত্তি করে তৈরি, এবং এনমারেটর চেইনিং-কেও অনুমতি দেয়।
আজ আমরা শিখব Enumerable ক্লাসের মেথডগুলো কিভাবে ইমপ্লিমেন্ট করা হয় এবং Enumerator অবজেক্টগুলো কিভাবে এনামারেশন মেথড চেইনিংকে সম্ভব করে।
অনুযায়ী, আমরা গভীরে যাব Enumerable মডিউল এবং Enumerator ক্লাসের নিজস্ব সংস্করণ ইমপ্লিমেন্ট করে। সুতরাং, আপনার ওভার-এঞ্জিনিয়ারিং হেলমেট প expuls 넣고 চলুন!
লিংকড লিস্ট
শুরু করার আগে, আসুন আগে লেখা লিংকড লিস্ট ক্লাসের একটি নতুন সংস্করণ নিয়ে কাজ করি।
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। আমাদের LinkedList ক্লাসে #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 মেথডটিও একইভাবে ইমপ্লিমেন্ট করা হয়েছে। কাউন্টার রাখার বদলে, এটি একটি একামুলেটর ব্যবহার করে, যা একটি খালি লিস্ট দিয়ে শুরু হয়। আমরা ইনপুট লিস্টের সব এলিমেন্টের ওপর লুপ চালাব, এবং প্রতিটি এলিমেন্টে পাস করা ব্লক yield করব। প্রতিটি yield-এর ফলাফল একামুলেটর লিস্টে অ্যাপেন্ড হবে।
ইনপুট লিস্টের সব এলিমেন্টের ওপর লুপ শেষে মেথডটি একামুলেটর রিটার্ন করে।
class LinkedList include DIYEnumerable #... endDIYEnumerable আমাদের 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এখন, আমরা আমাদের #map মেথডে #reverse_each ব্যবহার করব, যাতে এটি মূল ক্রমে রিটার্ন হয়।
irb> list = LinkedList.new(73, 12, 42) => [73, [12, [42]]] irb> list.map { |element| element * 10 } => [730, [120, [420]]] কাজ করল! যখনই আমরা লিংকড লিস্টে আমাদের #map মেথড কল করব, আমরা মূল লিস্টের মতো একই ক্রমে একটি নতুন লিস্ট পাব।
Enumerator দিয়ে এনামারেশন চেইনিং
আমাদের লিংকড লিস্ট ক্লাসে ইমপ্লিমেন্ট করা #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-এর মত, আমাদের এনামারেটর ক্লাস একটি অবজেক্টের মেথডের ওপর একটি র্যাপার। র্যাপ করা অবজেক্টে বাবল আপ করে, আমরা এনামারেশন মেথড চেইন করতে পারি।
এটি কাজ করে কারণ একটি DIYEnumerator ইনস্ট্যান্স নিজে enumerable। এটি #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আমাদের নিজস্ব এনামারেটর ব্যবহার করে, আমরা এনামারেশন চেইন করতে পারি মূল ক্রমে ফলাফল পেতে, #reverse_each মেথড কল করতে খালি ব্লক পাস না করেই।
irb> list = LinkedList.new(73, 12, 42) => [73, [12, [42]]] irb> list.map { |element| element * 10 } => [420, [120, [730]]] আগ্রহী এবং লেজি এনামারেশন
এটি আমাদের Enumerable মডিউল এবং Enumerator ক্লাসের ইমপ্লিমেন্টেশনে যাবতীয় চাপা ঝাঁপি। আমরা শিখলাম কিছু এনামারেবল মেথড কিভাবে কাজ করে এবং কিভাবে একটি এনামারেটর একটি এনামারেবল অবজেক্ট র্যাপ করে এনামারেশন চেইনিংয়ে সাহায্য করে।
हमारे दृष्टिकोण में कुछ समस्याएं हैं, हालाँकि। अपनी प्रकृति से, गणना आग्रही है, जिसका अर्थ है कि यह सूची पर लूप करता है जैसे ही enumerable विधियों में से एक को उस पर बुलाया जाता है। जबकि यह ज्यादातर मामलों में ठीक है, एक सूची को उल्टे क्रम में मैप करने से सूची दो बार उलटी हो जाती है, जो अनावश्यक होना चाहिए।
लूप की संख्या कम करने के लिए, हम Enumerator::Lazy का उपयोग कर सकते हैं ताकि लूपिंग को अंतिम क्षण तक विलंबित किया जा सके, और डुप्लिकेट सूची उलटने को एक-दूसरे को रद्द करने दिया जा सके।
हमें इसे भविष्य के एपिसोड के लिए सहेजना होगा, हालाँकि। रूबी के जादुई आंतरिक कामकाज में आगे के अभियानों को याद नहीं करना चाहते? रूबी मैजिक ई-मेल न्यूज़लेटर की सदस्यता लें, ताकि नए लेख प्रकाशित होते ही आपके इनबॉक्स में पहुंच जाएं।