ভেক্টর (STL - Vector)

Jan 30, 2014

সম্ভবত C++ Standard Template Library (STL) এর সবথেকে বেশি ব্যবহৃত কনটেইনারটি হল ভেক্টর। খুবই কাজের একটা জিনিস। খুব সাধারনভাবে বলতে গেলে, এটা অনেকটা অ্যারের মত। অ্যারেতে যেমন একটার পর একটা ভেরিয়েবল থাকে, তেমনি ভেক্টরেও তাই। একটার পর একটা সাজানো থাকে। আবার অ্যারেতে যেমন ইন্ডেক্স থাকে যেটা ব্যবহার করে সরাসরি ঐ ইন্ডেক্সের ভেরিয়েবল পাওয়া যায়, তেমনি ভেক্টরেও ইন্ডেক্স আছে এবং তা ঠিক একইভাবে অ্যারের মত করেই কাজ করে।

তাহলে স্বভাবতই প্রশ্ন আসে, ভেক্টর আর অ্যারের মধ্যে পার্থক্য কি? পার্থক্য হচ্ছে, অ্যারের সাইজ নির্দিষ্ট কিন্তু ভেক্টর resizable। আমরা যখন একটা অ্যারে ডিক্লেয়ার করি, তখন সেটার সাইজ বলে দিতে হয়। তাহলে সেই সাইজের একটা অ্যারে তৈরি হয়ে যায়। কিন্তু পরবর্তিতে সেটার সাইজ বড় বা ছোট করা যায় না। সেখানে যদি আমরা ভেক্টর ব্যবহার করি, তাহলে সহজেই সেটার সাইজ বড় ছোট করা যায়।

এখন প্রশ্ন হল, সাইজ ছোট বড় করে আমার লাভ কি? লাভটা বিভিন্ন প্রবলেম সলভ করতে গেলে বোঝা যায়। বেশির ভাগ ক্ষেত্রেই আমরা প্রয়োজনের চেয়ে বেশি সাইজের অ্যারে ডিক্লেয়ার করে রাখি। এতে অনেক মেমরি অপচয় হয়। এখন মনে হতে পারে, সলিউশন accepted হলেই হল, মেমরি অপচয় দিয়ে আমার কি? এটার উত্তর হল, অনেক প্রবলেমে 2D/3D অ্যারে লাগতে পারে। প্রবলেমটা এরকম হতে পারে যে দুইটা ডাইমেনশনে সর্বোচ্চ 20,000 টা করে এলিমেন্ট থাকবে। কিন্তু একই সাথে কখনও সব মিলিয়ে 30,000 এর বেশি এলিমেন্ট থাকবে না। তাহলে আমরা যদি [20000][20000] একটা অ্যারে ডিক্লেয়ার করতে যাই, সেটা কখনই সম্ভব না! তাই আমরা যদি 2D ভেক্টর ব্যবহার করি, তাহলে যেই ডাইমেনশনে যতগুলো এলিমেন্ট দরকার, সেটা তত বড় হবে। ফলে মোট 30000 টা এলিমেন্ট সহজেই রাখা সম্ভব! এভাবে ব্যাপারটা বোঝাতে আমার নিজেরই আসলে কষ্ট হচ্ছে। এধরনের প্রবলেম সামনে না এসে থাকলে আসলে এটা বোঝাটা

অনেক ক্ষেত্রেই ভেক্টর দিয়ে সহজেই ডাটা ম্যানেজ করা যায়, যেখানে অ্যারে ব্যবহার করলে তুলনামূলক ঝামেলা বেশি। দেখা যায় আমরা অনেকগুলো এলিমেন্টের অ্যারে ডিক্লেয়ার করে রেখেছি। এখন সেখানে কত পর্যন্ত আমাদের ডাটা রাখা আছে তার জন্য আরেকটা ভেরিয়েবল রাখতে হয়। আবার যখন নতুন কোন ডাটা সেখানে রাখতে যাই তখন মোট ডাটার সংখ্যা, অর্থাৎ কত ইন্ডেক্সে ডাটা রাখা আছে সেটাকে 1 বাড়িয়ে দিতে হয়। আপাতদৃষ্টিতে এটা তেমন কিছু মনে না হলেও, অনেক বড় ডাটাসেট নিয়ে কাজ করতে গেলে এটা বেশ ঝামেলাদায়ক হয়ে যায়। সেখানে যদি পুরো ব্যাপারটা আপনা থেকেই ম্যানেজ হত তাহলে অনেক সহজ হয়ে যেত। আর আপনা থেকে ম্যানেজ করার এই কাজটা করে নিতে পারে ভেক্টর! এছাড়া STL এর অন্যান্য কন্টেইনার এবং এলগোরিদমিক ফাংশনের সাথে একে সহজেই ব্যবহার করা যায়।

অনেক হল ভেক্টরের গুণগান! নিজে থেকে কয়েকবার ব্যবহার করলেই আসলে বোঝা যাবে যে এটা ব্যবহার করে প্রবলেম সলভ করলে কত সুবিধা। এখন এটা কিভাবে ব্যবহার করতে হয় তা দেখা যাক।

ভেক্টর ব্যবহার করার জন্য প্রথমেই যেটা করতে হবে তা হল, হেডার ফাইল ইনক্লুড করে নিতে হবে। এরপর অবশ্যই using namespace std; লিখতে হবে। ফাইলটা যেন .cpp extension এর হয় সেটাও খেয়াল রাখতে হবে। এখন ভেক্টর কিভাবে ডিক্লেয়ার করা হবে দেখা যাক।

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector <int> myVector;
    return 0;
}

ভেক্টর চাইলে globally-ও ডিক্লেয়ার করা যাবে, সিন্ট্যাক্স একই থাকবে। এখানে  ৮ নাম্বার লাইনে ভেক্টরটি ডিক্লেয়ার করা হয়েছে। প্রথমে vector লিখতে হবে। এরপর <> এর মধ্যে ডাটা টাইপ লিখতে হবে। int, double, float, char, unsigned ইত্যাদি যেকোনকিছুই সেটি হতে পারে। শুধু তাই নয়, নিজের ডিক্লেয়ার করা যেকোন struct বা class এরও ভেক্টর হতে পারে। যেমন vector myVector; এটাও হতে পারে! ডাটা টাইপের পর ভেক্টরটির নাম দিতে হয়, যেমনটি আমরা ভেরিয়েবল ডিক্লেয়ার করার সময় ভেরিয়েবলের একটা নাম দেই, তেমনি।

এখন ভেক্টর ক্লাসের অন্তর্গত প্রায়ই ব্যবহৃত হয় এমন কিছু ফাংশন দেখা যাক।

push_back( DATA_TYPE value )

এই ফাংশনটি ভেক্টরের মধ্যে value পুশ করবে। এটি ব্যবহার করে আমরা ডাটা insert করব।

size()

ফাংশনটি কল করার মুহূর্ত পর্যন্ত ভেক্টরে কতগুলো এলিমেন্ট আছে তা রিটার্ন করে। কিছু না থাকলে 0 রিটার্ন করবে।

empty()

ভেক্টরটি যদি empty হয়, অর্থাৎ কোন এলিমেন্ট নেই, সাইজ 0, তাহলে এটি true রিটার্ন করবে, অন্যথায় false রিটার্ন করবে।

begin(), end()

begin() ফাংশনটি ভেক্টরের শুরুর iterator রিটার্ন করে, আর end() তার উলটা অর্থাৎ শেষের iterator রিটার্ন করে। iterator অনেকটা পয়েন্টারের মত।

নিচে এই ফাংশনগুলোর ব্যবহার দেখানো হলঃ

int value, inputSize;
vector <int> myVector;
cin >> inputSize;
if(myVector.empty()) cout << "Vector is empty." << endl;
cout << "Size before insertion: " << myVector.size() << endl;
for(int i = 0; i < inputSize; i++) {
    cin >> value;
    myVector.push_back(value);  // Inserts the value
}
cout << "Insertion complete." << endl;
if(!myVector.empty()) cout << "Vector is not empty." << endl;
cout << "Size after insertion: " << myVector.size() << endl;
for(int i = 0; i < myVector.size(); i++) {
    cout << myVector[i] << ' ';
}
cout << endl;
sort(myVector.begin(), myVector.end());
for(int i = 0; i < myVector.size(); i++) {
    cout << myVector[i] << ' ';
}
cout << endl;
Input:
10
8 9 4 1 3 5 0 2 6 7

Output:
Vector is empty.
Size before insertion: 0
Insertion complete.
Vector is not empty.
Size after insertion: 10
8 9 4 1 3 5 0 2 6 7
0 1 2 3 4 5 6 7 8 9

মূলত এই কয়টি ফাংশনই অনেক বেশি ব্যবহৃত হয়। এছাড়া আরো অনেক ফাংশন আছে, constructor আছে, এগুলো অনেক বিস্তারিতভাবে এই লিংক থেকে জানা যাবে।

আরেকটি বিষয় বেশ কাজের, সেটি হল bool টাইপের ভেক্টর। true/false রাখার জন্য আমরা bool অ্যারে ডিক্লেয়ার করে থাকি। সে জায়গায় যদি আমরা ভেক্টর ডিক্লেয়ার করি তাহলে অনেক মেমরি বেঁচে যায়। কারণ যদিও bool ডাটা টাইপটা 8 bit সাইজের, bool ভেক্টর প্রতি এলিমেন্টের জন্য 1 bit জায়গা ব্যবহার করে। অর্থাৎ মেমরি ব্যবহার ৮ ভাগের ১ ভাগ হয়ে যাচ্ছে।

যদি 2D অ্যারের বিকল্প হিসেবে আমরা ভেক্টর ব্যবহার করতে চাই, তখন সিন্ট্যাক্সটা একটু অন্যরকম হবে।

vector <vector<int> > myVector;

অর্থাৎ int টাইপের ভেক্টরের ভেক্টর! myVector একটা ভেক্টর, যার প্রতিটি এলিমেন্ট হল vector , আরেকটি ভেক্টর, যার প্রতিটি এলিমেন্ট হল int! এখন myVector এ push_back করতে হলে একটা ভেক্টরকে আর্গুমেন্ট হিসেবে পাঠাতে হবে। ঝামেলা মনে হচ্ছে? যদি ঝামেলা মনে হয় তাহলে এটার বিকল্প হিসেবে যেটা করা যায় তা হল ভেক্টরের অ্যারে ডিক্লেয়ার করা যায়। vector myVector[10]; এভাবে। এখন myVector[0], myVector[1], … এগুলো প্রত্যেকে একেকটা ভেক্টর।

এই হল ভেক্টরের প্রাথমিক খুটিনাটি। আরো বিস্তারিত পাওয়া যাবে cplusplus.com এ!