Lesson ১৩পড়তে ১০ মিনিট লাগবে

এসটিএল কন্টেইনার (STL Containers)

একটি স্বয়ংসম্পূর্ণ বা ফুল্লি-স্টকড রান্নাঘর (fully-stocked kitchen) — যেখানে তাকের ওপরেই সবকিছু সাজানো রয়েছে, সেখানে আপনি শুধু শুধু নিচ থেকে হাঁড়ি-পাতিল বানাতে যাবেন কেন?

রান্নাঘরের সাথে তুলনা (The Kitchen Analogy)

সি++-এর স্ট্যান্ডার্ড টেমপ্লেট লাইব্রেরি বা এসটিএল (STL)-কে মূলত একটি সম্পূর্ণ সজ্জিত বা ফুল্লি ইকুইপড (fully equipped) প্রফেশনাল রান্নাঘরের (professional kitchen) সাথে তুলনা করা যেতে পারে। আপনার কি বিভিন্ন আইটেমের (items) তালিকা বা লিস্ট (list) সংরক্ষণ করার প্রয়োজন? এর জন্য এখানে একটি কন্টেইনার (container) রয়েছে। আপনার কি কি বা key-এর ওপর ভিত্তি করে দ্রুততম সময়ে (fast lookups) কোনো কিছু খুঁজে বের করা প্রয়োজন? এর জন্যও এখানে একটি কন্টেইনার রয়েছে। কিংবা কোনো শিডিউলিংয়ের (scheduling) জন্য আপনার কি একটি প্রায়োরিটি কিউয়ের (priority queue) প্রয়োজন? একদম চিন্তা নেই, এখানে সেটিও আগে থেকেই তৈরি করা (Already built) আছে।

এখানকার আসল কৌশলটি মূলত এর প্রতিটি হাঁড়ি-পাতিল (every pot and pan) সম্পর্কে জানা নয় — বরং এখানকার আসল কাজ হলো উপযুক্ত কাজের জন্য ঠিক কোন জিনিসটিকে বেছে নিতে হবে (which one to grab), সেটি জানা।

সিকোয়েন্স কন্টেইনার (Sequence Containers) — অর্ডারড কালেকশন (Ordered Collections)

এরা মূলত একটি নির্দিষ্ট ক্রমানুসারে (specific order) বিভিন্ন এলিমেন্ট বা উপাদানগুলোকে স্টোর (store) করে বা সংরক্ষণ করে।

  • vector — ডায়নামিক অ্যারে (Dynamic array)। এটিই হলো যেকোনো কাজের ক্ষেত্রে আপনার প্রথম ও প্রধান ডিফল্ট পছন্দ (default)। এটি দ্রুত র্যান্ডম অ্যাক্সেস (random access) এবং দ্রুত অ্যাপেন্ড (append) করার সুবিধা প্রদান করে।
  • deque — ডাবল-এন্ডেড কিউ বা Double-ended queue। এটি মূলত এর সামনে (front) এবং পেছনে (back) — উভয় দিক থেকেই দ্রুত ইনসার্ট/রিমুভ (insert/remove) করার সুবিধা প্রদান করে।
  • list — ডাবলি-লিঙ্কড লিস্ট (Doubly-linked list)। এটি মূলত যেকোনো জায়গা থেকে দ্রুত ইনসার্ট/রিমুভ (insert/remove) করার সুবিধা প্রদান করে (যদি আপনার কাছে একটি ইটারেটর বা iterator থাকে), তবে এতে কোনো প্রকার র্যান্ডম অ্যাক্সেস (random access) নেই।

অ্যাসোসিয়েটিভ কন্টেইনার (Associative Containers) — কি-নির্ভর লুকআপ (Key-Based Lookup)

  • map — সাজানো বা সর্টেড (Sorted) কি-ভ্যালু পেয়ার (key-value pairs)। এর লুকআপ টাইম (lookup time) হলো \(O(\log n)\)। এখানকার কি বা keys-গুলো মূলত একটি নির্দিষ্ট ক্রমানুসারে সাজানো (ordered) থাকে।
  • set — সাজানো বা সর্টেড (Sorted) ইউনিক ভ্যালু বা unique values। এটি অনেকটা এমন এক ধরনের ম্যাপের (map) মতো যার কোনো ভ্যালু (values) নেই।
  • unordered_map — হ্যাশ টেবিল (Hash table)। এর গড় লুকআপ টাইম (average lookup time) হলো \(O(1)\)। এর নির্দিষ্ট কোনো অর্ডারিং বা ordering নেই।
  • unordered_set — হ্যাশ সেট (Hash set)। ইউনিক ভ্যালু (unique values), এর নির্দিষ্ট কোনো অর্ডারিং বা ordering নেই।

কন্টেইনার অ্যাডাপ্টার (Container Adapters) — রেস্ট্রিক্টেড ইন্টারফেস (Restricted Interfaces)

  • stack — লিফো (LIFO)। এটি শুধু এর উপরের (top) অংশ থেকে পুশ/পপ (Push/pop) করে।
  • queue — ফিফো (FIFO)। এটি এর পেছনে (back) পুশ (Push) করে এবং সামনে (front) পপ (pop) করে।
  • priority_queue — এটি সব সময়ই এর সবচেয়ে বড় (largest) (বা সবচেয়ে ছোট) উপাদানটিকেই সবার আগে পপ (pops) করে বের করে আনে।

ম্যাপ বা map — ওয়ার্ড ফ্রিকোয়েন্সি কাউন্টার (Word Frequency Counter)

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> freq;
string words[] = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (const auto& w : words) {
freq[w]++; // operator[] মূলত কোনো কিছু মিসিং (missing) থাকলে একটি এন্ট্রি তৈরি করে (creates entry)
}
// ম্যাপ (map) মূলত কি বা key অনুযায়ী সাজানো (sorted) থাকে!
for (const auto& [word, count] : freq) {
cout << word << ": " << count << endl;
}
return 0;
}
Output
apple: 3
banana: 2
cherry: 1

সেট বা set — ইউনিক এলিমেন্টগুলো (Unique Elements) মূলত অটোমেটিকভাবেই বা Automatically সর্টেড (Sorted) বা সাজানো হয়ে থাকে

#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
s.insert(5);
s.insert(2);
s.insert(8);
s.insert(2); // ডুপ্লিকেট বা duplicate — এটি ইগনোরড বা ignored হয়ে যাবে
s.insert(5); // ডুপ্লিকেট বা duplicate — এটি ইগনোরড বা ignored হয়ে যাবে
cout << "Size: " << s.size() << endl;
cout << "Elements: ";
for (int x : s) cout << x << " ";
cout << endl;
// দ্রুত লুকআপ বা Fast lookup
if (s.count(8)) cout << "Found 8!" << endl; // 8 পাওয়া গেছে বা Found 8!
if (!s.count(99)) cout << "99 not found" << endl; // 99 পাওয়া যায়নি বা 99 not found
return 0;
}
Output
Size: 3
Elements: 2 5 8
Found 8!
99 not found

আনঅর্ডারড ম্যাপ (unordered_map) বনাম (vs.) ম্যাপ (map) — পারফরম্যান্স (Performance)

#include <iostream>
#include <map>
#include <unordered_map>
#include <chrono>
using namespace std;
using namespace chrono;
int main() {
const int N = 1000000;
// --- ম্যাপ বা map (সাজানো বা sorted, O(log n)) ---
map<int, int> ordered;
auto t1 = high_resolution_clock::now();
for (int i = 0; i < N; i++) ordered[i] = i;
auto t2 = high_resolution_clock::now();
cout << "map insert: "
<< duration_cast<milliseconds>(t2 - t1).count()
<< " ms" << endl;
// --- আনঅর্ডারড ম্যাপ বা unordered_map (হ্যাশ বা hash, গড় O(1) বা O(1) avg) ---
unordered_map<int, int> hashed;
t1 = high_resolution_clock::now();
for (int i = 0; i < N; i++) hashed[i] = i;
t2 = high_resolution_clock::now();
cout << "unordered_map insert: "
<< duration_cast<milliseconds>(t2 - t1).count()
<< " ms" << endl;
return 0;
}
Output
map insert: 612 ms
unordered_map insert: 198 ms

প্রায়োরিটি কিউ (priority_queue) — শীর্ষ-K (Top-K) বা সেরা এলিমেন্টগুলো

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
// ডিফল্ট (Default): ম্যাক্স-হিপ বা max-heap (সবচেয়ে বড়টি বা largest সবার ওপরে থাকে)
priority_queue<int> maxPQ;
for (int x : {3, 1, 4, 1, 5, 9, 2, 6}) {
maxPQ.push(x);
}
cout << "Top 3 largest: "; // শীর্ষ তিনটি বা Top 3 সবচেয়ে বড় এলিমেন্ট বা largest:
for (int i = 0; i < 3; i++) {
cout << maxPQ.top() << " ";
maxPQ.pop();
}
cout << endl;
// মিন-হিপ বা Min-heap: greater<int> ব্যবহার করুন
priority_queue<int, vector<int>, greater<int>> minPQ;
for (int x : {3, 1, 4, 1, 5, 9, 2, 6}) {
minPQ.push(x);
}
cout << "Top 3 smallest: "; // শীর্ষ তিনটি বা Top 3 সবচেয়ে ছোট এলিমেন্ট বা smallest:
for (int i = 0; i < 3; i++) {
cout << minPQ.top() << " ";
minPQ.pop();
}
cout << endl;
return 0;
}
Output
Top 3 largest: 9 6 5
Top 3 smallest: 1 1 2

স্ট্যাক (stack) — ব্র্যাকেট ম্যাচিং (Bracket Matching)

#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool isBalanced(const string& expr) {
stack<char> s;
for (char c : expr) {
if (c == '(' || c == '[' || c == '{') {
s.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (s.empty()) return false;
char top = s.top(); s.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{'))
return false;
}
}
return s.empty();
}
int main() {
cout << "{[()]} -> " << (isBalanced("{[()]}") ? "balanced" : "not balanced") << endl; // ব্যালেন্সড (balanced) বা নট ব্যালেন্সড (not balanced)
cout << "{[(])} -> " << (isBalanced("{[(])}") ? "balanced" : "not balanced") << endl;
cout << "((())) -> " << (isBalanced("((()))") ? "balanced" : "not balanced") << endl;
return 0;
}
Output
{[()]}  -> balanced
{[(])}  -> not balanced
((()))  -> balanced

কখন কোনটি ব্যবহার করবেন? (When to Use Which?)

প্রয়োজনীয়তা (Need)কন্টেইনার (Container)কেন? (Why)
যেকোনো সাধারণ বা জেনারেল-পারপাস লিস্ট (General-purpose list)vectorক্যাশ-ফ্রেন্ডলি (Cache-friendly), র্যান্ডম অ্যাক্সেসে দ্রুত (fast random access)
সামনে + পেছনে দ্রুত অপারেশন (ops) চালেতেdequeউভয় দিক (both ends) থেকেই O(1) পুশ/পপ বা push/pop
মাঝখানে ঘন ঘন ইনসার্ট/রিমুভের ক্ষেত্রেlistইটারেটরের (iterator) সাথে O(1) স্প্লাইস (splice)
সর্টেড (Sorted) বা সাজানো কি-ভ্যালু জোড়ার (pairs) ক্ষেত্রেmapO(log n), অর্ডারড ইটারেশন (ordered iteration)
কি-ভ্যালুর দ্রুত লুকআপের (key-value lookup) ক্ষেত্রেunordered_mapগড় বা average O(1)
ইউনিক এবং সাজানো ভ্যালুর ক্ষেত্রেsetO(log n), অটো-সর্টেড (auto-sorted)
LIFO (লিফো)stackকেবল ওপর বা top থেকেই পুশ/পপ (Push/pop) করে
FIFO (ফিফো)queueপেছনে (back) পুশ (Push) করে, সামনে (front) পপ (pop) করে
সব সময় যদি সর্বোচ্চ/সর্বনিম্ন মানটির দরকার হয়priority_queueহিপ-নির্ভর (Heap-based), O(log n) পুশ/পপ
Note: unordered_map মূলত গড়ে O(1) বা O(1) average টাইম নিলেও, এটি কিন্তু এর সবচেয়ে খারাপ অবস্থায় (হাশ কলিশনের বা hash collisions কারণে) O(n) বা O(n) worst case টাইম নিতে পারে। তবে ম্যাপ (map) সব সময়ই O(log n) টাইম নিয়ে থাকে। আপনার যখন কোনো প্রকার অর্ডারিং বা সাজানোর প্রয়োজন পড়বে না, তখন আনঅর্ডারড বা unordered_ ভার্সনগুলো ব্যবহার করাই শ্রেয় — কারণ এগুলো গড়ে তুলনামূলক অনেকটাই দ্রুত (faster) কাজ করে। কিন্তু যদি আপনার সর্টেড বা সাজানো ইটারেশনের (sorted iteration) প্রয়োজন পড়ে এবং এর যেকোনো অবস্থাতেই একটি গ্যারান্টিযুক্ত পারফরম্যান্স (guaranteed worst-case performance) বজায় রাখার প্রয়োজন পড়ে, তবে অবশ্যই map/set-এর সঙ্গেই লেগে থাকুন।
চ্যালেঞ্জ

ছোট কুইজ

std::unordered_map-এর ক্ষেত্রে লুকআপের (lookup) গড় টাইম কমপ্লেক্সিটি বা average time complexity কত?
Smart PointersIterators & Algorithms