এনিউমেরেটররা রুবিকে এত পাওয়ারফুল এবং ডাইনামিক ল্যাঙ্গুয়েজ বানানোর মূল কারণ। এবং লেজি এনিউমেরেটর এই ধাপে এক ধাপ এগিয়ে আপনাকে এক্সট্রিমলি লার্জ কলেকশনের সাথে দক্ষতার সাথে কাজ করতে দেয়।
ফাইলগুলো - যাহা হোক - লাইন বা ক্যারেক্টারের বিশাল কলেকশন। তাই লেজি এনিউমেরেটর ফাইলগুলোর সাথে কিছু খুব ইন্টেরেস্টিং এবং পাওয়ারফুল জিনিস করার সম্ভাবনা তৈরি করে।
এনিউমেরেটর আসলে কী?
যখনই আপনি each এর মত একটি মেথড ব্যবহার করেন, আপনি একটি এনিউমেরেটর তৈরি করেন। এটাই কারণ যে আপনি মেথড চেইন করতে পারেন যেমন [1,2,3].map { ... }.reduce { ... }। নিচের উদাহরণে আপনি দেখতে পাবেন। each কল করলে একটি এনিউমেরেটর রিটার্ন করে, jota আমি অন্যান্য ইটেরেটিভ অপারেশনের জন্য ব্যবহার করতে পারি।
# I swiped this code from Ruby's documentation https://ruby-doc.org/core-2.2.0/Enumerator.html enumerator = %w(one two three).each puts enumerator.class # => Enumerator enumerator.each_with_object("foo") do |item, obj| puts "#{obj}: #{item}" end # foo: one # foo: two # foo: three লেজি এনিউমেরেটর লার্জ কলেকশনের জন্য
নরমাল এনিউমেরেটর লার্জ কলেকশনের সাথে সমস্যায় পড়ে। কারণ হলো আপনি যে প্রতিটি মেথড কল করেন, সেটি পুরো কলেকশনের ওপর ইটারেট করতে চায়। আপনি নিজে এটি চেক করতে পারেন নিচের কোড রান করে:
# This code will "hang" and you'll have to ctrl-c to exit (1..Float::INFINITY).reject { |i| i.odd? }.map { |i| i*i }.first(5) reject মেথডটি শাশ্বতভাবে চলে, কারণ এটি একটি ইনফাইনিট কলেকশনের ওপর ইটারেট কখনো ফিনিশ করতে পারে না।
কিন্তু একটি ছোট যোগফলের সাথে, কোডটি পারফেক্টলি রান করে। যদি আমি শুধু lazy মেথড কল করি, রুবি স্মার্টলি কাজ করে এবং কেবল ততটুকু ইটারেশন করে যা কম্পিউটেশনের জন্য জরুরি। এই কেসে সেটি শুধু ১০ রো, যা ইনফাইনিটির চেয়ে অনেক ছোট।
(1..Float::INFINITY).lazy.reject { |i| i.odd? }.map { |i| i*i }.first(5) #=> [4, 16, 36, 64, 100] ছয় হাজার কপি মবি-ডিক
এই ফাইল ট্রিকগুলো টেস্ট করার জন্য, আমাদের একটি বড় ফাইল লাগবে। इतना বড় যে কোনো "ফেইলার টু בי লেজি" স্পষ্ট হয়ে যাবে।
আমি প্রজেক্ট গুটেনবার্গ থেকে মবি-ডিক ডাউনলোড করলাম, এবং তারপর একটি টেক্সট ফাইল তৈরি করলাম ১০০ কপি দিয়ে। এটো বড় ছিল না। তাই আমি এটাকে প্রায় ৬,০০০ পর্যন্ত বাড়ালাম। এর মানে এখন আমি احتمال ওয়ার্ল্ডে একমাত্র ব্যক্তি যার কাছে মবি-ডিকের ৬,০০০ কপি সম্বলিত একটি টেক্সট ফাইল রয়েছে। একটু হাম্বলিং। কিন্তু আমি ডিগ্রেস করছি।
আমি মবি ডিক ডাউনলোড করলাম এবং এটিকে অনেকবার ডুপ্লিকেট করলাম একটি বড় ফাইল পাওয়ার জন্য। সিনট্যাক্সটি বাশ নয়। এটি ফিশ শেল। আমি মনে করি আমি একমাত্র বাকি থাকা ব্যক্তি যিনি এটি ব্যবহার করেন।
ফাইলের জন্য এনিউমেরেটর পাওয়ার পদ্ধতি
এখানে একটি কুল রুবি ট্রিক রয়েছে যা আপনি احتمال ইতিমধ্যে ব্যবহার করেছেন, যদিও আপনি জানতেন না যে আপনি এটি ব্যবহার করছেন। রুবিতে প্রায় যেকোনো মেথড যা একটি কলেকশনের ওপর ইটারেট করে, যদি আপনি ব্লক পাস না করে সেটি কল করেন, তবে আপনাকে একটি Enumerator অবজেক্ট রিটার্ন করে। এর মানে কী?
এই উদাহরণটি বিবেচনা করুন। আমি একটি ফাইল ওপেন করতে পারি, এবং each_line ব্যবহার করে প্রতিটি লাইন প্রিন্ট করতে পারি। কিন্তু যদি আমি ব্লক ছাড়া কল করি, তবে আমি একটি এনিউমেরেটর পাই। ইন্টারেস্টিং মেথডগুলো হলো each_line, each_char এবং each_codepoint।
File.open("moby.txt") do |f| # ফাইলের প্রতিটি লাইন প্রিন্ট করুন f.each_line do |l| puts l end # এটিও ফাইলের প্রতিটি লাইন প্রিন্ট করে। কিন্তু এটি করে # `each` কল করে Enumerator-র ওপর যা `each_line` রিটার্ন করেছিল f.each_line.each do |l| puts l end end এই দুটি উদাহরণ почти একই দেখাচ্ছে, কিন্তু দ্বিতীয়টিই অద্ভুত ক্ষমতা আনলাক করার চাবি।
একটি ফাইলের এনিউমেরেটর ব্যবহার করা
একবার আপনি একটি এনিউমেরেটর পেলে যা ফাইলের সকল লাইন "কন্টেইন" করে, আপনি সেই লাইনগুলো যেকোনো রুবি অ্যারের মত স্লাইস এবং ডাইস করতে পারেন। এখানে কয়েকটা উদাহরণ।
file.each_line.each_with_index.map { |line, i| "Line #{ i }: #{ line }" }[3, 10] file.each_line.select { |line| line.size == 9 }.first(10) file.each_line.reject { |line| line.match /whale/i } এটি খুব কুল, কিন্তু এই উদাহরণগুলোর একটা বড় সমস্যা রয়েছে। এগুলো সকল ফাইলটি পুরোটা মেমোরিতে লোড করে এর আগে ইটারেট করার। ৬,০০০ কপি মবি-ডিক সম্বলিত একটি ফাইলের জন্য, ল্যাগ স্পষ্ট।
ফাইলের লাইনগুলো লেজি-লোড করা
যদি আমরা একটি বড় টেক্সট ফাইল স্ক্যান করি "whale" শব্দটির প্রথম ১০টি উদাহরণ খুঁজতে, তবে ১০তম ঘটনার পর আর লুকাতে থাকার দরকার নেই। خوشবর্তমান রুবির এনিউমেরেটরকে এটি বলা খুব সহজ। আপনি শুধু "lazy" কীওয়ার্ড ব্যবহার করুন।
নিচের উদাহরণগুলোতে, আমরা লেজি লোডিংর সুবিধা নিয়ে কিছু খুব সফিস্টিকেটেড জিনিস করব।
File.open("moby.txt") do |f| # "whale" শব্দ পাওয়া প্রথম ৩টি লাইন পান f.each_line.lazy.select { |line| line.match(/whale/i) }.first(3) # ফাইলের শুরুতে ফিরে যান। f.rewind # প্রথম তিনটি লাইনে লাইন নম্বর প্রিপেন্ড করুন f.each_line.lazy.each_with_index.map do |line, i| "LINE #{ i }: #{ line }" end.first(3) f.rewind # "whale" সম্বলিত প্রথম তিনটি লাইন তাদের লাইন নম্বরের সাথে পান f.each_line.lazy.each_with_index.map { |line, i| "LINE #{ i }: #{ line }" }.select { |line| line.match(/whale/i) }.first(3) end এটি শুধু ফাইলের জন্য নয়
সকেট, পাইপ, সিরিয়াল পোর্ট - এগুলো রুবিতে IO ক্লাস দিয়ে রিপ্রেজেন্ট করা হয়। এর মানে সেগুলো সবার each_line, each_char এবং each_codepoint মেথড রয়েছে। তাই আপনি এই ট্রিকটি সকল জন্য ব্যবহার করতে পারেন। খুব নাॅটি!
এটি ম্যাজিক নয়
দুর্ভাগ্যক্রমে, লেজি এনিউমেরেটর শুধু তখন স্পিড আপ করে যখন আপনি যে টাস্কটি অর্জন করতে চাচ্ছেন সেটি পুরো ফাইল পড়ার দরকার নেই। যদি আপনি একটি শব্দ খুঁজেন যেটি বইয়ের লাস্ট পেজে একমাত্র ঘটেছে, তবে আপনাকে পুরো বই পড়তে হবে সেটি খুঁজে বের করার জন্য। কিন্তু সেক্ষেত্রেও এই আপْرোচ নন-এনিউমেরেটর আপْرোচের চেয়ে স্লোওয়ার হয় না।