Learn C++ STL

lesson ১ / ৯ · stack আর queue: শেষে ঢুকলে আগে বেরোয়, আগে ঢুকলে আগে বেরোয়

Module ৬ · stack আর queue: শেষে ঢুকলে আগে বেরোয়, আগে ঢুকলে আগে বেরোয়

stack আর queue: আগে বেরোয় সবচেয়ে নতুনটা, নাকি সবচেয়ে পুরোনোটা

Freeপড়া

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

  • একটা std::stack আর একটা std::queue declare করে দুটোতেই push করতে, দেখতে আর pop করতে পারবে, আর বলতে পারবে কোনটা কোন element আগে ফেরত দেয়।
  • Adaptor কী, বুঝিয়ে বলতে পারবে: সরু দরজার একটা container, যার ভিতরে একটা deque, আর তুমি হাত দিতে পারো শুধু সেই মাথাগুলোয়, যেগুলো কাজের জন্য লাগে।
  • আগে পড়ো, তারপর pop: দুই লাইনের এই idiom লিখতে পারবে, কারণ pop() কিছুই ফেরত দেয় না; আর প্রতিবার দেখা বা pop-এর আগে empty() check করবে।

Bob একটা paragraph টাইপ করে, একটা শব্দ bold করে, তারপর ভুল করে একটা লাইন মুছে ফেলে। Undo চাপলে editor-কে আগে ফেরাতে হবে সবচেয়ে নতুন বদলটা, ঠিক থালার স্তূপের উপরের থালাটার মতো। Alice-এর বেকারির কাজ উল্টো: যে আগে এসেছে, সে আগে জিনিস পায়, মানে একটা লাইন। Vector বা deque দিয়ে দুটো কাজই চলত, কিন্তু দুটোই এমন কিছু নড়াচড়া করতে দেয়, যা এই কাজে কখনো হওয়া চলবে না। এই lesson প্রতিটা কাজকে এমন একটা container দেবে, যেটা শুধু ঠিক নড়াচড়াগুলোই করতে দেয়।

দুইটা কাজ, দুই রকম ক্রম

Bob-এর undo-র তালিকা আর Alice-এর লাইনে একই ধরনের জিনিস থাকে: আসার ক্রমে কয়েকটা item। তফাত শুধু একটাই, কোনটা আগে বেরোয়। Bob-এর তালিকায় আগে বেরোয় সবচেয়ে নতুনটা, এই ক্রমের নাম LIFO: last in, first out, শেষে ঢুকলে আগে বেরোয়। Alice-এর লাইনে আগে বেরোয় সবচেয়ে পুরোনোটা, এই ক্রমের নাম FIFO: first in, first out, আগে ঢুকলে আগে বেরোয়।

Stack হলো LIFO ক্রমের container, আর queue (উচ্চারণ "কিউ") হলো FIFO ক্রমের container। Stack-এর খোলা মাথা মাত্র একটা, তার নাম top। Queue-এর খোলা মাথা দুইটা: জিনিস ঢোকে back দিয়ে, মানে পেছনে, আর বেরোয় front দিয়ে, মানে সামনে।

Stack-এর খোলা মাথা একটা, queue-এর দুইটা stack: খোলা মাথা একটা queue: খোলা মাথা দুইটা 1 2 3 top push pop সবচেয়ে নতুনটা আগে বেরোয়: last in, first out (LIFO) 101 102 103 front back pop push সবচেয়ে পুরোনোটা আগে বেরোয়: first in, first out (FIFO)

তাই যেকোনো কাজকে একটাই প্রশ্ন করো: আগে কে বেরোবে, সবচেয়ে নতুনটা, নাকি সবচেয়ে পুরোনোটা? নতুনটা হলে stack, পুরোনোটা হলে queue।

Stack নড়ে এক মাথায়

এবার একটা stack-কে নড়তে দেখো। নিচের প্রতিটা ধাপ থালার একটা stack-এর উপর একটা করে call, থালাগুলোর নম্বর 1, 2 আর 3, যে ক্রমে আসে। খেয়াল করো, প্রতিটা push কোথায় গিয়ে বসে, আর প্রতিটা pop কোথা থেকে নেয়।

একটা stack<int>-এর উপর আটটা ধাপ, থালাগুলো লেখা stack-এর নিচ থেকে উপরে।

Callএরপর থালাগুলো, নিচ থেকে উপরেকী হলো
stack<int> plates(খালি)খালি stack-এর এখনো কোনো top নেই।
push(1)1থালা 1 সবার আগে ঢোকে, তাই আপাতত ওটাই top।
push(2)1 2থালা 2 বসে থালা 1-এর উপরে।
push(3)1 2 3থালা 3 সবচেয়ে নতুন, তাই ওটাই top।
top()1 2 3top() শুধু থালা 3-কে দেখে, কিছুই সরায় না।
pop()1 2pop() সরায় সবচেয়ে নতুন থালা 3-কে, আর থালা 2 আবার top হয়।
pop()1এরপর বেরোয় থালা 2।
pop()(খালি)থালা 1 ঢুকেছিল সবার আগে, বেরোল সবার শেষে; stack আবার খালি।

থালা ঢুকেছিল 1, 2, 3 ক্রমে, আর বেরোল 3, 2, 1 ক্রমে। উপরের থালাগুলো চলে যাওয়ার আগে থালা 1-এ কেউ হাতই দেয়নি। তাই stack জিনিস ফেরত দেয় ঢোকার ঠিক উল্টো ক্রমে।

Queue নড়ে দুই মাথায়

এবার একই রকম ধাপ, একটা queue-এর উপর। Alice-এর লাইনে তিনটা ticket ঢোকে, দুইটা বেরোয়, তারপর আরেকটা ঢোকে। Front আর back আলাদা করে খেয়াল করো।

একটা queue<int>-এর উপর নয়টা ধাপ, ticket-গুলো লেখা লাইনের সামনে থেকে পেছনে।

Callএরপর ticket-গুলো, সামনে থেকে পেছনেকী হলো
queue<int> line(খালি)খালি লাইনের এখনো কোনো front বা back নেই।
push(101)101Ticket 101 একা, তাই ও একসাথে front-ও, back-ও।
push(102)101 102Ticket 102 দাঁড়ায় পেছনে।
push(103)101 102 103Ticket 103 দাঁড়ায় পেছনে, আর front এখনো 101।
front()101 102 103front() দেখে সবচেয়ে পুরোনো 101-কে, কিছুই সরায় না।
back()101 102 103back() দেখে সবচেয়ে নতুন 103-কে।
pop()102 103pop() সামনে থেকে 101-কে সরায়: সবচেয়ে পুরোনোটা আগে বেরোয়।
push(104)102 103 104নতুন ticket তখনো পেছনেই দাঁড়ায়।
pop()103 104এরপর বেরোয় ticket 102, যে ক্রমে এসেছিল সেই ক্রমে।

Queue-এ push কাজ করে পেছনে আর pop সামনে, তাই দুজনের কখনো দেখা হয় না। Ticket 104 এসেছে সবার শেষে, তাই অপেক্ষা করে সবার পেছনে। তাই queue জিনিস ফেরত দেয় ঠিক যে ক্রমে ঢুকেছিল, সেই ক্রমেই।

প্রথম দিনেই যে syntax লাগবে

std::stack আর std::queue, আর ওদের রোজকার call-গুলো

#include <stack>
#include <queue>

stack<T> s;                an empty stack of T
s.push(x);                 put x on the top
s.top()                    the top element, the newest
s.pop();                   remove the top element; returns nothing

queue<T> q;                an empty queue of T
q.push(x);                 add x at the back
q.front()                  the front element, the oldest
q.back()                   the back element, the newest
q.pop();                   remove the front element; returns nothing

s.size()    s.empty()      how many, and whether there are none (q too)
  • stack<T> আর queue<T>: angle bracket-এ একটাই জিনিস, element-এর type, ঠিক vector-এর মতো। দুটোর header আলাদা।
  • push আর pop: দুটোতেই একই দুই নাম। Stack দুটো কাজই করে ওর একমাত্র মাথায়; queue push করে পেছনে আর pop করে সামনে।
  • top(), front() আর back(): শুধু দেখা। এরা কিছুই সরায় না।
  • pop(), top(), front() আর back(): শুধু container খালি না থাকলে। আগে empty() check করো।

তাই stack-এর কাজের call তিনটা, push, top আর pop, আর queue-এর চারটা: push, front, back আর pop।

Adaptor: সরু দরজার একটা container

std::stack আর std::queue নিজেরা কিছুই জমা রাখে না। প্রত্যেকে ভিতরে আরেকটা container রাখে, তুমি অন্য কিছু না বললে একটা deque, আর তোমার call-গুলো সেটার কাছে পৌঁছে দেয়। এভাবে আরেকটা class-কে মুড়ে রাখা class-কে বলে adaptor। Module 4-এর lesson 04 এটা আগেই দেখিয়েছে: ওর container_type program print করেছিল queue keeps a deque inside: yes।

Adaptor-এর পুরো কাজটাই হলো দরজাটা সরু করা। ভিতরের deque-এর সামনে আছে, পেছনে আছে, আর index দিয়ে পড়ার মতো মাঝখানও আছে। Stack তোমাকে ছুঁতে দেয় শুধু ওর top, আর queue শুধু ওর দুই মাথা। দরজায় নেই এমন call compile-ই হয় না, আর প্রতিটার বার্তা দেখাবে lesson 02।

Adaptor একটা deque-কে মুড়ে রাখে, আর খোলে শুধু কাজের দরজাগুলো ভিতরে একই deque, বাইরে আলাদা দরজা stack<int> s: দরজা একটা 10 20 30 40 push(x) call করে push_back(x) top() call করে back() pop() call করে pop_back() বন্ধ: front নেই, index নেই, begin নেই ভিতরে: একটা deque<int> queue<int> q: দরজা দুইটা 10 20 30 40 pop() call করে pop_front() front() call করে front() push(x) call করে push_back(x) back() call করে back() ভিতরে: একটা deque<int>

দরজার প্রতিটা call ভিতরের deque-এর উপর একটাই call, ওর কোনো একটা মাথায়। Module 4 দেখিয়েছে, deque-এর মাথাগুলো সস্তা, তাই এখানকার প্রতিটা push, দেখা আর pop-ও সস্তা। তাই adaptor নতুন কোনো জায়গা যোগ করে না: ও কিছু নড়াচড়া কেড়ে নেয়, আর রাখে শুধু কাজের গুলো।

Stack-এ push, top আর pop

push(x) x-কে top-এ বসায়। top() বলে এই মুহূর্তে top-এ কী আছে। pop() সেটা সরিয়ে দেয়। এই program প্রতিটা call-এর পরে top আর size print করে।

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

int main() {
    stack<int> s;
    s.push(10);
    s.push(20);
    s.push(30);
    cout << "top " << s.top() << ", size " << s.size() << '\n';
    s.pop();
    cout << "top " << s.top() << ", size " << s.size() << '\n';
    s.push(40);
    cout << "top " << s.top() << ", size " << s.size() << '\n';
    return 0;
}
top 30, size 3
top 20, size 2
top 40, size 3

Pop-এর পরে 20 আবার top-এ ফিরে এল, কারণ ও ছিল 30-এর নিচে। তারপর 40 বসল 20-এর উপরে। নিচের 10-কে একবারও দেখা গেল না: stack তোমাকে শুধু ওর top-টাই দেখায়। তাই top() সবসময় বলে সবচেয়ে শেষের সেই push-টার কথা, যেটা এখনো pop হয়নি।

Queue-এ push, front, back আর pop

Queue-এর push(x) x-কে পেছনে যোগ করে। front() হলো সবচেয়ে পুরোনো element, আর back() সবচেয়ে নতুনটা। pop() সামনেরটা সরায়।

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

int main() {
    queue<int> q;
    q.push(10);
    q.push(20);
    q.push(30);
    cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
    q.pop();
    cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
    q.push(40);
    cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
    return 0;
}
front 10, back 30, size 3
front 20, back 30, size 2
front 20, back 40, size 3

Call-গুলো stack-এর program-এরই, শুধু top-এর জায়গায় front আর back। অথচ pop এবার নিল 10, 30 না। Module 4 বলেছিল, queue-এর নড়াচড়া তিনটা: পেছনে push, সামনে দেখা, সামনে থেকে pop। back() হলো আরেকটা দেখা, ও কখনো কিছু নড়ায় না। তাই queue-এ pop সবসময় নেয় সেই element-টাকে, যে সবচেয়ে বেশিক্ষণ অপেক্ষা করেছে।

pop কিছুই ফেরত দেয় না: আগে পড়ো, তারপর pop

Bob এক লাইনেই top-এর মানটা নিতে আর সেটা সরাতে চায়, তাই ও লেখে এটা।

stack<int> s;
s.push(4);
int x = s.pop();

Runner-এর flag-এ GCC 12 থেমে যায় error: void value not ignored as it ought to be দিয়ে। void মানে "কোনো মান না": pop() element-টা সরায়, কিন্তু হাতে কিছুই ফেরত দেয় না। Queue-এর pop()-এর বেলাতেও একই কথা। Library কেন এভাবে বানানো, সেটা বলবে lesson 05। সমাধান দুই লাইনের: আগে একটা read, তারপর একটা pop।

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

int main() {
    stack<int> s;
    s.push(4);
    s.push(9);
    int x = s.top();
    s.pop();
    cout << "took " << x << ", now the top is " << s.top() << '\n';
    return 0;
}
took 9, now the top is 4

Queue-এ idiom-টা একই, শুধু top()-এর জায়গায় front()। তাই প্রায় প্রতিটা stack বা queue program-এ তুমি এই জোড়াটা লিখবে: আগে দেখা, তারপর pop।

খালি adaptor: Zara-র প্রথম test

Zara সবসময় খালি case আগে চালায়। ও একটা খালি stack-কে ওর top থালার কথা জিজ্ঞেস করে।

stack<int> plates;
cout << "top plate: " << plates.top() << '\n';

Runner-এর flag-এ এটা কোনো বার্তা ছাড়াই compile হয়েছে। Compiler Explorer-এর GCC 12.2-এ একবার চালাতে program কিছুই print করেনি, crash করেছে SIGSEGV দিয়ে, যে signal আসে নিজের না এমন memory পড়তে গেলে। এমনকি top plate: কথাগুলোও আসেনি, কারণ crash-এর সময় ওগুলো তখনো cout-এর কাছেই ধরা ছিল। তারপর ও একটা খালি queue-কে ওর front জিজ্ঞেস করে। সেবার print হয়েছে first in line: 0, আর program স্বাভাবিকভাবেই শেষ হয়েছে।

দুটোই undefined behaviour: কী হবে, C++ standard কিছুই বলে না। তাই এক run crash করে, আরেক run এমন একটা 0 print করে, যেটা কোনো customer না। খালি adaptor-এ pop()-ও undefined। তাই যে top(), front(), back() বা pop()-এর input তোমার হাতে নেই, তার আগে প্রতিবার if (!s.empty()) লেখো।

Example 1: তিনটা push, উল্টো ক্রমে pop

সবচেয়ে ছোট stack program: তিনটা push, তারপর একটা loop, যেটা stack খালি না হওয়া পর্যন্ত পড়ে আর pop করে।

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

int main() {
    stack<int> s;
    s.push(1);
    s.push(2);
    s.push(3);
    while (!s.empty()) {
        cout << "pop " << s.top() << '\n';
        s.pop();
    }
    return 0;
}
pop 3
pop 2
pop 1

Loop-এর শর্ত !s.empty() আসলে loop-এর ভিতরেই বসানো Zara-র check, তাই top() কখনো খালি stack পায় না। প্রতিটা pop হওয়া মান যদি আরেকটা stack-এ push করা হতো, সেই stack-এর একদম নিচে থাকত 3: এক stack অন্য stack-এ ঢাললে উল্টে যায়।

Run in Compiler
Example 2: সবচেয়ে ছোট queue program

একটা queue-তে সেই একই তিনটা push, আর front() দিয়ে সেই একই loop।

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

int main() {
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
    while (!q.empty()) {
        cout << "pop " << q.front() << '\n';
        q.pop();
    }
    return 0;
}
pop 1
pop 2
pop 3

দুই output পাশাপাশি রাখো: stack থেকে 3 2 1, queue থেকে 1 2 3। ছয় লাইনে LIFO আর FIFO।

Run in Compiler
Example 3: Bob-এর undo, দুই ধাপ পেছনে

Bob-এর তিনটা বদল যায় string-এর একটা stack-এ। Undo top-টা পড়ে, বলে কী ফেরানো হলো, তারপর ওটা pop করে।

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

int main() {
    stack<string> changes;
    changes.push("typed a paragraph");
    changes.push("made a word bold");
    changes.push("deleted a line");

    for (int press = 1; press <= 2; press++) {
        cout << "undo: " << changes.top() << '\n';
        changes.pop();
    }
    cout << "next undo would take back: " << changes.top() << '\n';
    cout << changes.size() << " change left\n";
    return 0;
}
undo: deleted a line
undo: made a word bold
next undo would take back: typed a paragraph
1 change left

লাইন মোছাটা সবার আগে ফিরল, কারণ ওটাই ঢুকেছিল সবার শেষে। ক্রমটা Bob-কে মনে রাখতে হয় না: stack-ই ওর হয়ে মনে রাখে।

Run in Compiler
Example 4: বেকারিতে Alice-এর লাইন

চারটা ticket ক্রম অনুযায়ী লাইনে দাঁড়ায়। Alice দুজনকে সার্ভিস দেয়, একজন নতুন customer আসে, আর program আগের আর পরের লাইনের খবর দেয়।

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

int main() {
    queue<int> line;
    for (int ticket = 101; ticket <= 104; ticket++) {
        line.push(ticket);
    }
    cout << line.size() << " waiting, front " << line.front() << ", back " << line.back() << '\n';

    for (int served = 0; served < 2; served++) {
        cout << "serving ticket " << line.front() << '\n';
        line.pop();
    }
    line.push(105);
    cout << line.size() << " waiting, front " << line.front() << ", back " << line.back() << '\n';
    return 0;
}
4 waiting, front 101, back 104
serving ticket 101
serving ticket 102
3 waiting, front 103, back 105

Ticket 105 দাঁড়াল 104-এর পেছনে, তাই আগে আসা কেউ পরে সার্ভিস পায় না। Module 4 একই লাইন বানিয়েছিল একটা deque-এ, যেখানে VIP লাফিয়ে সামনে চলে যেতে পারত। Queue-এর সামনে ঢোকার কোনো দরজা নেই, তাই এখানে কেউ পারে না।

Run in Compiler

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

  • প্রতিটা C আর C++ program-এর call stack. প্রতিটা function call ওর local variable-গুলো নিয়ে একটা frame push করে, আর return সেটা pop করে। যে function সবার শেষে call হয়েছে, সে-ই সবার আগে return করে, যেমনটা C track-এর call-stack ছবিতে দেখেছ।
  • CUPS, Linux আর macOS-এর print system. কোনো printer-এ পাঠানো job-গুলো ও একটা queue-তে রাখে। একই priority-র job-গুলো print হয় পাঠানোর ক্রমে।
  • Microsoft Word-এর Undo বোতাম. ওর drop-down তালিকায় সবার উপরে থাকে তোমার সবচেয়ে নতুন কাজটা। নিচের দিকের কোনো কাজ undo করলে ওর উপরের সব কাজও undo হয়ে যায়, মানে একটা stack কয়েকবার pop হলো।

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

১. Stack-এ vector-এর নাম ধরে call করা।

stack<int> s;
s.push_back(5);

Runner-এর flag-এ error: error: 'class std::stack<int>' has no member named 'push_back'। দুই adaptor-এই দরজাটার নাম push। তুমি push_back লিখবে, কারণ Module 2-এ ওটা একশোবার লিখেছ।

২. পড়ার আগেই pop করা।

stack<int> s;
s.push(4);
s.push(9);
s.pop();
cout << "took " << s.top() << '\n';

কোনো command line-এই কোনো বার্তা নেই, আর print হয় took 4। 9 তো আগেই চলে গেছে, তাই top() জবাব দিল ওর নিচের element দিয়ে। একটাই element push করা থাকলে এই লাইনগুলো একটা খালি stack পড়ত। আগে পড়ো, তারপর pop। তুমি ক্রম উল্টাবে, কারণ "আগে নিই, তারপর দেখি" শুনতে ঠিকই লাগে।

৩. Header লিখতে ভুলে যাওয়া।

#include <iostream>
using namespace std;

int main() {
    queue<int> q;
    q.push(5);
    cout << q.front() << '\n';
    return 0;
}

Runner-এর flag-এ error: error: 'queue' was not declared in this scope, তারপর GCC 12 নিজেই সমাধান বলে দেয়: note: 'std::queue' is defined in header '<queue>'; did you forget to '#include <queue>'?। Stack-এর জন্যও একইভাবে <stack> লাগে। তুমি এটা ভুলবে, কারণ ভিতরের deque-টা মনে হয় এমনিতেই চলে আসে।

মাথা খাটাও

Maria একই চারটা call চালায়, একবার একটা stack-এ, একবার একটা queue-তে।

#include <iostream>
#include <queue>
#include <stack>
using namespace std;

int main() {
    stack<int> s;
    queue<int> q;
    s.push(1);
    s.push(2);
    s.pop();
    s.push(3);
    q.push(1);
    q.push(2);
    q.pop();
    q.push(3);
    cout << s.top() << ' ' << q.front() << '\n';
    return 0;
}

Program কী print করে? চালানোর আগে বলো, প্রতিটা pop() কোন সংখ্যাটা সরাল, আর শেষে কোন container-এ কী আছে।

প্রতিটা call-এর পরে stack-কে নিচ থেকে উপরে আর queue-কে সামনে থেকে পেছনে লিখে ফেলো, দুই widget যেভাবে দেখিয়েছে।

অনুশীলন ১সহজ

Alice-এর বেকারি প্রতিটা customer-কে একটা ticket দেয়, আর counter সার্ভিস দেয় আসার ক্রমে। কেউ এলে সে লাইনের পেছনে দাঁড়ায়। Counter খালি হলে Alice সামনের customer-কে ডাকে। মাঝে মাঝে ও ডাকে যখন কেউ অপেক্ষায় নেই, তখন কেউ আসে না।

Input. এক লাইনে n, তারপর n-টা command, প্রতি লাইনে একটা। join NAME NAME-কে লাইনের পেছনে দাঁড় করায়, আর serve সামনের customer-কে ডাকে, যে লাইন ছেড়ে চলে যায়।

Output. প্রতিটা serve-এর জন্য এক লাইন: যে সার্ভিস পেল তার নাম, আর লাইন খালি হলে nobody শব্দটা। তারপর একদম শেষে এক লাইন: যারা এখনো অপেক্ষায়, তাদের নাম সামনে থেকে পেছনে, মাঝে একটা করে space, আর কেউ না থাকলে empty শব্দটা।

Constraints. 1 <= n <= 100000। একটা নাম 1 থেকে 10টা ছোট হাতের অক্ষর।

Sample. Input 7, তারপর serve, join amara, join bob, serve, join zara, serve, join kenji দিলে nobody, amara, bob আর zara kenji। প্রথম serve লাইন খালি পায়, আর শেষে অপেক্ষায় থাকে Zara আর Kenji।

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

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

    int n = 0;
    cin >> n;
    queue<string> line;
    for (int i = 0; i < n; i++) {
        string op;
        cin >> op;
        if (op == "join") {
            string name;
            cin >> name;
            // Add name at the back of line.
        } else {
            // op is "serve": print the name at the front and remove it,
            // or print nobody if line is empty.
        }
    }

    // Print the names still waiting, front to back, separated by single
    // spaces, or the word empty if nobody is left.

    return 0;
}

ticket-queue নামে grade হয়। এটা এই module-এর problem set-এর একটা free problem। Hidden test-এ আছে কেউ আসার আগেই একটা serve, আর এমন একটা লাইন, যেটা খালি হয়ে আবার ভরে। আরও আছে এমন একটা run, যার শেষে কেউ অপেক্ষায় নেই, আর 100000টা command।

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

Maria-র শব্দের খেলায় নিয়ম একটাই: যে শব্দ দুই মাথা থেকেই একই পড়া যায়, যেমন level, সেটা জেতে। ও এটা check করে এই lesson-এর দুই container দিয়ে। প্রতিটা অক্ষর যায় একটা stack-এ আর একটা queue-তে। তারপর stack অক্ষরগুলো দেয় শেষেরটা আগে, আর queue দেয় প্রথমটা আগে।

Input. এক লাইনে t, তারপর t-টা শব্দ, প্রতি লাইনে একটা।

Output. প্রতিটা শব্দের জন্য এক লাইন: দুই মাথা থেকে একই পড়া গেলে yes, নইলে no।

Constraints. 1 <= t <= 1000। প্রতিটা শব্দ 1 থেকে 1000টা ছোট হাতের অক্ষর।

Sample. Input 3, তারপর level, stack আর a দিলে yes, no আর yes। এক অক্ষরের শব্দ সবসময় জেতে।

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

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

    int t;
    cin >> t;
    for (int i = 0; i < t; i++) {
        string word;
        cin >> word;
        stack<char> from_end;
        queue<char> from_start;
        // Push every letter of word into both containers.
        // Then compare from_end.top() with from_start.front(), pop both,
        // and repeat until they are empty. Print yes or no.
    }
    return 0;
}

আলাদা করে grade হয় না। ভিতরের দিকে এগোনো দুইটা index দিয়েও একই কাজ হয়; এখানে আসল কথা হলো দুই ক্রমকে অক্ষরে অক্ষরে মুখোমুখি হতে দেখা।

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

Kenji একটা গুদামে crate সাজায়, আর নিয়ম হলো, একটা crate বসতে পারে শুধু নিজের সমান বা বেশি ভারী crate-এর উপরে। ওর crate-গুলো এসেছে এলোমেলো ক্রমে, একটাই স্তূপে। পাশে আরেকটা স্তূপের জন্য একটা খালি জায়গা আছে, আর ও হাতে একটা crate ধরে রাখতে পারে। ও একবারে একটা করে crate সরায়, সবসময় কোনো স্তূপের উপর থেকে কোনো স্তূপের উপরে।

Input. এক লাইনে n, তারপর n-টা ওজন: স্তূপের crate-গুলো, নিচ থেকে উপরে।

Output. সাজানো স্তূপের ওজনগুলো উপর থেকে নিচে, এক লাইনে, মাঝে একটা করে space।

Constraints. 1 <= n <= 2000। প্রতিটা ওজন 1 থেকে 1000000-এর মধ্যে। শুধু দুইটা stack আর হাতের crate-এর জন্য একটা variable ব্যবহার করো।

Sample. Input 5 আর 3 1 4 1 5 দিলে 1 1 3 4 5। সবচেয়ে হালকা crate-গুলো থাকে উপরে।

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

int main() {
    int n;
    cin >> n;
    stack<int> pile;
    for (int i = 0; i < n; i++) {
        int w;
        cin >> w;
        pile.push(w);
    }

    stack<int> sorted;
    // Move crates one at a time until pile is empty, so that every crate
    // in sorted sits on a heavier or equal one. You may hold one crate in
    // a variable, and you may move crates from sorted back onto pile.
    // Then print sorted from its top to its bottom.

    return 0;
}

আলাদা করে grade হয় না। Lesson যে কথাটা শুধু ইঙ্গিতে বলেছে, Example 1-এ: এক stack অন্য stack-এ ঢাললে উল্টে যায়। তাই পথে বাধা হয়ে থাকা crate পুরোনো স্তূপে একটু সরে দাঁড়াতে পারে, পরে আবার ফিরে আসে।

Run in Compiler

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

  • সবকিছুর জন্য vector বা deque নিলেই তো হয়, তাই না?

    নিতে পারো, contest-এর code-এ vector-কেই প্রায়ই stack হিসেবে চালানো হয়। কিন্তু একটা std::stack নিজেই বলে দেয় কাজটা কী, আর stack-এর যে নড়াচড়া করা চলে না, compiler সেগুলো সব আটকে দেয়। দুটোকে মেপে তুলনা করবে lesson 04।

  • pop() যে element-টা সরাল, সেটা আমাকে দেয় না কেন?

    কারণ library সরানো আর পড়া, এই দুটো কাজ আলাদা রাখার সিদ্ধান্ত নিয়েছে। কারণটা একটা element নিরাপদে copy করার সাথে জড়িত, পুরো গল্পটা বলবে lesson 05। ততদিন top() বা front() দিয়ে আগে পড়ো, তারপর pop।

  • Queue-এর back() কি চলে? Module 4 তো বলেছিল queue-এর নড়াচড়া তিনটা।

    চলে। তিনটা নড়াচড়া হলো পেছনে push, সামনে দেখা আর সামনে থেকে pop। back() হলো আরেকটা দেখা: ও সবচেয়ে নতুন element-টা দেখায়, কিছুই নড়ায় না।

  • Top-এর নিচের element-টা কি দেখা যায়?

    আগে top-টা pop না করে যায় না। সরু দরজা ঠিক যেভাবে বানানো, সেভাবেই কাজ করছে। কোনো কাজে মাঝখানটাও লাগলে, সেই কাজের দরকার ছিল একটা deque বা vector।

মূল কথা

  • Stack সবচেয়ে নতুন element-টা আগে ফেরত দেয় (LIFO); queue সবচেয়ে পুরোনোটা আগে দেয় (FIFO)।
  • Stack কাজ করে ওর top-এ: push, top, pop। Queue push করে পেছনে, pop করে সামনে, আর front আর back দিয়ে দুই মাথাই দেখতে পারে।
  • দুটোই adaptor: ভিতরে একটা deque, আর একটা সরু দরজা, যেটা দিয়ে শুধু ঠিক নড়াচড়াগুলোই যেতে পারে।
  • pop() কিছুই ফেরত দেয় না, তাই আগে top() বা front() দিয়ে পড়ো; int x = s.pop(); পায় void value not ignored as it ought to be।
  • খালি adaptor-এ দেখা বা pop করা undefined: এক run crash করেছে, আরেক run চুপচাপ একটা 0 print করেছে। আগে empty() check করো।
  • আরও গভীরে যেতে চাইলে: Under the Hood, ভেতরের container আর pop কেন কিছু ফেরত দেয় না (Pro)।

এরপর lesson 02 দুই adaptor-এরই প্রতিটা operation একটা একটা করে দেখাবে, প্রতিটার খরচসহ, আর ওরা যা যা করতে দেয় না, সেগুলোও।

lesson ১ শেষ

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

পরেরটা: stack আর queue-এর প্রতিটা operation, একটা একটা করে, খরচসহ

stack আর queue: আগে বেরোয় সবচেয়ে নতুনটা, নাকি সবচেয়ে পুরোনোটা | Learn C++ STL | Progsity