Learn C++ STL

lesson ২ / ৯ · string: যে text নিজের length নিজেই জানে

Module ৩ · string: যে text নিজের length নিজেই জানে

string-এর প্রতিটা operation, একটা একটা করে, খরচসহ

Freeপড়া

এই lesson-এ যা শিখবে

  • একজন পেশাদার programmer string-এর উপর যত operation call করে, size থেকে ostringstream পর্যন্ত, সবগুলো ঠিক syntax-এ লিখতে পারবে।
  • প্রতিটার খরচ বলতে পারবে, আর বুঝিয়ে বলতে পারবে কেন s += c সস্তা, কিন্তু loop-এ s = s + c ধীর।
  • text যে তিনভাবে ভুল হয়, সেগুলো সামলাতে পারবে: শেষের পরের position, কিছু না পাওয়া search, আর এমন সংখ্যা যেটা আসলে সংখ্যাই না।

একটা test file-এর জন্য Bob-এর লাগবে 100,000টা অক্ষরের একটা লাইন। ও loop-এ লেখে s = s + c;, আর program প্রায় আধা সেকেন্ড বসে থাকে, কখনো এক সেকেন্ডেরও বেশি। Amara লেখে s += c;, আর ওরটা শেষ হয় এক millisecond-এর কমে। দুটো লাইন দেখতে প্রায় একই, print-ও করে একই string। তফাতটা এই lesson-এর একটা খরচের লাইনে, আর শেষের chart-টা সেটা মেপে দেখায়।

খরচের লাইন কীভাবে পড়বে

String জিনিসটা কী, lesson 01 দেখিয়েছে: অক্ষরের একটা সারি, যে নিজের length নিজেই জানে। এখানে প্রতিটা operation শেষ হয় এক সারির একটা table দিয়ে: call, তার খরচ, আর কেন। খরচ লেখা হয় big-O notation-এ, Module 2-এর মতোই। এটা বলে text বড় হলে কাজ কীভাবে বাড়ে, কত nanosecond লাগে সেটা না।

খরচমানেউদাহরণ
O(1)length যা-ই হোক, একই অল্প কাজs.size(), অক্ষর 5টা হোক বা 50 লাখ
amortised O(1)অনেকগুলো call মিলিয়ে গড়ে constant; মাঝে মাঝে একটা call সবকিছু copy করেs += c, যেটা মাঝে মাঝে block বড় করে
O(n)যে n-টা অক্ষর ছোঁয়, কাজ ততই বাড়েs.insert(0, "x") প্রতিটা অক্ষর সরায়
O(n x m)n-টা জায়গার প্রতিটায় বড়জোর m-টা তুলনাn-টা অক্ষরের মধ্যে m অক্ষরের একটা শব্দ খোঁজা, সবচেয়ে খারাপ ক্ষেত্রে

এই lesson-এ n হলো যে string-এর উপর operation call করছ তার length, আর m হলো অন্য টুকরাটার length। তাই খরচের লাইন বলে দেয়, text যখন একটা নাম না হয়ে পুরো একটা বই, তখন কী হবে।

size, length আর empty

কত লম্বা?

s.size()      the number of characters, as a size_t
s.length()    exactly the same number as size()
s.empty()     true when size() is 0

size_t হলো unsigned পূর্ণসংখ্যার সেই type, যেটা library size আর position-এর জন্য ব্যবহার করে। এতে কোনো negative সংখ্যা রাখা যায় না।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string city = "Chattogram";
    string none;
    cout << city.size() << ' ' << city.length() << ' ' << city.empty() << '\n';
    cout << none.size() << ' ' << none.empty() << '\n';
    cout << none.size() - 1 << '\n';
    return 0;
}
10 10 0
0 1
18446744073709551615
Callখরচকারণ
s.size(), s.length(), s.empty()O(1)string নিজের length লিখে রাখে; C-এর strlen-এর মতো কিছু গুনতে হয় না

সাবধান: শেষ লাইনটা দেখো। খালি string-এর size() - 1 কিন্তু -1 না; unsigned সংখ্যা ঘুরে গিয়ে হয়ে যায় সবচেয়ে বড় size_t। তাই size() থেকে কিছু বিয়োগ করার আগে !s.empty() check করো।

operator[] আর at

Index দিয়ে একটা অক্ষর

s[i]       the character at index i, no check
s.at(i)    the same character, checked: throws std::out_of_range if i >= s.size()

দুটোই একটা char দেয়, যেটা পড়তেও পারো, বদলাতেও পারো। শেষের পরে s[i] কী করে, lesson 01 দেখিয়েছে। at আগে index-টা check করে। Index বেশি বড় হলে ও একটা exception throw করে: স্বাভাবিক চলা থামিয়ে দেয়, আর একটা error object দিয়ে খবরটা জানায়।

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

int main() {
    string name = "Maria";
    name[0] = 'm';
    name.at(4) = 'A';
    cout << name << ' ' << name[1] << ' ' << name.at(2) << '\n';
    try {
        cout << name.at(5) << '\n';
    } catch (const out_of_range& e) {
        cout << "caught: " << e.what() << '\n';
    }
    return 0;
}
mariA a r
caught: basic_string::at: __n (which is 5) >= this->size() (which is 5)

try { ... } catch (...) { ... } প্রথম block-টা চালায়। ভেতরের কিছু যদি out_of_range throw করে, program না থেমে লাফ দিয়ে চলে যায় catch block-এ। e.what() হলো library-র লেখা বার্তাটা। try না থাকলে একই call program-টাই শেষ করে দেয়। Compiler Explorer-এ run থেমেছে exit code 134 নিয়ে, আর error stream-এ এসেছে এটা:

cout << name.at(5) << '\n';
terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string::at: __n (which is 5) >= this->size() (which is 5)
Callখরচকারণ
s[i], s.at(i)O(1)অক্ষরের address হলো শুরু থেকে i byte পরে; at শুধু একটা তুলনা যোগ করে

সাবধান: name[5] পড়ে সেই লুকানো '\0', যেটা lesson 01 এঁকে দেখিয়েছে, অথচ name.at(5) throw করে। তাই at শুধু 0 থেকে size() - 1 পর্যন্ত index মানে, আর না মানলে জোরে জানিয়ে দেয়।

front আর back

দুই মাথা

s.front()    the first character, the same as s[0]
s.back()     the last character, the same as s[s.size() - 1]
#include <iostream>
#include <string>
using namespace std;

int main() {
    string word = "radar";
    cout << word.front() << word.back() << '\n';
    word.front() = 'R';
    cout << word << '\n';
    return 0;
}
rr
Radar
Callখরচকারণ
s.front(), s.back()O(1)জানা একটা address-এ একটা read

সাবধান: Zara-র প্রথম test সবসময় খালি string। সেখানে দুটো call-ই undefined behaviour। Compiler Explorer-এর একটা run-এ দুটোই চুপচাপ 0 দিয়েছে, আর কেউ program থামায়নি। তাই আগে !s.empty() check করো, কারণ কোনো error তোমাকে কিছু বলবে না।

+=, append, push_back আর pop_back

শেষ মাথা বদলানো

s += t;             add a string, a literal or one char at the end
s.append(t);        the same as s += t
s.append(n, c);     add n copies of the char c
s.push_back(c);     add one char at the end
s.pop_back();       remove the last char, return nothing
#include <iostream>
#include <string>
using namespace std;

int main() {
    string line = "Hello";
    line += ',';
    line += " Zara";
    line.append(3, '!');
    line.push_back('?');
    cout << line << '\n';
    line.pop_back();
    cout << line << " (" << line.size() << ")\n";
    return 0;
}
Hello, Zara!!!?
Hello, Zara!!! (14)
Callখরচকারণ
s += c, s.push_back(c)amortised O(1)string শেষে কিছু ফাঁকা জায়গা রেখে দেয়; জায়গা ফুরালে block দ্বিগুণ করে একবার copy করে
s += t, s.append(t)amortised O(m)নতুন m-টা অক্ষর copy হয়ে ঢোকে; পুরোনোগুলো যেখানে ছিল সেখানেই থাকে
s.pop_back()O(1)length এক কমে

সাবধান: খালি string-এ pop_back() undefined। Compiler Explorer-এর একটা run-এ পরে size() print করেছে 72057594037927935, আর run স্বাভাবিকভাবেই শেষ হয়েছে। তাই pop_back-এর আগেও back-এর মতো একই empty() check লাগে।

+, যেটা নতুন একটা string বানায়

জুড়ে নতুন string

a + b        a new string: the characters of a, then those of b

Lesson 01-এ দুইটা string জোড়া হয়েছিল + দিয়ে। একপাশে একটা string থাকতেই হবে; অন্য পাশে string, literal বা একটা char, যেকোনোটা চলে। কোনো পাশই বদলায় না।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string first = "Amara";
    string last = "Okafor";
    string full = first + ' ' + last;
    cout << full << " (" << full.size() << ")\n";
    cout << first << '\n';
    return 0;
}
Amara Okafor (12)
Amara
Callখরচকারণ
a + bO(n + m)নতুন একটা block বানিয়ে দুই পাশই সেখানে copy হয়

সাবধান: Bob-এর ধীর লাইনটা এটাই। শুধু একটা অক্ষর যোগ করতে s = s + c পুরো s-কে একটা নতুন string-এ copy করে। n ধাপের loop-এ মোট copy হয় প্রায় n2 / 2টা অক্ষর। s += c অক্ষরটা জায়গাতেই যোগ করে। তাই একবার একটা মান বানাতে +, আর একটা string বাড়াতে +=।

==, !=, < আর compare

দুইটা string-এর তুলনা

a == b, a != b              same characters in the same order?
a < b, a <= b, a > b, a >= b  dictionary order, byte by byte
a.compare(b)                negative, 0 or positive: a before, equal to, or after b

অভিধানের ক্রমে আগে প্রথম অক্ষর দুটো মেলানো হয়, তারপর দ্বিতীয়, এভাবে যতক্ষণ না দুটো আলাদা হয়। যার byte-এর মান ছোট, সে আগে। একটা string আগেই ফুরিয়ে গেলে ছোটটা আগে আসে।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string a = "Zara";
    string b = "apple";
    cout << (a == "Zara") << ' ' << (a != b) << ' ' << (a < b) << '\n';
    cout << (string("app") < "apple") << '\n';
    int r = a.compare(b);
    if (r < 0) {
        cout << a << " comes before " << b << '\n';
    } else if (r > 0) {
        cout << a << " comes after " << b << '\n';
    } else {
        cout << "equal\n";
    }
    return 0;
}
1 1 1
1
Zara comes before apple

Maria জানতে চায়, "Zara" কেন "apple"-এর আগে আসে। উত্তরটা ASCII table-এ: 'Z' হলো 90, আর 'a' হলো 97। প্রতিটা বড় হাতের অক্ষর প্রতিটা ছোট হাতের অক্ষরের চেয়ে ছোট।

Callখরচকারণ
==, <, compareবড়জোর O(n)শুরু থেকে byte মেলানো হয়, আর প্রথম অমিলেই কাজ থামে

সাবধান: compare শুধু উত্তরের চিহ্নটার কথা দেয়। Test করো r < 0, কখনো r == -1 না। তাই compare-এর উত্তর পড়ো ওর চিহ্ন দেখে, আর মনে রাখো string-এর ক্রম মানে byte-এর ক্রম, অভিধানে ছাপা ক্রম না।

substr

String-এর একটা টুকরা, নতুন string হিসেবে

s.substr(pos, len)   len characters starting at index pos
s.substr(pos)        from pos to the end
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

int main() {
    string date = "2026-10-07";
    cout << date.substr(0, 4) << '|' << date.substr(5, 2) << '|' << date.substr(8) << '\n';
    cout << date.substr(8, 100) << '\n';
    cout << '[' << date.substr(10) << "]\n";
    try {
        cout << date.substr(11) << '\n';
    } catch (const out_of_range& e) {
        cout << "caught: " << e.what() << '\n';
    }
    return 0;
}
2026|10|07
07
[]
caught: basic_string::substr: __pos (which is 11) > this->size() (which is 10)

দ্বিতীয় সংখ্যাটা একটা length, শেষের index না। Length শেষ পার হয়ে গেলে সেটা ছেঁটে দেওয়া হয়, যেমন substr(8, 100) দেখাচ্ছে। শুরুটা ঠিক size() হলে পাওয়া যায় খালি string। শুরুটা size() পার হলে throw হয় out_of_range।

Callখরচকারণ
s.substr(pos, len)O(len)টুকরাটা copy হয়ে নতুন একটা string-এ যায়

সাবধান: substr একটা copy বানায়। টুকরাটা বদলালে s কখনো বদলায় না। তাই লম্বা text-এর উপর loop-এর ভেতরে substr মানে প্রতিবার একটা copy-র খরচ।

In C++20

starts_with আর ends_with এক call-এই একটা prefix (শুরুর অংশ) বা suffix (শেষের অংশ) test করে, s.substr(0, 8) == "https://"-এর মতো কোনো copy না বানিয়ে। দুটোই আছে string-এ, আর C++17-এর string_view-এও, যে type অক্ষরগুলো নিজের না করে শুধু দেখে (lesson 04 এটা শেখাবে)। Playground-এর C++17-এ একই লাইন থেমে যায় error: 'std::string' {aka 'class std::__cxx11::basic_string<char>'} has no member named 'starts_with' দিয়ে। নিচের Run button Playground খোলে C++20-এ।

#include <iostream>
#include <string>
#include <string_view>
using namespace std;

int main() {
    string url = "https://progsity.io/learn";
    cout << url.starts_with("https://") << ' ' << url.ends_with(".pdf") << '\n';
    string_view file = "report.pdf";
    cout << file.starts_with('r') << ' ' << file.ends_with(".pdf") << '\n';
    return 0;
}
1 0
1 1

প্রতিটা call শুধু prefix বা suffix-এ যতগুলো অক্ষর আছে, ততগুলোই মেলায়, তাই খরচ O(m)।

Run in Compiler

find, rfind আর string::npos

খোঁজা

s.find(x)         index of the first x (a char or a string), or string::npos
s.find(x, pos)    the same, starting the search at index pos
s.rfind(x)        index of the last x, or string::npos
string::npos      "no position": the largest value a size_t can hold
#include <iostream>
#include <string>
using namespace std;

int main() {
    string path = "docs/stl/string.html";
    cout << path.find('/') << ' ' << path.rfind('/') << '\n';
    cout << path.find("stl") << ' ' << path.find('/', 5) << '\n';
    size_t hash = path.find('#');
    if (hash == string::npos) {
        cout << "no #\n";
    }
    cout << string::npos << '\n';
    return 0;
}
4 8
5 8
no #
18446744073709551615
Callখরচকারণ
s.find(c), s.rfind(c)O(n)এক মাথা থেকে প্রতিটা অক্ষর একবার করে দেখা হয়
s.find(t), s.rfind(t)GCC-র library-তে বড়জোর O(n x m)সম্ভাব্য প্রতিটা জায়গায় বড়জোর m-টা অক্ষর মেলানো হয়

সাবধান: find কখনো -1 return করে না। কিছু না পেলে ও return করে string::npos, যেটা Playground-এ 18446744073709551615। ফলটা একটা size_t-এ রাখো, আর string::npos-এর সাথে মেলাও, program-টা যেমন করেছে। তাই "পাওয়া যায়নি" মানে বিশাল একটা সংখ্যা, negative কিছু না।

find_first_of আর find_last_of

একটা set-এর যেকোনো অক্ষর খোঁজা

s.find_first_of(set)       index of the first char that is in set, or npos
s.find_last_of(set)        index of the last char that is in set, or npos
s.find_first_not_of(set)   index of the first char that is NOT in set, or npos
s.find_last_not_of(set)    index of the last char that is NOT in set, or npos
#include <iostream>
#include <string>
using namespace std;

int main() {
    string line = "price: 450 taka";
    size_t first = line.find_first_of("0123456789");
    size_t last = line.find_last_of("0123456789");
    cout << first << ' ' << last << ' ' << line.substr(first, last - first + 1) << '\n';

    string padded = "   Zara  ";
    size_t b = padded.find_first_not_of(' ');
    size_t e = padded.find_last_not_of(' ');
    cout << '[' << padded.substr(b, e - b + 1) << "]\n";
    return 0;
}
7 9 450
[Zara]

দ্বিতীয় অর্ধেকটা string-টাকে trim করে: দুই মাথার space কেটে ফেলে। আসল text কোথায় শুরু আর কোথায় শেষ, সেটা বের করার উপায় হলো _not_of জোড়াটা।

Callখরচকারণ
find_first_of(set) আর ওর তিন ভাইk অক্ষরের set-এ বড়জোর O(n x k)string-এর প্রতিটা অক্ষর set-এর সাথে মেলানো হয়

সাবধান: argument-টা অক্ষরের একটা set, কোনো শব্দ না। Bob-এর s.find_first_of("cat") থামে যেকোনো জায়গার প্রথম c, a বা t-তে। শব্দটা খুঁজতে চাইলে find ব্যবহার করো।

insert আর erase

মাঝখানে যোগ আর বাদ

s.insert(pos, t)       put the string t before index pos
s.insert(pos, n, c)    put n copies of the char c before index pos
s.erase(pos, len)      remove len characters starting at pos
s.erase(pos)           remove everything from pos to the end
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "Dhaka 2026";
    s.insert(5, " city");
    cout << s << '\n';
    s.insert(0, 1, '[');
    s.append(1, ']');
    cout << s << '\n';
    s.erase(6, 5);
    cout << s << '\n';
    s.erase(6);
    cout << s << '\n';
    return 0;
}
Dhaka city 2026
[Dhaka city 2026]
[Dhaka 2026]
[Dhaka
Callখরচকারণ
s.insert(pos, t)O(n - pos + m), তাই সামনে O(n)জায়গা বানাতে pos-এর পরের প্রতিটা অক্ষর ডানে সরে
s.erase(pos, len)O(n - pos)ফাঁক ভরতে তার পরের প্রতিটা অক্ষর বাঁয়ে সরে

সাবধান: এক সংখ্যার s.erase(6) index 6 থেকে শেষ পর্যন্ত সব মুছে দেয়, একটা অক্ষর না। একটা অক্ষর মুছতে লেখো s.erase(6, 1)। তাই erase-এও দ্বিতীয় সংখ্যাটা length, ঠিক substr-এর মতো।

replace আর clear

একটা টুকরা বদলে লেখা, আর খালি করা

s.replace(pos, len, t)   remove len characters at pos and put t there
s.clear()                remove every character; size() becomes 0
#include <iostream>
#include <string>
using namespace std;

int main() {
    string msg = "Meet at 5 pm at the gate";
    msg.replace(8, 4, "6:30 pm");
    cout << msg << '\n';
    size_t at = msg.find("gate");
    msg.replace(at, 4, "library");
    cout << msg << '\n';
    msg.clear();
    cout << '[' << msg << "] " << msg.size() << ' ' << msg.empty() << '\n';
    return 0;
}
Meet at 6:30 pm at the gate
Meet at 6:30 pm at the library
[] 0 1

নতুন টুকরাটা পুরোনোটার চেয়ে লম্বাও হতে পারে, ছোটও হতে পারে। "5 pm"-এ ছিল 4টা অক্ষর, আর "6:30 pm"-এ আছে 7টা, তাই লাইনের বাকিটা 3 ঘর ডানে সরে গেছে।

Callখরচকারণ
s.replace(pos, len, t)O(n - pos + m)length আলাদা হলে লেজটা সরে, আর m-টা অক্ষর লেখা হয়
s.clear()O(1)একটা char-এর কোনো ধোয়ামোছা লাগে না, তাই শুধু length বদলায়; memory থেকে যায়

সাবধান: replace একটাই জায়গা বদলায়, যেটা তুমি index দিয়ে বলে দাও। ও নিজে কিছু খোঁজে না। একটা শব্দের প্রতিটা copy বদলাতে find দিয়ে একটা loop লাগে, আর অনুশীলন 3 ঠিক সেটাই চায়।

c_str আর data

অক্ষরগুলো C string হিসেবে

s.c_str()    a const char* to the characters, ending in '\0'
s.data()     the same pointer as c_str()

কিছু function এসেছে C থেকে, আর ওরা চায় একটা const char*, মানে এমন অক্ষরের একটা pointer, যেগুলো শেষ হয় '\0' দিয়ে। %s সহ printf আর strlen এমন দুইটা।

#include <cstdio>
#include <cstring>
#include <string>
using namespace std;

int main() {
    string name = "Kenji";
    const char* p = name.c_str();
    printf("%s has %d letters\n", p, (int)strlen(p));
    printf("%s again\n", name.data());
    return 0;
}
Kenji has 5 letters
Kenji again
Callখরচকারণ
s.c_str(), s.data()O(1)string আগে থেকেই অক্ষরগুলোর পরে একটা '\0' রেখে দেয়; ও শুধু address-টা দিয়ে দেয়

সাবধান: pointer-টা ঠিক থাকে শুধু ততক্ষণ, যতক্ষণ string বদলায় না। Block বড় করে দেওয়া একটা +=-এর পরে অক্ষরগুলো হয়তো নতুন কোনো জায়গায় চলে গেছে। তাই যেখানে লাগবে ঠিক সেখানেই c_str() call করো, আর pointer-টা কখনো জমিয়ে রেখো না।

stoi, stol, stoll আর stod

Text থেকে সংখ্যা

stoi(s)          an int      stol(s)    a long
stoll(s)         a long long stod(s)    a double
stoi(s, &used)   the same, and used becomes the number of characters read

প্রতিটা শুরুর space বাদ দেয়, যতগুলো অক্ষর মিলে একটা সংখ্যা হয় ততগুলো পড়ে, আর যেটা মেলে না সেখানেই থামে।

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

int main() {
    cout << stoi("42") + 1 << ' ' << stoi("  -7") << '\n';
    cout << stoi("12abc") << '\n';
    size_t used = 0;
    int v = stoi("12abc", &used);
    cout << v << " used " << used << '\n';
    cout << stoll("9000000000") << ' ' << stod("3.75") * 2 << '\n';
    try {
        cout << stoi("abc") << '\n';
    } catch (const invalid_argument& e) {
        cout << "invalid_argument: " << e.what() << '\n';
    }
    try {
        cout << stoi("3000000000") << '\n';
    } catch (const out_of_range& e) {
        cout << "out_of_range: " << e.what() << '\n';
    }
    return 0;
}
43 -7
12
12 used 2
9000000000 7.5
invalid_argument: stoi
out_of_range: stoi

stoi("12abc") কোনো আপত্তি ছাড়াই দেয় 12, আর used বলে মাত্র 2টা অক্ষর পড়া হয়েছে। শুরুতে কোনো সংখ্যা না থাকলে throw হয় invalid_argument। Type-এ না আঁটা বড় সংখ্যায় throw হয় out_of_range। দুই ক্ষেত্রেই what() শুধু function-টার নাম।

Callখরচকারণ
stoi, stol, stoll, stodd-টা অক্ষর পড়লে O(d)প্রতিটা digit একবার পড়া হয়

সাবধান: 64-bit Linux-এ, যেখানে Playground চলে, একটা long 8 byte, ঠিক একটা long long-এর সমান। 64-bit Windows-এ এটা 4। বড় পূর্ণসংখ্যার জন্য stoll লেখো, ওটা সব জায়গায় একই মানে দেয়। তাই সংখ্যার আকার দেখে function বাছো, আর text যখন কোনো user-এর কাছ থেকে আসে, তখন দুইটা exception-ই catch করো।

to_string

সংখ্যা থেকে text

to_string(x)   x as a string; x may be int, long long, double and the rest
#include <iostream>
#include <string>
using namespace std;

int main() {
    int marks = 87;
    string line = "Zara: " + to_string(marks) + "/100";
    cout << line << " (" << line.size() << ")\n";
    cout << to_string(-5) << ' ' << to_string(2.5) << '\n';
    return 0;
}
Zara: 87/100 (12)
-5 2.500000
Callখরচকারণ
to_string(x)d-টা digit-এ O(d)নতুন একটা string-এ প্রতিটা digit একবার লেখা হয়

সাবধান: একটা double সবসময় ছয় ঘর দশমিকসহ আসে, তাই 2.5 হয়ে যায় 2.500000। cout যেভাবে print করে সেভাবে সংখ্যা চাইলে একটা ostringstream ব্যবহার করো, দুই section পরে।

delimiter দিয়ে getline

বেছে নেওয়া একটা অক্ষর পর্যন্ত পড়া

getline(in, s)         read up to '\n', which is dropped
getline(in, s, ',')    read up to ',', which is dropped; '\n' is now an ordinary char

Lesson 01 পুরো একটা লাইন পড়েছে getline(cin, s) দিয়ে। তৃতীয় একটা argument, একটা char, ঠিক করে দেয় ও কোথায় থামবে। ওই অক্ষরটাকে বলে delimiter, মানে এক টুকরা আর পরের টুকরার মাঝের দাগ। এখানে in যেকোনো input stream: cin, বা নিচের string stream-গুলো।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string name;
    while (getline(cin, name, ',')) {
        cout << '[' << name << "]\n";
    }
    return 0;
}
[Alice]
[Bob]
[Maria
]

ওই output-টা এক লাইনের input Alice,Bob,Maria-এর জন্য। শেষ নামটা দেখো: বন্ধ করার bracket চলে গেছে পরের লাইনে।

Callখরচকারণ
getline(in, s, c)k-টা অক্ষর পড়লে O(k)প্রতিটা একবার পড়া হয়, আর amortised O(1)-এ যোগ হয়

সাবধান: delimiter দিলে লাইনের শেষটা আর দশটা অক্ষরের মতোই একটা অক্ষর, তাই শেষ টুকরাটা ওর '\n' রেখে দিয়েছে। পরিষ্কার উপায় হলো দুই ধাপ: আগে getline(cin, line) দিয়ে লাইনটা পড়ো, তারপর লাইনটা ভাগ করো। দ্বিতীয় ধাপের হাতিয়ারটাই পরের section।

istringstream: একটা লাইন থেকে মান বের করা

যে stream একটা string থেকে পড়ে

#include <sstream>
istringstream in(line);      a stream over a copy of line
in >> x                      read the next value, exactly as cin >> x
while (in >> x)              stops at the end, or at text that is not an x
getline(in, field, ',')      split the line on a char

Module 1-এর fast input-এর lesson istringstream-এর কথা আগেই বলে রেখেছিল: cin-এ যে >> loop চলে, সেটা একটা string-এও চলে। এবার পুরো হাতিয়ারটা। Stream লাইনের ভেতরে নিজের position মনে রাখে, তাই প্রতিটা read শুরু হয় আগেরটা যেখানে থেমেছিল সেখান থেকে।

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    istringstream row("Amara 85 90 77");
    string name;
    int a, b, c;
    row >> name >> a >> b >> c;
    cout << name << " total " << a + b + c << '\n';

    istringstream nums("4 8 x 15");
    int x, sum = 0;
    while (nums >> x) {
        sum += x;
    }
    cout << "sum " << sum << '\n';

    istringstream cities("Dhaka,Sylhet,Khulna");
    string city;
    while (getline(cities, city, ',')) {
        cout << '[' << city << "]\n";
    }
    return 0;
}
Amara total 252
sum 12
[Dhaka]
[Sylhet]
[Khulna]

যোগফল 12, 27 না। >> যখন x-এর সামনে পড়েছে, তখন কোনো int পড়তে পারেনি, তাই stream fail করেছে: নিজেকে ভাঙা বলে চিহ্ন দিয়েছে, আর এরপর প্রতিটা read false দেয়। Loop ওখানেই থেমেছে, আর 15 কখনো পড়াই হয়নি।

Callখরচকারণ
istringstream in(line)O(n)stream লাইনটার নিজের একটা copy রাখে
in >> x, getline(in, field, c)k-টা অক্ষর পড়লে O(k)পড়া শুধু সামনে এগোয়, কখনো পেছনে যায় না

সাবধান: fail করা stream fail-ই থাকে। একটাকে বারবার ব্যবহার না করে প্রতিটা লাইনের জন্য নতুন একটা istringstream বানাও। তাই একটা লাইন ভাগ করা মানে একটা নতুন stream, যতক্ষণ না ফুরায় ততক্ষণ পড়া।

ostringstream: << দিয়ে text বানানো

যে stream একটা string-এ লেখে

#include <sstream>
ostringstream out;     an empty stream
out << x << " text";   write values, exactly as cout << x
out.str()              the whole text so far, as a string
#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    ostringstream out;
    out << "Bob ran " << 5 << " km in " << 27.5 << " min";
    string report = out.str();
    cout << report << '\n';
    cout << report.size() << " characters\n";
    return 0;
}
Bob ran 5 km in 27.5 min
24 characters

27.5 সংখ্যাটা বের হয়েছে 27.5 হিসেবে, cout যেভাবে print করে, to_string-এর 27.500000 হিসেবে না। cout দিয়ে যা যা print করতে পারো, সবই একটা ostringstream-এ লিখে একটা string হিসেবে রেখে দিতে পারো।

Callখরচকারণ
out << xk-টা অক্ষর লিখলে amortised O(k)stream নিজের buffer বাড়ায় ঠিক +=-এর মতো
out.str()O(n)এ পর্যন্ত লেখা সবকিছুর একটা copy return করে

সাবধান: str() copy করে। প্রতিটা <<-এর পরে না, শেষে একবার call করো। তাই নিয়মটা হলো: সবকিছু একটা stream-এ লেখো, তারপর string-টা একবার নাও।

reserve আর capacityIntermediate

দরকারের আগেই জায়গা

s.capacity()    how many characters fit before the block must grow
s.reserve(n)    make the capacity at least n; the size does not change
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cout << s.size() << ' ' << s.capacity() << '\n';
    s.reserve(100);
    cout << s.size() << ' ' << s.capacity() << '\n';
    for (int i = 0; i < 60; i++) {
        s += 'x';
    }
    cout << s.size() << ' ' << s.capacity() << '\n';
    return 0;
}
0 15
0 100
60 100

GCC 12-এ একটা খালি string-এ আগে থেকেই 15টা অক্ষরের জায়গা ছিল। কেন 15, আর ওই অক্ষরগুলো কোথায় থাকে, সেটা lesson 05-এর গল্প। reserve(100)-এর পরে 60টা append-এ একবারও block বড় করতে হয়নি।

Callখরচকারণ
s.capacity()O(1)string এটা লিখে রাখে
s.reserve(n)block বড় করলে O(n), না করলে O(1)নতুন block মানে প্রতিটা অক্ষর সেখানে copy করা

সাবধান: reserve জায়গা বানায়, অক্ষর না; s.reserve(100)-এর পরেও s[0] শেষের বাইরে। তাই reserve কাজে আসে শুধু তখন, যখন শেষ length আগে থেকেই জানো।

সব খরচ এক table-এ, আর Bob-এর মাপ

Operationখরচ
size, length, empty, [], at, front, back, pop_back, c_str, data, capacity, clearO(1)
একটা char-এর +=, push_backamortised O(1)
m-টা অক্ষরের += বা appendamortised O(m)
a + bO(n + m), নতুন একটা string
==, <, compareবড়জোর O(n)
substr(pos, len)O(len), একটা copy
একটা char-এর find, rfind; m অক্ষরের string-এরO(n); বড়জোর O(n x m)
find_first_of আর ওর ভাইয়েরা, k অক্ষরের setবড়জোর O(n x k)
pos-এ insert, erase, replaceO(n - pos), সাথে যতগুলো অক্ষর লেখা হয়
stoi, stoll, stod, to_stringd-টা digit-এ O(d)
getline, in >> x, out << xk-টা অক্ষর সরালে O(k)
reserveblock বড় করলে O(n)

এবার Bob-এর লাইনটা মেপে দেখা যাক। নিচের program 100,000 অক্ষরের একটা string বানায়, আর <chrono>-এর steady_clock দিয়ে শুধু loop-টার সময় মাপে। আমরা এটা Compiler Explorer-এ চালিয়েছি, Playground-এর -O2 -std=c++17-এ GCC 12, সাতবার। আরও দুইটা version-এ বদলেছে শুধু loop-এর লাইনটা: একবার s += c;, আরেকবার s.insert(0, 1, c);, আর প্রতিটাও চলেছে সাতবার।

#include <chrono>
#include <iostream>
#include <string>
using namespace std;

int main() {
    const int n = 100000;
    auto start = chrono::steady_clock::now();
    string s;
    for (int i = 0; i < n; i++) {
        char c = 'a' + i % 26;
        s = s + c;
    }
    auto stop = chrono::steady_clock::now();
    chrono::duration<double, milli> took = stop - start;
    cout << s.size() << " characters in " << took.count() << " ms\n";
    return 0;
}

প্রতিটা run একটা লাইন print করে, আগে size, তারপর millisecond-এ সময়, আর সময়টা run থেকে run-এ বদলেছে। Chart-এ সাতটা run-এর মাঝেরটা bar হিসেবে, আর সবচেয়ে কম আর সবচেয়ে বেশিটা আড়াআড়ি লাইন হিসেবে দেখানো আছে।

100,000 অক্ষরের string বানানো: s += c বনাম s.insert(0, 1, c) আর s = s + c 100,000 অক্ষর বানানোর সময়, GCC 12, -O2 -std=c++17, প্রতিটা সাতবার চালানো Bar: মাঝের run। লাইন: সবচেয়ে দ্রুত থেকে সবচেয়ে ধীর run। Grid-এর প্রতিটা ধাপ দশ গুণ লম্বা। s += c 0.37 ms (0.29 থেকে 0.72) s.insert(0, 1, c) 73 ms (70 থেকে 96) s = s + c 416 ms (402 থেকে 1142) 0.1 ms 1 ms 10 ms 100 ms 1 s 10 s Output একই, শেষ দুটোর প্রতি ধাপে খরচও O(n); তবে s = s + c প্রতি ধাপে নতুন string বানায় আর ফেলে দেয়।

+=-এর loop কখনো পুরো এক millisecond-ও নেয়নি। প্রতিটা run-এ +-এর loop অন্তত 500 গুণ ধীর ছিল। সামনে insert করাও প্রতি call-এ O(n), তবু সেটা s = s + c-কে হারিয়েছে, কারণ ও একই block-এর ভেতরেই অক্ষরগুলো সরায়। তার উপর +-এর loop 100,000 বার নতুন block বানায় আর পুরোনোটা ফেরত দেয়। তাই পুরো loop-এর জন্য দুটো খরচের লাইনই বলে O(n2), আর মাপটা দেখায় প্রতিবার নতুন string-এর বাড়তি দাম।

Example 1: David একটা web address ভাগ করে

David-এর tool একটা web address-এর অংশগুলো print করে। find প্রতিটা সীমানা খুঁজে দেয়, আর substr দুই সীমানার মাঝখান থেকে কেটে নেয়। দ্বিতীয় address-এ কোনো ? নেই, এটাই Zara-র case।

#include <iostream>
#include <string>
using namespace std;

void show_parts(const string& url) {
    size_t scheme_end = url.find("://");
    size_t host_start = scheme_end + 3;
    size_t path_start = url.find('/', host_start);
    size_t query_start = url.find('?', path_start);

    cout << "scheme " << url.substr(0, scheme_end) << '\n';
    cout << "host   " << url.substr(host_start, path_start - host_start) << '\n';
    cout << "path   " << url.substr(path_start, query_start - path_start) << '\n';
    if (query_start == string::npos) {
        cout << "query  (none)\n";
    } else {
        cout << "query  " << url.substr(query_start + 1) << '\n';
    }
}

int main() {
    show_parts("https://progsity.io/learn/stl?lang=bn");
    show_parts("http://example.com/about");
    return 0;
}
scheme https
host   progsity.io
path   /learn/stl
query  lang=bn
scheme http
host   example.com
path   /about
query  (none)

দ্বিতীয় address-এ query_start হলো npos, তাই path-এর length বিশাল। substr বেশি লম্বা length শেষে এসে ছেঁটে দেয়, তাই path হয়ে যায় বাকি পুরোটা। ওই নিয়মটাই একটা বাড়তি if-এর কাজ করে দিয়েছে।

Run in Compiler
Example 2: Alice ওর পড়ার session-গুলো যোগ করে

Alice প্রতিটা পড়ার session লিখে রাখে শুরু আর শেষের সময় দিয়ে, প্রতি লাইনে একটা session। ছোট একটা function find, substr আর stoi দিয়ে "07:45"-কে মাঝরাতের পরের minute-এ বদলায়। একটা istringstream প্রতিটা লাইন ভাগ করে, আর একটা ostringstream report জমায়, যেটা print হয় একবারেই।

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int to_minutes(const string& hhmm) {
    size_t colon = hhmm.find(':');
    int h = stoi(hhmm.substr(0, colon));
    int m = stoi(hhmm.substr(colon + 1));
    return h * 60 + m;
}

int main() {
    ostringstream out;
    string line;
    int total = 0;
    while (getline(cin, line)) {
        istringstream in(line);
        string start, stop;
        in >> start >> stop;
        int minutes = to_minutes(stop) - to_minutes(start);
        total += minutes;
        out << start << " to " << stop << ": " << minutes << " min\n";
    }
    out << "total " << total / 60 << " h " << total % 60 << " min\n";
    cout << out.str();
    return 0;
}
07:45 to 09:10: 85 min
13:05 to 14:00: 55 min
20:30 to 22:15: 105 min
total 4 h 5 min

ওই output-টা তিন লাইনের input-এর জন্য: 07:45 09:10, 13:05 14:00 আর 20:30 22:15। stoi("07") হলো 7, কারণ সামনের শূন্যটা আর দশটা digit-এর মতোই একটা digit। Report-টা memory-তে বানানো হয়েছে, আর print হয়েছে একটাই cout দিয়ে।

Run in Compiler
Example 3: Maria file-এর extension খোঁজে

Maria প্রতিটা file নামের extension চায়: শেষ dot-এর পরের অংশটা। rfind শেষ dot-টা খুঁজে দেয়। Zara আগেই তিনটা edge case যোগ করে: একদম dot নেই, নাম শুরুই হয় dot দিয়ে, আর নাম শেষ হয় dot দিয়ে।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string file;
    while (cin >> file) {
        size_t dot = file.rfind('.');
        if (dot == string::npos || dot == 0 || dot + 1 == file.size()) {
            cout << file << ": no extension\n";
        } else {
            cout << file << ": " << file.substr(dot + 1) << '\n';
        }
    }
    return 0;
}
report.final.pdf: pdf
notes: no extension
photo.JPG: JPG
.bashrc: no extension
archive.: no extension

ওই output-টা এক লাইনের input report.final.pdf notes photo.JPG .bashrc archive.-এর জন্য। report.final.pdf-এর প্রথম dot-টা rfind পার হয়ে গেছে, অথচ find ওখানেই থামত। dot == string::npos test-টা আগে আসে, তাই npos থেকে কখনো dot + 1 হিসাব হয় না।

Run in Compiler

এটা কোথায় কাজে লাগে

  • nginx. এই web server প্রতিটা request-এর path মেলায় ওর location prefix-গুলোর সাথে, আর যেটা মেলে তার মধ্যে সবচেয়ে লম্বাটা রাখে। ওই test-টা path-এর উপর একটা starts_with।
  • Linux-এর /proc file-গুলো। /proc/meminfo সাধারণ text, প্রতিটা মানের জন্য এক লাইন, যেমন MemTotal:, তারপর একটা সংখ্যা আর kB। free command এটা পড়ে, আর একটা C++ tool এটা পড়ে getline আর একটা istringstream দিয়ে।
  • CSV file, RFC 4180. এই format-এ যে field-এর ভেতরে comma আছে, সেটা double quote-এর ভেতরে রাখা হয়। তাই getline(in, field, ',') ঠিক কাজ করে শুধু সেই file-এ, যেখানে quote করা field নেই বলে তুমি নিশ্চিত। এজন্যই আসল CSV reader-রা quote-ও parse করে।

যে ভুলগুলো সবাই করে

১. substr-এর দ্বিতীয় সংখ্যাকে শেষের index ভাবা।

string date = "2026-10-07";
string month = date.substr(5, 7);

কোনো command line-এই বার্তা নেই। month হয় "10-07", index 5 থেকে সাতটা অক্ষর, "10" না। লেখো date.substr(5, 2), আর দুই মাথাই জানা থাকলে date.substr(start, end - start)। এই ভুলটা তুমি করবে, কারণ Python-এর slice আর আরও অনেক language শেষটা নেয়।

২. সংখ্যা না-ও হতে পারে এমন text-এ stoi call করা।

string age = "twenty";
int years = stoi(age);

কোনো বার্তা ছাড়াই compile হয়। Compiler Explorer-এ run থেমেছে exit code 134 নিয়ে, আর error stream-এ এসেছে terminate called after throwing an instance of 'std::invalid_argument', তারপর what(): stoi। Text যখন কোনো মানুষের কাছ থেকে আসে, call-টা try আর catch (const invalid_argument& e)-এ মুড়ে দাও। তুমি এটা বাদ দেবে, কারণ প্রতিটা sample input-এ একদম পরিষ্কার একটা সংখ্যা থাকে।

৩. একটা string-কে একটা char-এর সাথে মেলানো।

string s = "Maria";
if (s == 'M') {
    cout << "starts with M\n";
}

Error, আর তার পরে এক পাতা note। পড়ার মতো লাইন প্রথমটাই: error: no match for 'operator==' (operand types are 'std::string' {aka 'std::__cxx11::basic_string<char>'} and 'char')। একটা string কখনো একটা অক্ষরের সমান হয় না। লেখো s[0] == 'M' (!s.empty() check করার পরে) বা s == "M"। তুমি এটা লিখে ফেলবে, কারণ 'M' আর "M" দেখতে প্রায় একই।

৪. getline-কে delimiter হিসেবে একটা string দেওয়া।

while (getline(in, city, ",")) {

Error: error: no matching function for call to 'getline(std::istringstream&, std::string&, const char [2])'। Delimiter হলো একটা char, তাই single quote-এ লেখো ','। তুমি double quote-এর দিকে হাত বাড়াবে, কারণ লাইনের বাকিটা পুরোটাই string নিয়ে।

মাথা খাটাও

একটা নামে কোনো 'x' নেই, Bob সেটা দুইভাবে check করে।

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "Maria";
    if (s.find('x') == -1) {
        cout << "test 1: not found\n";
    }
    unsigned int pos = s.find('x');
    if (pos == string::npos) {
        cout << "test 2: not found\n";
    }
    cout << "done\n";
    return 0;
}

Playground-এর flag-এ Compiler Explorer-এর GCC 12-এ এটা print করে test 1: not found আর done, কিন্তু test 2 কখনো না। find কখনো -1 return করে না, তবু test 1 কেন কাজ করে, বুঝিয়ে বলো। তারপর বলো, 64-bit machine-এ test 2 কেন কখনোই pass করতে পারে না।

sizeof(size_t), sizeof(unsigned int), string::npos আর pos print করে দেখো। তারপর ভাবো, একটা size_t-এর সাথে মেলানোর সময় -1-এর কী হয়।

অনুশীলন ১সহজ

David-এর mailing list-এর প্রতিটা address ওর @-এ ভাগ করতে হবে। তার আগের অংশ আর পরের অংশ print করো।

Input. n, তারপর n-টা email address, প্রতি লাইনে একটা।

Output. প্রতিটা address-এর জন্য এক লাইন: @-এর আগের অংশ, একটা space, আর পরের অংশ।

Constraints. 1 <= n <= 20000; প্রতিটা address-এ বড়জোর 100টা ASCII character, কোনো space নেই, ঠিক একটা @, আর ওর দুই পাশে অন্তত একটা করে character।

Sample. Input 3, maria@school.example, bob.rahman@mail.example.com আর z@x.io দিলে তিন লাইনে maria school.example, bob.rahman mail.example.com আর z x.io।

#include <iostream>
#include <string>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    for (int i = 0; i < n; i++) {
        string email;
        cin >> email;

        // Find the '@'. Print the part before it, a space,
        // and the part after it, on one line.
    }

    return 0;
}

email-domain নামে গ্রেড হয়, এই module-এর problem set-এর একটা free problem।

Run in Compiler
অনুশীলন ২মাঝারি

Kenji-র bank report-এ বড় বড় সংখ্যা print হয়, আর 1234567 এক নজরে কেউ পড়তে পারে না। প্রতিটা সংখ্যা print করো ডান দিক থেকে গুনে প্রতি তিন digit পরপর একটা comma দিয়ে।

Input. n, তারপর n-টা পূর্ণসংখ্যা, প্রতি লাইনে একটা।

Output. প্রতিটা সংখ্যা নিজের লাইনে, ওর comma-সহ।

Constraints. 1 <= n <= 20000, আর প্রতিটা পূর্ণসংখ্যা -1018 থেকে 1018-এর মধ্যে।

Sample. Input 5, 0, 999, 1000, -1234567 আর 1000000000000000000 দিলে পাঁচ লাইনে 0, 999, 1,000, -1,234,567 আর 1,000,000,000,000,000,000।

#include <iostream>
#include <string>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    for (int i = 0; i < n; i++) {
        long long x = 0;
        cin >> x;

        // Turn x into text with to_string. Print it with a comma
        // between every group of three digits, counted from the right.
        // A minus sign stays in front, with no comma after it.
    }

    return 0;
}

thousands-separator নামে গ্রেড হয়, এই module-এর problem set-এর একটা Pro problem।

Run in Compiler
অনুশীলন ৩মাঝারি

Amara এক লাইনের একটা text ঠিক করছে: একটা শব্দ যতবার আছে, প্রতিবার সেটা আরেকটা শব্দ হয়ে যাবে। বাঁ থেকে ডানে খোঁজো, আর এইমাত্র বসানো কোনো টুকরার ভেতরে কখনো খুঁজবে না। দুইটা মিল একটার উপর আরেকটা পড়লে বাঁয়েরটা জেতে, তাই aaa-তে aa-কে b দিয়ে বদলালে পাওয়া যায় ba, আর গোনা হয় 1।

Input. লাইন 1: text, যাতে space থাকতে পারে। লাইন 2: from। লাইন 3: to।

Output. লাইন 1-এ নতুন text, আর লাইন 2-এ কয়বার বদলানো হলো।

Constraints. Text-এ 1 থেকে 100000টা ASCII character; from আর to-এর প্রতিটায় 1 থেকে 10টা character, কোনো space নেই।

Sample. Input I like cats. My cat likes catnip., cat আর dog দিলে দুই লাইনে I like dogs. My dog likes dognip. আর 3।

#include <iostream>
#include <string>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string text, from, to;
    getline(cin, text);
    getline(cin, from);
    getline(cin, to);

    // Replace every occurrence of from in text, left to right.
    // Never search inside a piece you have just put in.
    // Print the new text, then the number of replacements, on two lines.

    return 0;
}

replace-word নামে গ্রেড হয়, এই module-এর problem set-এর একটা Pro problem।

Run in Compiler
অনুশীলন ৪কঠিন

Zara একটা sign-up form test করছে, যার বয়সের ঘরে যেকোনো text লেখা যায়। প্রতিটা entry-র জন্য ঠিক করো, পুরো entry-টা এমন একটা পূর্ণসংখ্যা কি না, যেটা একটা int-এ আঁটে। stoi একা 12abc-ও মেনে নেয়, তাই শুধু ওতে চলবে না।

Input. এক লাইনে n, তারপর n-টা entry, প্রতি লাইনে একটা। কোনো entry-তে space নেই।

Output. প্রতিটা entry-র জন্য এক লাইন। ঠিক entry মানে চাইলে একটা + বা -, তারপর শুধু digit, আর মানটা একটা int-এ আঁটে। ঠিক entry-র মান একটা int হিসেবে print করো, আর বাকি যেকোনো entry-র জন্য print করো invalid।

Constraints. 1 <= n <= 1000। প্রতিটা entry-তে 1 থেকে 30টা printable ASCII অক্ষর।

Sample. Input 5, তারপর 42, -17, 12abc, 99999999999 আর 007 দিলে 42, -17, invalid, invalid আর 7।

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    for (int i = 0; i < n; i++) {
        string entry;
        cin >> entry;

        // Print the entry's int value if the whole entry is a number
        // that fits in an int; otherwise print "invalid".
    }

    return 0;
}

আলাদা করে গ্রেড হয় না। Lesson যে কথাটা শুধু ইঙ্গিতে বলেছে: stoi বলে দিতে পারে ও কয়টা অক্ষর ব্যবহার করেছে, আর একটা catch ওর দুইটা exception-কে একটা উত্তরে বদলে দিতে পারে।

Run in Compiler

সচরাচর যে প্রশ্নগুলো আসে

  • C-এর atoi-এর চেয়ে stoi ভালো কেন?

    Playground-এ atoi("abc") দেয় 0, তাই খারাপ text আর আসল 0-কে আলাদা করা যায় না। stoi বদলে throw করে, আর ওর দ্বিতীয় argument বলে দেয় কতটা পড়েছে। দুটোই "12abc"-কে পড়ে 12।

  • একটা লাইন ভাগ করতে find আর substr ব্যবহার করব, নাকি একটা string stream?

    Space দিয়ে আলাদা করা মানের জন্য >> সহ istringstream সবচেয়ে ছোট। একটা delimiter থাকলে getline(in, field, ',') পরিষ্কার। টুকরাগুলোর মাঝে আলাদা আলাদা দাগ থাকলে find আর substr নাও, যেমন David-এর web address-এ। Lesson 03 দুই রকম ভাগ পাশাপাশি লিখে দেখাবে।

  • বড় হাত-ছোট হাত না মেনে দুইটা string কীভাবে মেলাব, যাতে "apple" আসে "Zara"-র আগে?

    ছোট হাতের copy বানিয়ে মেলাও। Lesson 03 প্রতিটা অক্ষর ছোট করে <cctype>-এর tolower দিয়ে। Library-র নিজের < সবসময় byte মেলায়।

মূল কথা

  • size, [], at, back আর c_str হলো O(1); at check করে আর std::out_of_range throw করে, আর খালি string-এ front, back, pop_back undefined।
  • s += c amortised O(1); s = s + c পুরো string copy করে, 100,000 অক্ষরে মাপা হয়েছে 402 থেকে 1142 ms।
  • substr(pos, len) নেয় একটা length, বেশি লম্বা হলে ছেঁটে দেয়, আর pos > size() হলে throw করে; erase আর replace-ও length নেয়।
  • "পাওয়া যায়নি" বোঝাতে find return করে string::npos; ফলটা একটা size_t-এ রাখো আর npos-এর সাথে মেলাও।
  • stoi প্রথম অ-digit-এ থামে, আর throw করে invalid_argument বা out_of_range; istringstream একটা লাইন থেকে মান বের করে, আর ostringstream একটা লাইন বানায়।
  • আরও গভীরে যেতে চাইলে: Under the Hood, ছোট string-এর buffer আর + এর খরচ (Pro)।

এরপর lesson 03 এই operation-গুলো দিয়ে পুরো program বানাবে, একটা শব্দ গোনা থেকে শুরু করে পুরো একটা text report পর্যন্ত।

lesson ২ শেষ

শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।

পরেরটা: পুরো program: একটা শব্দ থেকে একটা text report পর্যন্ত

string-এর প্রতিটা operation, একটা একটা করে, খরচসহ | Learn C++ STL | Progsity