সরাসরি পর্বে যান
পর্ব ১২ · ৫৬-এর মধ্যে · মডিউল ২

লুপ — একই কাজ বারবার করানোর কৌশল (while, for, do-while)

Loops — while, for, do-while, Off-by-One Errors & Nested Loops

Taught in Bangla, this lesson teaches C loops: while, for and do-while and when to use each, accumulator patterns, off-by-one errors, infinite loops, break and continue, and nested loops.

এই পর্বে যা শিখবেন

  • while, for ও do-while-এর গঠন, আর কোন পরিস্থিতিতে কোনটি বেছে নেবেন
  • গোনা, শর্ত আর sentinel লুপ — এবং যোগফল, গড়, সর্বোচ্চ বের করার পরিচিত ছাঁচ
  • Off-by-one ভুল কেন হয়, আর বেড়ার খুঁটির ধাঁধা দিয়ে কীভাবে এড়াবেন
  • অসীম লুপ — কোনটি দুর্ঘটনা, কোনটি ইচ্ছাকৃত নকশা; break, continue আর লুপের ভেতরে লুপ
  • AI-এর লেখা লুপ যাচাইয়ের একটি কাজের চেকলিস্ট

১ · কম্পিউটারের আসল শক্তি — একই কাজ হাজারবার

নির্বাচনের রাতে একটি কেন্দ্রে ভোট গোনা হচ্ছে। দুই হাজার ব্যালট। প্রতিটি ব্যালট হাতে নিয়ে দেখা, কোন প্রার্থীর ঘরে সিল, তারপর সেই প্রার্থীর হিসাবে একটি দাগ। কাজটি কঠিন নয় — কিন্তু দুই হাজারবার করতে হবে। মানুষ ক্লান্ত হয়, মনোযোগ হারায়, গুনতে ভুল করে। কম্পিউটার এর কোনোটাই করে না।

প্রোগ্রামিংয়ের বেশিরভাগ বাস্তব কাজ আসলে এই রকম। একটি দোকানের পুরো মাসের বিক্রি যোগ করা। একটি স্কুলের ৮০০ ছাত্রের প্রত্যেকের ফল দেখে কে পাস, কে ফেল তা বের করা। bKash-এর মতো একটি সেবায় প্রতিটি লেনদেন যাচাই করা। প্রতিটি ক্ষেত্রে কাজটি একটাই, শুধু বারবার করতে হবে — প্রতিবার ভিন্ন ডেটা নিয়ে।

একই নির্দেশ বারবার চালানোর ব্যবস্থাকে বলে loop. লুপের প্রতিটি একবার ঘোরাকে বলে একটি iteration. যে অংশটি বারবার চলে, সেটি লুপের body, আর যে প্রশ্নের উত্তর দেখে লুপ ঠিক করে আরেকবার ঘুরবে কি না, সেটি loop condition.

ক্লাসে শিক্ষক রোল কল করছেন। তিনি প্রতিটি রোল নম্বরের জন্য আলাদা নিয়ম মনে রাখেন না। তাঁর মাথায় একটিই নিয়ম: “রোল ১ থেকে শুরু করো। নাম ডাকো, উপস্থিতি লেখো, পরের রোলে যাও। শেষ রোল পার হলে থামো।” ক্লাসে ৪০ জন থাকুক বা ১২০ জন — নিয়ম একই। লুপও ঠিক তাই: একবার নিয়ম লিখুন, কম্পিউটার যতবার দরকার ততবার খাটাবে।

পর্ব ০২-এ আপনি দেখেছিলেন, pattern recognition থেকেই লুপের জন্ম — একই ছাঁচ বারবার দেখা গেলে সেটিকে একবার লিখে বারবার ব্যবহার করা। আর পর্ব ১১-এ শিখেছেন if দিয়ে সিদ্ধান্ত নেওয়া। লুপকে আপনি ভাবতে পারেন “বারবার জিজ্ঞেস করা if” হিসেবে: শর্ত সত্য হলে কাজটি করো, তারপর আবার শর্তটি জিজ্ঞেস করো।

মূল ধারণা

প্রতিটি লুপে চারটি জিনিস থাকে, লেখা থাকুক বা না থাকুক: (১) শুরুর অবস্থা — কোন মান থেকে শুরু; (২) শর্ত — কখন পর্যন্ত চলবে; (৩) কাজ — প্রতিবার কী হবে; (৪) অগ্রগতি — প্রতিবার কী বদলাবে, যাতে একসময় শর্ত মিথ্যা হয়। লুপের প্রায় সব বাগ এই চারটির কোনো একটিতে লুকিয়ে থাকে।

“একই ধাপ বারবার” ধারণাটি কম্পিউটারের চেয়েও পুরোনো। ১৮৪৩ সালে Ada Lovelace Charles Babbage-এর Analytical Engine নিয়ে লেখা তাঁর নোটে দেখিয়েছিলেন, কীভাবে একগুচ্ছ নির্দেশ বারবার চালিয়ে ধারাবাহিক সংখ্যা হিসাব করা যায়। আর ১৯৫০-এর দশকে IBM-এর দল যখন Fortran ভাষা বানায়, তার অন্যতম প্রধান বৈশিষ্ট্য ছিল DO লুপ — বিজ্ঞানীদের হাজার হাজার বার একই গণনা চালানোর জন্য।

২ · while — শর্ত যতক্ষণ সত্য, ততক্ষণ চলো

সবচেয়ে সরল লুপ হলো while loop. ইংরেজি “while” মানে “যতক্ষণ”। এর গঠন:

while (condition) {
    /* body: condition সত্য থাকলে এই অংশ চলে */
}

নিয়মটি খুব সহজ। প্রথমে শর্ত যাচাই হয়। সত্য হলে body একবার চলে, তারপর আবার শর্তে ফিরে আসে। মিথ্যা হলে body বাদ দিয়ে লুপের পরের লাইনে চলে যায়। খেয়াল করুন, শর্ত আগে দেখা হয় — তাই শুরুতেই শর্ত মিথ্যা হলে body একবারও চলে না।

while লুপের পথ

Check first, then run the body, then check again

  1. শর্ত যাচাইstock > 0 ?সত্য না মিথ্যা
  2. সত্য → bodystock--;কাজ করে আবার শর্তে ফেরা
  3. মিথ্যা → বাইরেnext lineলুপ শেষ
ছবি ১২.১ — while প্রতিবার body চালানোর আগে শর্ত দেখে।

একটি চায়ের দোকানের কথা ভাবুন। সকালে ফ্লাস্কে ৩০ কাপ চা আছে। দোকানি জানেন না আজ কতজন খদ্দের আসবেন — তাঁর নিয়ম শুধু: “যতক্ষণ ফ্লাস্কে চা আছে, খদ্দের এলে এক কাপ দাও।” এখানে আগে থেকে জানা নেই লুপ কতবার ঘুরবে। জানা আছে শুধু কখন থামতে হবে। এমন পরিস্থিতিতেই while সবচেয়ে স্বাভাবিক।

int cups = 30;
while (cups > 0) {
    /* একজন খদ্দেরকে এক কাপ দেওয়া হলো */
    cups = cups - 1;        // অগ্রগতি: এটি না থাকলে লুপ কখনো থামবে না
}

শেষ লাইনটি খেয়াল করুন। cups = cups - 1 না লিখলে cups সবসময় 30 থাকবে, শর্ত সবসময় সত্য থাকবে, আর লুপ কোনোদিন থামবে না। while লুপে অগ্রগতির লাইনটি body-র ভেতরে আপনাকেই মনে করে লিখতে হয় — compiler মনে করিয়ে দেবে না। নতুনদের অসীম লুপের সবচেয়ে সাধারণ কারণ এটিই।

৩ · for — গোনার কাজ, তিনটি অংশ এক লাইনে

অনেক লুপে আগে থেকেই জানা থাকে কতবার ঘুরবে — ১ থেকে ৬০ পর্যন্ত রোল, মাসের ৩০ দিন, ১২ মাস। এমন লুপে একটি counter ভ্যারিয়েবল থাকে, যা প্রতিবার এক এক করে বাড়ে। এই কাজটির জন্যই for loop:

for (init; condition; update) {
    /* body */
}

for (int roll = 1; roll <= 60; roll++) {
    /* roll নম্বর ছাত্রের উপস্থিতি নাও */
}

① init — শুরু

লুপ শুরুর আগে মাত্র একবার চলে। সাধারণত counter-কে প্রথম মান দেয়: int roll = 1.

একবারই

② condition — শর্ত

প্রতিটি ঘোরার আগে যাচাই হয়। সত্য হলে body চলে, মিথ্যা হলে লুপ শেষ।

প্রতিবার, body-র আগে

③ update — অগ্রগতি

প্রতিটি ঘোরার শেষে, body-র পরে চলে: roll++. তারপর আবার শর্তে ফেরা।

প্রতিবার, body-র পরে

লক্ষ করুন, লেখার ক্রম আর চলার ক্রম এক নয়। update লেখা আছে শর্তের ঠিক পাশে, কিন্তু চলে body-র পরে। এই জায়গাটিতেই নতুনরা সবচেয়ে বেশি গুলিয়ে ফেলেন। নিচের debugger-এ ১, ৩, ৫ — এই বিজোড় সংখ্যাগুলোর যোগফল বের হচ্ছে, আর counter প্রতিবার ২ করে বাড়ছে। “পরে” চাপুন, আর ডানের “এখন চলছে” সারিটি খেয়াল করুন। সবুজ দাগের লাইনটি এইমাত্র চলেছে।

1#include <stdio.h>23int main(void) {4    int sum = 0;5    for (int i = 1; i <= 5; i += 2) {6        sum = sum + i;7    }8    printf("sum = %d\n", sum);9    return 0;10}
ভ্যারিয়েবলের মান (Watch)
এখন চলছে
i
sum
iteration (কতবার ঘুরল)
  1. লুপের আগে। sum-এ রাখা হলো 0. যোগফল জমানোর ভ্যারিয়েবল সবসময় লুপের বাইরে, লুপের আগে তৈরি হয় — ভেতরে হলে প্রতিবার আবার 0 হয়ে যেত।
  2. ① init — একবারই। int i = 1 চলল। পুরো লুপে এই অংশটি আর কখনো চলবে না।
  3. ② শর্ত। 1 ≤ 5? হ্যাঁ। তাই body-তে ঢোকার অনুমতি মিলল।
  4. ③ body. sum = 0 + 1 = 1. প্রথম iteration শেষ।
  5. ④ update. এবার চলল i += 2, i হলো 3. খেয়াল করুন — update লেখা আছে লুপের প্রথম লাইনেই, কিন্তু চলল body-র পরে।
  6. ② আবার শর্ত। 3 ≤ 5? হ্যাঁ। init আর চলছে না — লুপ সরাসরি শর্তে ফিরেছে।
  7. ③ body. sum = 1 + 3 = 4.
  8. ④ update. i হলো 5.
  9. ② শর্ত। 5 ≤ 5? হ্যাঁ, সমান হলেও সত্য — কারণ চিহ্নটি <=. এখানে < থাকলে 5 বাদ পড়ত।
  10. ③ body. sum = 4 + 5 = 9. তৃতীয় iteration শেষ।
  11. ④ update. i হলো 7. update শর্ত দেখে না — সে অন্ধভাবে বাড়িয়ে দেয়।
  12. ② শর্ত মিথ্যা। 7 ≤ 5? না। body আর চলবে না, লুপ শেষ। মোট ৩টি iteration, কিন্তু শর্ত যাচাই হয়েছে ৪ বার — শেষবার “না” শোনার জন্য। লুপ থেকে বেরোলেই i আর থাকে না, কারণ সে for-এর ভেতরে তৈরি হয়েছিল।
  13. ফল ছাপা। স্ক্রিনে এলো sum = 9. হাতে মিলিয়ে নিন: 1 + 3 + 5 = 9. পুরো ক্রমটি মনে রাখুন — init একবার, তারপর শর্ত → body → update চক্র, শর্ত মিথ্যা হলেই শেষ।

for আর while — আসলে একই জিনিস, দুই চেহারায়

উপরের for লুপটি while দিয়েও লেখা যায়। পাশাপাশি রাখলে বোঝা যায়, for আসলে কিছু নতুন করে না — শুধু ছড়িয়ে থাকা তিনটি অংশকে এক লাইনে গুছিয়ে রাখে।

int i = 1;              // ① init
while (i <= 5) {        // ② condition
    sum = sum + i;      // ③ body
    i += 2;             // ④ update
}

তাহলে for কেন? কারণ পাঠকের চোখ এক লাইনেই পুরো লুপের নিয়ন্ত্রণ দেখতে পায়: কোথা থেকে শুরু, কোথায় শেষ, কীভাবে এগোয়। body লম্বা হলে while-এর update লাইনটি নিচে কোথাও হারিয়ে যায়, আর ভুলে বাদ পড়ার সম্ভাবনা বাড়ে। for-এ তিনটি অংশ একসাথে থাকায় কোনোটি বাদ পড়লে চোখে লাগে।

৪ · do-while — আগে কাজ, পরে প্রশ্ন

কিছু কাজ আছে যা অন্তত একবার করতেই হবে, তারপর প্রশ্ন আসে আবার করব কি না। মোবাইলে *247# ডায়াল করলে আগে মেনু আসে — তারপরই আপনি বেছে নেন কী করবেন, নাকি বেরিয়ে যাবেন। মেনু না দেখিয়ে তো জিজ্ঞেস করা যায় না “আবার দেখাব?” এমন পরিস্থিতির জন্য do-while loop:

do {
    /* মেনু দেখাও, ব্যবহারকারীর পছন্দ পড়ো */
} while (choice != 0);      // শেষে সেমিকোলন লাগে!

এখানে শর্ত থাকে নিচে। তাই body আগে একবার চলে, তারপর শর্ত দেখা হয়। শর্ত শুরু থেকেই মিথ্যা হলে কী হয়, দুটি লুপ পাশাপাশি রেখে দেখুন:

1int choice = 0;2while (choice != 0) {3    printf("while body\n");4}5do {6    printf("do body\n");7} while (choice != 0);8printf("done\n");
ভ্যারিয়েবলের মান (Watch)
choice
choice != 0 ?
while-এর body কতবার
do-while-এর body কতবার
  1. শুরু। choice-এ 0 রাখা হলো। দুটি লুপেরই শর্ত একই: choice != 0 — যা এখন মিথ্যা।
  2. while: আগে প্রশ্ন। শর্ত যাচাই হলো — 0 != 0? মিথ্যা। তাই body-তে ঢোকাই হলো না। লাইন ৩ একবারও চলেনি, স্ক্রিন ফাঁকা।
  3. do-while: আগে কাজ। do কোনো প্রশ্ন না করেই body চালাল — স্ক্রিনে এলো do body. শর্ত এখনো দেখাই হয়নি।
  4. do-while: এবার প্রশ্ন। নিচে এসে শর্ত যাচাই — 0 != 0? মিথ্যা। তাই আর ফিরে যাবে না, লুপ শেষ।
  5. ফলাফল। একই শর্ত, একই মান — অথচ while-এর body চলেছে ০ বার, do-while-এর ১ বার। এই একটি পার্থক্যই ঠিক করে কোনটি কখন বেছে নেবেন।

কোন লুপ কখন বেছে নেবেন

পরিস্থিতিভালো পছন্দউদাহরণ
আগে থেকেই জানা কতবার ঘুরবেforরোল ১ থেকে ৬০, মাসের ৩০ দিন
কতবার জানা নেই, শুধু থামার শর্ত জানাwhileফ্লাস্কে চা যতক্ষণ আছে, ফাইল যতক্ষণ শেষ হয়নি
কাজটি অন্তত একবার করতেই হবেdo-whileমেনু দেখানো, “আবার চেষ্টা করবেন?” জিজ্ঞেস করা

এটি কঠোর নিয়ম নয়, পাঠকের সাথে একটি চুক্তি। for দেখলে অভিজ্ঞ প্রোগ্রামার ধরে নেন “এটি গোনার লুপ”; while দেখলে ভাবেন “এটি কোনো ঘটনার অপেক্ষায়”। সঠিক লুপ বেছে নেওয়া মানে কোডটিকে নিজের উদ্দেশ্য নিজেই বলে দিতে দেওয়া। বাস্তবে do-while তিনটির মধ্যে সবচেয়ে কম ব্যবহৃত হয়।

৫ · লুপের তিন ধরন আর জমানোর ছাঁচ

লুপ কীভাবে থামে, সেই অনুযায়ী তাদের তিন ভাগে ভাবা যায়। এই ভাগগুলো চিনলে নতুন সমস্যা দেখে দ্রুত বুঝবেন কোন ছাঁচ লাগবে।

🔢 গোনার লুপ

কতবার ঘুরবে আগেই জানা। counter একটি সীমা পর্যন্ত বাড়ে।

১২ মাসের বেতন যোগ

❓ শর্তের লুপ

কোনো অবস্থা না বদলানো পর্যন্ত চলে। কতবার, আগে বলা যায় না।

ব্যালান্স শেষ না হওয়া পর্যন্ত কিস্তি

🚩 Sentinel লুপ

একটি বিশেষ “থামার সংকেত” মান না আসা পর্যন্ত ইনপুট পড়ে।

0 লিখলে দামের তালিকা শেষ

তৃতীয় ধরনটি একটু বুঝিয়ে বলি। ধরুন দোকানের ক্যাশিয়ার একের পর এক জিনিসের দাম টাইপ করছেন। কয়টি জিনিস, তা প্রোগ্রাম জানে না। তাই নিয়ম করা হলো: “দাম শেষ হলে 0 লিখুন।” এই 0 কোনো আসল দাম নয় — এটি একটি পাহারাদার সংকেত, যাকে বলে sentinel. শর্ত হলো: “যতক্ষণ পড়া মান sentinel নয়, ততক্ষণ চলো।”

Sentinel এমন মান হতে হবে যা আসল ডেটায় কখনো আসতে পারে না। দামের জন্য 0 ঠিক আছে। কিন্তু পরীক্ষার নম্বরের জন্য 0 sentinel হিসেবে বিপজ্জনক — কেউ সত্যিই 0 পেতে পারে, আর তখন তালিকা মাঝপথে থেমে যাবে। নম্বরের জন্য তাই -1 ভালো পছন্দ, কারণ ঋণাত্মক নম্বর হয় না।

Accumulator — লুপের ভেতরে ফল জমানো

লুপ নিজে কোনো উত্তর দেয় না — উত্তর জমা হয় একটি ভ্যারিয়েবলে, প্রতিবার একটু একটু করে। এই ভ্যারিয়েবলকে বলে accumulator, অনেকটা মাটির ব্যাংকের মতো, যেখানে প্রতিদিন কয়েকটি টাকা পড়ে। চারটি ছাঁচ প্রায় সব প্রোগ্রামে বারবার দেখবেন:

যা চাইলুপের আগেলুপের ভেতরে প্রতিবারলুপের পরে
যোগফলsum = 0sum += x—
কয়টিcount = 0count++—
গড়sum ও count দুটোই 0দুটোই হালনাগাদsum / count — count 0 কি না আগে দেখে
সর্বোচ্চ / সর্বনিম্নপ্রথম মানটিই রাখুনif (x > max) max = x—

সর্বোচ্চ খোঁজার ছাঁচটি পর্ব ০২-এ আপনি loop invariant দিয়ে প্রমাণ করেছিলেন — “এখন পর্যন্ত দেখা মানগুলোর সবচেয়ে বড়টি max-এ আছে।” একই চিন্তা বাকি তিনটিতেও খাটে: sum হলো “এখন পর্যন্ত দেখা মানগুলোর যোগফল”, count হলো “এখন পর্যন্ত কয়টি দেখা হয়েছে”। লুপ শেষে “এখন পর্যন্ত” মানে “সবগুলো” — তাই উত্তর ঠিক।

নিচের প্রোগ্রামটি একসাথে চারটি ছাঁচই ব্যবহার করে। নম্বর পড়ে -1 না আসা পর্যন্ত, তারপর কয়টি, গড় আর সর্বোচ্চ দেখায়। ইনপুট আগে থেকেই দেওয়া আছে: 72 85 64 91 -1.

#include <stdio.h>

int main(void) {
    int mark, count = 0, sum = 0, max = 0;
    printf("Enter marks (-1 to stop):\n");
    while (scanf("%d", &mark) == 1 && mark != -1) {
        if (count == 0 || mark > max)
            max = mark;          // first mark, or a new highest
        sum += mark;             // accumulator: total
        count++;                 // counter: how many
    }
    if (count == 0) {
        printf("No marks entered.\n");
    } else {
        printf("Count: %d\n", count);
        printf("Average: %.2f\n", (double)sum / count);
        printf("Highest: %d\n", max);
    }
    return 0;
}

লুপের শর্তে দুটি প্রশ্ন একসাথে: scanf(...) == 1 মানে “সত্যিই একটি সংখ্যা পড়া গেছে তো?”, আর mark != -1 মানে “এটি sentinel নয় তো?” দুটিই সত্য হলে তবেই body চলে — তাই -1 কখনো যোগফলে ঢোকে না। count == 0 পরীক্ষাটি পর্ব ০২-এর শিক্ষা মেনে প্রথম নম্বরটিকেই max-এর শুরুর মান বানায়। আর লুপের পরে count == 0 যাচাই না করলে প্রথমেই -1 দিলে শূন্য দিয়ে ভাগ হতো। ইনপুট বদলে শুধু -1 দিয়ে চালিয়ে দেখুন।

৬ · Off-by-one — বেড়ার খুঁটির পুরোনো ধাঁধা

একটি প্রশ্ন দিয়ে শুরু করি। আপনার বাড়ির সামনে ১০ মিটার লম্বা একটি বেড়া দেবেন। প্রতি ১ মিটার পরপর একটি খুঁটি পুঁততে হবে, দুই মাথাতেও খুঁটি থাকবে। কয়টি খুঁটি কিনবেন?

প্রথম মনে আসে ১০। কিন্তু উত্তর ১১। কারণ খুঁটি বসে মিটারের দাগে — ০, ১, ২, … ১০ — আর ০ থেকে ১০ পর্যন্ত দাগ ১১টি। খুঁটির মাঝের ফাঁক (রেলিং) ১০টি। “ফাঁক গুনছি, না খুঁটি গুনছি?” — এই গুলিয়ে ফেলাকে বলে fencepost error. আর লুপ একবার বেশি বা একবার কম ঘুরলে তাকে বলে off-by-one error — প্রোগ্রামিংয়ের সবচেয়ে পরিচিত বাগগুলোর একটি।

নিচের প্রোগ্রামটি ৪ মিটারের একটি বেড়া এঁকে খুঁটি গুনছে। + হলো খুঁটি, --- হলো দুই খুঁটির মাঝের রেলিং। ধাপে ধাপে দেখুন, শেষ খুঁটিটি কোন iteration-এ আসে:

1#include <stdio.h>23int main(void) {4    int posts = 0;5    for (int m = 0; m <= 4; m++) {6        printf("+");7        posts++;8        if (m < 4) printf("---");9    }10    printf("\nposts = %d\n", posts);11    return 0;12}
ভ্যারিয়েবলের মান (Watch)
m (মিটারের দাগ)
খুঁটি (posts)
রেলিং
  1. শুরু। খুঁটি গোনার counter posts = 0. বেড়া ৪ মিটার, তাই দাগগুলো হবে 0, 1, 2, 3, 4.
  2. প্রথম দাগ 0. খেয়াল করুন, গোনা শুরু হলো 0 থেকে, 1 থেকে নয় — বেড়ার শুরুর বিন্দুটিও একটি দাগ। শর্ত 0 ≤ 4 সত্য।
  3. m = 0: প্রথম খুঁটি পোঁতা হলো, তারপর পরের খুঁটি পর্যন্ত একটি রেলিং। খুঁটি ১, রেলিং ১।
  4. m = 1: আরেকটি খুঁটি আর রেলিং। খুঁটি ২, রেলিং ২।
  5. m = 2: খুঁটি ৩, রেলিং ৩।
  6. m = 3: খুঁটি ৪, রেলিং ৪। চার মিটার রেলিং হয়ে গেছে! এখানেই সেই ফাঁদ। শর্ত যদি m < 4 হতো, লুপ এখানেই থামত — রেলিংয়ের শেষ মাথা ঝুলে থাকত খুঁটি ছাড়া।
  7. m = 4: শেষ খুঁটি পোঁতা হলো। কিন্তু m < 4 মিথ্যা, তাই এর পরে আর রেলিং নেই — বেড়া এখানেই শেষ। খুঁটি ৫, রেলিং ৪।
  8. লুপ শেষ। m হলো 5, আর 5 ≤ 4 মিথ্যা। ফল: posts = 5. সাধারণ নিয়ম: n মিটার বেড়ায় রেলিং n টি, খুঁটি n + 1টি। লুপে দাগ গুনলে <=, ফাঁক গুনলে < — প্রতিবার নিজেকে জিজ্ঞেস করুন, “আমি কী গুনছি?”

< নাকি <= — একটি সহজ অভ্যাস

C-তে প্রায় সব গোনা 0 থেকে শুরু হয় (পর্ব ১৬-এ array-তে এর কারণ পরিষ্কার হবে)। তাই সবচেয়ে প্রচলিত ও নিরাপদ ছাঁচটি হলো:

for (int i = 0; i < n; i++)
ঠিক n বার ঘোরে: 0, 1, 2, … n − 1

এই ধরনের পরিসরকে বলে half-open range — শুরুর মানটি ভেতরে, শেষের মানটি বাইরে। এর তিনটি সুবিধা: কতবার ঘুরবে তা সরাসরি পড়া যায় (শেষ − শুরু = n), খালি পরিসর (n = 0) নিজে থেকেই ঠিকমতো সামলায়, আর দুটি পরিসর জোড়া দিলে মাঝখানে কিছু দুবার গোনা হয় না, বাদও পড়ে না।

লুপের শুরুi-এর মানগুলোকতবার
for (i = 0; i < 10; i++)0 … 9১০
for (i = 0; i <= 10; i++)0 … 10১১ — একবার বেশি!
for (i = 1; i <= 10; i++)1 … 10১০
for (i = 1; i < 10; i++)1 … 9৯ — একবার কম!

যেকোনো লুপ লিখে দুটি edge case-এ মনে মনে চালিয়ে দেখুন: প্রথম iteration-এ counter কত, আর শেষ iteration-এ কত। এই দুটি সীমানা মিললে মাঝখানের সব সাধারণত ঠিক থাকে। Off-by-one বাগ প্রায় সবসময় সীমানাতেই লুকায়।

৭ · অসীম লুপ — কখনো দুর্ঘটনা, কখনো নকশা

যে লুপের শর্ত কখনো মিথ্যা হয় না, সেটি চিরকাল ঘুরতে থাকে — একে বলে infinite loop. প্রোগ্রাম আটকে যায়, CPU-র ব্যবহার বেড়ে যায়, কিছুই ছাপা হয় না (বা অনবরত ছাপা হতেই থাকে)। Terminal-এ এমন প্রোগ্রাম থামাতে Ctrl + C চাপুন।

দুর্ঘটনায় তৈরি অসীম লুপ — চারটি পরিচিত ফাঁদ

/* ১. অগ্রগতি ভুলে যাওয়া */
int i = 0;
while (i < 10) { printf("%d\n", i); }       // i কখনো বাড়ে না

/* ২. ঠিক মানটি লাফিয়ে পার হয়ে যাওয়া */
for (int x = 0; x != 10; x += 3) { }        // 0, 3, 6, 9, 12 ... 10 কখনো আসে না

/* ৩. শর্তের পরে ভুল সেমিকোলন */
while (i < 10);                              // ; নিজেই একটি ফাঁকা body!
{ i++; }

/* ৪. দশমিক সংখ্যার সমতা */
for (double d = 0.0; d != 1.0; d += 0.1) { } // 0.1 দশবার যোগে ঠিক 1.0 হয় না

দ্বিতীয় আর চতুর্থ ফাঁদের সমাধান একই: থামার শর্তে != না লিখে < লিখুন। x < 10 হলে 12-তে পৌঁছেও লুপ থামবে। চতুর্থটির কারণ আপনি পর্ব ০৫-এ দেখেছেন — 0.1 বাইনারিতে হুবহু রাখা যায় না, তাই floating-point যোগফল 1.0-এর খুব কাছে গিয়েও সমান হয় না। তৃতীয়টি সবচেয়ে চতুর: সেমিকোলনটিকেই compiler লুপের body ভাবে, নিচের { i++; } লুপের অংশই নয়।

ইচ্ছা করে বানানো অসীম লুপ

মজার ব্যাপার হলো, পৃথিবীর অনেক গুরুত্বপূর্ণ প্রোগ্রাম ইচ্ছা করেই চিরকাল চলে। C-তে এটি সাধারণত এভাবে লেখা হয়:

while (1) {             // অথবা for (;;)
    wait_for_next_event();
    handle_it();
}

🌐 ওয়েব সার্ভার

অনুরোধের অপেক্ষা → উত্তর পাঠানো → আবার অপেক্ষা। মাঝরাতেও কেউ ওয়েবসাইট খুলতে পারেন, তাই সার্ভার কখনো “শেষ” হয় না। এই ছাঁচকে বলে event loop.

দিনরাত চলে

🎮 গেম

ইনপুট পড়া → খেলার অবস্থা হালনাগাদ → স্ক্রিনে আঁকা। এই চক্র প্রতি সেকেন্ডে সাধারণত ৩০ বা ৬০ বার ঘোরে, যতক্ষণ না খেলোয়াড় বের হন।

game loop

🔌 Microcontroller

মাইক্রোওয়েভ বা ডিজিটাল মিটারের ভেতরের microcontroller-এ ফিরে যাওয়ার মতো কোনো অপারেটিং সিস্টেম নেই। তাই main-এর শেষে থাকে চিরস্থায়ী লুপ। Arduino-তে এর নামই loop().

বিদ্যুৎ থাকা পর্যন্ত
পার্থক্যটা কোথায়

দুর্ঘটনার অসীম লুপ আর ইচ্ছাকৃত অসীম লুপের পার্থক্য একটি প্রশ্নে: লুপটি কি প্রতিবার কিছু অর্থপূর্ণ কাজ করছে, আর বের হওয়ার কোনো পরিকল্পিত পথ কি আছে? সার্ভার প্রতিবার একটি অনুরোধ সামলায়, আর বন্ধের সংকেত এলে break করে বেরিয়ে যায়। দুর্ঘটনার লুপ শুধু একই জায়গায় ঘোরে, কোনো অগ্রগতি নেই।

৮ · break, continue আর লুপের ভেতরে লুপ

break — কাজ হয়ে গেছে, এবার বের হও

৬০ জনের তালিকায় একটি নির্দিষ্ট রোল খুঁজছেন। ১২ নম্বরেই পেয়ে গেলেন। বাকি ৪৮টি দেখার আর দরকার নেই। break লুপটিকে সেই মুহূর্তেই থামিয়ে লুপের পরের লাইনে নিয়ে যায় — শর্ত যা-ই থাকুক।

for (int roll = 1; roll <= 60; roll++) {
    if (roll == target) {
        printf("Found roll %d\n", roll);
        break;              // বাকি iteration বাদ, লুপ থেকে সোজা বাইরে
    }
}

continue — এটি বাদ দাও, পরেরটিতে যাও

continue লুপ থামায় না — শুধু এই iteration-এর বাকি অংশ বাদ দিয়ে পরের iteration-এ চলে যায়। ধরুন নম্বর পড়ার সময় কেউ ভুল করে 150 লিখল। সেটি বাদ দিয়ে পরের নম্বর পড়তে চান:

while (scanf("%d", &mark) == 1 && mark != -1) {
    if (mark < 0 || mark > 100)
        continue;           // অবৈধ নম্বর: যোগ না করেই পরের ইনপুটে
    sum += mark;
    count++;
}

for লুপে continue করলে update অংশ (i++) তবুও চলে। কিন্তু while লুপে update যদি body-র শেষে লেখা থাকে, continue সেটিকেও লাফিয়ে পার হয়ে যায় — আর তৈরি হয় অসীম লুপ। দুটি লুপকে “একই জিনিস” ভাবার এটিই একমাত্র বড় ব্যতিক্রম। আরও মনে রাখুন: break আর continue শুধু সবচেয়ে ভেতরের লুপের ওপর কাজ করে।

লুপের ভেতরে লুপ — সারি আর আসন

হলরুমে পরীক্ষার আসন সাজানো হচ্ছে। প্রতিটি সারির জন্য সেই সারির প্রতিটি আসনে একটি রোল নম্বর বসাতে হবে। “প্রতিটি সারির জন্য” একটি লুপ, আর তার ভেতরে “প্রতিটি আসনের জন্য” আরেকটি লুপ। একে বলে nested loop. ২টি সারি আর প্রতি সারিতে ৩টি আসন নিয়ে দেখুন, ভেতরের লুপ কীভাবে প্রতিটি সারিতে নতুন করে শুরু হয়:

1for (int r = 1; r <= 2; r++) {2    for (int s = 1; s <= 3; s++) {3        printf("R%dS%d  ", r, s);4    }5    printf("\n");6}
ভ্যারিয়েবলের মান (Watch)
r (সারি)
s (আসন)
ভেতরের body মোট কতবার
  1. বাইরের লুপ শুরু। r = 1, অর্থাৎ প্রথম সারি। 1 ≤ 2 সত্য, তাই ভেতরে ঢুকছে। ভেতরের লুপ এখনো শুরু হয়নি।
  2. ভেতরের লুপ, আসন ১। s = 1. ছাপা হলো R1S1 — সারি ১, আসন ১।
  3. আসন ২। r একই আছে, শুধু s বাড়ছে। ভেতরের লুপ পুরো শেষ না হওয়া পর্যন্ত বাইরের লুপ অপেক্ষা করে।
  4. আসন ৩। প্রথম সারি পূর্ণ। এরপর s হবে 4, 4 ≤ 3 মিথ্যা — ভেতরের লুপ শেষ।
  5. সারি শেষ, নতুন লাইন। লাইন ৫ চলল, পর্দায় পরের লাইনে যাওয়া হলো। এবার বাইরের লুপের update: r হবে 2.
  6. দ্বিতীয় সারি — ভেতরের লুপ আবার শুরু থেকে। খেয়াল করুন, s আবার 1! ভেতরের for-এর init প্রতিটি সারিতে নতুন করে চলে। এটিই nested loop-এর মূল কথা।
  7. আসন ২। R2S2.
  8. আসন ৩। R2S3. দ্বিতীয় সারিও পূর্ণ।
  9. সব শেষ। r হলো 3, 3 ≤ 2 মিথ্যা। ভেতরের body মোট চলেছে ৬ বার = ২ সারি × ৩ আসন। সাধারণ নিয়ম: বাইরে n বার, ভেতরে m বার হলে ভেতরের কাজ হয় n × m বার।

এবার একটি পরিচিত nested loop নিজে চালিয়ে দেখুন — নামতার ছক। বাইরের লুপ সারি (r), ভেতরের লুপ কলাম (c), আর প্রতিটি ঘরে r × c. %4d প্রতিটি সংখ্যাকে ৪ ঘর চওড়া জায়গায় বসায়, তাই কলামগুলো সোজা থাকে।

#include <stdio.h>

int main(void) {
    int n = 5;
    printf("   |");
    for (int c = 1; c <= n; c++)
        printf("%4d", c);                 // header row
    printf("\n---+--------------------\n");
    for (int r = 1; r <= n; r++) {        // outer loop: rows
        printf("%2d |", r);
        for (int c = 1; c <= n; c++) {    // inner loop: columns
            printf("%4d", r * c);
        }
        printf("\n");                     // end of one row
    }
    return 0;
}

ভেতরের লুপ একটি পুরো সারি ছাপে, তারপর বাইরের লুপ "\n" দিয়ে পরের লাইনে নিয়ে যায়। n = 5 হলে ঘর ২৫টি। এবার n-কে 10 করে দেখুন — ঘর ১০০টি, কাজ চার গুণ। কারণ সারি দ্বিগুণ এবং কলামও দ্বিগুণ হয়েছে। (header-এর ড্যাশের সংখ্যা তখন মিলবে না — সেটি ঠিক করা আপনার অনুশীলন।)

লুপের ভেতরে লুপ — খরচ কত দ্রুত বাড়ে

এই গুণের নিয়মটি নিরীহ দেখালেও বাস্তবে খুব গুরুত্বপূর্ণ। ধরুন একটি কলেজের ১০,০০০ ছাত্রের মধ্যে একই মোবাইল নম্বর দুবার আছে কি না দেখতে চান, আর প্রত্যেককে বাকি প্রত্যেকের সাথে মিলিয়ে দেখছেন — দুটি nested loop দিয়ে। তুলনা হবে প্রায় ১০,০০০ × ১০,০০০ = ১০ কোটি বার। ছাত্র ১০ গুণ বাড়লে কাজ বাড়বে ১০০ গুণ। এক লুপের ভেতরে আরেক লুপ মানেই খরচ গুণ হয়ে যাওয়া। এই খরচ মাপার আনুষ্ঠানিক ভাষা — time complexity আর Big-O — আসবে পর্ব ৩৯-এ। আপাতত শুধু অভ্যাসটি গড়ুন: nested loop দেখলেই জিজ্ঞেস করুন, “n বড় হলে কী হবে?”

৯ · AI-এর যুগে — লুপ যাচাইয়ের দক্ষতা

লুপ লেখা AI-এর জন্য খুব সহজ কাজ, আর বেশিরভাগ সময় সে ঠিকই লেখে। সমস্যা হলো, লুপের ভুলগুলো চোখে পড়ে না — কোড পরিপাটি দেখায়, compile হয়, সাধারণ ইনপুটে ঠিক উত্তরও দেয়। ভুলটি ধরা পড়ে সীমানায়: খালি তালিকা, শেষ উপাদান, খুব বড় সংখ্যা। AI-এর দেওয়া লুপ হাতে পেলে তাই কোড পড়ার আগে তিনটি প্রশ্ন করুন: কোথা থেকে শুরু? কখন থামে? সত্যিই কি থামে?

AI-এর লেখা লুপে যেসব ভুল প্রায়ই দেখা যায়

ভুলকেমন দেখায়ফল ও সমাধান
Off-by-onei <= n যেখানে হওয়ার কথা i < nএকবার বেশি ঘোরে, শেষে সীমার বাইরের মান পড়ে। প্রথম ও শেষ iteration হাতে মেলান।
উল্টো গোনায় unsignedfor (unsigned i = n; i >= 0; i--)unsigned কখনো ঋণাত্মক হয় না — 0-র পরে বিশাল সংখ্যায় ঘুরে যায়, শর্ত সবসময় সত্য, লুপ কখনো থামে না। -Wextra এখানে warning দেয়। int ব্যবহার করুন।
লম্বা লুপে overflowint sum দিয়ে ১ থেকে ১ লাখ যোগআসল যোগফল 5000050000, যা int-এর সীমা (প্রায় ২১৪ কোটি) ছাড়িয়ে যায় — signed overflow হলো undefined behavior. long long নিন।
প্রতিবার অকারণ হিসাবfor (i = 0; i < strlen(s); i++)প্রতিটি iteration-এ পুরো লেখার দৈর্ঘ্য আবার গোনা হতে পারে — লম্বা লেখায় খুব ধীর। দৈর্ঘ্য লুপের আগে একবার বের করে রাখুন।
Accumulator রিসেট ভুলে যাওয়াপ্রতি সারির যোগফলের rowSum = 0 বাইরের লুপের বাইরেদ্বিতীয় সারির যোগফলে প্রথম সারির যোগফলও ঢুকে যায়। রিসেট রাখুন বাইরের লুপের ভেতরে।
Sentinel-ও হিসাবে ঢোকাআগে যোগ, পরে -1 যাচাইগড় ভুল আসে। sentinel যাচাই হোক যোগের আগে।

AI-কে শুধু “লুপ লিখে দাও” না বলে, লুপের হিসাবও চেয়ে নিন। নিচের prompt-এর শেষ অংশটি খেয়াল করুন — AI-কে নিজের লুপ হাতে trace করতে বাধ্য করা হচ্ছে, যা অনেক ভুল আগেই ধরিয়ে দেয়:

C-তে একটি প্রোগ্রাম লিখে দাও যা ব্যবহারকারীর কাছ থেকে পরীক্ষার নম্বর পড়বে (-1 লিখলে থামবে) এবং পাসের সংখ্যা (৩৩ বা তার বেশি) ও গড় দেখাবে। শর্ত: ১. প্রতিটি লুপের জন্য আলাদা করে বলো — শুরুর মান কী, থামার শর্ত কী, প্রতিবার কী বদলায়, আর লুপটি যে অবশ্যই থামবে তার কারণ। ২. কোনো নম্বর না দিয়েই প্রথমে -1 দিলে, আর মাত্র একটি নম্বর দিলে প্রোগ্রাম কী করবে, বলো। ৩. ইনপুট "40 25 -1" দিয়ে লুপটি হাতে trace করে একটি টেবিল দেখাও: প্রতি iteration-এ প্রতিটি ভ্যারিয়েবলের মান। ৪. printf-এর লেখা ইংরেজিতে রাখো, আর gcc -Wall -Wextra-তে যেন কোনো warning না আসে।

AI-এর লুপ হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১for লুপকে কেন তিনটি অংশ দিয়ে বানানো হলো? শুধু “১ থেকে ১০” লেখার সহজ ব্যবস্থা কেন নয়?

পুরোনো ভাষাগুলোর গোনার লুপ সত্যিই সহজ ছিল। Fortran-এর DO লুপ বা ALGOL-এর for … step … until — দুটোতেই আপনি বলতেন “এই ভ্যারিয়েবল এই মান থেকে ওই মান পর্যন্ত, এত করে বাড়বে।” সহজ, কিন্তু সীমিত। শুধু সমান ধাপে বাড়া-কমা সংখ্যা গোনা যেত।

C-র নকশাকারেরা একটি সাহসী সিদ্ধান্ত নিলেন: for-এর তিনটি অংশই হবে যেকোনো সাধারণ expression. ফলে একই কাঠামোয় লেখা যায়:

  • দ্বিগুণ করে বাড়া: for (x = 1; x < 1000; x *= 2) — যা পুরোনো ছাঁচে সম্ভব ছিল না।
  • শর্ত যেকোনো কিছু: for (; balance > 0; balance -= kisti) — কিস্তি দিতে দিতে ব্যালান্স শেষ।
  • পরে আসবে: পর্ব ৩৩-এ linked list-এর প্রতিটি ঘরে যেতে লিখবেন for (p = head; p != NULL; p = p->next) — এখানে কোনো সংখ্যাই গোনা হচ্ছে না!
  • যেকোনো অংশ ফাঁকা: for (;;) — তিনটিই বাদ, অর্থাৎ চিরকাল।

তাহলে আলাদা while থাকতে for কেন? কারণ পড়ার সুবিধা। লুপের সব বাগ থাকে চারটি জায়গায় — শুরু, শর্ত, অগ্রগতি, কাজ। for প্রথম তিনটিকে এক লাইনে জড়ো করে, যাতে body যত লম্বাই হোক, লুপের “নিয়ন্ত্রণ” এক নজরে দেখা যায়। এটি কম্পিউটারের জন্য নয় — compiler-এর কাছে for আর while প্রায় একই machine code তৈরি করে। এটি মানুষের জন্য।

অবশ্য এই স্বাধীনতার একটি দামও আছে। যেহেতু তিনটি অংশে যা খুশি লেখা যায়, অনেকে একটি লাইনে এত কিছু ঠেসে দেন যে পড়াই কঠিন হয়ে যায়। তাই পরে আসা অনেক ভাষা (Python-এর for x in …, আধুনিক C++-এর range-for) আবার সহজ ছাঁচে ফিরে গেছে — “এই সংগ্রহের প্রতিটির জন্য” — কারণ বাস্তবে বেশিরভাগ লুপ ঠিক এটাই করে, আর তাতে off-by-one-এর সুযোগই থাকে না।

মূল উপলব্ধি: ভাষার নকশা সবসময় শক্তি আর সরলতার মধ্যে দর-কষাকষি। C বেছে নিয়েছিল শক্তি ও নমনীয়তা; পরের ভাষাগুলো সাধারণ কাজের জন্য সরলতা ফিরিয়ে এনেছে। দুটোই যুক্তিসংগত।

প্র ০২while আর for কি সত্যিই সমতুল্য? তাহলে একটি ভাষায় তিন রকম লুপ রাখার মানে কী?

প্রায় সমতুল্য — তবে “প্রায়” শব্দটির ভেতরেই আসল শিক্ষা। যেকোনো for (A; B; C) { D }-কে লেখা যায় A; while (B) { D; C; } হিসেবে। উল্টোটাও সহজ: while (B) মানে for (; B; ). কিন্তু দুটি সূক্ষ্ম পার্থক্য আছে:

  • continue: for-এ continue করলে update (C) চলে। রূপান্তরিত while-এ C আছে body-র শেষে, তাই continue সেটিকে লাফিয়ে যায় — হুবহু রূপান্তর করলে অসীম লুপ হতে পারে।
  • ভ্যারিয়েবলের আয়ু: for (int i = 0; …)-এর i শুধু লুপের ভেতরে থাকে (C99 থেকে)। while-এর জন্য বাইরে ঘোষণা করলে লুপের পরেও বেঁচে থাকে — ভুল করে পরে ব্যবহারের সুযোগ তৈরি হয়।

আরও গভীরে যাই। ১৯৬৬ সালে Corrado Böhm ও Giuseppe Jacopini প্রমাণ করেন, যেকোনো হিসাবযোগ্য প্রোগ্রাম মাত্র তিনটি গঠন দিয়ে লেখা যায়: ক্রম (একটির পর একটি), বাছাই (if) আর পুনরাবৃত্তি (একটি লুপ)। অর্থাৎ একটি লুপই যথেষ্ট! এর দুই বছর পর, ১৯৬৮ সালে Edsger Dijkstra-র বিখ্যাত চিঠি “Go To Statement Considered Harmful” প্রকাশিত হয়, যা structured programming আন্দোলনকে জোরালো করে — এলোমেলো লাফ (goto) বাদ দিয়ে এই কয়েকটি পরিষ্কার গঠনে প্রোগ্রাম লেখা।

তাহলে তিনটি কেন? কারণ প্রোগ্রাম কম্পিউটারের চেয়ে মানুষ বেশি পড়ে। তিনটি লুপ তিনটি ভিন্ন উদ্দেশ্য প্রকাশ করে — গোনা, অপেক্ষা, অন্তত-একবার। সঠিকটি বেছে নিলে পাঠক কোড না পড়েই উদ্দেশ্য বুঝে যান। আর পর্ব ১৫-এ দেখবেন, লুপ ছাড়াও পুনরাবৃত্তি সম্ভব — একটি ফাংশন নিজেকে ডেকে (recursion). গণিতের দিক থেকে সেটিও লুপের সমান শক্তিশালী।

মূল উপলব্ধি: “সমান শক্তিশালী” আর “সমান উপযোগী” এক কথা নয়। ভাষার বাড়তি গঠনগুলো নতুন ক্ষমতা দেয় না, দেয় স্পষ্টতা — আর বড় সফটওয়্যারে স্পষ্টতাই সবচেয়ে দামি।

প্র ০৩Off-by-one কেন প্রোগ্রামিংয়ের সবচেয়ে পরিচিত বাগগুলোর একটি? অভিজ্ঞ প্রোগ্রামাররাও কেন এতে পড়েন?

প্রোগ্রামারদের মধ্যে একটি পুরোনো রসিকতা আছে: “কম্পিউটার বিজ্ঞানে কঠিন সমস্যা দুটি — cache invalidation, নাম দেওয়া, আর off-by-one error.” তিনটি বলা হলো “দুটি” বলে — রসিকতাটি নিজেই একটি off-by-one! কিন্তু এত সাধারণ কেন?

  • বিন্দু বনাম ফাঁক: মানুষের মন সহজে দুটি গুলিয়ে ফেলে। শুক্রবার থেকে রবিবার — কয় দিন? তিন দিন (শুক্র, শনি, রবি), কিন্তু পার্থক্য করলে ২। বেড়ার খুঁটির ধাঁধা এই একই বিভ্রান্তি।
  • ০ থেকে, না ১ থেকে: দৈনন্দিন জীবনে আমরা ১ থেকে গুনি — রোল ১, পৃষ্ঠা ১। C গোনে ০ থেকে। দুই অভ্যাসের সংঘাতে একবার বেশি বা কম হয়।
  • সীমানা ভেতরে না বাইরে: “১ থেকে ১০” বললে ১০ কি অন্তর্ভুক্ত? বাংলায় আমরা সাধারণত হ্যাঁ ধরি, কিন্তু কোডে < লিখলে না।

১৯৮২ সালে Edsger Dijkstra একটি ছোট নোটে (“Why numbering should start at zero”) এই সমস্যার একটি পরিষ্কার সমাধান প্রস্তাব করেন: পরিসর লিখুন শুরু ভেতরে, শেষ বাইরে — 0 ≤ i < n. তাঁর যুক্তি ছিল, এতে উপাদানের সংখ্যা সরাসরি n − 0 = n, খালি পরিসর লেখা যায় (0 ≤ i < 0), আর গোনা ০ থেকে শুরু করলে শেষ সীমা ঠিক উপাদানের সংখ্যাই হয়। আজকের C, Python, Java — প্রায় সবাই এই ছাঁচ মেনে চলে।

Off-by-one শুধু ভুল উত্তর দেয় না, কখনো নিরাপত্তার ছিদ্রও তৈরি করে। একটি লুপ যদি ১০ ঘরের জায়গায় ১১তম ঘরে লেখে, সেটি পাশের মেমরি নষ্ট করে — এটি এক ধরনের buffer overflow, যার কারণে বাস্তবে বহু নিরাপত্তা-ত্রুটি হয়েছে। তাই পেশাদার কোড রিভিউয়ে সীমানার শর্তগুলো আলাদা করে খুঁটিয়ে দেখা হয়, আর test case-এ সবসময় থাকে ০টি, ১টি আর সর্বোচ্চ সংখ্যক উপাদান।

মূল উপলব্ধি: Off-by-one বুদ্ধির অভাব নয় — মানুষের স্বাভাবিক গোনার অভ্যাস আর মেশিনের নিয়মের সংঘাত। সমাধানও তাই অভ্যাস: একটি ছাঁচ (half-open) বেছে নিয়ে সবসময় সেটিই ব্যবহার করা, আর সীমানা হাতে যাচাই করা।

প্র ০৪Compiler কি আপনার লুপ দ্রুত করে দিতে পারে? কীভাবে, আর কোথায় সে থেমে যায়?

পারে, এবং খুব ভালোভাবেই। প্রোগ্রামের বেশিরভাগ সময় কাটে লুপের ভেতরে, তাই optimization-এর বড় অংশই লুপ নিয়ে। -O2 দিলে compiler কয়েকটি কৌশল খাটায়:

  • বাইরে তুলে আনা (hoisting): লুপের ভেতরে এমন হিসাব যার ফল কখনো বদলায় না — যেমন rate * 100 — compiler সেটি লুপের আগে একবার হিসাব করে রাখে। ১০ লাখ বারের কাজ হয়ে যায় একবারের।
  • লুপ খুলে লেখা (loop unrolling): প্রতিবার শর্ত যাচাই আর লাফ দেওয়ারও খরচ আছে। compiler body-কে ৪ বার পাশাপাশি লিখে লুপকে ৪ গুণ কম ঘোরায়।
  • একসাথে কয়েকটি (vectorization): আধুনিক CPU এক নির্দেশে ৪টি বা ৮টি সংখ্যা একসাথে যোগ করতে পারে। সহজ লুপ দেখলে compiler সেই নির্দেশ ব্যবহার করে।
  • গণিতের শর্টকাট: ১ থেকে n পর্যন্ত যোগফলের মতো সরল লুপকে কিছু compiler পুরো লুপ বাদ দিয়ে সরাসরি সূত্রে বদলে দিতে পারে।

কোথায় থামে? compiler কেবল তখনই বদলায়, যখন প্রমাণ করতে পারে যে ফলাফল একই থাকবে। i < strlen(s)-এর ক্ষেত্রে, লুপের ভেতরে s বদলায় না — এটি নিশ্চিত হলে compiler কখনো কখনো strlen-কে বাইরে তুলে আনে। কিন্তু ভেতরে কোনো ফাংশন ডাকা হলে, বা পয়েন্টারের মাধ্যমে লেখা হলে, সে আর নিশ্চিত হতে পারে না — তখন প্রতিবার আবার হিসাব হয়। তাই compiler-এর ওপর ভরসা না রেখে নিজেই দৈর্ঘ্য আগে বের করে রাখা ভালো অভ্যাস।

একটি সূক্ষ্ম দিকও আছে। C-র নিয়মে signed overflow undefined behavior, তাই compiler ধরে নিতে পারে int i কখনো উপচে পড়বে না, আর সেই অনুমানে লুপ বদলে ফেলে। আপনার লুপে সত্যিই overflow ঘটলে -O0-এ এক রকম আর -O2-এ আরেক রকম ফল আসতে পারে — পর্ব ০৩-এর সতর্কতা এখানেও খাটে।

মূল উপলব্ধি: সরল, পরিষ্কার লুপ লিখুন — compiler সেটি সবচেয়ে ভালো বোঝে ও সবচেয়ে বেশি দ্রুত করে। “চালাক” কোড অনেক সময় compiler-এর হাত বেঁধে দেয়। আর মনে রাখুন, কোনো optimizer একটি খারাপ অ্যালগরিদমকে ভালো বানাতে পারে না — n × n-এর nested loop n-এর লুপ হয়ে যায় না।

প্র ০৫সার্ভার আর embedded যন্ত্র কেন ইচ্ছা করেই চিরকাল লুপে থাকে? তারা আটকে গেলে কী হয়?

সাধারণ একটি প্রোগ্রামের জীবন হলো: শুরু → কাজ → শেষ। কিন্তু কিছু প্রোগ্রামের কাজই হলো অপেক্ষা করা আর সাড়া দেওয়া। তাদের কোনো “শেষ” নেই।

  • সার্ভার: একটি ওয়েব সার্ভারের লুপ মোটামুটি এই: নতুন অনুরোধের অপেক্ষা → অনুরোধ পড়া → উত্তর পাঠানো → আবার অপেক্ষা। অপেক্ষার সময় সে CPU নষ্ট করে না — অপারেটিং সিস্টেম তাকে ঘুম পাড়িয়ে রাখে, অনুরোধ এলে জাগায়। এই নকশাটিই event loop. বন্ধ করতে হলে অপারেটিং সিস্টেম একটি সংকেত পাঠায়, আর প্রোগ্রাম গুছিয়ে break করে বেরিয়ে আসে।
  • গেম: প্রতিটি “ফ্রেম”-এ ইনপুট পড়া, অবস্থা হালনাগাদ, আঁকা। ৬০ ফ্রেম প্রতি সেকেন্ড মানে প্রতিটি ঘোরার জন্য বাজেট মাত্র ১৬ মিলিসেকেন্ডের কিছু বেশি। একটি iteration ধীর হলেই খেলা “ল্যাগ” করে।
  • Embedded যন্ত্র: একটি ডিজিটাল ওজন মাপার যন্ত্র বা ট্রাফিক সিগন্যালের embedded system-এ প্রায়ই কোনো অপারেটিং সিস্টেম থাকে না। main শেষ হলে ফিরে যাওয়ার কোনো জায়গাই নেই! তাই প্রোগ্রাম শুরুতে একবার যন্ত্রাংশ প্রস্তুত করে, তারপর while (1)-এ ঢুকে বিদ্যুৎ থাকা পর্যন্ত সেন্সর পড়ে আর সাড়া দেয়।

আটকে গেলে? যে যন্ত্রের পাশে কোনো মানুষ নেই — মাঠের সেচ-পাম্পের নিয়ন্ত্রক বা দূরের কোনো সেন্সর — সেটি একটি বাগে অসীম লুপে আটকে গেলে কে রিস্টার্ট করবে? এর জন্য থাকে watchdog timer — একটি আলাদা হার্ডওয়্যার ঘড়ি, যা নির্দিষ্ট সময় পরপর প্রোগ্রামের কাছ থেকে “আমি ঠিক আছি” সংকেত আশা করে। মূল লুপ প্রতি iteration-এ সেটি পাঠায়। প্রোগ্রাম কোথাও আটকে গেলে সংকেত বন্ধ হয়, আর watchdog পুরো যন্ত্রটি রিসেট করে দেয়।

মূল উপলব্ধি: অসীম লুপ নিজে ভালো বা খারাপ নয়। প্রশ্ন হলো লুপটি নিয়ন্ত্রিত কি না — প্রতিবার অর্থপূর্ণ কাজ, অপেক্ষার সময় বিশ্রাম, বের হওয়ার পরিকল্পিত পথ, আর আটকে গেলে উদ্ধারের ব্যবস্থা। ভালো সিস্টেম-ডিজাইন মানে ব্যর্থতার কথা আগেই ভেবে রাখা।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — খাতা-কলমে লুপ “চালানো”-ই আসল কাজ।

  1. হাতে trace করুন: for (int i = 10; i > 0; i -= 3) printf("%d ", i); — কী ছাপা হবে, আর body কতবার চলবে? লুপ শেষে শর্ত কতবার যাচাই হয়েছে?

    ছাপা হবে 10 7 4 1 — body চলবে ৪ বার। 1 থেকে 3 কমালে i হয় -2, আর -2 > 0 মিথ্যা, তাই লুপ থামে। শর্ত যাচাই হয়েছে ৫ বার: চারবার “হ্যাঁ”, শেষবার “না”। সবসময় শর্ত যাচাই body-র চেয়ে একবার বেশি হয় — এটিও একরকম বেড়ার খুঁটি!

  2. খুঁটির ধাঁধা, দুই রূপে: (ক) ১০০ মিটার রাস্তার এক মাথা থেকে আরেক মাথা পর্যন্ত প্রতি ১০ মিটারে একটি ল্যাম্পপোস্ট, দুই মাথাতেও। কয়টি? (খ) একটি গোল মাঠের চারপাশে ৪০ মিটার বেড়া, প্রতি মিটারে খুঁটি। কয়টি?

    (ক) ১১টি — ১০টি ফাঁক, খুঁটি একটি বেশি।

    (খ) ৪০টি — গোল বেড়ায় শেষ মাথা আবার শুরুর মাথায় মেলে, তাই শেষ খুঁটি আর প্রথম খুঁটি একই। এখানে খুঁটি আর ফাঁক সমান। শিক্ষা: সূত্র মুখস্থ না করে প্রতিবার ছবি এঁকে ভাবুন, শুরু আর শেষ কোথায়।

  3. বাগ খুঁজুন: নিচের কোড ১ থেকে ১০ ছাপার কথা, কিন্তু কিছুই ছাপে না এবং প্রোগ্রাম আটকে যায়। কেন? int i = 1; while (i <= 10); { printf("%d\n", i); i++; }

    while (i <= 10)-এর পরের সেমিকোলনটিই লুপের body — একটি ফাঁকা নির্দেশ। i কখনো বাড়ে না (কারণ i++ লুপের বাইরে, যেখানে কখনো পৌঁছানো যায় না), তাই 1 ≤ 10 চিরকাল সত্য। সেমিকোলনটি মুছে দিলেই ঠিক। Clang এমন ক্ষেত্রে সাধারণত warning দেয় যে লুপের body ফাঁকা; GCC সবসময় দেয় না। তাই এই ভুলটি চোখ দিয়েই চিনে রাখুন — শর্তের বন্ধনীর ঠিক পরে সেমিকোলন মানেই সন্দেহ।

  4. প্রোগ্রাম বাড়ান: marks_avg.c-তে দুটি জিনিস যোগ করুন — সর্বনিম্ন নম্বর, আর কতজন পাস করেছে (৩৩ বা তার বেশি)। তারপর নামতার ছকে n = 10 দিলে header-এর ড্যাশ লাইনটি নিজে থেকে সঠিক দৈর্ঘ্যের হয়, এমন একটি লুপ লিখুন।

    সর্বনিম্নের জন্য max-এর মতোই ছাঁচ: int min = 0; লুপের আগে, আর ভেতরে if (count == 0 || mark < min) min = mark; — খেয়াল রাখুন এটি যেন count++-এর আগে থাকে। পাসের জন্য আরেকটি counter: if (mark >= 33) pass++;. ড্যাশ লাইনের জন্য প্রতিটি কলাম ৪ ঘর চওড়া, তাই for (int k = 0; k < 4 * n; k++) printf("-"); — half-open ছাঁচে ঠিক 4n টি ড্যাশ। ইনপুট 72 85 64 91 -1 দিয়ে যাচাই করুন: সর্বনিম্ন 64, পাস ৪ জন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমfor (int i = 0; i < 3; i++) শুরু হলে প্রথম ঘোরায় কী কী ঘটে, সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ① init — i = 0 (পুরো লুপে একবারই)
  2. ② শর্ত যাচাই — 0 < 3 সত্য
  3. ③ body একবার চলে
  4. ④ update — i++, i হলো 1
  5. ⑤ আবার শর্ত যাচাই — সত্য হলে আবার body

for (A; B; C) { D } — একটি সম্পূর্ণ iteration-এ কোন ক্রমে চলে (প্রথমবারের কথা)?

  • A → B → C → D
  • A → B → D → C
  • A → D → B → C
  • B → A → D → C

শর্ত শুরু থেকেই মিথ্যা। কোন লুপের body তবুও একবার চলবে?

  • while
  • for
  • do-while
  • কোনোটিরই নয়

for (int i = 0; i <= 10; i++) — body কতবার চলবে?

  • ১১ বার
  • ১০ বার
  • ৯ বার
  • অসীম বার

১২ মিটার লম্বা একটি সোজা বেড়া, প্রতি ৩ মিটারে একটি খুঁটি, দুই মাথাতেও। কয়টি খুঁটি লাগবে?

  • ৪টি
  • ১২টি
  • ১৩টি
  • ৫টি

AI লিখেছে for (unsigned i = 5; i >= 0; i--). কী হবে?

  • 5, 4, 3, 2, 1, 0 — ৬ বার চলে থামবে
  • লুপ কখনো থামবে না
  • compile-ই হবে না
  • body একবারও চলবে না

লুপের ভেতরে continue চললে কী হয়?

  • পুরো লুপ থেমে যায়, পরের লাইনে চলে যায়
  • পুরো প্রোগ্রাম বন্ধ হয়ে যায়
  • এই iteration-এর বাকি অংশ বাদ দিয়ে পরের iteration-এ যায়
  • লুপ একদম শুরু থেকে আবার শুরু হয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রতিটি লুপের চার অংশ: শুরু, শর্ত, কাজ, অগ্রগতি — বেশিরভাগ বাগ এর কোনো একটিতে।
  2. for গোনার জন্য, while অজানা সংখ্যক বারের জন্য, do-while অন্তত একবারের জন্য। for-এর ক্রম: init একবার → শর্ত → body → update.
  3. জমানোর ছাঁচ: sum, count, গড়, max/min — accumulator লুপের আগে শুরু হয়, sentinel কখনো হিসাবে ঢোকে না।
  4. Off-by-one: খুঁটি = ফাঁক + ১; প্রচলিত ছাঁচ for (i = 0; i < n; i++); প্রথম ও শেষ iteration হাতে যাচাই করুন।
  5. অসীম লুপ দুর্ঘটনায় হয় (অগ্রগতি নেই, !=, ভুল ;), আবার সার্ভার-গেম-যন্ত্রে নকশায়ও থাকে। Nested loop-এ কাজ হয় n × m বার।

পরের পর্বে: নামতার ছক বা গড় বের করার কোড বারবার লিখতে হলে কী করবেন? প্রতিবার কপি-পেস্ট নয় — একবার লিখে একটি নাম দিন, তারপর যতবার খুশি ডাকুন। পরের পর্বে আসছে ফাংশন — বড় কাজকে ছোট, পরীক্ষাযোগ্য টুকরোয় ভাগ করার কৌশল।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান