Learn C++ STL

lesson ২ / ৯ · C থেকে যতটুকু C++ দরকার

Module ১ · C থেকে যতটুকু C++ দরকার

দ্রুত input-output: contest-এর code একই লাইন দিয়ে শুরু হয় কেন

Freeপড়া

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

  • main-এর একদম শুরুতে দ্রুত input-এর লাইন দুইটা লিখতে পারবে, আর প্রতিটা কী বন্ধ করে, বলতে পারবে।
  • while (std::cin >> x) দিয়ে input শেষ না হওয়া পর্যন্ত পড়তে পারবে, আর std::getline দিয়ে পুরো লাইন পড়তে পারবে, রয়ে যাওয়া newline-এর কারণে একটা লাইনও না হারিয়ে।
  • মাপজোখ দেখে std::endl-এর বদলে '\n' বেছে নিতে পারবে, আর sync বন্ধ করার পরে printf আর std::cout মেশানো কেন bug, বুঝিয়ে বলতে পারবে।

Kenji-র program এক মিলিয়ন সংখ্যা পড়ে, আর প্রতিটা আবার print করে। Program-টা ঠিক, তবু judge-এ গিয়ে পায় Time Limit Exceeded। Kenji তো আগেভাগেই optimise করতে ভালোবাসে, ও loop-টা নতুন করে লিখতে বসে যায়। অথচ loop-এ কোনো দোষ নেই। সময় যাচ্ছে পড়া আর print করায়, আর main-এর শুরুর দুইটা লাইন সেটা সারিয়ে দেয়। কতটা সারায়, এই lesson সেটা মেপে দেখায়।

Buffer, sync আর tie

Buffer হলো memory-র একটা টুকরা, যেখানে character-রা অপেক্ষা করে। Program output সেখানে জমায়, তারপর operating system-এর হাতে দেয় বড় বড় খেপে, একটা একটা character করে না। অপেক্ষায় থাকা character-গুলো হাতে তুলে দেওয়াকে বলে flush। Input-ও একই নিয়মে চলে: একটা বড় খেপ ঢোকে, আর read-গুলো সেখান থেকে নেয়।

নিজে থেকে C++ দুইটা কথা দিয়ে রাখে, আর দুইটাতেই সময় খরচ হয়। প্রথমটা হলো sync: std::cin আর std::cout C-এর stdin আর stdout-এর সাথে তাল মিলিয়ে চলে। প্রতিটা character যায় C-এর input-output function-এর ভিতর দিয়ে, তাই printf আর std::cout পালা করে নিরাপদে লিখতে পারে। এই নিরাপত্তার দাম তুমি দাও প্রতিটা character-এ, printf একবারও call না করলেও।

দ্বিতীয়টা হলো tie: std::cin বাঁধা থাকে std::cout-এর সাথে, তাই প্রতিটা read-এর আগে std::cout flush হয়। Keyboard-এর সামনে এতে সুবিধা আছে: program অপেক্ষা শুরু করার আগেই "Enter n:"-এর মতো প্রশ্নটা screen-এ চলে আসে। কিন্তু judge কোনো প্রশ্ন পড়ে না।

দ্রুত input-এর লাইন দুইটা

std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
  • std::ios::sync_with_stdio(false); sync বন্ধ করে, তখন C++ stream-গুলো নিজেদের আলাদা buffer পায়।
  • std::cin.tie(nullptr); বাঁধনটা কেটে দেয়, তখন read-এর আগে আর std::cout flush হয় না। nullptr হলো C++-এর null pointer, C-এর NULL-এর type-ওয়ালা সঙ্গী।
  • দুইটাই বসে main-এর একদম শুরুতে, কোনো input বা output-এর আগে।
  • দাম: এরপর থেকে program শুধু std::cin আর std::cout ব্যবহার করে, scanf বা printf আর কখনো না।
Example 1: contest-এর কাঠামো

Track-এর বাঁধা গড়নে সবচেয়ে ছোট program: লাইন দুইটা, পড়া, হিসাব, তারপর '\n' দিয়ে print।

#include <iostream>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int price = 0;
    int count = 0;

    std::cin >> price >> count;
    std::cout << price * count << '\n';
    return 0;
}
100

ওই output-টা input 25 4-এর জন্য। এই module-এর প্রতিটা গ্রেড হওয়া problem এই গড়ন দিয়েই শুরু হয়।

Run in Compiler

তাই প্রথম লাইনটা C++ stream-দের নিজের buffer দেয়, আর দ্বিতীয়টা প্রতিটা read-এর আগের flush থামিয়ে দেয়।

std::endl মানে '\n', সাথে একটা flush

std::endl একটা newline পাঠায়, তারপর std::cout flush করে। '\n' শুধু newline-টা পাঠায়। বাইরে যে character-গুলো যায়, সেগুলো একই।

#include <iostream>

int main()
{
    std::cout << "first line" << std::endl;
    std::cout << "second line" << '\n';
    return 0;
}
first line
second line

Output দেখে flush ধরা যায় না, ধরা যায় শুধু সময়ে। প্রতিটা flush মানে operating system-এর কাছে একবার যাওয়া, আর প্রতি লাইনে একবার করে গেলে সেটা জমে পাহাড় হয়। std::endl ঠিক তখনই কাজের, যখন একটা লাইন এখনই বের হওয়া দরকার। যেমন, একটা interactive judge তোমার প্রশ্নের জন্য বসে আছে। আরেকটা হলো এমন একটা লাইন, যেটা পরের মুহূর্তে program crash করলেও তোমাকে দেখতেই হবে। তাই output হুবহু একই; std::endl শুধু বদলায় লাইনটা কখন বের হবে।

মেপে দেখা: এক মিলিয়ন সংখ্যা ঢোকা আর বের হওয়া Intermediate

চারটা program n = 1,000,000 পড়ে, তারপর এক মিলিয়ন পূর্ণসংখ্যা, আর প্রতিটা আবার print করে নিজের লাইনে। ওদের মধ্যে তফাত শুধু লাইন দুইটায় আর লাইন শেষ করার ধরনে। নিচে সবচেয়ে দ্রুতটা, কাজের চারপাশে একটা timer বসানো।

#include <chrono>
#include <cstdio>
#include <iostream>

int main()
{
    std::freopen("out.txt", "w", stdout);
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n = 0;

    auto start = std::chrono::steady_clock::now();
    std::cin >> n;
    for (int i = 0; i < n; i++) {
        int x = 0;
        std::cin >> x;
        std::cout << x << '\n';
    }
    std::cout.flush();
    auto stop = std::chrono::steady_clock::now();

    long long ms = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
    std::FILE* f = std::fopen("out.txt", "r");
    std::fseek(f, 0, SEEK_END);
    std::cerr << n << " numbers, " << std::ftell(f) << " bytes written, " << ms << " ms\n";
    return 0;
}

তিনটা খুঁটিনাটি বলে রাখা দরকার। Compiler Explorer 32 KB output-এর পরে program থামিয়ে দেয়, তাই stream চালু হওয়ার আগেই std::freopen stdout-কে একটা file-এ পাঠিয়ে দেয়, out.txt-এ। std::chrono-এর লাইনগুলো loop-এর আগে আর পরে একটা ঘড়ি দেখে; auto লম্বা type-টা compiler-কে দিয়ে লিখিয়ে নেয়, আর সেটা বুঝিয়ে বলবে lesson 04। শেষের লাইনগুলো file-টা মাপে, আর ফলাফল print করে stderr-এ, মানে error stream-এ।

বাকি তিনটা program হয় লাইন দুইটা বাদ দেয়, নয়তো '\n'-কে std::endl বানায়, নয়তো দুইটাই করে। প্রতিটা একবার করে চালানো হয়েছে Compiler Explorer-এ, x86-64 GCC 12.2-এ, runner-এর command line দিয়ে: g++ -O2 -std=c++17। Input-এ ছিল 0 থেকে 99-এর মধ্যে এক মিলিয়ন পূর্ণসংখ্যা, প্রতি লাইনে দশটা, সব মিলিয়ে 2,893,323 byte। প্রতিটা program লিখেছে একই 2,893,315 byte।

Programলাইন দুইটালাইনের শেষেসময়, একবার চালিয়ে
Aনাstd::endl750 ms
Bনা'\n'488 ms
Cহ্যাঁstd::endl515 ms
Dহ্যাঁ'\n'58 ms
এক মিলিয়ন পূর্ণসংখ্যা পড়া আর print করা: মাপা চারটা সময় 1,000,000 পূর্ণসংখ্যা পড়া আর print করা (bar যত ছোট, তত দ্রুত) A: সাধারণ, std::endl 750 ms B: সাধারণ, '\n' 488 ms C: লাইন দুইটা, std::endl 515 ms D: লাইন দুইটা, '\n' 58 ms Compiler Explorer, x86-64 GCC 12.2, g++ -O2 -std=c++17 (runner-এর flag), প্রতিটা একবার চালিয়ে। ঢুকেছে: 0 থেকে 99-এর 1,000,000 পূর্ণসংখ্যা, 2,893,323 byte। বের হয়েছে: 2,893,315 byte, একটা file-এ। Bar-এর দৈর্ঘ্যই সময়: 440 px মানে 750 ms।

D-তে দুইটা বদলই আছে, আর এটা নিয়েছে 58 ms, যেখানে A নিয়েছে 750 ms: প্রায় 13 গুণ দ্রুত। মাঝের দুইটা দেখায়, কেন দুইটাই লাগে। C-তে লাইন দুইটা আছে, কিন্তু std::endl প্রতি লাইনে একবার flush করে: operating system-এর কাছে এক মিলিয়ন বার যাওয়া। B-তে লাইনের শেষে '\n', কিন্তু tie তখনো প্রতিটা read-এর আগে std::cout flush করে, আর program প্রতি লাইনেই একবার পড়ে।

লাইন দুইটার কাজ আলাদা করে দেখতে, একই site-এ একই input দিয়ে প্রতিটা আরও পাঁচবার চালানো হয়েছে। শুধু sync-এর লাইন, সাথে '\n', নিয়েছে 433 থেকে 2,158 ms, কারণ tie তখনো প্রতিটা read-এর আগে flush করে। শুধু tie-এর লাইন নিয়েছে 128 থেকে 193 ms: প্রতি read-এ flush নেই, কিন্তু প্রতিটা character তখনো C-এর function-এর ভিতর দিয়ে যায়। তিনটা বদল একসাথে থাকলেই কেবল পড়া আর লেখা দুইটাই চলে বড় বড় খেপে।

এই run-গুলো একটা ব্যস্ত, ভাগাভাগি করা মেশিনে, আর Playground-এর runner আরেকটা মেশিন। আবার চালালে A, B আর C কয়েকশো millisecond করে সরে গেছে, এমনকি নিজেদের মধ্যে ক্রমও বদলেছে, অথচ D থেকেছে 58 থেকে 66 ms-এ। ভরসা রাখো D-এর এগিয়ে থাকায়, ঠিক কত millisecond তাতে না। তাই লাইন দুইটা আর '\n' একসাথে চলে: তিনটার যেকোনো একটা বাদ দিলে program অন্তত দুই গুণ ধীর হয়েছে, আর বেশিরভাগ run-এ সাত গুণেরও বেশি।

Input শেষ না হওয়া পর্যন্ত পড়া: while (std::cin >> x)

অনেক input বলেই না কয়টা মান আসছে। তখন input ফুরানো পর্যন্ত পড়ে যাও। std::cin >> x ফেরত দেয় std::cin-কেই, আর lesson 01-এর শিকলগুলো এভাবেই কাজ করে। একটা stream-কে শর্ত হিসেবে বসালে সেটা ততক্ষণ সত্য, যতক্ষণ তার শেষ read সফল হয়েছে।

তাই while (std::cin >> x) একটা মান পড়ে, read কাজ করল কি না দেখে, আর body চালায় শুধু সেই মানের জন্য, যেটা সত্যিই এসেছে। Read ব্যর্থ হয় input শেষ হলে, বা এমন কিছু এলে যেটা সংখ্যা না। তখন loop থেমে যায়। Zara ওর তাপমাত্রার খাতা থেকে গোনে কয় দিন শূন্যের নিচে ছিল।

Loop-টা 4, -2, 7 আর -5 পড়ে, আর শূন্যের নিচের দুইটা গোনে। পঞ্চম read input-এর শেষে গিয়ে ব্যর্থ হয়, তাই loop শেষ হয় below = 2 নিয়ে।

Example 2: Zara-র শূন্যের নিচের দিনগুলো

দুই লাইনের মাঝের newline >>-এর কাছে আরেকটু space ছাড়া কিছু না, তাই loop সোজা ওটার উপর দিয়ে পড়ে যায়।

#include <iostream>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int x = 0;
    int below = 0;

    while (std::cin >> x) {
        if (x < 0) {
            below++;
        }
    }
    std::cout << "days below zero: " << below << '\n';
    return 0;
}
days below zero: 2

ওই output-টা দুই লাইনের input 4 -2 আর 7 -5-এর জন্য। Zara আগে খালি input দিয়ে চালায়: প্রথম read-ই ব্যর্থ হয়, body একবারও চলে না, আর program print করে days below zero: 0।

Run in Compiler

Keyboard থেকে দিলে input তোমাকেই শেষ করতে হয়: Linux আর macOS-এ Ctrl+D, Windows-এ Ctrl+Z, তারপর Enter। Playground-এ input শেষ হয় input box-এর লেখা যেখানে শেষ, সেখানে। তাই যতগুলো সংখ্যা এসেছে, loop ঠিক ততবার চলে, আর যে read ব্যর্থ হয়, সেটাই loop শেষ করে।

পুরো লাইন: std::getline আর রয়ে যাওয়া newline

>> প্রতিটা space-এ থেমে যায়, তাই একটা বইয়ের নাম পুরোটা পড়তে পারে না। std::getline(std::cin, line) পরের newline পর্যন্ত প্রতিটা character একটা std::string-এ পড়ে, space-সহ। Newline-টা নিজে ফেলে দেয়। খালি লাইন দিলে পাওয়া যায় খালি string।

>>-এর মতো এটাও stream-টা ফেরত দেয়, তাই while (std::getline(std::cin, line)) input শেষ না হওয়া পর্যন্ত লাইন ধরে ধরে পড়ে। এবার ফাঁদটা। Maria-র library-র input-এ প্রথম লাইনে একটা সংখ্যা, পরের লাইনে বইয়ের নাম।

#include <iostream>
#include <string>

int main()
{
    int copies = 0;
    std::string title;

    std::cin >> copies;
    std::getline(std::cin, title);
    std::cout << "copies: " << copies << '\n';
    std::cout << "title: [" << title << "]\n";
    return 0;
}
copies: 3
title: []

ওই output-টা দুই লাইনের input 3 আর The Little Prince-এর জন্য। বইয়ের নাম খালি! >> 3-এর ঠিক পরেই থেমেছে, আর ওই লাইনের newline-টা stream-এ রেখে গেছে। তারপর std::getline পড়েছে ওই newline পর্যন্ত: একটা খালি লাইন, যেটা আসলে প্রথম লাইনেরই বাকি অংশ।

সমাধান হলো, পরের লাইন পড়ার আগে সংখ্যার লাইনটা শেষ করে নেওয়া। এমন একটা string-এ আরেকবার std::getline, যেটা তুমি কখনো ব্যবহার করবে না, কাজটা করে দেয়, আর সংখ্যার পরের space থাকলে সেগুলোও খেয়ে ফেলে।

Example 3: Maria-র library-র বইয়ের নাম

Maria আগে পড়ে কয়টা নাম আসছে, তারপর প্রতিটা নাম আলাদা লাইনে, আর প্রতিটা print করে তার ক্রম আর দৈর্ঘ্যসহ।

#include <iostream>
#include <string>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n = 0;
    std::string rest;
    std::string title;

    std::cin >> n;
    std::getline(std::cin, rest);
    for (int i = 1; i <= n; i++) {
        std::getline(std::cin, title);
        std::cout << i << ". " << title << " (" << title.size() << " characters)\n";
    }
    return 0;
}
1. The Little Prince (17 characters)
2. Matilda (7 characters)
3. Treasure Island (15 characters)

ওই output-টা input 3, তারপর তিন লাইনে তিনটা নামের জন্য। title.size() হলো string-এ কয়টা character আছে, space-ও গোনা হয়। rest-এর লাইনটা মুছে দাও, নামগুলো এক ঘর করে সরে যাবে: 1 নম্বর নাম আসবে খালি।

Run in Compiler

তাই >> কোনো লাইনের শেষ সংখ্যাটা পড়ার পরে একটা std::getline সেই লাইনটা শেষ করে, আর পরেরটা পড়ে আসল লাইন।

একটা লাইনের ভিতর দিয়ে character ধরে ধরে হাঁটা

একটা std::string পড়া যায় C-এর char array-এর মতো করে। line.size() বলে এতে কয়টা character আছে, আর line[i] হলো index i-এর character, 0 থেকে শুরু। একটা লাইনের শব্দগুলো খুঁজে বের করতে এটুকুই যথেষ্ট, C-তে যেভাবে করতে।

এখানে শব্দ মানে এমন character-এর একটা টানা সারি, যেগুলো space না। index i-তে একটা শব্দ শুরু হয়, যখন line[i] space না, আর হয় i হলো 0, নয়তো line[i - 1] একটা space। এই পরীক্ষা পরপর দুই-তিনটা space-কে পাত্তা দেয় না, লাইনের শুরু বা শেষের space-কেও না।

Example 4: David-এর বইয়ের আদ্যক্ষর

David ওর পড়ার তালিকার প্রতিটা নাম ছোট করে আদ্যক্ষরে নিয়ে আসে। ও input শেষ না হওয়া পর্যন্ত লাইন ধরে ধরে পড়ে।

#include <iostream>
#include <string>

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

    while (std::getline(std::cin, line)) {
        std::string initials;
        for (int i = 0; i < (int)line.size(); i++) {
            bool starts = line[i] != ' ' && (i == 0 || line[i - 1] == ' ');
            if (starts) {
                initials += line[i];
            }
        }
        std::cout << initials << '\n';
    }
    return 0;
}
tlotr
atwied

atotc

ওই output-টা চার লাইনের input-এর জন্য: the lord of the rings, তারপর বাড়তি space-সহ around the world in eighty days, তারপর একটা খালি লাইন, তারপর a tale of two cities। খালি লাইনটার জন্যও loop একবার চলেছে, আর একটা খালি লাইন print করেছে। initials += line[i] একটা string-এর শেষে একটা character জুড়ে দেয়। (int) cast-টা i-এর type-এর সাথে মেলায়, কারণ size() দেয় একটা unsigned গোনা।

Run in Compiler

তাই একটা লাইন আসলে character-এর একটা array, যেটার উপর দিয়ে হাঁটা যায়, আর শব্দ শুরু হয় যেখানে space বা লাইনের শুরুর পরে একটা non-space আসে।

Sync বন্ধ করার পরে printf আর std::cout

Bob এখনো scanf আর printf লেখে, আর মাঝে মাঝে অভ্যাসের বশে লাইন দুইটাও বসিয়ে দেয়। Sync বন্ধ থাকলে দুই পরিবারই নিজের নিজের buffer রাখে, আর ওদের ক্রম ঠিক রাখার দায় কারও না।

#include <cstdio>
#include <iostream>

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

    std::printf("1. printf\n");
    std::cout << "2. cout\n";
    std::printf("3. printf\n");
    std::cout << "4. cout\n";
    return 0;
}

দুইটা system কী print করেছে, দেখো। কোনোটাই code-এর ক্রমে না।

কোথায় চলেছেলাইনগুলোর ক্রম
Compiler Explorer, Linux-এ x86-64 GCC 12.2, runner-এর flag, তিনবার চালিয়েপ্রতিবার 2, 4, 1, 3
একটা Windows মেশিন, MinGW-w64 GCC 14.2, g++ -O2 -std=c++17, output একটা pipe-এ, দুইবার চালিয়েদুইবারই 1, 3, 2, 4

Program শেষ হলে প্রতিটা buffer খালি হয়, আর কোনটা আগে খালি হবে, সেটা নির্ভর করে নিচের library-র উপর। তাই একই program তোমার মেশিনে ঠিক দেখাতে পারে, আর judge-এ গিয়ে উল্টাপাল্টা হয়ে যেতে পারে। নিয়ম হলো, এক program-এ এক পরিবার। scanf আর printf চাইলে সবখানে ওদেরই ব্যবহার করো, আর লাইন দুইটা বাদ দাও; ওরা এমনিতেই দ্রুত। তাই sync একবার বন্ধ হলে মেশানো output-এর ক্রম আর তোমার হাতে থাকে না।

String-এর ভিতরের সংখ্যা: std::istringstream-এর দিকে একটা ইশারা

কখনো কখনো যে সংখ্যাগুলো দরকার, সেগুলো আগে থেকেই একটা std::string-এর ভিতরে থাকে, যেমন std::getline-এর আনা একটা লাইন। std::istringstream, যেটা আসে <sstream> header থেকে, এমন একটা stream, যেটা stdin-এর বদলে একটা string থেকে পড়ে। >> এর উপর ঠিক std::cin-এর মতোই কাজ করে।

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::string prices = "12 7 30";
    std::istringstream in(prices);
    int x = 0;

    while (in >> x) {
        std::cout << "price " << x << '\n';
    }
    return 0;
}
price 12
price 7
price 30

String stream ঠিকমতো শেখাবে Module 3। আপাতত এটুকু মনে রাখো: std::cin-এর জন্য যে >> loop লিখতে পারো, সেটা একটা string-এর উপরেও চলে।

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

  • Codeforces-এর interactive problem। এদের statement বলে দেয়, প্রতিটা query-র পরে flush করতে হবে, fflush(stdout) বা cout.flush() দিয়ে। Buffer-এ পড়ে থাকা query judge পর্যন্ত পৌঁছায়ই না, আর run শেষ হয় Idleness limit exceeded দিয়ে।
  • USACO Guide। C++-এর দ্রুত input-output নিয়ে এদের page এই lesson-এর লাইন দুইটাই লিখতে বলে, দ্বিতীয়টা cin.tie(0) হিসেবে।
  • nginx। এই web server নিজের access log লেখে নিজস্ব buffer দিয়ে, printf বা std::cout দিয়ে না। এর access_log directive buffer= আর flush= setting নেয়: '\n' বনাম std::endl-এর সেই একই দর-কষাকষি।

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

১. eof() দেখে loop চালানো।

while (!std::cin.eof()) {
    std::cin >> x;
    if (x < 0) {
        below++;
    }
}

কোনো command line-এই বার্তা নেই। দুই লাইনের input 4 -2 আর 7 -5 দিলে Compiler Explorer-এ এটা print করেছে days below zero: 3, একটা বেশি। -5 পড়ার পরেও শেষের newline-টা পড়া বাকি, তাই eof() তখনো মিথ্যা। পরের read ব্যর্থ হয়, x-এ -5-ই থেকে যায়, আর body ওটাকে আবার গোনে। Read-টা শর্তের ভিতরে বসাও: while (std::cin >> x)। তুমি eof()-ওয়ালা রূপটাই লিখবে, কারণ ওটা শুনতে ইংরেজি বাক্যের মতো।

২. বড় loop-এ std::endl।

for (int i = 0; i < n; i++) {
    std::cin >> x;
    std::cout << x << std::endl;
}

কোনো command line-এই বার্তা নেই, খরচ শুধু সময়ে। উপরের মাপজোখে লাইন দুইটাসহ এই loop নিয়েছে 515 ms; '\n' দিলে নিয়েছে 58 ms। লেখো '\n', আর কোনো লাইন এখনই বের হওয়া দরকার হলে জেনেবুঝে flush করো। তুমি std::endl-এর দিকে হাত বাড়াবে, কারণ পুরনো বই আর অনেক tutorial প্রতিটা লাইন ওটা দিয়েই শেষ করে।

৩. লাইন দুইটার পরে Bob-এর scanf।

std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> a;
std::scanf("%d", &b);

কোনো command line-এই বার্তা নেই। Input 3 4 দিলে Compiler Explorer print করেছে a = 3, b = 0। প্রথম >> পুরো input-টাই std::cin-এর নিজের buffer-এ তুলে নিয়েছে, তাই scanf পড়ার মতো কিছুই পায়নি। এক program-এ এক পরিবার রাখো। যখনই একটা scanf লাইন বদলানোর চেয়ে টাইপ করা সহজ মনে হবে, Bob ঠিক এটাই করবে।

মাথা খাটাও

Kenji একটা দলের মোট score খেলোয়াড়দের মধ্যে ভাগ করে।

#include <iostream>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int total = 0;
    int players = 0;

    std::cin >> total >> players;
    std::cout << "total score: " << total << '\n';
    std::cout << "per player: " << total / players << '\n';
    return 0;
}

Zara ওটাকে input দেয় 120 0। Compiler Explorer-এ, runner-এর flag-এ, program থেমেছে SIGFPE signal দিয়ে, আর কিছুই print করেনি। অথচ প্রথম লাইনটা crash-এর আগেই ঠিকঠাক হিসাব হয়ে গিয়েছিল। Program যখন মারা গেল, total score: 120 তখন কোথায় ছিল? শুধু ওই লাইনের '\n'-কে std::endl বানাও: এবার output-এ কী দেখবে বলে মনে হয়, আর কেন?

"Buffer, sync আর tie" অংশটা আবার পড়ো। <<-এর পরে একটা লাইন কোথায় অপেক্ষা করে, আর program স্বাভাবিকভাবে শেষ হলে সেই জায়গা খালি করে কে?

অনুশীলন ১সহজ

Zara-র step counter বলে না কয়টা reading জমিয়েছে। শুধু ঢেলে দেয়, কিছু এক লাইনে, কিছু পরের লাইনে, যতক্ষণ না ফুরায়। Zara দুইটা সংখ্যা চায়: সব reading-এর যোগফল, আর মোট কয়টা।

Input. শূন্য বা তার বেশি পূর্ণসংখ্যা, space আর newline দিয়ে আলাদা, input শেষ হওয়া পর্যন্ত।

Output. এক লাইনে দুইটা পূর্ণসংখ্যা, মাঝে একটা space: সবগুলোর যোগফল, তারপর মোট কয়টা। কোনো পূর্ণসংখ্যাই না থাকলে print করো 0 0।

Constraints. সর্বোচ্চ 80000টা পূর্ণসংখ্যা। প্রতিটা -1000000000 থেকে 1000000000-এর মধ্যে। Time limit: প্রতিটা test-এ 1 সেকেন্ড।

Sample. দুই লাইনের input 3 5 আর -2 দিলে 6 3: 3 + 5 + (-2) = 6, তিনটা পূর্ণসংখ্যা থেকে।

#include <iostream>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    // Read integers until the input ends. Print their sum and how many
    // there were, on one line, separated by one space.

    return 0;
}

sum-until-end নামে গ্রেড হয়। Hidden test-এ আছে একটা খালি input, শুধু ফাঁকা লাইনের একটা input, আর 80000টা পূর্ণসংখ্যা।

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

রচনা জমা দেওয়ার আগে Alice মিলিয়ে নেয় সেটা কত লম্বা হলো। একটাই std::getline loop দিয়ে ওর লাইন আর শব্দ গোনো।

Input. শূন্য বা তার বেশি লাইনের লেখা, input শেষ হওয়া পর্যন্ত। কোনো লাইন খালি হতে পারে, বা তাতে শুধু space থাকতে পারে।

Output. এক লাইনে দুইটা পূর্ণসংখ্যা, মাঝে একটা space: লাইনের সংখ্যা, তারপর শব্দের সংখ্যা। শব্দ মানে space নয় এমন character-এর একটা টানা সারি।

Constraints. সর্বোচ্চ 1000টা লাইন, প্রতিটায় সর্বোচ্চ 1000টা character, কোনো tab নেই।

Sample. তিনটা লাইন the cat sat, একটা খালি লাইন আর on the mat দিলে 3 6।

#include <iostream>
#include <string>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::string line;
    int lines = 0;
    int words = 0;

    // Read the input line by line with std::getline until it ends.
    // Count every line, and every word inside every line.

    std::cout << lines << ' ' << words << '\n';
    return 0;
}

আলাদা করে গ্রেড হয় না। std::getline দিয়ে লাইন পড়া গ্রেড করে words-per-line, অনুশীলন ৩।

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

David ওর notes-এর জন্য একটা index বানাচ্ছে। ওর দরকার প্রতিটা শব্দ, সাথে সেটা যে লাইনে আছে তার নম্বর। কিছু লাইন খালি, আর কিছু লাইনে বাড়তি space আছে, যেখানে ও space bar দুইবার চেপে ফেলেছে।

Input. প্রথম লাইনে একটা পূর্ণসংখ্যা n। তারপর n-টা লেখার লাইন। কোনো লাইন খালি হতে পারে, space দিয়ে শুরু বা শেষ হতে পারে, আর দুইটা শব্দের মাঝে কয়েকটা space থাকতে পারে।

Output. প্রতিটা শব্দের জন্য output-এর একটা লাইন, শব্দগুলো যে ক্রমে আসে সেই ক্রমে। প্রতিটায় থাকে শব্দটা লেখার যে লাইনে আছে তার নম্বর (প্রথম লেখার লাইন 1), একটা space, তারপর শব্দটা। যে লেখার লাইনে কোনো শব্দ নেই, সেটা কিছুই print করে না। পুরো লেখায় কোনো শব্দই না থাকলে কিছুই print কোরো না।

Constraints. 1 <= n <= 1000। প্রতিটা লেখার লাইনে সর্বোচ্চ 1000টা character: অক্ষর, অঙ্ক, যতিচিহ্ন আর space, কোনো tab নেই। Time limit: প্রতিটা test-এ 1 সেকেন্ড।

Sample. Input 3, তারপর the cat sat, একটা খালি লাইন আর on the mat দিলে ছয়টা লাইন: 1 the, 1 cat, 1 sat, 3 on, 3 the আর 3 mat। লেখার লাইন 2 খালি, তাই সেটা কিছু print করে না; লাইন 3-এর কোনো space-এর সারিই খালি শব্দ বানায় না।

#include <iostream>
#include <string>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n = 0;
    std::cin >> n;

    // Read the n lines of text that follow with std::getline.
    // For every word, print the number of its line (1 to n),
    // one space and the word, one word per output line.

    return 0;
}

words-per-line নামে গ্রেড হয়। Hidden test-এ আছে খালি বা শুধু space-ওয়ালা লেখার লাইন, শেষে newline ছাড়া একটা শেষ লাইন, আর 1000টা খালি লাইন।

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

প্রতি সন্ধ্যায় Amara-র বেকারির প্রতিটা শাখা বিক্রির একটা লাইন পাঠায়। তাতে প্রতিটা বিক্রির জন্য একটা পূর্ণসংখ্যা, যতগুলো বিক্রি হয়েছে ততগুলো, আর ফেরত দেওয়া টাকা negative সংখ্যা হিসেবে। যে শাখা কিছুই বিক্রি করেনি, সে পাঠায় একটা খালি লাইন। শাখা কয়টা, বা কোন লাইনে কয়টা বিক্রি, কেউ বলে না।

Input. এক বা তার বেশি লাইন, input শেষ হওয়া পর্যন্ত। প্রতিটা লাইনে শূন্য বা তার বেশি পূর্ণসংখ্যা, space দিয়ে আলাদা। প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

Output. Input-এর প্রতিটা লাইনের জন্য একটা লাইন, একই ক্রমে, তাতে সেই লাইনের যোগফল (খালি লাইনের জন্য 0)। তারপর একটা শেষ লাইন: total শব্দটা, একটা space, আর input-এর সব পূর্ণসংখ্যার যোগফল।

Constraints. 1 থেকে 20000টা লাইন। পুরো input-এ সর্বোচ্চ 80000টা পূর্ণসংখ্যা। প্রতিটা -1000000000 থেকে 1000000000-এর মধ্যে। Time limit: প্রতিটা test-এ 1 সেকেন্ড।

Sample. চারটা লাইন 120 80 -20, 45, একটা খালি লাইন আর 7 7 7 দিলে পাঁচ লাইনে 180, 45, 0, 21 আর total 246।

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    // Read the input line by line until it ends. Print the total of each
    // line on its own line, then one last line: the word total, one
    // space, and the total of every number in the input.

    return 0;
}

line-totals নামে গ্রেড হয়। Hidden test-এ আছে একটা খালি লাইন, শুধু space-ওয়ালা একটা লাইন, আর 20000টা লাইন।

Run in Compiler

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

  • scanf কি std::cin-এর চেয়ে দ্রুত?

    লাইন দুইটা না থাকলে, হ্যাঁ। থাকলে আর তেমন না। একই এক মিলিয়ন সংখ্যায় একটা scanf আর printf রূপ Compiler Explorer-এ একবার চালিয়ে নিয়েছে 95 ms, যেখানে program D নিয়েছে 58 ms। পুরো program এক পরিবার ব্যবহার করলে যেকোনোটাই চলে।

  • while (std::cin >> x) শেষ হওয়ার পরে x-এ কী থাকে?

    ওটার উপর ভরসা কোরো না। Compiler Explorer-এ input শেষ হওয়ার পরে x-এ তখনো ছিল শেষ সংখ্যাটা, -5। Input-এ একটা সংখ্যার জায়গায় snow শব্দটা থাকলে, ব্যর্থ read x-এ 0 রেখে দিয়েছে। মানগুলো loop-এর ভিতরেই ব্যবহার করো।

  • রয়ে যাওয়া newline-টা std::cin >> std::ws দিয়ে সারালে হয় না?

    std::ws সব whitespace পার হয়ে যায়, তাই পরের লাইনে লেখা থাকলে এটা কাজ করে। কিন্তু এটা খালি লাইনগুলোও পার হয়ে যায়, পরের লাইনের শুরুর space-গুলোও। খালি লাইন যেখানে গোনায় ধরা হয়, যেমন David-এর index-এ, সেখানে এটা ওগুলো হারিয়ে ফেলে। ফেলে দেওয়ার জন্য রাখা std::getline ঠিক একটা লাইনই শেষ করে।

মূল কথা

  • std::ios::sync_with_stdio(false); C++ stream-দের নিজের buffer দেয়, আর std::cin.tie(nullptr); প্রতিটা read-এর আগের flush থামায়।
  • std::endl মানে '\n', সাথে একটা flush: লেখো '\n', আর কোনো লাইন এখনই বের করা দরকার হলে জেনেবুঝে flush করো।
  • Compiler Explorer-এ প্রতিটা একবার মেপে: এক মিলিয়ন সংখ্যা ঢোকা আর বের হওয়ায় কোনো বদল ছাড়া 750 ms, দুইটা বদলসহ 58 ms।
  • while (std::cin >> x) যতগুলো মান এসেছে ততবার চলে, আর শেষ হয় যে read ব্যর্থ হয় সেটায়।
  • std::getline পুরো একটা লাইন পড়ে; একটা >>-এর পরে আগে সেই লাইনটা শেষ করো।
  • Sync একবার বন্ধ হলে printf বা scanf-কে std::cout বা std::cin-এর সাথে কখনো মিশিও না।

এরপর pointer দিয়ে লেখা Maria-র swap হয়ে যায় একটাও star ছাড়া swap, আর lesson 03 বুঝিয়ে বলে সেই &-এর কথা, যেটা এখন নতুন একটা মানে নিয়েছে।

lesson ২ শেষ

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

পরেরটা: reference: একই বাক্সের দ্বিতীয় নাম

দ্রুত input-output: contest-এর code একই লাইন দিয়ে শুরু হয় কেন | Learn C++ STL | Progsity