কাজের জায়গায় যখন আগে শেখা এলগোরিদম, ডাটা স্ট্রাকচার কাজে লাগে!
স্কুলে/কলেজে/ইউনিভার্সিটিতে যেইসব এলগোরিদম আর ডাটা স্ট্রাকচার আমরা শিখি, কিংবা ইন্টারভিউ'র প্রিপারেশন নিতে গিয়ে প্রব্লেম সলভিং প্রাকটিস করতে যেইসব জটিল সল্যুশন আমরা ব্যবহার করি - তার কতটুকু জবের দৈনন্দিন সত্যিকার কাজে গিয়ে আসলে লাগে? আমার ধারণা খুব একটা না। অন্তত আমার লাগে না। কিন্তু তাই বলে আমি বলছি না যে এইগুলো শেখার দরকার নাই, প্র্যাক্টিস করা বৃথা - মাথা কাজ করানোর জন্য এইসব খুবই জরুরি। আর কাজের জায়গায় যখন কোনো একটা প্রব্লেম সল্ভ করতে গিয়ে আগের শেখা কোনো এলগোরিদম, ডাটা স্ট্রাকচার কিংবা টেকনিক কাজে লাগে, তখন আসলেই খুব ভালো লাগে। এতো ভূমিকা দেয়ার কারণ হচ্ছে, কয়েকদিন আগে আমার তাই-ই হয়েছিল, জেনেরিক প্রব্লেমটা আর সল্যুশনটা শেয়ার করার লোভ সামলাতে পারছি না। নিচে দিচ্ছি:
প্রব্লেম: ধরা যাক, n সংখ্যক লিস্টে কিছু নম্বর আছে। আপনাকে সেগুলো (পাইথনে) list of lists আকারে ইনপুট দেয়া হবে। আরেকটা সংখ্যা, p দেয়া হবে। আপনার কাজ হবে, p লেংথ-এর একটা লিস্ট রিটার্ন করা, যেখানে ইনপুটের n লিস্টের সবগুলো থেকে প্রায় সমান সংখক এলিমেন্ট নিতে হবে। আর ডুপ্লিকেট থাকতে পারবে না। দুইটা উদাহরণ দেই:
কেস ১:
list_of_lists = [[1, 2, 3], [3, 4, 5], [6, 7]], p = 6;
expected output = [1, 3, 6, 2, 4, 7]
list_of_lists = [[1, 2, 3], [3, 4, 5], [6, 7]], p = 7;
expected output = [1, 3, 6, 2, 4, 7, 5]
প্রব্লেমটা আশাকরি বোঝা গেছে। কেস-১ সিম্পল। সবগুলা ইনপুট লিস্ট থেকে ২ টা করে নিয়ে ৬ সাইজের আউটপুট লিস্ট তৈরী করলেই হলো। কেস-২ একটু জটিল, যেহেতু আউটপুট লিস্টে ডুপ্লিকেট থাকতে পারবে না, ইনপুটের দ্বিত্বীয় লিস্ট থেকে 5 নিয়ে ৭ সাইজের লিস্ট তৈরী করা লাগলো।
সল্যুশনটা কী হবে? আমার সলুশনটা বলার আগে বলে নেই: সেটাতে আমি ধাপে ধাপে পৌছাইসি। আমার প্রথমদিনের সলুশনটা জটিল ছিল, পরে একটা ইমপ্রুভমেন্ট মাথায় আসলে ইমপ্লিমেন্ট করি। পরের দিন আরেকটু ভালো সল্যুশন মাথায় আসলে সেটা ইমপ্লিমেন্ট করে আলটিমেট সলুশনে পৌছাইসি। যদিও আগের সল্যুশন গুলো ঠিকই ছিল।
সল্যুশন: প্রথমেই খেয়াল করে দেখি যে একটা পরিচিত আলগোরিদমের সাথে প্রব্লেমটার মিল আছে। merge sort আলগোরিথমের মার্জ অংশে আমরা দুইটা সর্টেড লিস্ট থেকে একটা আউটপুট লিস্ট বানাই। আর এখানেও অনেকটা তাই, শুধু দুইটার বদলে n-সংখ্যাক লিস্ট, আর সর্টেডর বদলে শর্ত হচ্ছে আউটপুট লিস্ট ইউনিক হতে হবে। অর্থাৎ, যতক্ষণ p সাইজের লিস্ট হচ্ছে না, ততক্ষন এক এক করে প্রতিটা n -লিস্টের এলিমেন্ট নিবো, একটা set ব্যবহার করে ইউনিক কিনা টেস্ট করে আউটপুট লিস্টে এপেন্ড করতে থাকবো। আর যদি ইউনিক না হয়, তাহলে লিস্টের পরের এলিমেন্টটা নিবো, যতক্ষণ লিস্টে এলিমেন্ট থাকে। কিছু কর্নার কেস বাদে, এইটাই বেসিক সল্যুশন।
কর্নার কেস গুলো কি? কয়েকটা হতে পারে, যেমন: হাতের লিস্টে যদি এলিমেন্ট না থাকে? যদি আউটপুট p -র থেকে ইনপুট n লিস্টের সাইজ কম: (n < p) হয়। যদি লিস্ট গুলোতে ডুপ্লিকেট সংখ্যা বেশি থাকে - মূলত এই কয়টাই। কিছু চেক অ্যাড করে সেটাও হ্যান্ডেল করা যাবে।
এরপর আরেকটু ভাবার পর মনে হলো, একটা কিউ ব্যবহার করে তো কোডটা আরো ভালো করা যায়, count ব্যবহার করে এতো জটিল করার তো দরকার নাই। আগে সেই কোডটার স্ক্রিনশট দিচ্ছি, তারপর নিচে ব্যাখ্যা করছি:
আইডিয়াটা হচ্ছে, জেনেরেটরগুলোকে শুরুতে একটা কিউতে ঢুকিয়ে রাখলাম। এরপর যতক্ষণ output লিস্টের সাইজ p না হয় এবং কিউয়ে এলিমেন্ট থাকে, ততক্ষন ডিকিউ করে কিউ থেকে জেনেরেটর বের করে নিলাম। এরপর জেনারেটর থেকে এলিমেন্ট next() কল করে নিলাম, এলিমেন্ট ইউনিক হলে output লিস্টে, set -এ এপেন্ড, অ্যাড করে নিলাম। তারপর হাতের জেনেরেটরকে আবারো কিউ-এ ঢুকিয়ে দিলাম। আর যদি, এলিমেন্ট ইউনিক না হয়, তাহলে জেনেরেটর থেকে next ()- কল করে আবারো একই কাজ করতে থাকলাম (while True) । যদি কখনো next() কল করে জেনারেটরে এলিমেন্ট না পাই, তখন জেনারেটর StopIteration এক্সেপশন থ্রো করবে। সেই এক্সেপশন হ্যান্ডেল করার জন্য কিছুই না, শুধু হাতের জেনেরেটরকে পুনরায় আর কিউতে অ্যাড না করলেই হবে - অর্থাৎ, এলিমেন্ট যখন আর নাই-ই তাহলে সেটা কিউ থেকে ফেলে দিতে হবে।
https://replit.com/join/vhqitodokp-ishtiaquehussai
--ইশতিয়াক, ৭ ব্যাচ, সিএসই, ডিইউ