Learn C++ STL

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

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

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

Freeপড়া

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

  • ছয়টা সম্পূর্ণ string program লিখতে পারবে, শব্দ গোনা থেকে শুরু করে এমন একটা marks tool পর্যন্ত, যেটা CSV file পড়ে আর যার নামের ভিতরে space থাকে।
  • একটা লাইনকে একটা character-এ দুইভাবে ভাঙতে পারবে, find আর substr দিয়ে, আবার getline(in, field, ',') দিয়ে, আর বলতে পারবে কোনটা কখন মানায়।
  • String stream দুই দিকেই ব্যবহার করতে পারবে: লাইনের ভিতর থেকে মান পড়তে istringstream, আর print করার আগে পুরো report বানাতে ostringstream।

C-এ তুমি একটা marks report লিখেছিলে, যেখানে প্রতিটা ছাত্রের জন্য ছিল char name[20]। নামে একটা space থাকলেই scanf("%s") ভেঙে যেত, আর বিশ অক্ষরের নাম ভাঙত array-টাকে। এই lesson সেই report আবার লেখে string দিয়ে, আর নাম যত লম্বা, ঠিক তত লম্বাই থাকতে পারে। ছয়টা program ধাপে ধাপে সেখানে পৌঁছায়, প্রতিটা আগেরটার চেয়ে একটু বড়। প্রতিটা program-এর উপরের "নতুন জিনিস" লাইনটা আগে পড়ো; program-টা ঠিক ওটার জন্যই।

Program 1: শব্দ গোনো, আর ওদের character

নতুন জিনিস: while (cin >> word) input শেষ না হওয়া পর্যন্ত প্রতিবার একটা করে শব্দ পড়ে, আর size() প্রতিটা শব্দ আসামাত্র ওর মাপ বলে দেয়।

এখানে শব্দ মানে space ছাড়া character-এর একটা টানা সারি, string-এ >> ঠিক এটাই পড়ে। Lesson 01 একবার পড়া দেখিয়েছিল; এই program সেটা loop-এ চালায়। Zara সবার আগে খালি input দিয়ে test করে, তাই ওটার জন্য আলাদা একটা লাইন আছে।

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

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

    string word;
    int count = 0;
    int characters = 0;
    int long_words = 0;
    while (cin >> word) {
        count++;
        characters += word.size();
        if (word.size() >= 5) {
            long_words++;
        }
    }

    if (count == 0) {
        cout << "no words\n";
        return 0;
    }
    cout << "words: " << count << '\n';
    cout << "characters: " << characters << '\n';
    cout << "5 or more characters: " << long_words << '\n';
    return 0;
}
words: 9
characters: 35
5 or more characters: 3

ওই output-টা দুই লাইনের input the quick brown fox আর jumps over the lazy dog-এর জন্য। দুই লাইনের মাঝের newline >>-এর কাছে আরেকটা space ছাড়া কিছু না। প্রতিটা শব্দ আসে ওর মাপমতো একটা string-এ, আর size() সাথে সাথে ওর দৈর্ঘ্য বলে দেয়: quick, brown আর jumps-এ পাঁচটা করে। তাই loop যত খুশি শব্দ পড়ে, যত লম্বাই হোক, আর কোনো size তোমাকে ঠিক করে দিতে হয় না।

Program 2: vector<int>(26) দিয়ে অক্ষর গোনা

নতুন জিনিস: vector<int> count(26, 0), আর index হিসেবে tolower(u) - 'a', যেখানে u হলো unsigned char-এ cast করা character-টা।

Amara জানতে চায়, একটা বাক্য pangram কি না, মানে ওতে 26টা অক্ষরই আছে কি না। প্রতিটা অক্ষর পায় একটা করে বাক্স: 'a' হলো 97 আর 'z' হলো 122, তাই c - 'a' চলে 0 থেকে 25 পর্যন্ত। isalpha আর tolower আসে <cctype> থেকে; প্রথমটা জিজ্ঞেস করে "এটা কি অক্ষর?", দ্বিতীয়টা বড় হাতের অক্ষরকে ছোট হাতের বানায়।

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

int main() {
    string line;
    while (getline(cin, line)) {
        vector<int> count(26, 0);
        for (char c : line) {
            unsigned char u = c;
            if (isalpha(u)) {
                count[tolower(u) - 'a']++;
            }
        }

        string missing;
        int top = 0;
        for (int i = 0; i < 26; i++) {
            if (count[i] == 0) {
                missing += char('a' + i);
            }
            if (count[i] > count[top]) {
                top = i;
            }
        }

        cout << line << '\n';
        cout << "  most used: " << char('a' + top) << ", " << count[top] << " times\n";
        if (missing.empty()) {
            cout << "  a pangram: all 26 letters\n";
        } else {
            cout << "  missing " << missing.size() << ": " << missing << '\n';
        }
    }
    return 0;
}
Sphinx of black quartz, judge my vow
  most used: a, 2 times
  a pangram: all 26 letters
Kenji fixed one bug and found two more
  most used: e, 4 times
  missing 9: chlpqsvyz

Cast কেন? PC-র GCC-তে char signed, তাই 127-এর উপরের একটা byte হয়ে যায় negative সংখ্যা। বাংলা অক্ষরের প্রতিটা byte-ই এমন। <cctype>-এর function-গুলো শুধু সেই মানগুলোর জন্য ঠিক করা, যেগুলো একটা unsigned char-এ ধরে, তাই negative মান দিলে undefined behaviour। আগে c-কে unsigned char u-তে copy করলে প্রতিটা byte হয় 0 থেকে 255, আর তখন isalpha ওই byte-গুলোকে সোজা না বলে দেয়। তাই vector-টাই হলো table, আর cast-টা table-টাকে ইংরেজি ছাড়া অন্য text থেকে নিরাপদ রাখে।

Program 3: একটা লাইন ওর comma-গুলোতে ভাঙো

নতুন জিনিস: একটা লাইনকে field-এ কাটার দুইটা উপায়: find আর substr, আবার একটা istringstream-এর উপর getline(in, field, ',')।

Amara-র class file-এর প্রতিটা লাইনে একটা নাম আর তিনটা নম্বর, মাঝে মাঝে comma। প্রথম উপায়ে কাটাকাটি নিজের হাতে। line.find(',', start) বলে দেয় start-এ বা তার পরে পরের comma-টা কোথায়, আর কোনো comma না থাকলে দেয় string::npos। একটা field চলে start থেকে ওই comma-র ঠিক আগ পর্যন্ত।

find আর substr দিয়ে Amara Okafor,85,90,77 ভাঙা "Amara Okafor,85,90,77": find কোথায় থামে, আর substr কী কাটে find: 12 find: 15 find: 18 A m a r a sp O k a f o r , 8 5 , 9 0 , 7 7 0 5 11 12 13 15 16 18 19 20 substr(0, 12): Amara Okafor substr(13, 2) substr(16, 2) substr(19) প্রতিটা টুকরো শুরু হয় একটা comma-র ঠিক পরে; ওর দৈর্ঘ্য হলো পরের comma-র জায়গা বাদ ওই শুরু। 18-এর পরে find দেয় npos, তাই শেষ টুকরোটা চলে লাইনের শেষ পর্যন্ত।
#include <iostream>
#include <string>
using namespace std;

int main() {
    string line;
    getline(cin, line);

    size_t start = 0;
    int field = 1;
    while (true) {
        size_t comma = line.find(',', start);
        if (comma == string::npos) {
            cout << field << ": [" << line.substr(start) << "] to the end\n";
            break;
        }
        cout << field << ": [" << line.substr(start, comma - start) << "] comma at " << comma << '\n';
        start = comma + 1;
        field++;
    }
    return 0;
}
1: [Amara Okafor] comma at 12
2: [85] comma at 15
3: [90] comma at 18
4: [77] to the end

ওই output-টা input লাইন Amara Okafor,85,90,77-এর জন্য। substr-এর দ্বিতীয় argument একটা দৈর্ঘ্য, শেষের জায়গা না, তাই লিখতে হয় comma - start। Bracket-গুলো দেখায় যে নামের ভিতরের space-টা রয়ে গেছে, যেটা >> হারিয়ে ফেলত।

দ্বিতীয় উপায়ে কাটার কাজটা করে একটা stream। Lesson 02 তৃতীয় একটা argument-সহ getline দেখিয়েছিল: ওই character পর্যন্ত পড়ে, আর character-টা ফেলে দেয়। লাইনটা একটা istringstream-এ রাখো, তাহলে প্রতিটা call তোমার হাতে দেবে পরের field-টা।

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

int main() {
    string line;
    getline(cin, line);

    istringstream in(line);
    string field;
    int k = 1;
    while (getline(in, field, ',')) {
        cout << k << ": [" << field << "]\n";
        k++;
    }
    return 0;
}
1: [Amara Okafor]
2: [85]
3: [90]
4: [77]

একই input থেকে একই চারটা field, অথচ কোনো জায়গার হিসাব রাখতে হয়নি। দুই উপায়ের তুলনা এরকম।

প্রশ্নfind আর substrgetline(in, field, ',')
প্রতিটা field-এর জায়গা কি পাওয়া যায়?হ্যাঁ, find সেটা return করেনা, শুধু text
Separator কি এক character-এর বেশি হতে পারে, যেমন ", "?হ্যাঁ, find একটা string-ও নেয়না, এটা একটাই char
Code কতটা?একটা loop, আর ঠিক রাখতে হয় একটা শুরু আর একটা দৈর্ঘ্যloop-এর একটা শর্ত
কী কী লাগে?শুধু <string><sstream>, আর stream-এর ভিতরে লাইনের একটা copy

তাই শুধু field-গুলো চাইলে getline নাও, আর জায়গা লাগলে বা separator লম্বা হলে find।

Program 4: প্রতিটা লাইন থেকে সংখ্যা বের করা

নতুন জিনিস: প্রতিটা লাইনের জন্য একটা নতুন istringstream, যেটা >> দিয়ে পড়া হয় খালি না হওয়া পর্যন্ত, তাই একটা খালি লাইন থেকে কোনো সংখ্যাই আসে না।

Alice-এর ফোন ওর হাঁটার হিসাব রাখে। প্রতিটা লাইন একটা দিন, তাতে প্রতিবার হাঁটার step সংখ্যা, আর যে দিন একবারও হাঁটেনি, সেটা একটা খালি লাইন। ও চায় প্রতিদিন কয়বার হেঁটেছে আর কত step, আর সব দিন মিলিয়ে মোট কত।

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

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

    string line;
    int day = 0;
    long long all_steps = 0;
    while (getline(cin, line)) {
        day++;
        istringstream in(line);
        int steps;
        int walks = 0;
        long long total = 0;
        while (in >> steps) {
            walks++;
            total += steps;
        }

        cout << "day " << day << ": ";
        if (walks == 0) {
            cout << "no walks\n";
        } else {
            cout << walks << (walks == 1 ? " walk, " : " walks, ") << total << " steps\n";
        }
        all_steps += total;
    }
    cout << "all " << day << " days: " << all_steps << " steps\n";
    return 0;
}
day 1: 3 walks, 5400 steps
day 2: no walks
day 3: 1 walk, 5000 steps
day 4: 2 walks, 5000 steps
all 4 days: 15400 steps

ওই output-টা চার লাইনের input-এর জন্য: 1200 3400 800, একটা খালি লাইন, 5000 আর 2500 2500। বাইরের loop লাইন পড়ে, তাই খালি লাইনটাও ওর চোখে পড়ে; শুধু cin >> হলে ওটা টপকে যেত, আর day 2 হারিয়ে যেত। Stream-টা বানানো হয় loop-এর ভিতরে, তাই প্রতিটা দিন শুরু হয় নতুন করে। মানে দিনগুলোকে আলাদা রাখে লাইন, আর একটা দিনের ভিতরের সংখ্যাগুলো পড়ে string stream।

Program 5: ostringstream-এ বানানো একটা রসিদ

নতুন জিনিস: একটা report-এর পুরো body লেখা হয় একটা ostringstream-এ, setw আর setprecision দিয়ে, তারপর str() দিয়ে একবারে print।

David-এর দোকান রসিদ print করে। রসিদের প্রথম লাইনে লেখা থাকে ওতে কয়টা লাইন, আর সেই সংখ্যা জানা যায় শেষ জিনিসটা পড়ার পরেই। তাই program আগে জিনিসের লাইনগুলো একটা string-এ লিখে রাখে, তারপর heading print করে, তারপর string-টা। setw(n) পরের মানটাকে n character জায়গা জুড়ে বসায়; left আর right ঠিক করে কোন দিক ঘেঁষে; setprecision(2)-এর সাথে fixed দেয় দশমিকের পরে দুই অঙ্ক। এগুলো সবই আসে <iomanip> থেকে, আর যেকোনো output stream-এ চলে।

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

int main() {
    ostringstream body;
    body << fixed << setprecision(2);

    string item;
    int qty;
    double price;
    double total = 0;
    int lines = 0;
    while (cin >> item >> qty >> price) {
        double cost = qty * price;
        body << left << setw(10) << item << right << setw(4) << qty
             << " x " << setw(6) << price << " = " << setw(8) << cost << '\n';
        total += cost;
        lines++;
    }
    body << string(34, '-') << '\n';
    body << left << setw(26) << "TOTAL" << right << setw(8) << total << '\n';

    cout << "David's shop, " << lines << " lines\n";
    cout << body.str();
    return 0;
}
David's shop, 4 lines
pencil       3 x   0.50 =     1.50
notebook     2 x   2.25 =     4.50
eraser       1 x   0.80 =     0.80
ruler        1 x   1.20 =     1.20
----------------------------------
TOTAL                         8.00

ওই output-টা চার লাইনের input-এর জন্য: pencil 3 0.50, notebook 2 2.25, eraser 1 0.80 আর ruler 1 1.20। জিনিসের প্রতিটা লাইন 10 + 4 + 3 + 6 + 3 + 8 = 34 character, তাই dash-গুলো string(34, '-')। Setting-গুলো stream-এই থেকে যায়, তাই fixed একবার লিখলেই হয়। তাই একটা ostringstream হলো এমন একটা খসড়া, যেটা কেউ দেখার আগেই তুমি শেষ করে নিতে পারো।

Program 6: CSV marks toolIntermediate

নতুন জিনিস: সব টুকরো মিলে একটা আসল tool: লাইনের জন্য getline, field-এর জন্য getline(in, field, ','), নম্বরের জন্য stoi, আর সবচেয়ে লম্বা নামের সমান চওড়া একটা কলাম।

CSV মানে "comma-separated values", মানে text হিসেবে রাখা একটা table: প্রতি লাইনে একটা সারি, field-গুলোর মাঝে comma। যেকোনো spreadsheet program একটা sheet CSV হিসেবে save করতে পারে, তাই marks-এর তালিকা প্রায়ই এভাবেই হাতবদল হয়। এখানে প্রতিটা লাইন name,mark,mark,mark। Tool-টা প্রতিটা ছাত্রের গড়সহ একটা table print করে, তারপর প্রতিটা পরীক্ষার পুরো class-এর গড়। Lesson 02-এর stoi "85"-এর মতো একটা field-কে বানায় int 85।

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

int main() {
    vector<pair<string, vector<int>>> rows;
    size_t width = 4; // at least as wide as the word "name"
    string line;
    while (getline(cin, line)) {
        if (line.empty()) {
            continue;
        }
        istringstream in(line);
        string name, field;
        getline(in, name, ',');
        vector<int> marks;
        while (getline(in, field, ',')) {
            marks.push_back(stoi(field));
        }
        width = max(width, name.size());
        rows.push_back({name, marks});
    }

    ostringstream table;
    table << fixed << setprecision(1);
    table << left << setw(width) << "name" << right
          << setw(5) << "T1" << setw(5) << "T2" << setw(5) << "T3" << setw(9) << "average" << '\n';
    vector<int> test_sum(3, 0);
    for (const auto& [name, marks] : rows) {
        int total = 0;
        table << left << setw(width) << name << right;
        for (int t = 0; t < 3; t++) {
            table << setw(5) << marks[t];
            total += marks[t];
            test_sum[t] += marks[t];
        }
        table << setw(9) << total / 3.0 << '\n';
    }
    table << left << setw(width) << "class" << right;
    for (int t = 0; t < 3; t++) {
        table << setw(5) << (double)test_sum[t] / rows.size();
    }
    table << '\n';

    cout << rows.size() << " students\n" << table.str();
    return 0;
}
5 students
name                 T1   T2   T3  average
Amara Okafor         85   90   77     84.0
Bob Smith            48   55   61     54.7
Maria Lopez Garcia   90   94   88     90.7
Kenji Sato           81   79   85     81.7
Zara Ali             67   72   70     69.7
class              74.2 78.0 76.2

ওই output-টা পাঁচ লাইনের input-এর জন্য: Amara Okafor,85,90,77, Bob Smith,48,55,61, Maria Lopez Garcia,90,94,88, Kenji Sato,81,79,85 আর Zara Ali,67,72,70।

নামের কলাম 18 চওড়া, কারণ সবচেয়ে লম্বা নাম Maria Lopez Garcia-তে 18টা character। এই সংখ্যা কেউ বেছে দেয়নি; data-ই ঠিক করেছে। C-এ char name[20] এর চেয়ে লম্বা নাম কেটে ফেলত, আর এখানে 200 character-এর নাম এলে কলামটা শুধু চওড়া হয়। total / 3.0 ভাগ করে একটা double দিয়ে, তাই 164 / 3 print হয় 54.7, 54 না। তাই C report-এর দুইটা সীমা, নামের দৈর্ঘ্য আর নামের ভিতরের space, দুটোই আর নেই।

Example 1: পুরো নাম থেকে নামের আদ্যক্ষর

Maria নিজের note-এ সই করে নামের আদ্যক্ষর দিয়ে। একটা istringstream নামের শব্দগুলো একটা একটা করে দেয়, আর প্রতিটার প্রথম character রেখে পরে একটা dot বসানো হয়।

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

string initials(const string& full_name) {
    istringstream in(full_name);
    string word, result;
    while (in >> word) {
        result += word[0];
        result += '.';
    }
    return result;
}

int main() {
    cout << initials("Maria Lopez Garcia") << '\n';
    cout << initials("  Amara   Okafor ") << '\n';
    cout << "[" << initials("") << "]\n";
    return 0;
}
M.L.G.
A.O.
[]

বাড়তি space-এ কোনো খরচ নেই, কারণ >> ওগুলো টপকে যায়। এখানে word[0] নিরাপদ, কারণ >> কখনো খালি শব্দ দেয় না।

Run in Compiler
Example 2: সামনে শূন্য বসানো ticket code

Alice-এর counter A-007-এর মতো ticket দেয়। setfill('0') দিলে setw space-এর বদলে শূন্য দিয়ে ফাঁক ভরে, আর ostringstream ফলাফলটাকে এমন একটা string বানায়, যেটা রেখে দেওয়া বা জোড়া লাগানো যায়।

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

string ticket_code(char counter, int number) {
    ostringstream out;
    out << counter << '-' << setw(3) << setfill('0') << number;
    return out.str();
}

int main() {
    cout << ticket_code('A', 7) << '\n';
    cout << ticket_code('B', 42) << '\n';
    cout << ticket_code('C', 123) << '\n';
    string both = ticket_code('A', 7) + ", " + ticket_code('A', 8);
    cout << both << " (" << both.size() << " characters)\n";
    return 0;
}
A-007
B-042
C-123
A-007, A-008 (12 characters)

to_string(7) দিত "7", ফাঁক ভরার কোনো উপায় ছাড়া। Stream তোমাকে <iomanip>-এর সব setting দেয়, আর str() text-টা ফেরত দেয়।

Run in Compiler
Example 3: Bob-এর সরানো অক্ষরের সংকেত

Bob বার্তা লুকায় প্রতিটা অক্ষরকে বর্ণমালায় 3 ঘর সরিয়ে, তাই a হয় d, আর z ঘুরে গিয়ে হয় c। কাজটা করে Program 2-এর অক্ষর থেকে index বানানোর কৌশল, আর ঘুরিয়ে আনে % 26।

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

string shift(string text, int k) {
    for (char& c : text) {
        unsigned char u = c;
        if (islower(u)) {
            c = 'a' + (c - 'a' + k) % 26;
        } else if (isupper(u)) {
            c = 'A' + (c - 'A' + k) % 26;
        }
    }
    return text;
}

int main() {
    string secret = shift("Meet Zara at noon", 3);
    cout << secret << '\n';
    cout << shift(secret, 23) << '\n';
    return 0;
}
Phhw Cdud dw qrrq
Meet Zara at noon

আরও 23 ঘর সরালে মোট হয় 26, মানে পুরো এক পাক, তাই বার্তা আগের জায়গায় ফিরে আসে। shift জেনেশুনেই ওর text value হিসেবে নেয়: copy-টা বদলে সেটাই return করে, আর caller-এর string যেমন ছিল তেমনই থাকে।

Run in Compiler

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

  • Git, C-এর সাথে তুলনা। Git লেখা C-এ, তাই ওকে নিজের বড় হতে পারা string নিজেই লিখতে হয়েছে, struct strbuf: একটা buffer, ওর দৈর্ঘ্য len আর ওর জায়গা alloc, শেষে সবসময় একটা '\0'। std::string তোমাকে এটাই দেয়, কোনো খাটুনি ছাড়া।
  • Linux-এর /proc file। /proc/meminfo-এর মতো file memory-র হিসাব দেয় text-এর লাইনে, প্রতিটায় একটা নাম, একটা সংখ্যা আর একটা একক। free command ওই file পড়ে আর প্রতিটা লাইন ভাঙে, মানে একটা আসল system-এ Program 4-এর কাজ।
  • LLVM-এর StringRef। এই compiler project-এর string type-এ একটা split আছে, যেটা একটা separator character নেয়, আর ফেরত দেয় ওটা প্রথম যেখানে মেলে তার আগের অংশ আর বাকিটা। এটা Program 3-এর find আর substr একটা call-এ, text-এর এমন একটা view-এর উপর, যেটাকে lesson 04-এ তুমি দেখবে string_view নামে।

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

১. substr-কে দৈর্ঘ্যের বদলে শেষের জায়গা দেওয়া।

size_t comma = line.find(',', start);
cout << "[" << line.substr(start, comma) << "]\n";

কোনো command line-এই বার্তা নেই। Amara Okafor,85,90,77-এর উপর loop print করেছে [Amara Okafor], তারপর [85,90,77] আর [90,77]। প্রথম field ঠিক এসেছে শুধু এজন্য যে ওটা শুরু হয় 0-তে, যেখানে শেষের জায়গা আর দৈর্ঘ্য একই সংখ্যা। লেখো substr(start, comma - start)। Bob এই ভুলটা করে, কারণ অন্য অনেক ভাষার slice শেষের জায়গা নেয়।

২. সব লাইনের জন্য একটাই istringstream।

istringstream in;
while (getline(cin, line)) {
    in.str(line);
    int x, total = 0;
    while (in >> x) {
        total += x;
    }
    cout << total << '\n';
}

কোনো বার্তা নেই। 1200 3400 800, 5000 আর 2500 2500 লাইনগুলোর জন্য print হয়েছে 5400, তারপর 0 আর 0। প্রথম লাইনের শেষ read-টা fail করেছিল, আর str() stream-কে নতুন text দেয় ঠিকই, কিন্তু সেই fail মুছে দেয় না। Program 4-এর মতো stream-টা loop-এর ভিতরে বানাও। তুমি একটাই রেখে দেবে, কারণ প্রতি লাইনে নতুন object বানানো অপচয় মনে হয়।

৩. >> দিয়ে n পড়ে, তারপর getline দিয়ে প্রথম সারি।

cin >> n;
for (int i = 0; i < n; i++) {
    getline(cin, line);
    istringstream in(line);
    getline(in, name, ',');
    getline(in, field, ',');
    cout << name << ' ' << stoi(field) << '\n';
}

Compile-এর সময় কোনো বার্তা নেই, -Wall -Wextra দিয়েও না। Input 2 আর দুইটা সারি দিয়ে চালালে program থেমেছে এটা লিখে:

terminate called after throwing an instance of 'std::invalid_argument'
  what():  stoi

>> 2-এর পরের newline রেখে গিয়েছিল, তাই প্রথম getline পড়েছে একটা খালি লাইন, আর stoi("")-এর কাছে বদলানোর মতো কোনো সংখ্যাই নেই। cin >> n-এর ঠিক পরে আরেকটা getline দিয়ে লাইনটা শেষ করো, Module 1-এর fast input lesson যেমন দেখিয়েছিল। তুমি এটা ভুলবে, কারণ input দেখে মনে হয় প্রতি লাইনে একটা করে মান।

মাথা খাটাও

Program 3-এর লাইনে প্রতি দুই comma-র মাঝে একটা করে মান ছিল। আসল file এত গোছানো হয় না। এখানে একটা field খালি, আর লাইনটা শেষ হয়েছে একটা comma দিয়ে।

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

int main() {
    istringstream in("a,,b,");
    string field;
    int count = 0;
    while (getline(in, field, ',')) {
        count++;
        cout << count << ": [" << field << "]\n";
    }
    cout << count << " fields\n";
    return 0;
}

এটা কয়টা field print করবে, আর প্রতি জোড়া bracket-এর ভিতরে কী থাকবে? শেষ comma-র পরে কি একটা খালি field আছে, যেভাবে একটা spreadsheet খালি শেষ কলাম দেখাত?

Comma-গুলো গোনো। তারপর ভাবো, stream-এ পড়ার মতো আর কিছুই না থাকলে getline কী করে।

অনুশীলন ১সহজ

Maria-র কাছে একটা text আছে, আর ও জানতে চায় কোন অক্ষর কতবার এসেছে। বড় হাতের আর ছোট হাতের অক্ষর একই অক্ষর হিসেবে গোনা হয়।

Input. এক বা একাধিক লাইনের text, input শেষ না হওয়া পর্যন্ত।

Output. যে অক্ষর এসেছে তার প্রতিটার জন্য এক লাইন, a থেকে z ক্রমে: অক্ষরটা, একটা space, ওর count। কোনো অক্ষরই না এলে none শব্দটা।

Constraints. Text বড়জোর 1000000 byte-এর ASCII।

Sample. Input Hello, World! আর Zara 2026 দিলে নয়টা লাইন: a 2, d 1, e 1, h 1, l 3, o 2, r 2, w 1 আর z 1।

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

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

    vector<int> count(26, 0);
    string line;
    while (getline(cin, line)) {
        // For every letter of the line, turn a capital into a small
        // letter, then add 1 to count[letter - 'a'].
    }

    // For each letter from a to z that appears, print the letter,
    // a space and its count. Print "none" if no letter appears.

    return 0;
}

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

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

Amara-র class file-এর প্রতিটা লাইনে একজন ছাত্রের নাম আর তিনটা নম্বর, মাঝে comma। নামের ভিতরে space থাকতে পারে। ও চায় সবচেয়ে বেশি মোট নম্বর পাওয়া ছাত্রকে।

Input. n, তারপর n-টা লাইন name,mark1,mark2,mark3।

Output. লাইন 1-এ সবচেয়ে ভালো নাম, আর লাইন 2-এ ওর মোট। সমান হলে, ওদের মধ্যে প্রথমজন।

Constraints. 1 <= n <= 20000। নামে 1 থেকে 40টা character: অক্ষর, ভিতরে একটা করে space, কোনো comma নেই, আর দুই মাথায় কোনো space নেই। প্রতিটা mark 0 থেকে 100।

Sample. Input 4, Maria Rose,90,85,77, Bob,100,60,95, Zara Bell,88,95,72 আর Kenji,70,70,70 দিলে দুই লাইনে Bob আর 255।

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

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

    int n = 0;
    cin >> n;
    string line;
    getline(cin, line); // finish the line that held n

    for (int i = 0; i < n; i++) {
        getline(cin, line);

        // Split the line at its commas: a name, then three marks.
        // Keep the name with the highest total; the first one wins a tie.
    }

    // Print the best name on one line and its total on the next.

    return 0;
}

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

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

Zara প্রতিটা লাইন উল্টো দিক থেকে পড়ে, শব্দ ধরে ধরে। Input-এর space-গুলো এলোমেলো, কিন্তু ওর output হতে হবে গোছানো।

Input. n, তারপর n-টা লাইন, প্রতিটায় অক্ষর আর digit-এর 1 থেকে 1000টা word, এক বা একাধিক space দিয়ে আলাদা।

Output. প্রতিটা লাইনের word উল্টো ক্রমে, একটা করে space দিয়ে আলাদা।

Constraints. পুরো input বড়জোর 1000000 byte।

Sample. Input 3, the quick brown fox, Zara tests edge cases আর hello দিলে তিন লাইনে fox brown quick the, cases edge tests Zara আর hello।

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

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

    int n = 0;
    cin >> n;
    string line;
    getline(cin, line); // finish the line that held n

    for (int i = 0; i < n; i++) {
        getline(cin, line);

        // Read the words of the line, then print them from the last
        // to the first, separated by single spaces.
    }

    return 0;
}

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

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

আসল CSV file-এ কোনো field-এ comma থাকলে সেটাকে double quote-এ মুড়ে দেয়, তাই "Okafor, Jr" একটাই field থাকে। Program 3-এর দুইটা split-এর কোনোটাই এটা জানে না। এমন একটা লেখো, যেটা জানে।

Input. Input শেষ হওয়া পর্যন্ত লাইন। Field-গুলোর মাঝে comma। একটা field double quote-এ মোড়া থাকতে পারে, আর তখন ওর ভিতরে comma থাকতে পারে। Quote-এ মোড়া field-এর ভিতরে কোনো quote থাকে না।

Output. প্রতিটা লাইনের জন্য ওর field-গুলো, quote ছাড়া, মাঝে " | "।

Constraints. সবচেয়ে বেশি 1000টা লাইন, প্রতিটা সবচেয়ে বেশি 1000 character, শুধু ASCII।

Sample. Input Amara,"Okafor, Jr",85 আর "Hill Road, 12",David,"red, green" দিলে Amara | Okafor, Jr | 85 আর Hill Road, 12 | David | red, green।

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

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

    string line;
    while (getline(cin, line)) {
        vector<string> fields;
        // Walk the line one character at a time, with a flag that says
        // whether you are inside quotes. A comma inside quotes belongs to
        // the field; a comma outside quotes ends it. Drop the quotes.

        // Print the fields of the line separated by " | ".
    }
    return 0;
}

আলাদা করে গ্রেড হয় না। find বা getline একা এটা পারে না: একটা comma-র মানে নির্ভর করে ওর আগে কী এসেছে তার উপর, তাই loop-কে একটা অবস্থা মনে রাখতে হয়।

Run in Compiler

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

  • Contest-এ কোন split ব্যবহার করব?

    সাধারণত getline(in, field, ','): ছোট, আর ভুল করা কঠিন। Separator এক character-এর বেশি হলে, বা প্রতিটা field কোথায় শুরু সেটা লাগলে, নাও find আর substr।

  • " 85" বা "85 " পেলে stoi কী করে?

    দুটোতেই দেয় 85। stoi শুরুর space টপকে যায়, আর থামে প্রথম এমন character-এ, যেটা সংখ্যার অংশ হতে পারে না। শুধু সামনে কোনো অঙ্কই নেই এমন field, যেমন "" বা "abc", ওকে throw করায়, ভুল ৩-এর মতো।

  • চলতে চলতে print না করে report কেন একটা ostringstream-এ বানাব?

    কারণ output-এর কিছু অংশ নির্ভর করে পরে জানা কথার উপর, যেমন David-এর লাইনের সংখ্যা। একটা string দুইবার print করা যায়, file-এ লেখা যায়, বা size() দিয়ে মাপা যায়। যে output কখনো ধরে রাখতে হবে না, সেটা সরাসরি print করাই ঠিক আছে।

  • Program 6 কি বাংলা নামের কলাম মিলিয়ে দেবে?

    না। setw ফাঁক ভরে byte গুনে, আর lesson 01 দেখিয়েছিল UTF-8-এ একটা বাংলা অক্ষর 3 byte। বাংলা নাম কম ফাঁক পাবে, আর ওর কলামগুলো বাঁ দিকে সরে যাবে। Byte না গুনে অক্ষর গোনে এমন library-র নাম বলবে lesson 04।

মূল কথা

  • while (cin >> word) শব্দ ধরে ধরে পড়ে, আর size() প্রতিটা শব্দের মাপ দেয়; যেকোনো শব্দের মাপে string নিজেই বড় হয়।
  • vector<int> count(26, 0) আর tolower(u) - 'a' অক্ষর গোনে, প্রতিটা char-কে unsigned char-এ cast করার পরে।
  • জায়গা লাগলে ভাঙো find আর substr(start, length) দিয়ে, শুধু field লাগলে getline(in, field, ',') দিয়ে।
  • প্রতি লাইনে একটা নতুন istringstream ওর ভিতরের মানগুলো পড়ে; খালি লাইন থেকে কিছুই আসে না, ব্যস।
  • setw আর setprecision-সহ একটা ostringstream আগে পুরো report বানায়, তারপর str() সেটা একবারে print করে।
  • আরও গভীরে যেতে চাইলে: Under the Hood, ছোট string-এর buffer আর + এর খরচ (Pro)।

এরপর lesson 04 জানতে চায়, কখন string ঠিক জিনিস না, আর ওর বদলে একটা char, একটা string_view বা একটা vector<char> বেছে দিতে আঁকে একটা chart আর একটা flowchart।

lesson ৩ শেষ

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

পরেরটা: string কখন ব্যবহার করবে, আর কখন না

পুরো program: একটা শব্দ থেকে একটা text report পর্যন্ত | Learn C++ STL | Progsity