রুবি ম্যাজিকের আরেকটি épisod-এর সময়! এইবার, আমরা রুবির সবচেয়ে জাদুই ফিচারগুলোর একটি দেখব, যা আপনি রুবির enumারেবল ক্লাসগুলো যেমন Array, Hash এবং Range এর সাথে কাজ করার সময় অধিকাংশ মেথড ব্যবহার করেন। প্রক্রিয়ায়, আমরা শিখব enumারেবল অবজেক্টের সাথে কী করা যায়, এনুমেরেশন কীভাবে কাজ করে, এবং একটি মাত্র মেথড ইমপ্লিমেন্ট করে একটি অবজেক্টকে কীভাবে enumারেবল বানানো যায়।
Enumerable, #each এবং Enumerator
এনুমেরেশন বলতে অবজেক্টগুলোর ওপর ট্রাভার্সাল বোঝায়। রুবিতে, আমরা একটি অবজেক্টকে enumারেবল বলি যখন এটি আইটেমের একটি সেট বর্ণনা করে এবং उन جميعاً এর ওপর লুপ করার একটি মেথড।
বিল্ট-ইন enumারেবলগুলো তাদের এনুমেরেশন ফিচার পায় Enumerable মডিউল ইনক্লুড করে, যা #include?, #count, #map, #select এবং #uniq এর মত মেথড প্রদান করে, অন্যান্যসহ। অ্যারে এবং হ্যাশের সাথে জড়িত অধিকাংশ মেথড আসলে এই ক্লাসগুলোতে ইমপ্লিমেন্টেড নয়, সেগুলো ইনক্লуд করা হয়।
নোট: কিছু মেথড, যেন Array ক্লাসে #count এবং #take, হैं স্পেসিফিক্যালি অ্যারের জন্য ইমপ্লিমেন্টেড Enumerable মডিউলের যেগুলোর বদলে। সেটি সাধারণত অপারেশন দ্রুত করার জন্য করা হয়।
Enumerable মডিউল একটি মেথড নামে #each-র ওপর নির্ভর করে, যেটি যেকোনো ক্লাসে ইমপ্লিমেন্ট করা থাকতে হবে যাতে এটি ইনক্লуд করা হয়। যখন একটি ব্লকসহ একটি অ্যারেতে কল করা হয়, #each মেথডটি অ্যারেতে প্রতিটি এলিমেন্টের জন্য ব্লক এক্সিকিউট করবে।
irb> [1,2,3].each { |i| puts "* #{i}" }
* 1
* 2
* 3
=> [1,2,3]
যদি আমরা #each মেথডটি একটি অ্যারেতে কল করি বिना একটি ব্লক পাস করা যাতে প্রতিটি এলিমেন্টের জন্য এক্সিকিউট করা হয়, আমরা একটি Enumerator ইনস্ট্যান্স পাব।
irb> [1,2,3].each
=> #
Enumerator ইনস্ট্যান্সগুলো বর্ণনা করে কীভাবে একটি অবজেক্টের ওপর ইটারেট করতে হয়। Enumeratorها অবজেক্টগুলো ম্যানুয়ালি ইটারেট করে এবং এনুমেরেশন চেইন করে।
irb> %w(dog cat mouse).each.with_index { |a, i| puts "#{a} is at position #{i}" }
dog is at position 0
cat is at position 1
mouse is at position 2
=> ["dog", "cat", "mouse"]
#with_index মেথডটি চেঞ্জড enumerators কীভাবে কাজ করে সেটার একটি ভালো উদাহরণ। এই উদাহরণে, অ্যারেতে #each কল করা হয় একটি enumerator রিটার্ন করতে। তারপর, #with_index কল করা হয় প্রতিটি অ্যারে এলিমেন্টে ইনডেক্স যোগ করতে যাতে প্রতিটি এলিমেন্টের ইনডেক্স প্রিন্ট করা যায়।
অবজেক্ট enumারেবল বানানো
হাডের নিচে, #max, #map এবং #take এর মত মেথডগুলো #each মেথডের ওপর নির্ভর করে ফাংশন করার জন্য।
def max
max = nil
each do |item|
if !max || item > max
max = item
end
end
max
end
অন্তরঙ্গভাবে, Enumerable-এর মেথডগুলোর C ইমপ্লিমেন্টেশন আছে, কিন্তু উপরের উদাহরণটি মোটামুটি দেখাচ্ছে #max কীভাবে কাজ করে। #each ব্যবহার করে সব ভ্যালু ওভার লুপ করে এবং সবচেয়ে উচ্চ মানটি মনে রাখে, এটি maksimum মান রিটার্ন করে।
def map(&block)
new_list = []
each do |item|
new_list << block.call(item)
end
new_list
end
#map ফাংশন পাস করা ব্লককে প্রতিটি আইটেমসহ কল করে এবং ফলাফল একটি নতুন লিস্টে রাখে সব ভ্যালু ওভার লুপ করার পর রিটার্ন করার জন্য।
যেহেতু Enumerable-এ সব মেথড কিছুটা পরিমাণে #each মেথড ব্যবহার করে, আমাদের কাস্টম ক্লাস enumারেবল বানানোর প্রথম ধাপ হলো #each মেথড ইমপ্লিমেন্ট করা।
#each ইমপ্লিমেন্ট করা
#each ফাংশন ইমপ্লিমেন্ট করে এবং একটি ক্লাসে Enumerable মডিউল ইনক্লুড করে, এটি enumারেবল হয়ে যায় এবং #min, #take এবং #inject এর মত মেথড ফ্রিতে পায়।
যদিও অধিকাংশ সিচুয়েশনতে একটি এক্সিস্টিং অবজেক্টের মত অ্যারে ফলব্যাক করা যায় এবং সেটিতে #each মেথড কল করা যায়, চলুন একটি উদাহরণ দেখি যেখানে আমাদের এটি স্ক্র্যাচ থেকে লিখতে হয়। এই উদাহরণে, আমরা একটি লিঙ্কড লিস্ট-এ #each ইমপ্লিমেন্ট করব এটি enumারেবল বানাতে।
লিঙ্কড লিস্ট: অ্যারে ছাড়া লিস্ট
একটি লিঙ্কড লিস্ট হলো ডেটা এলিমেন্টের একটি সংগ্রহ, যেখানে প্রতিটি এলিমেন্ট পরবর্তীটিকে নির্দেশ করে। লিস্টে প্রতিটি এলিমেন্টের দুটি মান থাকে, যাকে হেড এবং টেইল বলে। হেড এলিমেন্টের মান ধারণ করে, এবং টেইল লিস্টের বাকি অংশের লিঙ্ক।
[42, [12, [73, nil]]
তিনটি ভ্যালু (৪২, ১২ এবং ৭৩) সহ একটি লিঙ্কড লিস্টের জন্য, প্রথম এলিমেন্টের হেড ৪২, এবং টেইল দ্বিতীয় এলিমেন্টের লিঙ্ক। দ্বিতীয় এলিমেন্টের হেড ১২, এবং টেইল তৃতীয় এলিমেন্ট ধারণ করে। তৃতীয় এলিমেন্টের হেড ৭৩, এবং টেইল nil, যা লিস্টের শেষ ইন্ডিকেট করে।
রুবিতে, একটি লিঙ্কড লিস্ট তৈরি করা যায় একটি ক্লাস ব্যবহার করে যেটি দুটি ইনস্ট্যান্স ভেরিয়েবল ধারণ করে @head এবং @tail নামে।
class LinkedList
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
end
#<< মেথডটি লিস্টে নতুন ভ্যালু যোগ করতে ব্যবহৃত হয়, যা পাস করা ভ্যালুকে হেড হিসেবে এবং পূর্বের লিস্টকে টেইল হিসেবে একটি নতুন লিস্ট রিটার্ন করে কাজ করে।
এই উদাহরণে, #inspect মেথডটি যোগ করা হয়েছে যাতে আমরা লিস্টের ভিতর দেখতে পারি যেসব এলিমেন্ট আছে সেগুলো চেক করতে।
irb> LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
এখন যেহেতু আমাদের একটি লিঙ্কড লিস্ট আছে, চলুন এটিতে #each ইমপ্লিমেন্ট করি। #each ফাংশন একটি ব্লক নেয় এবং অবজেক্টে প্রতিটি ভ্যালুর জন্য সেটি এক্সিকিউট করে। আমাদের লিঙ্কড লিস্টে ইমপ্লিমেন্ট করার সময়, আমরা লিস্টের রিকার্সিভ প্রাকৃতিক ব্যবহার করতে পারি আমাদের সুবিধার জন্য পাস করা ব্লককে লিস্টের @head-এ কল করে, এবং #each @tail-এ কল করে, যদি সেটি অস্তিত্বে থাকে।
class LinkedList
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
block.call(@head)
@tail.each(&block) if @tail
end
end
আমাদের লিঙ্কড লিস্টের ইনস্ট্যান্সে #each কল করলে, এটি পাস করা ব্লকে কারেন্ট @head দিয়ে কল করে। তারপর, এটি @tail-এ লিঙ্কড লিস্টে each কল করে যদি টেইল nil নয়।
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each { |item| puts item }
42
12
73
=> nil
এখন যেহেতু আমাদের লিঙ্কড লিস্ট #each-এ রেসপন্ড করে, আমরা include Enumerable করতে পারি আমাদের লিস্ট enumারেবল বানাতে।
class LinkedList
include Enumerable
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
block.call(@head)
@tail.each(&block) if @tail
end
end
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.count
=> 3
irb> list.max
=> 73
irb> list.map { |item| item * item }
=> [1764, 144, 5329]
irb> list.select(&:even?)
=> [42, 12]
Enumerator ইনস্ট্যান্স রিটার্ন করা
আমরা এখন আমাদের লিঙ্কড লিস্টে সব ভ্যালু ওভার লুপ করতে পারি, কিন্তু আমরা enumারেবল ফাংশন চেইন করতে পারি না। সেটা করতে, আমাদের Enumerator ইনস্ট্যান্স রিটার্ন করতে হবে যখন আমাদের #each ফাংশনটি ব্লক ছাড়া কল করা হয়।
class LinkedList
include Enumerable
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
if block_given?
block.call(@head)
@tail.each(&block) if @tail
else
to_enum(:each)
end
end
end
একটি অবজেক্টকে একটি enumerator-এ র্যাপ করতে, আমরা সেটিতে #to_enum মেথড কল করি। আমরা :each পাস করি, কারণ সেটি সেই মেথড যা enumerator ইন্টারন্যালি ব্যবহার করা উচিত।
এখন, আমাদের #each মেথড ব্লক ছাড়া কল করলে আমাদের এনুমেরেশন চেইন করতে দেবে।
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each
=> #
irb> list.map.with_index.to_h
=> {42=>0, 12=>1, 73=>2}
নয় লাইন কোড এবং একটি include
Enumerable মডিউল ব্যবহার করে #each ইমপ্লিমেন্ট করে এবং আমাদের নিজস্ব থেকে Enumerator অবজেক্ট রিটার্ন করে, আমরা আমাদের লিঙ্কড লিস্টকে সুপারচার্জ করতে পারলাম নয় লাইন কোড এবং একটি include যোগ করে।
এটি রুবিতে enumারেবলrances আমাদের ওভারভিউ সমাপ্ত করে। আমরা জানতে চাই এই আর্টিকেল সম্পর্কে আপনি কী ভাবেন, অথবা আপনার কোনো প্রশ্ন আছে কিনা। আমরা সবসময় টপিক ইনভেস্টিগেট এবং এক্সপ্লেইন করার জন্য তত্পর, তাই যদি রুবিতে কোনো জাদুই জিনিস থাকে যা আপনি পড়তে চান, @AppSignal-এ আমাদের জানাতে সংকোচ করবেন না!