Learn C Programming

lesson ১ / ৭ · সিদ্ধান্ত নেওয়া

Module ৫ · সিদ্ধান্ত নেওয়া

If, else if, else: পথ বেছে নেওয়া

Freeপড়া

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

  • if, if else আর else if ladder লিখতে পারবে, প্রতিটা body-র চারপাশে brace দিয়ে।
  • একটা ladder-কে trace table দিয়ে মিলিয়ে দেখতে পারবে: কোন input কোন পথে গেল।
  • বলতে পারবে ladder-এর ক্রম কেন উত্তর বদলে দেয়, আর condition-এর ভিতরে একটা = কেন bug।

Module 4-এ Amara leap year-এর নিয়মটা এক expression-এ লিখেছিল, আর ওটা 1 বা 0 ছাপত। ওর calendar app কিন্তু চাইছিল একটা বাক্য: "2024 has 366 days"।

Screen-এ একটা 1 দেখানো আর একটা বাক্য বলা এক জিনিস না। দুইটা বাক্যের একটা ছাপতে হলে program-কে বেছে নিতে হবে কোন লাইনটা চলবে।

এখন পর্যন্ত প্রতিটা program প্রতিটা লাইন চালিয়েছে, উপর থেকে নিচে, প্রতিবার। আজ রাস্তাটা দুই ভাগ হচ্ছে।

if: যে block শুধু উত্তর শূন্য না হলে চলে

if statement একটা প্রশ্ন করে। উত্তর সত্য হলেই শুধু একটা block চালায়। C-তে "সত্য" মানে "শূন্য না", Module 2-এর সেই নিয়ম।

if statement

if (condition) {
    statements
}
  • if হলো keyword, আর এর পরে সব সময় বন্ধনী আসে।
  • condition যেকোনো expression। শূন্য মানে মিথ্যা; বাকি সব মান সত্য।
  • Brace-এর ভিতরে থাকে body: যে লাইনগুলো শুধু condition সত্য হলে চলে।
  • শেষ brace-এর পরে program নিজের মতো এগিয়ে যায়, প্রশ্নের উত্তর যা-ই হোক।
#include <stdio.h>

int main(void)
{
    int stock = 7;

    if (stock < 10) {
        printf("reorder\n");
    }
    printf("stock checked\n");
    return 0;
}
reorder
stock checked

7-কে 12 করে দাও, তখন শুধু দ্বিতীয় লাইনটা ছাপবে। Brace-এর ভিতরের printf বাদ পড়ে; পরেরটা পড়ে না।

মানে if program থামায় না, কোনো বেরোনোর রাস্তাও বাছে না। ও শুধু ঠিক করে একটা block চলবে কি না, তারপর program চলতে থাকে।

else: দুইটা পথের ঠিক একটা

else মিথ্যা উত্তরকে নিজের একটা block দেয়। দুইটা block-এর ঠিক একটাই চলে: কখনো দুইটাই না, কখনো একটাও-না-এমনও না।

#include <stdio.h>

int main(void)
{
    int year = 2024;
    int leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;

    if (leap) {
        printf("%d has 366 days\n", year);
    } else {
        printf("%d has 365 days\n", year);
    }
    return 0;
}
2024 has 366 days

Expression-টা Module 4-এর Amara-র লেখা, একটুও বদলায়নি। নতুন শুধু এটা যে ওর 1 বা 0 এখন ঠিক করে দেয় কোন printf চলবে।

তার মানে leap year-এর নিয়ম একটুও বদলায়নি। ওটা ছাপার মতো উত্তর থেকে কাজে লাগানোর মতো উত্তর হয়ে গেছে।

else if ladder: প্রথম যে condition সত্য, সে-ই জেতে

বাস্তবের বেশির ভাগ প্রশ্নের উত্তর দুইটার বেশি। C ওগুলোকে else if দিয়ে শিকলের মতো জোড়ে, আর এই শিকলটাকে সাধারণত ladder বলে।

else if ladder

if (condition_1) {
    runs when condition_1 is true
} else if (condition_2) {
    runs when condition_1 was false and condition_2 is true
} else {
    runs when every condition above was false
}
  • C উপর থেকে condition-গুলো একটা একটা করে পরীক্ষা করে।
  • প্রথম যেটা সত্য, সেটার block চলে, আর তার নিচের সব পরীক্ষা বাদ পড়ে।
  • শেষের else না দিলেও চলে। না দিলে, কোনোটার সাথে না মেলা মানের জন্য কিছুই চলে না।
#include <stdio.h>

int main(void)
{
    int marks = 72;

    if (marks >= 80) {
        printf("distinction\n");
    } else if (marks >= 60) {
        printf("merit\n");
    } else if (marks >= 40) {
        printf("pass\n");
    } else {
        printf("fail\n");
    }
    return 0;
}
merit

Ladder পড়ার হাতিয়ার হলো trace table। প্রতিটা input-এর জন্য একটা সারি, আর C আসলে যে পরীক্ষাগুলো চালিয়েছে সেগুলোর একটা কলাম।

marksযে পরীক্ষাগুলো চলল, ক্রমেযে পথে গেল
95>= 80 সত্যdistinction
72>= 80 মিথ্যা, >= 60 সত্যmerit
60>= 80 মিথ্যা, >= 60 সত্যmerit
45>= 80 মিথ্যা, >= 60 মিথ্যা, >= 40 সত্যpass
12তিনটাই মিথ্যাfail, মানে else

72-এর সারির মাঝের কলামটা পড়ো। >= 40 পরীক্ষাটা চলেইনি। প্রথম সত্য উত্তরেই ladder থেমে গেছে।

else if ladder, একটা flowchart হিসেবে হ্যাঁ হলে ladder শেষ; শুধু না হলেই পরের পরীক্ষা marks >= 80 হ্যাঁ distinction না marks >= 60 হ্যাঁ merit না marks >= 40 হ্যাঁ pass না else: fail 72 marks-এর পথ: প্রথম পরীক্ষায় না, দ্বিতীয়তে হ্যাঁ, আর তৃতীয়টা চলেই না।

এবার ক্রমের নিয়ম। Bob, যে সব সময় তাড়াহুড়া করে, একই ladder লেখে সবচেয়ে সহজ পরীক্ষা দিয়ে শুরু করে।

#include <stdio.h>

int main(void)
{
    int marks = 95;

    if (marks >= 40) {
        printf("pass\n");
    } else if (marks >= 60) {
        printf("merit\n");
    } else if (marks >= 80) {
        printf("distinction\n");
    } else {
        printf("fail\n");
    }
    return 0;
}
pass

95-কে এর ভিতর দিয়ে trace করো। প্রথম পরীক্ষা, >= 40, সত্য, তাই ladder ওখানেই থামে। 62 আর 100-এর বেলাতেও একই কথা।

40 থেকে উপরের প্রতিটা মার্ক প্রথম পরীক্ষাতেই পাশ করে যায়, তাই merit আর distinction-এর block কখনো চলতেই পারে না। Compiler কিছুই বলে না; Bob কী বোঝাতে চেয়েছিল, সেটা ওর জানার উপায় নেই।

তাই যেকোনো ladder-এর নিয়মটা এই। একটা condition-এর পরিসর যদি আরেকটার পরিসরকে ভিতরে ধরে রাখে, ছোট পরিসরটাকে আগে বসাতে হবে। এখানে "80 আর তার বেশি" আছে "40 আর তার বেশি"-র ভিতরে, তাই ওটা উপরে যায়।

যেকোনো int-ই condition, আর 0 মানে মিথ্যা

if-এর পরের বন্ধনীতে তুলনা থাকতেই হবে, এমন না। সংখ্যা হয় এমন যেকোনো expression চলে, আর শুধু শূন্য মানে না।

#include <stdio.h>

int main(void)
{
    int stock = 0;
    int n = -7;

    if (stock) {
        printf("in stock\n");
    } else {
        printf("sold out\n");
    }

    if (n % 2) {
        printf("%d is odd\n", n);
    }
    return 0;
}
sold out
-7 is odd

if (stock) মানে হুবহু if (stock != 0)। "কিছু আছে কি" ধরনের গণনার জন্য ছোট রূপটা পড়তে ভালো লাগে।

দ্বিতীয় পরীক্ষাটা একটু সূক্ষ্ম। -7 % 2 হলো -1, Module 4-এর sign-এর নিয়ম, আর -1 শূন্য না। তাই negative বিজোড় সংখ্যাতেও পরীক্ষাটা সত্য, যেখানে n % 2 == 1 হতো মিথ্যা।

তার মানে condition আলাদা কোনো জাতের মান না। ওটা একটা সংখ্যা, আর শুধু শূন্যই না বলে।

একটা সমান-চিহ্ন: যে bug compile হয়ে যায়

= একটা মান রাখে। == একটা প্রশ্ন করে। Condition-এর ভিতরে প্রথমটা প্রায় সব সময়ই টাইপের ভুল।

#include <stdio.h>

int main(void)
{
    int marks = 35;

    if (marks = 100) {
        printf("full marks\n");
    }
    printf("marks is now %d\n", marks);
    return 0;
}
full marks
marks is now 100

একসাথে দুইটা জিনিস ভুল হলো। Assignment-এর মান 100, যেটা শূন্য না, তাই পরীক্ষাটা সব সময় সত্য। আর ছাত্রটার আসল মার্কটা মুছে গেছে।

Playground এটা চুপচাপ compile করে, কারণ ওখানে -Wall নেই। নিজের মেশিনে GCC 12-এর gcc -Wall বলে warning: suggest parentheses around assignment used as truth value [-Wparentheses]।

তাই যে if তোমার দেওয়া প্রতিটা input-এই সত্য, ওখানে আগে একা একটা = খোঁজো।

প্রতিটা body-তে brace, আর যে bug সবাইকে কারণটা শিখিয়েছে

Body যদি একটাই statement হয়, C brace ছাড়া if লিখতে দেয়। এই track সেই ছাড় কখনো নেয় না, আর কারণটা এখানে।

2014-র February-তে Apple iOS আর macOS-এর TLS code-এর একটা fix ছাড়ে, যেটার নাম CVE-2014-1266। Server-এর signature যাচাই করা একটা function-এ brace ছাড়া একটা if-এর নিচে পরপর দুইটা হুবহু এক লাইন ছিল: goto fail;। প্রথম লাইনটাই শুধু if-এর। দ্বিতীয়টা প্রতিবার চলত, আসল যাচাইটা টপকে যেত, আর বলত সব ঠিক আছে। লোকে ওটাকে ডাকে "goto fail"।

শুধু printf দিয়েই একই ফাঁদ বানানো যায়।

#include <stdio.h>

int main(void)
{
    int marks = 35;

    if (marks >= 40)
        printf("pass\n");
        printf("certificate printed\n");
    return 0;
}
certificate printed

Indentation দেখে মনে হয় দুইটা লাইনই if-এর। Compiler indentation-কে পাত্তাই দেয় না, তাই শুধু প্রথমটাই ওর।

Playground আবারও চুপ। নিজের মেশিনের gcc -Wall বলে warning: this 'if' clause does not guard... [-Wmisleading-indentation]। Brace থাকলে প্রশ্নটাই ওঠে না।

তাই brace সাজসজ্জা না। Body কোথায় শেষ, C-কে সেটা বলার একমাত্র উপায় ওটাই।

Example 1: input পড়া সবচেয়ে ছোট if

একটা condition, একটা body, আর উত্তর না হলে কিছুই না।

#include <stdio.h>

int main(void)
{
    int celsius = 0;
    scanf("%d", &celsius);

    if (celsius < 0) {
        printf("freezing\n");
    }
    return 0;
}
freezing

ওই output-টা -3 input-এর জন্য। 5 দিলে program কিছুই ছাপে না, আর ওকে ঠিক সেটাই করতে বলা হয়েছিল।

Run in Compiler
Example 2: if else, একটা negative input দিয়ে

দুইটা পথ, আর negative সংখ্যা কোন পথে যাবে সেটা ঠিক করে Module 4-এর ভাগশেষের নিয়ম।

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    if (n % 2 == 0) {
        printf("%d is even\n", n);
    } else {
        printf("%d is odd\n", n);
    }
    return 0;
}
-7 is odd

ওই output-টা -7 input-এর জন্য। 0-র সাথে মেলালে দুই sign-এই কাজ করে, কারণ -1 ভাগশেষও তো 0 না।

Run in Compiler
Example 3: নতুন শিখছে এমন কেউ আসলে যে ladder লেখে

বয়স দেখে সিনেমার টিকিট। একটা পাহারা আগে অসম্ভব input বাদ দেয়, তারপর band-গুলো সবচেয়ে ছোট বয়স থেকে উপরে ওঠে।

#include <stdio.h>

int main(void)
{
    int age = 0;
    scanf("%d", &age);

    if (age < 0) {
        printf("invalid age\n");
    } else if (age < 3) {
        printf("ticket 0\n");
    } else if (age < 12) {
        printf("ticket 150\n");
    } else if (age >= 60) {
        printf("ticket 200\n");
    } else {
        printf("ticket 300\n");
    }
    return 0;
}
ticket 200

ওই output-টা 65 input-এর জন্য। এখানে পরীক্ষাগুলোয় <, তাই "3-এর কম" আছে "12-এর কম"-এর ভিতরে, আর ওটাই আগে আসে। নিয়ম সেই একটাই, শুধু উল্টো দিক থেকে পড়া।

Run in Compiler

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

  • পরীক্ষার ফল। বাংলাদেশের শিক্ষা বোর্ড একটা grade-এর table ছাপে: 80 বা তার বেশি A+, আর 33-এর নিচে F। যে program ওটা ছাপে, সেটা একটা else if ladder, সবচেয়ে কড়া band আগে।
  • Thermostat। Linux kernel-এর step_wise thermal governor তাপমাত্রাকে কয়েকটা trip point-এর সাথে মেলায়। কয়েকটা if পরপর ঠিক করে ঠান্ডা করা বাড়বে, কমবে, না একই থাকবে।
  • Input যাচাই। nginx web server client_max_body_size-এর চেয়ে বড় request body-কে status 413 দিয়ে ফিরিয়ে দেয়। একটা তুলনা, একটা branch, আসল কাজ শুরুর আগেই।
  • Login পরীক্ষা। OpenSSH-এর server ভুল password-এর চেষ্টাগুলো গোনে, আর MaxAuthTries-টা ভুলের পরে সংযোগ কেটে দেয়। ওটা একটা counter-এর উপর if।

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

১. Condition-এ একটা সমান-চিহ্ন।

if (marks = 100) {
    printf("full marks\n");
}

Playground-এ নীরব। নিজের মেশিনের gcc -Wall বলে warning: suggest parentheses around assignment used as truth value [-Wparentheses]। পরীক্ষাটা সব সময় সত্য, আর marks-এর মান মুছে যায়। == লেখো। জোরে টাইপ করলেই এটা হবে, কারণ দুইটা একই চাবি।

২. Condition-এর ঠিক পরেই একটা semicolon।

if (marks >= 40);
{
    printf("pass\n");
}

Playground-এ নীরব। নিজের মেশিনের gcc -Wall জানায় this 'if' clause does not guard... [-Wmisleading-indentation], আর -Wextra যোগ করে suggest braces around empty body in an 'if' statement [-Wempty-body]। Semicolon-টাই একটা ফাঁকা body, তাই প্রতিটা মার্কেই "pass" ছাপে। ওটা মুছে দাও; if-এর লাইন কখনো semicolon দিয়ে শেষ হয় না।

৩. মুখে যে ক্রমে বলো, সেই ক্রমে চিহ্ন লেখা।

if (marks =< 40) {
    printf("fail\n");
}

প্রতিটা command line-এ error, Playground-সহ: error: expected expression before '<' token। Operator-টা <=, কোণটা আগে। মুখে তো বলো "less than or equal", চিহ্নও ইংরেজি কথাটার ক্রমেই বসে।

৪. সবচেয়ে চওড়া পরীক্ষাটা সবার উপরে।

if (marks >= 40) {
    printf("pass\n");
} else if (marks >= 80) {
    printf("distinction\n");
}

কোনো command line-এই বার্তা নেই। দ্বিতীয় block কখনো চলতে পারে না, কারণ 80 ছোঁয়া প্রতিটা মার্ক আগেই প্রথম পরীক্ষায় পাশ করে গেছে। ছোট পরিসর আগে বসাও, আর বিশ্বাস করার আগে প্রতিটা band থেকে একটা করে input trace করো।

মাথা খাটাও

x positive না হলে Bob "b" চায়। মাথায় যেভাবে বাক্যটা আসে, ও সেভাবেই লাইনটা লেখে।

#include <stdio.h>

int main(void)
{
    int x = -1;
    int y = 5;

    if (x > 0)
        if (y > 0)
            printf("a\n");
    else
        printf("b\n");

    printf("done\n");
    return 0;
}

এটা শুধু done ছাপে। বলো তো, x -1 হলে "b" কেন আসে না। তারপর কঠিন অর্ধেকটা: x আর y-এর কোন মানে "b" ছাপে?

একটা else-কে ঠিক একটা if-এর হতে হয়। Indentation হলো Bob-এর মতামত; compiler-এর নিজের একটা নিয়ম আছে।

অনুশীলন ১সহজ

Alice বাংলাদেশের শিক্ষা বোর্ডের table দিয়ে report card ছাপে। 80 থেকে 100 হলো A+, 70 থেকে 79 A, 60 থেকে 69 A-, আর 50 থেকে 59 B। তারপর 40 থেকে 49 C, 33 থেকে 39 D, আর 0 থেকে 32 F।

Input. এক লাইনে একটা পূর্ণসংখ্যা marks।

Output. এক লাইনে grade-টা, আর marks যদি 0-র নিচে বা 100-র উপরে হয় তাহলে invalid।

Constraints. -1000 <= marks <= 1000।

Sample. Input 79 দিলে A। Input 101 দিলে invalid।

#include <stdio.h>

int main(void)
{
    int marks = 0;
    scanf("%d", &marks);

    /* One guard for the impossible marks, then one rung per band. */

    return 0;
}

grade-bands নামে গ্রেড হয়। Table-এর প্রতিটা সীমাই hidden test, দুই পাশ থেকেই।

Run in Compiler
অনুশীলন ২সহজ

Kenji তিনটা ল্যাপ দৌড়েছে, আর leaderboard শুধু ওর সেরা score দেখায়। দুইটা ল্যাপের score সমান হতে পারে।

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

Output. এক লাইনে তিনটার মধ্যে সবচেয়ে বড়টা।

Constraints. প্রতিটা মান -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 3 9 5 দিলে 9। Input 7 2 7 দিলে 7।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Two separate ifs are enough. Equal values must not break it. */

    return 0;
}

largest-of-three নামে গ্রেড হয়। Hidden test-গুলোয় সবচেয়ে বড় মানটা তিনটা জায়গাতেই ঘুরে আসে।

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

Amara-র calendar-এর যেকোনো বছরের জন্য একটা সোজা উত্তর দরকার: leap year, না না?

Input. এক লাইনে একটা পূর্ণসংখ্যা y।

Output. এক লাইনে: y leap year হলে yes, নয়তো no।

Constraints. 1 <= y <= 9999।

Sample. Input 1900 দিলে no। Input 2000 দিলে yes।

#include <stdio.h>

int main(void)
{
    int y = 0;
    scanf("%d", &y);

    /* The Module 4 expression decides; an if else prints the word. */

    return 0;
}

আলাদা করে গ্রেড হয় না। Problem lesson-এর date-check এই নিয়মটাই একটা বড় প্রশ্নের ভিতরে ব্যবহার করে।

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

Zara তিনটা দৈর্ঘ্য দিয়ে একটা geometry app test করে। প্রতিটা দৈর্ঘ্য বাকি দুইটার যোগফলের চেয়ে ছোট হতে হবে, নইলে triangle নেই। যোগফলের সমান হলে আকারটা চ্যাপ্টা, ওটা গোনা হয় না।

Input. এক লাইনে তিনটা পূর্ণসংখ্যা a, b আর c।

Output. এক লাইনে: not a triangle, নইলে equilateral, isosceles বা scalene।

Constraints. 1 <= a, b, c <= 1000000000।

Sample. Input 2 2 3 দিলে isosceles। Input 1 2 3 দিলে not a triangle।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Is it a triangle at all? Then the narrowest kind first. */

    return 0;
}

triangle-kind নামে গ্রেড হয়। ধাপগুলোর ক্রমটাই পুরো কঠিন অংশ।

Run in Compiler

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

  • Body এক লাইনের হলেও কি সত্যিই brace লাগবে?

    C বাধ্য করে না। এই track করে, কারণ পরে কেউ যে লাইনটা যোগ করবে, সেটা দেখে মনে হবে পাহারার ভিতরে, অথচ থাকবে না। দুইটা অক্ষর তার তুলনায় খুব সস্তা দাম।

  • else if কি আলাদা একটা keyword?

    না। ওটা একটা else, যার body ঘটনাচক্রে আরেকটা if। দুইটা শব্দ এক লাইনে লেখা সাজানোর অভ্যাস মাত্র, আর প্রতিটা C programmer ladder-টা ওভাবেই পড়ে।

  • আলাদা আলাদা কয়েকটা if না লিখে ladder কেন?

    আলাদা statement-গুলো সবগুলোই পরীক্ষা হয়, তাই একাধিক block চলতে পারে। 95 মার্কের জন্য তিনটা আলাদা পরীক্ষা distinction, merit আর pass তিনটাই ছাপত। Ladder প্রথম সত্য পরীক্ষাতেই থামে, তাই ঠিক একটা block চলে।

  • if (x) কি হুবহু if (x != 0)-এর মতো?

    হ্যাঁ, compiler-এর কাছে। "কিছু আছে কি" ধরনের flag বা গণনার জন্য ছোট রূপটা নাও। শূন্যটা যখন আসল একটা মান যার সাথে তুলনা করছ, তখন লম্বা রূপটা নাও।

  • লম্বা ladder কি program ধীর করে দেয়?

    এখন মাপতে পারবে এমনভাবে না। Lesson 5 দেখাবে GCC একটা condition থেকে আসলে কী বানায়, আর সেটা প্রায়ই source দেখে যা মনে হয় তার চেয়ে কম।

মূল কথা

  • if নিজের body চালায় শুধু condition শূন্য না হলে।
  • else মিথ্যা উত্তরকে নিজের body দেয়, আর দুইটার ঠিক একটাই চলে।
  • Ladder উপর থেকে পরীক্ষা করে, আর প্রথম সত্য condition-এই থামে।
  • Trace table দেখায় কোন পরীক্ষাগুলো চলল; এক পরিসর আরেকটাকে ধরে রাখলে ছোটটা আগে।
  • Condition-এর ভিতরে একা = মান বসিয়ে দেয়, আর Playground ওটা নিয়ে চুপ থাকে।
  • প্রতিটা body-তে brace, এক লাইনের body-তেও।

এরপর একটা if ঢুকবে আরেকটা if-এর ভিতরে, আর একটা else ভুলে যাবে ও আসলে কার।

lesson ১ শেষ

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

পরেরটা: Nested condition আর ঝুলে থাকা else