Module ৩ · কনস্ট্যান্ট, qualifier, ইনপুট আর আউটপুট
ইনপুট ভুল এলে: যা পড়লে তা যাচাই করা
এই lesson-এ যা শিখবে
scanf-র ফেরত দেওয়া গোনাকে "কাজ হলো কি" প্রশ্নের একমাত্র সৎ উত্তর হিসেবে পড়তে পারবে।- দেখাতে পারবে, ব্যর্থ পড়া তোমার variable-এ হাত দেয় না আর খারাপ অক্ষরটা stream-এই থেকে যায়।
- সীমার যাচাই 1 বা 0 হিসেবে ছাপতে পারবে, আর ওটার উপর যে শাখা কাজ করে সেটা কোথায় শেখানো হয় বলতে পারবে।
Maria-র till একটা অঙ্ক চায় আর কেউ লিখে দেয় twelve। Till তবু একটা receipt ছেপে দেয়। ওখানকার সংখ্যাটা twelve-ও না, শূন্যও না। ও জিজ্ঞেস করে ওটা কোথা থেকে এলো, আর দোকানের কেউ বলতে পারে না।
এই lesson গোড়া থেকে শেষ পর্যন্ত একটাই ভাবনা। scanf program-কে আগেই বলে দিয়েছিল কিছুই পড়া হয়নি, আর program সেটা ছুড়ে ফেলে দিয়েছে। নিচের সবটুকুই তার পরে কী ঘটে, আর তার বদলে তুমি কী করবে।
ফেরত পাওয়া মান একটা গোনা, আর EOF কোনো গোনা না
একটা মান পড়ার পরে তিন রকম উত্তর আসে, আর তিনটার মানে তিন রকম।
| ফেরত | যা ঘটেছে | তোমার variable-এ যা আছে |
|---|---|---|
1 | জিনিসটা পড়া আর জমা হয়েছে | input থেকে আসা মান |
0 | input ছিল, আর সেটা মেলেনি | আগে যা ছিল তাই |
-1 | তার আগেই input ফুরিয়ে গেছে; এটাই EOF | আগে যা ছিল তাই |
প্রথম দুইটা গুলিয়ে ফেলা সহজ আর তফাতটা গুরুত্বপূর্ণ। 0 মানে সংখ্যার জায়গায় একটা শব্দ এসেছে, আর ওই শব্দটা এখনো অপেক্ষা করছে। -1 মানে কিছুই আসেনি, আর কখনো আসবেও না।
EOF হলো <stdio.h>-র একটা macro। এই toolchain-এ ওটা -1, আর এই কারণেই যে program ফেরত পাওয়া মান ছাপে সেটা input-র শেষে -1 ছাপে।
কয়েকটা specifier থাকলে গোনাটা যতগুলো ভরা হলো ততটা পর্যন্ত ওঠে, আর প্রথম যেটা ব্যর্থ হয় সেখানেই থেমে যায়। তিন specifier-র পড়া 2 ফেরত দিলে বুঝতে হবে তৃতীয়টা ঘটেইনি।
ব্যর্থ পড়া তোমার variable-কে যেমন ছিল তেমনই রেখে দেয়
এই অংশটা কাউকে বলা হয় না, আর এটাই Maria-র receipt-টাকে এত ধাঁধার মতো করে তোলে।
পড়া ব্যর্থ হলে scanf variable-কে শূন্য করে না, খালি করে না, আর ছোঁয়ও না। Call-র আগে variable-এ যে মান ছিল, পরেও সেটাই থাকে।
#include <stdio.h>
int main(void)
{
int amount = -1;
int read = scanf("%d", &amount);
printf("scanf returned %d\n", read);
printf("amount is now %d\n", amount);
return 0;
}
scanf returned 0
amount is now -1
ওই output-টা twelve input-র জন্য। দ্বিতীয় লাইনের -1 হলো declaration-র -1, অছোঁয়া।
একই program 42 input দিয়ে চালাও, ও ছাপবে 1 আর 42। Variable একই variable; কেবল গোনাটাই তোমাকে বলল দুই গল্পের কোনটা ঘটেছে।
তাই বুঝেশুনে একটা শুরুর মান দেওয়াটা গোছানোর ব্যাপার না, ওটাই সেই উত্তর যা পড়া ব্যর্থ হলে তোমার program ছাপবে। ওটা ইচ্ছে করে বেছে নাও।
যে অক্ষরে পড়া আটকাল সেটা stream-এই থেকে যায়
চমকের দ্বিতীয় অর্ধেক। ব্যর্থ একটা %d যে লেখাটা মেলেনি সেটা গিলে ফেলে না। twelve-র t এখনো পরের অপেক্ষমাণ অক্ষর।
তাই পরের %d সেই একই t-র সামনে পড়ে, একইভাবে ব্যর্থ হয়, আর আবার 0 ফেরত দেয়। Stream পরিষ্কার না করে যে program আবার চেষ্টা করে, সেটা চিরকাল চেষ্টা করতেই থাকে।
#include <stdio.h>
int main(void)
{
int amount = -1;
char stuck = '?';
int first = scanf("%d", &amount);
int second = scanf("%d", &amount);
int third = scanf(" %c", &stuck);
printf("first read returned %d\n", first);
printf("second read returned %d\n", second);
printf("third read returned %d and got [%c]\n", third, stuck);
printf("amount is still %d\n", amount);
return 0;
}
first read returned 0
second read returned 0
third read returned 1 and got [t]
amount is still -1
ওই output-টা twelve input-র জন্য। তৃতীয় লাইনটা পড়ো: সংখ্যার দুইটা পড়া যে অক্ষরে আটকেছিল সেটা t, আর একটা %c ওটাকে দিব্যি তুলে নেয়।
এটাই প্রমাণ। Stream এক চুলও নড়েনি, কারণ %d কেবল সেই অক্ষরগুলোই গেলে যেগুলো ও কাজে লাগাতে পারে।
খারাপ লাইনের বাকিটা ঠিকভাবে পরিষ্কার করতে একটা loop লাগে, যেটা Module 6-র বিষয়। উপরের মতো একটা খারাপ অক্ষর সরিয়ে দেওয়া, আজ তুমি এটুকুই পারো।
If ছাড়াই সীমার যাচাই: তুলনাটা ছেপে দাও
পড়া হয়েছে কি না জানা input যাচাইয়ের অর্ধেক। বাকি অর্ধেক হলো মানটা যুক্তিসঙ্গত কি না, আর ওটা একটা তুলনা।
Module 2-র lesson 4 বলেছিল তুলনা একটা expression, যার মান 1 বা 0। একটা যাচাই জানাতে এটুকুই লাগে, যদিও ওটার উপর কাজ করা Module 5-র দায়িত্ব।
দুইটা যাচাইকে && দিয়ে জোড়া Module 4-র বিষয়, তাই ততদিন দুইটা যাচাই মানে দুইটা লাইন।
#include <stdio.h>
int main(void)
{
const int LOWEST = 1;
const int HIGHEST = 100;
int marks = 0;
int read = scanf("%d", &marks);
printf("read ok : %d\n", read == 1);
printf("at least %3d : %d\n", LOWEST, marks >= LOWEST);
printf("at most %3d : %d\n", HIGHEST, marks <= HIGHEST);
return 0;
}
read ok : 1
at least 1 : 1
at most 100 : 0
ওই output-টা 150 input-র জন্য। পড়া হয়েছে, মানটা সবচেয়ে কম সীমার উপরে, আর সবচেয়ে বেশি সীমার নিচে না।
এবার twelve দিয়ে চালাও আর উত্তরটা মন দিয়ে পড়ো।
read ok : 0
at least 1 : 0
at most 100 : 1
তৃতীয় লাইনটা বলছে মানটা সীমার ভিতরে। সত্যিই ভিতরে, কারণ variable-এ এখনো declaration-র দেওয়া 0 বসে আছে, আর 0 তো 100-র বেশি না।
প্রথম লাইনটা থাকার পুরো কারণই এটা। যে মান কখনো পড়াই হয়নি তার সীমার যাচাই আসলে তোমার নিজের শুরুর মান নিয়ে একটা উত্তর। দেখতে ওটা হুবহু input নিয়ে দেওয়া উত্তরের মতোই।
তাই ক্রমটা নির্দিষ্ট: আগে জিজ্ঞেস করো পড়া হয়েছে কি না, তারপরই জিজ্ঞেস করো মানটা যুক্তিসঙ্গত কি না।
Fgets, এক অনুচ্ছেদে
Input পড়ার আরেকটা পথ আছে, আর ওটা এই lesson-র বেশির ভাগটাই এড়িয়ে যায়। fgets গোটা একটা লাইন, ফাঁকা জায়গাসহ, অক্ষরের একটা array-তে তুলে নেয়, তারপর তুমি নিজে ওটাকে টুকরো করো।
ও কখনো অর্ধেক পড়া সংখ্যা stream-এ ফেলে রাখে না, কারণ ও সবসময় গোটা লাইনটাই গিলে নেয়। Module 10 ওটা ঠিকমতো শেখায়, যখন লেখা নিয়ে কাজ করা তোমার হাতে আসে।
ততদিন scanf আর তার ফেরত পাওয়া গোনাই সৎ যন্ত্র, আর এই track-র প্রতিটা judge problem ওটাই ব্যবহার করে।
ভুল output নিয়ে judge কী করে
Judge তোমার program-কে নিজের রাখা একটা input দিয়ে চালায়, তুমি যা ছেপেছ সব ধরে রাখে, আর প্রত্যাশিত output-র সাথে অক্ষরে অক্ষরে মেলায়।
তুমি কী বোঝাতে চেয়েছিলে সেটা ও জানে না। ঠিক একটা মান আর এক ফাঁকা জায়গা সরে যাওয়া একটা লাইন, আর অর্থহীন একটা মান, judge-র কাছে দুইটাই এক রায়।
কথাটা দুই দিকেই কাটে, আর এই কারণেই এই module দুইটা lesson খরচ করেছে হুবহু ছাপা শেখাতে। এর মানে আরও একটা: খারাপ input-এ যে program যুক্তিসঙ্গত উত্তর ছাপে, তার জন্য ওকে কোনো পুরস্কার দেওয়া হয় না।
Judge-এ input সবসময় বৈধ, কারণ statement সেটা কথা দিয়েছিল। তবু ফেরত পাওয়া মান জরুরি, কারণ একই program যখন কোনো মানুষ চালায়, তখন এত ভাগ্য থাকে না।
একটা মান, একটা গোনা, দুই লাইন output। এই module-র প্রতিটা graded problem এই গড়ন থেকেই শুরু।
#include <stdio.h>
int main(void)
{
int n = 0;
int read = scanf("%d", &n);
printf("returned %d\n", read);
printf("value %d\n", n);
return 0;
}
returned 1
value 12
ওই output-টা 12 input-র জন্য। twelve input-এ একই program ছাপে 0 আর 0, আর দ্বিতীয় 0-টা এসেছে declaration থেকে।
তিন ঘরের একটা record, যেখানে তৃতীয়টা নেই। গোনাগুলো ঠিক বলে দেয় input কোথায় থেমেছে।
#include <stdio.h>
int main(void)
{
int roll = 0;
int marks = 0;
int bonus = 0;
int read = scanf("%d %d %d", &roll, &marks, &bonus);
printf("fields read : %d\n", read);
printf("roll : %d\n", roll);
printf("marks : %d\n", marks);
printf("bonus : %d\n", bonus);
return 0;
}
fields read : 2
roll : 47
marks : 88
bonus : 0
ওই output-টা 47 88 input-র জন্য। দুইটা ঘর ভরেছে, তৃতীয়টা অছোঁয়া, আর যে 0 ছাপা হলো সেটা declaration-র 0, কারো না-পাওয়া নম্বর না।
ফেরত পাওয়া একটা সংখ্যাই তোমাকে বলল কোন ঘরগুলো বিশ্বাস করা যায়। Program-র আর কিছুই সেটা পারত না।
Run in Compilerপড়া, সীমা, আর একটা লাইন যেটা কেবল সব ঠিক থাকলেই 1। গুণটা Module 4-র আনা &&-র জায়গায় দাঁড়িয়ে আছে।
#include <stdio.h>
int main(void)
{
const int LOWEST = 0;
const int HIGHEST = 100;
int marks = -1;
int read = scanf("%d", &marks);
printf("read ok : %d\n", read == 1);
printf("in range : %d\n", (marks >= LOWEST) * (marks <= HIGHEST));
printf("usable : %d\n", (read == 1) * (marks >= LOWEST) * (marks <= HIGHEST));
return 0;
}
read ok : 1
in range : 1
usable : 1
ওই output-টা 88 input-র জন্য। twelve input-এ তিনটা লাইনই হয় 0, 0 আর 0, কারণ declaration-র -1 সবচেয়ে কম সীমার নিচে।
ওই -1 ঠিক এই কারণেই বেছে নেওয়া। শুরুর মান 50 হলে মাঝের লাইনটা এমন একটা input-র জন্য 1 বলত, যেটা কখনো পড়াই হয়নি।
Run in Compilerএটা কোথায় কাজে লাগে
- Web form-র server দিকের যাচাই। Browser-র যাচাইটা কেবল সুবিধার জন্য, আর server প্রতিটা যাচাই আবার করে, কারণ browser-কে পাশ কাটানো যায়। গড়নটা হুবহু এক: ঘরটা এসেছে কি না, আর মানটা সীমার ভিতরে কি না।
- চালু হওয়ার সময়ের configuration loader। যে program setting file পড়ে আর port number হিসেবে
eightyপায়, তার শুরু করতে অস্বীকার করা উচিত, port 0-তে চালু হওয়া না। গোনাটাই ওকে জানায়। - কোনো ব্যাংকের transaction import। প্রতিটা record পড়া হয়, গোনা হয় আর সীমা যাচাই হয়, আর যেটা ব্যর্থ হয় সেটা লাইন নম্বরসহ reject file-এ যায়। কিছুই আন্দাজ করা হয় না, কারণ আন্দাজ করা অঙ্কটা কারো টাকা।
- এই track-র problem-র পিছনের judge। ও তোমার program-র output একই যত্নে পড়ে। এক অক্ষর সরে যাওয়া লাইন wrong answer, অল্পের জন্য ফসকে যাওয়া না।
যে ভুলগুলো সবাই করে
১. ফেরত পাওয়া গোনা গ্রাহ্য না করা।
int amount = 0;
scanf("%d", &amount);
printf("total: %d\n", amount * 2);
দুই command line-র কোনোটাতেই কিছু বলে না, আর program একদম বৈধ। twelve input-এ ও পুরো আত্মবিশ্বাসে ছাপে total: 0। সমাধান হলো scanf যা ফেরত দিল সেটা রেখে দিয়ে ছেপে ফেলা, আর এই module-র প্রতিটা graded starter সেটাই করে।
২. ব্যর্থ পড়া variable-কে শূন্য করে দেয় ভাবা।
int marks;
scanf("%d", &marks);
printf("%d\n", marks);
Playground বা -Wall দেওয়া local build, কেউই এখানে কিছু বলে না: marks-এর address scanf-কে দেওয়া হয়েছে, তাই compiler ধরে নেয় পড়াটা ওটা লিখে দিয়েছে। পড়া ব্যর্থ হলে marks-এ ওই memory-তে যা ছিল তাই থাকে, আর Module 2-র lesson 1 দেখিয়েছিল সেটা শূন্য হওয়ার কোনো নিশ্চয়তা না।
৩. ব্যর্থ হওয়ার পরে কিছু পরিষ্কার না করেই আবার পড়া।
int n = 0;
scanf("%d", &n);
scanf("%d", &n);
কিছুই বলে না, আর খারাপ input-এ দুইটা পড়াই 0 ফেরত দেয়। যে অক্ষরটা আটকাল সেটা কখনো গেলাই হয়নি, তাই দ্বিতীয় পড়াও ওটারই সামনে পড়ে। খারাপ input আগে না সরিয়ে আবার চেষ্টা করাটা আসলে চেষ্টাই না।
৪. পড়া যাচাইয়ের আগেই সীমা যাচাই করা।
int marks = 0;
scanf("%d", &marks);
printf("%d\n", marks <= 100);
কিছুই বলে না, আর twelve input-র জন্য ছাপে 1। যাচাইটা সত্যি, তবে সেটা declaration থেকে আসা একটা 0 নিয়ে, কারো দেওয়া input নিয়ে না। আগে জিজ্ঞেস করো পড়া হয়েছে কি না, সবসময়।
একটা পড়া আসলে কী করল সেটা জানাও: ও কী গোনা ফেরত দিল, আর তারপর variable-এ কী আছে।
Input. বড়জোর এক লাইন, তাতে একটা token, হয় একটা পূর্ণসংখ্যা নয় একটা শব্দ। ফাঁকাও হতে পারে।
Output. দুই লাইন: returned আর গোনাটা, তারপর value আর variable-টা।
Constraints. পূর্ণসংখ্যা -1000000 থেকে 1000000, শব্দ 1 থেকে 20 অক্ষরের, আর variable 0 থেকে শুরু।
Sample. Input twelve দিলে returned 0 তারপর value 0।
#include <stdio.h>
int main(void)
{
int amount = 0;
int read = scanf("%d", &amount);
/* Two lines. Print what scanf gave back, then what is in the box. */
return 0;
}
read-report নামে graded। দুইটা লুকানো test কথাটা চোখে দেখায়: input 0 দিলে দ্বিতীয় লাইন একই থাকে আর প্রথম লাইন বদলায়, আর ফাঁকা input -1 ছাপে।
Zara-র sensor-কে কেবল একটা সীমার ভিতরে বিশ্বাস করা হয়। দুইটা তথ্য ছেপে দাও আর মানুষকে পড়তে দাও।
Input. এক লাইনে তিনটা পূর্ণসংখ্যা: সবচেয়ে কম যতটা চলে, সবচেয়ে বেশি যতটা চলে, আর পাঠটা।
Output. দুই লাইন, প্রতিটা 1 বা 0: সবচেয়ে কমের সমান বা বেশি, তারপর সবচেয়ে বেশির সমান বা কম।
Constraints. -1000000 <= সবচেয়ে কম <= সবচেয়ে বেশি <= 1000000। কোনো if না আর কোনো && না।
Sample. Input 1 100 50 দিলে 1 তারপর 1।
#include <stdio.h>
int main(void)
{
int low = 0;
int high = 0;
int reading = 0;
scanf("%d %d %d", &low, &high, &reading);
/* Two lines, each a comparison printed with %d. No if anywhere. */
return 0;
}
in-range নামে graded। সীমার দুই প্রান্তই লুকানো test, আর প্রান্তের সমান পাঠ সীমার ভিতরে ধরা হয়।
দুইটা সংখ্যা পড়ো, যেখানে দ্বিতীয়টা নাও থাকতে পারে, আর দুইটাই ছাপো, দ্বিতীয়টা না থাকলে 0 দেখিয়ে।
Input. এক বা দুইটা পূর্ণসংখ্যা, মাঝে ফাঁকা জায়গা।
Output. তিন লাইন: গোনা, প্রথম মান, দ্বিতীয় মান।
কোনো if ছাড়া করো। হারিয়ে যাওয়া দ্বিতীয় মানের 0-টা তুমি লিখবে না, ওটা declaration-এ তোমার বেছে নেওয়া জিনিস।
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int read = scanf("%d %d", &a, &b);
/* Three printf calls. Nothing decides anything. */
return 0;
}
এই module-এ graded না; এর graded আত্মীয় হলো problem lesson-র line-parser, যেটা এই কাজটাই তিনবার করে। একটা সংখ্যা দিয়ে চালাও, দুইটা দিয়ে চালাও, আর কিছু না দিয়ে চালাও।
যে প্রশ্নগুলো সবার মনে আসে
Judge-এ input যদি সবসময় বৈধই হয়, তাহলে যাচাই করব কেন?
কারণ অভ্যাসটা সস্তা আর তার বিকল্পটা চোখে পড়ে না। যে program তুমি কোনো মানুষের জন্য লেখো, বা অন্যের বানানো কোনো file-র জন্য লেখো, সেখানে এটা আর অনুশীলন থাকে না।
খারাপ input সরিয়ে আবার চেষ্টা করব কীভাবে?
লাইনের শেষ পর্যন্ত অক্ষর পড়ে যেতে হয়, আর তার জন্য একটা loop লাগে। Module 6 loop-টা দেয় আর ঠিক এই সমস্যায় ফিরে আসে।
EOFকি সবসময় -1?Standard কেবল বলে ওটা একটা ঋণাত্মক পূর্ণসংখ্যার constant। Playground-এ আর এই course-র প্রতিটা compiler-এ ওটা -1, আর -1 না লিখে
EOFলেখাটাই সেটাকে গুরুত্বহীন করে রাখে।এই module-র starter-গুলো প্রতিটা variable initialise করে কেন?
যাতে ব্যর্থ একটা পড়ার একটা নির্দিষ্ট উত্তর থাকে। এটা Module 2-র lesson 1-র সেই নিয়মই, সাথে দ্বিতীয় একটা কারণ: এখানে শুরুর মানটাই program ছাপবে।
পড়া ব্যর্থ হলে কি একটা বার্তা ছাপতে পারি?
একটা শাখা ছাড়া না, আর শাখা Module 5-র বিষয়। আজকের সৎ কাজটা হলো গোনাটা ছাপা, যেটা পাঠককে একই কথা একটা সংখ্যায় বলে দেয়।
মূল কথা
scanfফেরত দেয় ও কয়টা জিনিস ভরল: একটা গোনা, আর input ফুরালেEOF।- ব্যর্থ পড়া তোমার variable-এ হাত দেয় না, তাই তার শুরুর মানটাই তুমি ছাপবে।
- যে অক্ষরে পড়া আটকাল সেটা stream-এই থাকে, তাই সাথে সাথে আবার চেষ্টা করলে একইভাবে আটকায়।
- তুলনার মান 1 বা 0, তাই সীমার যাচাই
ifছাড়াই ছাপা যায়। - মান যাচাইয়ের আগে পড়া হয়েছে কি না যাচাই করো, নইলে তুমি নিজের শুরুর মানকেই যাচাই করছ।
- Judge তোমার output অক্ষরে অক্ষরে মেলায় আর তুমি কী বোঝাতে চেয়েছিলে সেটা জানে না।
এরপর পাঁচটা lesson একসাথে করে দশটা graded problem, যেখানে judge-র মতামতই একমাত্র মতামত।
lesson ৫ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Problem: পড়া আর ছাপানো