জাভাস্ক্রিপ্ট ইটারেটরের প্রাকটিক্যাল ক্লাস
সপ্তাহ খানেক আগে আমি একটা পোস্ট লিখেছিলাম, জাভাস্ক্রিপ্টের ইটারেটর নিয়ে। পোস্টটা লেখার সময়েই বুঝেছিলাম, কোন প্রাকটিক্যাল ইউজ কেস ছাড়া শুধু গৎবাঁধা কিছু ঊদাহরণ দিয়ে ইটারেটরের গুরুত্ব বোঝানো যাবে না। তাই তখনই ঠিক করে রেখেছিলাম, ইটারেটরের প্রাকটিক্যাল ইউজ নিয়ে একটা পোস্ট লিখব। সে কারণেই এই লেখার অবতারণা।
লিংকড লিস্ট
প্রথমেই দেখা যাক, একটা লিংকড লিস্টের ওপর আমরা কীভাবে for…of
লুপ চালাতে পারি। লিংকড লিস্টে ডেটা কীভাবে থাকে মনে আছে তো? শুরুতে একটা হেড নোড থাকে। সেই হেড নোডে থাকে তার পরের নোডের রেফারেন্স, সেখানে থাকে তারও পরের নোডের রেফারেন্স, এভাবে চলতে থাকে।
স্বভাবতই এর ওপরে for…of
লুপ চালানো সম্ভব না। কিন্তু, প্রোগ্রামারদের ডিকশনারিতে অসম্ভব বলে কোন শব্দ থাকে না। প্রোগ্রামারদের ডিকশনারির সেই পাতাটা উইপোকা আগেই খেয়ে ফেলে। সুতরাং, দেখা যাক আমরা জাভাস্ক্রিপ্টে কীভাবে লিংকড লিস্টের ওপর for…of
লুপ চালাতে পারি।
প্রথমেই দেখা যাক, আমদের লিংকড লিস্টের রিপ্রেজেন্টেশনটা।
অর্থাৎ, আমরা আমাদের লিংকড লিস্টের হেড, কর্নেল নিকোলাস জোসেফ ফিউরি, থেকে ডক্টর স্ট্রেঞ্জের অ্যাকসেস পাবো। তার থেকে থরের অ্যাকসেস পাব। এভাবে চলতে চলতে স্পাইডার ম্যানে গিয়ে থামব।
এখন এই ইটারেশনের ইটারেটর ভার্শন কীভাবে লিখতে পারি, সেটা দেখে নেয়া যাক।
সিম্পল অ্যাজ হেল! আমরা head
থেকে শুরু করব। সেই সুপারহিরোর নাম yield
করব। node
এর ভ্যালু তার পরবর্তী node
(অর্থাৎ node.next
) এ অ্যাসাইন করব। আবার yield
করব। এভাবে চলতে থাকবে যতক্ষণ নোডটার ভ্যালু null
না হয়।
তারপরই আমরা চাইলে যখন তখন এই superHeroes()
ফাংশনের ওপর লুপ চালিয়ে দিতে পারব।
ব্যাস! হয়ে গেল আমাদের লিংকড লিস্ট ইটারেবল। আর এই for…of
সিনট্যাক্স নিঃসন্দেহে লুপের ভেতরেই null
চেক করা আর node.next
অ্যাসাইন করার চেয়ে অনেক বেশি পরিচ্ছন্ন।
বাইনারি ট্রি
এবার আমরা দেখব ইটারেটরের মাধ্যমে আমরা কিভাবে একটা বাইনারি ট্রি এর ওপর for…of
লুপ চালাতে পারি। প্রথমেই আমাদের বাইনারি ট্রি এর স্ট্রাকচারটা দেখে নিই।
এই ট্রি’টা ছোট থেকে বড় আকারে সাজানো আছে। এখানে আমরা দেখতে পাচ্ছি, সবচেয়ে ছোট ভ্যালুটা আছে একেবারে বামের লিফ নোডে আর সবচেয়ে বড় ভ্যালুটা আছে সবচেয়ে ডানের লিফ নোডে। তাই, এটার প্রিন্ট অর্ডার হওয়া উচিৎ 9, 12, 14, 17, 19, 23, 50, 72, 54, 67, 76.
এখন দেখা যাক এটাকে আমরা জাভাস্ক্রিপ্টে কীভাবে রিপ্রেজেন্ট করতে পারি।
এখন আমরা এটাকে ইন-অর্ডারে অর্থাৎ, প্রথমে লেফট চাইল্ড, তারপর প্যারেন্ট, সবশেষে রাইট চাইল্ড এভাবে ইটারেট করতে চাই। বাইনারি ট্রি এর ইন-অর্ডার প্রিন্ট করা মনে আছে নিশ্চয়ই। এবার তার জন্যে ইটারেটরটা লিখে ফেলি।
সেই পুরোনো কোড। শুধু দরকার মতো কয়েকটা yield
আর *
বসিয়ে দেয়া। এখানে খেয়াল করার মত ব্যাপার হচ্ছে, আমরা যখন একটা ফাংশনকে yield
করব(line 3, 7), তখন তার আগে একটা *
সাইন দিতে হবে। নইলে জাভাস্ক্রিপ্ট পুরো ফাংশনটার রিটার্ন ভ্যালুকে yield
করে দেবে। অবশ্য, আমি সবাইকে এনকারেজ করব ভেতরে *
সাইনটা না দিলে আউটপুট কেমন আসে সেটা একবার দেখতে। তাহলে পুরো ব্যাপারটা আরো ভাল ভাবে বোঝা যাবে। *
সাইন দিয়ে জাভাস্ক্রিপ্টকে বোঝানো হয় যে, পুরোটা একবারে না, এই ফাংশনের ভেতরে যে ভ্যালুগুলোকে yield
করা আছে, তুমি আবার তার প্রতিটাকে একটা একটা করে yield
করো।
ব্যাস! আমাদের বাইনারি ট্রি লুপ চালানোর জন্য তৈরি।
মেমরি ইফিশিয়েন্ট ইটারেশন
ধরা যাক, আমাদের একটা ফোল্ডারে দশটা ফাইল আছে। প্রতিটা ফাইলের সাইজ 100MB. আমাদের সবগুলো ফাইলকে প্রসেস করতে হবে। প্রসেস করার জন্য আমরা একটা থার্ড পার্টি লাইব্রেরি ব্যবহার করছি। সেই লাইব্ররি ফাংশনে আমাদের সবগুলো ফাইলের কনটেন্ট পাঠাতে হবে। এখন চাইলে আমরা সবগুলো ফাংশনের কন্টেন্ট রিড করে একটা অ্যারেতে রেখে সেই অ্যারেটা পাস করে দিতে পারি। সেই অ্যারেটা আমার মেমরির 1GB জায়গা খেয়ে নেবে। যেখানে সাধারণ ইউজারদের র্যামের আকারই হয় দুই কি চার জিবি। অথবা আমরা একটা ইটারেটর ডিফাইন করতে পারি। লুপের প্রতি ইটারেশনে আমাদের ইটারেটর একটা করে ফাইল রিড করবে। সুতরাং, প্রকৃতপক্ষে আমাদের মেমরি খরচ হবে 100MB।
এই কাজটার একটা pseudo-code দেখা যাক।
এই তরিকায় মেমরি ইউজ অনেক কমিয়ে ফেলা যায়।
সিপিইউ ইফিশিয়েন্ট ইটারেশন
আমার অফিসে একটা অ্যাপ্লিকেশন ডেভেলপ করা হচ্ছে অনলাইন এক্সাম নেয়ার জন্য। সেটার একটা রিকয়ারমেন্ট ছিল ডায়নামিকভাবে প্রশ্ন তৈরি করা। ইউজার বলে দেবে মোট কয়টা প্রশ্ন থাকবে, কোন ডিফিকাল্টি থেকে কয়টা প্রশ্ন থাকবে, কোন ট্যাগ থেকে কয়টা প্রশ্ন থাকবে… এমন দশ বারোটা ক্রাইটেরিয়া। সেই সব ক্রাইটেরিয়া মিলিয়ে প্রশ্নের একটা কম্বিনেশন তৈরি করে ইউজারকে দেখাতে হবে। ইউজারের পছন্দ হলে ভাল, নইলে পরের কম্বিনেশন তৈরি করতে হবে।
এখন ঝামেলা হচ্ছে সিস্টেমে থাকা লাখ লাখ প্রশ্ন থেকে সব ক্রাইটেরিয়া মিলিয়ে সবগুলো কম্বিনেশন তৈরি করতে গেলে যতগুলো কম্বিনেশন তৈরি হবে (এক লাখ প্রশ্ন থেকে একশটা নিলে), তার সংখ্যা হচ্ছে — 1,019,745,118,068,508,355,816,143,175,813,864,936,340,849,501,519,143,871,777,101,229,221,311,704,707,028,464,150,801,159,851,819,171,048,218,667,041,957,300,317,610,197,834,902,713,500,555,020,173,048,960,798,706,784,754,817,501,032,954,266,210,951,200,883,251,987,669,862,675,631,847,360,156,537,041,811,409,331,039,637,713,779,759,572,781,307,343,494,274,990,819,880,664,139,309,679,429,703,675,390,591,419,799,883,108,282,575,404,188,096,902,252,124,000.
হ্যাঁ, ঠিক দেখেছেন। সংখ্যাটা আসলেই এত বড়। সুতরাং, বোঝাই যাচ্ছে সবগুলো কম্বিনেশন বের করে একটা অ্যারেতে রেখে, সেই অ্যারে থেকে একটা একটা করে ইউজারকে দেখানো আসলে একটা অসম্ভব সল্যুশন। তাই, আমরা যেটা করেছিলাম সেটা হচ্ছে — ইটারেটর। আমরা একটা ইটারেটর বানিয়ে নিয়েছিলাম কম্বিনেশন জেনারেট করার। স্বভাবতই ইটারেটরটা লুপের প্রতি ইটারেশনে একটা করে কম্বিনেশন জেনারেট করত। তিন চার ইটারেশনের পরেই ইউজারের একটা ইটারেশন পছন্দ হয়ে যেত। আর তাই 343 সংখ্যার একটা সংখ্যা সংখ্যক বার ইটারেশনের বদলে আমাদের ইটারেশন লাগে তিন কি চারটা। বেঁচে যায় বিপুল পরিমাণে সিপিইউ ক্লক সাইকেল।
আশা করি, ইটারেটরের এই প্রাকটিক্যাল ইউজ কেসগুলো আপনার পরবর্তী ইটারেটিভ প্রবলেমটার সলভের সময়ে দ্বিতীয়বার ভাবনার খোরাক যোগাবে। কখন কোডের রিডাবিলিটি আর কখনও ইফিসিয়েন্সির জন্য ইটারেটর জুড়ি মেলা সত্যিই ভার। জাভাস্ক্রিপ্টের এমনই কোন ফিচার নিয়ে অন্য কোনদিন কথা হবে।