Lesson ১৪পড়তে ৯ মিনিট লাগবে

ইটারেটর এবং অ্যালগরিদম (Iterators & Algorithms)

যেকোনো কন্টেইনারের (container) জন্য একটি সর্বজনীন রিমোট (universal remote), যেখানে শর্টিং (sorting), সার্চিং (searching) এবং ট্রান্সফর্মিংয়ের (transforming) জন্য আগে থেকেই বিভিন্ন প্রোগ্রাম (pre-built programs) তৈরি করা থাকে

এক সর্বজনীন বা ইউনিভার্সাল রিমোট (The Universal Remote)

ধরুন আপনার কাছে একটি টিভি (TV), একটি ব্লু-রে প্লেয়ার (Blu-ray player) এবং একটি সাউন্ড সিস্টেম (sound system) রয়েছে — এদের প্রত্যেকটিই আবার ভিন্ন ভিন্ন ব্র্যান্ডের (different brand)। এখন এই তিনটি আলাদা আলাদা রিমোটের ব্যবহার শেখার বদলে, আপনি চাইলে এমন একটি ইউনিভার্সাল বা সর্বজনীন রিমোট (universal remote) নিতে পারেন, যা এই তিনটির সাথেই সমানভাবে কাজ করবে।

আর সি++ (C++)-এ এই ইটারেটরগুলোও (iterators) ঠিক একই কাজ করে থাকে। আপনার ডেটাগুলো একটি vector, একটি map, একটি set, কিংবা একটি list-এর যেখানেই থাকুক না কেন, এই ইটারেটরগুলো মূলত আপনাকে এখানকার যেকোনো উপাদান বা এলিমেন্টগুলোতে (elements) যাওয়ার জন্য শুধুমাত্র একটি নির্দিষ্ট ইন্টারফেস (one consistent interface) প্রদান করে। আর এখানকার অ্যালগরিদমগুলো (algorithms) হলো ওই রিমোটে প্রি-প্রোগ্রাম (pre-programmed) করা বিভিন্ন বোতামের (buttons) মতো — যেমন শর্ট (sort), সার্চ (search), ট্রান্সফর্ম (transform), কাউন্ট (count) ইত্যাদি — যা সব সময়ই কাজ করার জন্য প্রস্তুত (ready to go) থাকে।

begin() এবং end()

প্রতিটি এসটিএল কন্টেইনারই (STL container) মূলত দুটি প্রধান ইটারেটর (iterators) প্রদান করে থাকে:

  • begin() — এটি মূলত এর প্রথম (first) এলিমেন্টটির দিকে পয়েন্ট (points) করে থাকে
  • end() — এটি মূলত এর ওই সর্বশেষ এলিমেন্টের ঠিক পরের (one past the last) ঘরটিকে পয়েন্ট (points) করে (এটি মূলত একটি সেন্টিনেল বা sentinel, এটি কোনো আসল এলিমেন্ট বা real element নয়)

এখানকার এই হাফ-ওপেন রেঞ্জটিই বা [begin, end) রেঞ্জটিই হলো এখানকার যেকোনো অ্যালগরিদমের মূল ভিত্তি (foundation)। এর মানে হলো: "এখান থেকে শুরু করো, এবং ঠিক এখানকার আগেই (before) থামো (stop)।"

ইটারেটর ক্যাটাগরিগুলো (Iterator Categories)

সব ইটারেটর এক রকম হয় না (Not all iterators are equal)। এগুলো মূলত ভিন্ন ভিন্ন হায়ারার্কি (hierarchy) বা পর্যায়ক্রম তৈরি করে থাকে:

  • ইনপুট (Input) — এগুলোকে শুধু একবার সামনের দিকে (forward) পড়া (Read) যায় (যেমন কোনো স্ট্রিম বা stream থেকে কিছু পড়া)
  • আউটপুট (Output) — এগুলোকে শুধু একবার সামনের দিকে (forward) লেখা (Write) যায়
  • ফরওয়ার্ড (Forward) — এগুলোতে একাধিকবার পড়া/লেখা (Read/write) যায় (যেমন, forward_list)
  • বাই-ডিরেকশনাল (Bidirectional) — এগুলো দিয়ে সামনে + পেছনে (Forward + backward) দিকে কাজ করা যায় (যেমন, list, map, set)
  • র‍্যান্ডম অ্যাক্সেস (Random Access) — এগুলো দিয়ে O(1) সময়ের মধ্যে যেকোনো জায়গায় জাম্প (Jump anywhere) করা যায় (যেমন, vector, deque)

যেকোনো অ্যালগরিদমের (Algorithms) কাজ করার জন্য মূলত একটি সর্বনিম্ন বা মিনিমাম (minimum) ইটারেটর ক্যাটাগরির (iterator category) প্রয়োজন পড়ে। যেমন std::sort-এর জন্য র‍্যান্ডম অ্যাক্সেস (random access) প্রয়োজন, তাই এটি ভেক্টরগুলোতে (vectors) কাজ করলেও সাধারণ লিস্টগুলোতে (lists) কাজ করে না।

একটি ভেক্টর শর্ট (Sorting) করা

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {5, 2, 8, 1, 9, 3};
// ডিফল্ট শর্ট (Default sort): ছোট থেকে বড় (ascending)
sort(v.begin(), v.end());
cout << "Ascending: ";
for (int x : v) cout << x << " ";
cout << endl;
// স্ট্রিংগুলোর জন্য ল্যাম্বডা (lambda) ফাংশনের সাহায্যে কাস্টম শর্ট (Custom sort): বড় থেকে ছোট (descending)
sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
cout << "Descending: ";
for (int x : v) cout << x << " ";
cout << endl;
return 0;
}
Output
Ascending:  1 2 3 5 8 9
Descending: 9 8 5 3 2 1

ফাইন্ড (find) এবং কাউন্ট (count)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
// 9 এর প্রথম অবস্থানটি বা occurrence-টি খুঁজে বের করা বা Find
auto it = find(v.begin(), v.end(), 9);
if (it != v.end()) {
cout << "Found 9 at index " << distance(v.begin(), it) << endl;
}
// 5 কতবার এসেছে তা কাউন্ট (Count) করা
int c = count(v.begin(), v.end(), 5);
cout << "5 appears " << c << " times" << endl;
// count_if-এর সাহায্যে বিভিন্ন জোড় সংখ্যাগুলো বা even numbers কাউন্ট (Count) করা
int evens = count_if(v.begin(), v.end(), [](int x) {
return x % 2 == 0;
});
cout << "Even numbers: " << evens << endl;
return 0;
}
Output
Found 9 at index 5
5 appears 3 times
Even numbers: 3

ট্রান্সফর্ম (transform) — আপারকেসে রূপান্তর বা Convert to Uppercase

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string msg = "hello, algorithms!";
// প্রতিটি ক্যারেক্টারকে (character) আপারকেসে (uppercase) ট্রান্সফর্ম (Transform) করুন
transform(msg.begin(), msg.end(), msg.begin(),
[](char c) { return toupper(c); });
cout << msg << endl;
// একটি নতুন স্ট্রিংয়ে (new string) ট্রান্সফর্ম করুন (প্রতিটি ক্যারেক্টারের ASCII মানকে বা char's ASCII দ্বিগুণ বা doubling করে)
string doubled(msg.size(), ' ');
transform(msg.begin(), msg.end(), doubled.begin(),
[](char c) { return c + 1; });
cout << doubled << endl;
return 0;
}
Output
HELLO, ALGORITHMS!
IFMMP-!BMHPSJUINT"

অ্যাকুমুলেট (accumulate) — এর যোগফল (Sum) এবং বিভিন্ন কাস্টম ফোল্ড (Custom Fold)

#include <iostream>
#include <vector>
#include <numeric> // accumulate-টি মূলত এখানেই (here) থাকে!
#include <string>
using namespace std;
int main() {
vector<int> nums = {1, 2, 3, 4, 5};
// যোগফল বা Sum
int sum = accumulate(nums.begin(), nums.end(), 0);
cout << "Sum: " << sum << endl;
// গুণফল বা Product (কাস্টম বাইনারি অপারেশন)
int product = accumulate(nums.begin(), nums.end(), 1,
[](int a, int b) { return a * b; });
cout << "Product: " << product << endl;
// স্ট্রিংগুলোকে যুক্ত (Concatenate) করা
vector<string> words = {"C++", " is", " powerful"};
string sentence = accumulate(words.begin(), words.end(), string(""));
cout << sentence << endl;
return 0;
}
Output
Sum: 15
Product: 120
C++ is powerful

রিমুভ-ইরেজ ইডিওম (The Remove-Erase Idiom)

এটি সি++ (C++)-এর সবচেয়ে গুরুত্বপূর্ণ প্যাটার্নগুলোর (patterns) মধ্যে একটি। std::remove মূলত কোনো কন্টেইনার (container) থেকে সরাসরি কোনো এলিমেন্টকে রিমুভ (remove) বা অপসারণ করে না। এটি মূলত আপনার প্রয়োজনীয় এলিমেন্টগুলোকে বা আপনি যেগুলোকে রাখতে চান (keep) সেগুলোকে শুধু সামনের দিকে সরিয়ে (moves) দেয়, এবং এর নতুন একটি "লজিক্যাল এন্ড (logical end)"-কে বোঝানোর জন্য একটি নতুন ইটারেটর রিটার্ন (returns) করে। এতে এখানকার ওই কন্টেইনারটির (container) সাইজ বা আকার (size) কোনোভাবেই পরিবর্তিত (change) হয় না! তাই একে সত্যিই ছোট (shrink) করার জন্য আপনাকে অবশ্যই erase() ব্যবহার করে কল বা call করতে হবে।

রিমুভ-ইরেজ ইডিওম (Remove-Erase Idiom)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 2, 4, 2, 5};
// ভুল (WRONG): একা remove কখনোই ভেক্টরটিকে ছোট (shrink) করতে পারে না
auto newEnd = remove(v.begin(), v.end(), 2);
cout << "After remove (size still " << v.size() << "): ";
for (int x : v) cout << x << " ";
cout << endl;
// সঠিক (RIGHT): এর newEnd থেকে actual end বা আসল শেষ পর্যন্ত সব ইরেজ বা erase করা হলো
v.erase(newEnd, v.end());
cout << "After erase (size now " << v.size() << "): ";
for (int x : v) cout << x << " ";
cout << endl;
// এক লাইনে (In one line) (ক্লাসিক ইডিওম বা classic idiom):
vector<int> v2 = {10, 20, 30, 20, 40};
v2.erase(remove(v2.begin(), v2.end(), 20), v2.end());
cout << "One-liner: ";
for (int x : v2) cout << x << " ";
cout << endl;
// সি++২০ (C++20): আরও অনেক বেশি সহজ (simpler)!
// std::erase(v2, 30); // এটি সকল 30-কে রিমুভ (removes) করে দেয় এবং ভেক্টরটিকে ছোট (shrinks) করে
return 0;
}
Output
After remove (size still 7): 1 3 4 5 4 2 5
After erase  (size now 4): 1 3 4 5
One-liner: 10 30 40

সি++২০ রেঞ্জ প্রিভিউ (C++20 Ranges Preview)

সি++২০ (C++20) মূলত বিভিন্ন অ্যালগরিদমগুলোকে (algorithms) আরও সহজে বা ক্লিনার (cleaner) উপায় ব্যবহার (use) করার জন্য রেঞ্জ (ranges)-এর ধারণাটি নিয়ে এসেছে। এখানে আলাদাভাবে begin() এবং end()-কে পাস (passing) করার বদলে, আপনি চাইলে সরাসরি সম্পূর্ণ কন্টেইনারটিকেই (container) পাস করতে পারবেন:

// পুরোনো নিয়মে (Old way)
std::sort(v.begin(), v.end());

// সি++২০ রেঞ্জ (C++20 ranges)
std::ranges::sort(v);

এছাড়াও এই রেঞ্জগুলো মূলত ভিউগুলোকেও (views) সাপোর্ট (support) করে থাকে — এগুলো মূলত অত্যন্ত লেজি বা ধীর (lazy), কম্পোজেবল বা জোড়া লাগানো যায় এমন ট্রান্সফরমেশন (composable transformations), যা ডেটাকে কখনও কপি (copy) করে না। এটিই হলো সি++ (C++) অ্যালগরিদমগুলোর ভবিষ্যৎ (future)।

Note: এই রিমুভ-ইরেজ ইডিওমটি (The remove-erase idiom): std::remove আসলে সরাসরি কোনো এলিমেন্টগুলোকে সরিয়ে (remove) ফেলে না — এটি শুধুমাত্র এর সমস্ত অবাঞ্ছিত এলিমেন্টগুলোকে (unwanted elements) একেবারে শেষের দিকে (end) সরিয়ে দেয় এবং এর নতুন একটি লজিক্যাল এন্ড (logical end) বা শেষ নির্দেশ করার জন্য একটি ইটারেটর (iterator) ফেরৎ পাঠায়। এখানকার ওই কন্টেইনারটিকে (container) সত্যিকার অর্থে ছোট করার (shrink) জন্য আপনাকে অবশ্যই erase() ব্যবহার করতে হবে। তবে এর এক-ধাপ সমাধানের (one-step solution) জন্য সি++২০ (C++20)-এ আপনি চাইলে এর পরিবর্তে std::erase_if ব্যবহার করতে পারেন।
চ্যালেঞ্জ

ছোট কুইজ

কেন std::sort মূলত vector-এর ক্ষেত্রে কাজ করলেও list-এ কাজ করে না?
STL ContainersTemplates