রবিন-কার্প (Rabin-Karp)
ধরুন আপনি ৫০০ পৃষ্ঠার একটি বইয়ে আপনার এক বন্ধুর নাম খুঁজছেন। প্রতিটি পৃষ্ঠার প্রতিটি অক্ষর ধরে ধরে পরীক্ষা করতে গেলে তো সারা জীবন লেগে যাবে! কিন্তু কেমন হবে যদি আপনি আপনার বন্ধুর নামের একটি ছোট ফিঙ্গারপ্রিন্ট (fingerprint) বা সংক্ষিপ্ত নম্বর তৈরি করে নেন, এবং প্রথমে শুধু ফিঙ্গারপ্রিন্টগুলো মিলিয়ে দেখেন? যখনই কোনো ফিঙ্গারপ্রিন্ট মিলবে, শুধুমাত্র তখনই আপনি আসল অক্ষরগুলো পড়ে দেখবেন।
রবিন-কার্প (Rabin-Karp) ঠিক এই কাজটিই করে থাকে। এটি প্যাটার্নের জন্য একটি ফিঙ্গারপ্রিন্ট (যাকে হ্যাশ বা hash বলা হয়) তৈরি করে, তারপর পুরো টেক্সটের ওপর দিয়ে একটি উইন্ডো স্লাইড করে নিয়ে যায় এবং উভয় ফিঙ্গারপ্রিন্ট তুলনা করে। বেশিরভাগ উইন্ডোই মুহূর্তের মধ্যে ফিঙ্গারপ্রিন্ট টেস্টে বাদ পড়ে যায়। খুব বিরল ক্ষেত্রেই যখন ফিঙ্গারপ্রিন্ট মিলে যায়, শুধু তখনই পুরো অক্ষরগুলো ধরে ধরে মেলানোর প্রয়োজন হয়।
রোলিং হ্যাশ ট্রিক (The Rolling Hash Trick)
এর আসল চাতুর্যটি হলো: যখন আপনি উইন্ডোটিকে এক ধাপ ডানে সরাবেন, তখন আপনাকে পুরো ফিঙ্গারপ্রিন্টটি শূন্য থেকে নতুন করে হিসাব করতে হবে না। এর বদলে আপনি এটিকে রোল (roll) করবেন — অর্থাৎ সবচেয়ে বামের অক্ষরের মানটি বিয়োগ করবেন, পুরোটাকে বামে শিফট করবেন (shift left), এবং নতুন যোগ হওয়া সবচেয়ে ডানদিকের অক্ষরের মানটি যোগ করবেন। এই কৌশলের কারণে উইন্ডো সরানোর প্রতিটি ধাপের জন্য \(O(m)\) এর বদলে মাত্র \(O(1)\) সময় লাগে। এটিকেই বলা হয় রোলিং হ্যাশ (rolling hash)।
গাণিতিকভাবে, টেক্সটের i অবস্থান থেকে শুরু হওয়া m দৈর্ঘ্যের একটি উইন্ডোর জন্য:hash = (s[i]·b^(m-1) + s[i+1]·b^(m-2) + ... + s[i+m-1]) mod p
এক ধাপ স্লাইড বা সরানোর পর: new_hash = (old_hash - s[i]·b^(m-1)) × b + s[i+m]) mod p
স্পিউরিয়াস হিট (Spurious Hits): যখন ফিঙ্গারপ্রিন্ট ভুল কথা বলে
মাঝে মাঝে সম্পূর্ণ ভিন্ন দুটি স্ট্রিংয়ের ফিঙ্গারপ্রিন্ট একই হয়ে যেতে পারে — একে হ্যাশ কলিশন (hash collision) বলা হয়। যখন এমনটি ঘটে, তখন নিশ্চিত হওয়ার জন্য রবিন-কার্প অ্যালগরিদম প্রতিটি অক্ষর ধরে ধরে পুরো প্যাটার্নটি মিলিয়ে দেখে। মেলানোর পর যদি দেখা যায় সেগুলো আসলে এক নয়, তবে একে বলা হয় স্পিউরিয়াস হিট (spurious hit) বা মিথ্যা মিল। একটি ভালো মানের হ্যাশ এবং বেশ বড় একটি মৌলিক সংখ্যা বা প্রাইম মডুলাস (prime modulus) ব্যবহার করলে, স্পিউরিয়াস হিট প্রায় হয়ই না বললেই চলে এবং এর ফলে বাস্তবে অ্যালগরিদমটি প্রায় \(O(n+m)\) সময়ের কাছাকাছি থাকে।
একসাথে একাধিক প্যাটার্ন খোঁজা
এই জায়গাতেই রবিন-কার্প অ্যালগরিদম তার আসল জাদু দেখায়। আপনি যদি একসাথে k সংখ্যক ভিন্ন ভিন্ন প্যাটার্ন খুঁজতে চান, তবে আগে সবগুলোর (k) হ্যাশ বের করে একটি হ্যাশ সেটে (hash set) সংরক্ষণ করুন। এরপর উইন্ডো সরানোর সময় প্রতিটি উইন্ডোকে সেটের সাথে মাত্র একবার \(O(1)\) সময়ে মিলিয়ে দেখলেই হবে। এতে মোট সময় লাগবে: \(O(n + \Sigma m_i)\) — যা সবগুলো ইনপুট সাইজের যোগফলের সাপেক্ষে লিনিয়ার (linear)। অন্যদিকে, অন্যান্য অ্যালগরিদম (যেমন KMP) ব্যবহার করলে k সংখ্যক প্যাটার্নের জন্য পুরো কাজটি k বার আলাদাভাবে করতে হতো।
রবিন-কার্প প্যাটার্ন সার্চ (Rabin-Karp Pattern Search)
কখন আপনার রবিন-কার্প ব্যবহার করা উচিত?
- একসাথে একাধিক প্যাটার্ন খোঁজার ক্ষেত্রে — এটিই এর মূল শক্তি বা সুপারপাওয়ার
- যখন আপনি ভালো গড় সময়ের (average-case behavior) জন্য একটি সাধারণ ইমপ্লিমেন্টেশন খুঁজছেন
- ২ডি (2D) প্যাটার্ন ম্যাচিংয়ের ক্ষেত্রে (যেখানে হ্যাশিংকে গ্রিডে সম্প্রসারিত করা যায়)
মাত্র একটি প্যাটার্ন খোঁজার ক্ষেত্রে, ওয়ার্স্ট কেসে (worst case) KMP বা Boyer-Moore অপেক্ষাকৃত দ্রুততর হতে পারে। কিন্তু যখন আপনার কাছে ডজন খানেক বা শত শত প্যাটার্ন থাকে, তখন রবিন-কার্পের হ্যাশ-সেট কৌশলকে হারিয়ে দেওয়া বেশ কঠিন কাজ।
Complexity
ছোট কুইজ
পড়া চালিয়ে যান