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

কম্পিউটেশনাল থিংকিং — কোড লেখার আগে যেভাবে ভাবতে হয়

Computational Thinking — How to Think Before You Code

Taught in Bangla, this lesson shows how to think before writing code: decomposition, pattern recognition and abstraction, what makes an algorithm, pseudocode and flowcharts, edge cases, loop invariants, and why binary search is fast.

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

  • যেকোনো বড় সমস্যাকে ছোট, সমাধানযোগ্য টুকরোয় ভাঙার কৌশল
  • অ্যালগরিদম কাকে বলে, আর pseudocode ও flowchart দিয়ে কীভাবে কাগজে লিখবেন
  • সীমানার ইনপুট (edge case) খুঁজে বের করা — যেখানে প্রোগ্রাম সবচেয়ে বেশি ভুল করে
  • একটি অ্যালগরিদম ঠিক উত্তর দেবে কি না, সেটা যুক্তি দিয়ে বোঝা, আর কোনটি দ্রুত তা তুলনা করা
  • AI-কে এমন নির্ভুল নির্দেশ দেওয়া, যাতে সে ভুল বোঝার সুযোগ না পায় — এবং তার উত্তর যাচাই করা

১ · কম্পিউটার ঠিক যা বলা হয়, শুধু তাই করে

ছুটির দিনের বিকেল। মা ডেকে বললেন, “এক কাপ চা বানাও তো।” আপনি রান্নাঘরে গেলেন, পাতিলে পানি নিলেন, চুলা জ্বালালেন, পানি ফুটলে চা-পাতা দিলেন, ছেঁকে কাপে ঢাললেন, দুধ-চিনি মিশিয়ে মায়ের হাতে দিলেন। পাঁচ মিনিটের কাজ। মা আপনাকে একটি বাক্যই বলেছিলেন — বাকি সব আপনি নিজেই বুঝে নিয়েছেন।

এবার ধরুন একই কথা বললেন একটি রোবটকে, যে শুধু হুবহু নির্দেশ মানে। “চা বানাও” শুনে সে দাঁড়িয়ে রইল — চা কী, কোথায় থাকে, কীসে বানায়, কিছুই জানে না। আপনি ধৈর্য ধরে বললেন, “কাপে চা-পাতা দাও, তারপর পানি দাও।” রোবট পুরো প্যাকেট চা-পাতা কাপে ঢেলে কলের ঠান্ডা পানি ভরে দিল। কাপ উপচে মেঝে ভেসে গেল। রোবটের কোনো দোষ নেই — আপনি বলেননি কতটুকু পাতা, বলেননি পানি গরম করতে হবে, বলেননি কোথায় থামতে হবে।

মানুষ ফাঁকা জায়গা নিজে বুঝে পূরণ করে।
কম্পিউটার ফাঁকা জায়গা পেলে হয় থেমে যায়, নয়তো ভুল কাজ করে।

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

প্রোগ্রামিং শেখার প্রথম ধাপ তাই কোনো ভাষার নিয়ম মুখস্থ করা নয়। প্রথম ধাপ হলো এমনভাবে ভাবতে শেখা, যাতে আপনার নির্দেশে কোনো ফাঁক না থাকে। এই ভাবনার কৌশলের নাম computational thinking. কথাটি জনপ্রিয় হয় ২০০৬ সালে, যখন কম্পিউটার বিজ্ঞানী Jeannette Wing একটি প্রবন্ধে লেখেন — পড়া, লেখা আর অঙ্কের মতোই এটি সবার জন্য একটি মৌলিক দক্ষতা, শুধু প্রোগ্রামারদের জন্য নয়।

মূল ধারণা

Computational thinking মানে কম্পিউটারের মতো যান্ত্রিক হওয়া নয়। এর মানে হলো একটি সমস্যাকে এমন রূপে সাজানো, যাতে একজন মানুষ বা যন্ত্র কোনো অনুমান ছাড়াই ধাপে ধাপে সমাধান করতে পারে। C, Python বা অন্য যেকোনো ভাষা শুধু সেই সাজানো ভাবনাটি লেখার মাধ্যম।

AI আসার পরে কথাটি আরও জরুরি হয়েছে। ChatGPT বা Claude-এর মতো AI মানুষের মতো কিছুটা অনুমান করতে পারে — কিন্তু সেই অনুমান সবসময় আপনার মনের কথার সাথে মেলে না। আপনি “ছাত্রদের সর্বোচ্চ নম্বর বের করার প্রোগ্রাম” চাইলেন। দুজন সমান নম্বর পেলে কার নাম দেখাবে? তালিকা খালি হলে কী হবে? আপনি না বললে AI নিজেই কিছু একটা ধরে নেবে, আর সেটা আপনাকে জানাবেও না। এই যুগের আসল দক্ষতা হলো সমস্যাকে এত নির্ভুলভাবে বর্ণনা করা, যাতে মানুষ, মেশিন বা AI — কেউই ভুল বুঝতে না পারে।

২ · সমস্যা ভাবার চারটি কৌশল

অভিজ্ঞ প্রোগ্রামাররা নতুন সমস্যা হাতে পেলে প্রায় একই চারটি কৌশলে ভাবেন। এগুলো আলাদা আলাদা ধাপ নয় — একসাথে, বারবার ঘুরে ফিরে ব্যবহার হয়। প্রতিটি আমাদের চেনা জীবন থেকে দেখি।

🧩 Decomposition — ভাঙা

বড় কাজকে ছোট ছোট কাজে ভাগ করা, যতক্ষণ না প্রতিটি টুকরো সহজে করা যায়। “বিয়ের আয়োজন” শুনলে মাথা ঘোরে; “কাল সকালে বাবুর্চিকে ফোন করা” শুনলে কাজটি পরিষ্কার।

উদাহরণ: বিয়ের আয়োজন

🔁 Pattern recognition — মিল খোঁজা

আলাদা সমস্যার ভেতরে একই কাঠামো চেনা। bKash-এ Send Money, মোবাইল রিচার্জ, বিদ্যুৎ বিল — তিনটিই আসলে একই ছাঁচ: কাকে → কত টাকা → PIN → নিশ্চিত করা।

উদাহরণ: মোবাইল ব্যাংকিং

🗺️ Abstraction — বাদ দেওয়া

যা দরকার শুধু সেটুকু রাখা। ঢাকা মেট্রো রেলের ম্যাপে শুধু স্টেশনের নাম আর ক্রম থাকে — রাস্তার বাঁক, দোকান, দূরত্বের সঠিক মাপ থাকে না। তবু যাত্রীর কাজ চলে যায়।

উদাহরণ: মেট্রো রেলের ম্যাপ

📋 Algorithm — ধাপ সাজানো

সমাধানটিকে নির্দিষ্ট ক্রমের নির্দেশে লেখা, যাতে অন্য কেউ মানলেও একই ফল পায়। রিকশাওয়ালাকে পথ বলার মতো: “সোজা গিয়ে দ্বিতীয় মোড়ে বাঁয়ে, মসজিদের পরে ডানে।”

উদাহরণ: রিকশার পথ বলা

Decomposition — একটি বড় কাজ কীভাবে ভাঙবেন

চারটির মধ্যে decomposition সবচেয়ে বেশি কাজে লাগে, তাই এটি ধাপে ধাপে দেখি। বড় বোনের বিয়ের দায়িত্ব পড়েছে আপনার ঘাড়ে। কোথা থেকে শুরু করবেন? নিচের ডায়াগ্রামে “পরে” চাপুন।

💍 বিয়ের আয়োজনবিশাল, অস্পষ্ট কাজ
অতিথিতালিকা ও দাওয়াত
ভেন্যুকমিউনিটি সেন্টার
খাবারকত জনের, কী মেন্যু
সাজসজ্জাগেট, মঞ্চ, আলো
মেন্যু ঠিক করাআম্মার সাথে আলোচনা
অতিথির সংখ্যা জানাঅতিথি-দল থেকে
বাবুর্চিকে বায়না৩টি জায়গায় দাম যাচাই
প্লেট-গ্লাস ভাড়াডেকোরেটরের দোকান
  1. শুরুতে একটি বিশাল কাজ। “বিয়ের আয়োজন” — এভাবে ভাবলে কোথা থেকে শুরু করবেন বোঝা যায় না, দুশ্চিন্তাই বাড়ে। কম্পিউটারের কাছে “সর্বোচ্চ নম্বর বের করো” কথাটিও শুরুতে এমনই একটি অস্পষ্ট বাক্য।
  2. প্রথম ভাঙন: চারটি বড় ভাগ। অতিথি, ভেন্যু, খাবার, সাজসজ্জা। এখন চারজন আত্মীয়কে চারটি দায়িত্ব দেওয়া যায় — তাঁরা একসাথে, আলাদাভাবে কাজ করতে পারবেন। বড় সফটওয়্যারও এভাবেই ভাগ করে আলাদা দলকে দেওয়া হয়।
  3. আরও ভাঙন: “খাবার” কাজটি খুলে দেখা। খাবারের ভেতরেও কয়েকটি ছোট কাজ আছে। খেয়াল করুন, “অতিথির সংখ্যা জানা” কাজটি অতিথি-দলের ওপর নির্ভর করে। ভাঙার সময় কোন টুকরো কোন টুকরোর ওপর নির্ভরশীল, সেটাও চোখে পড়ে।
  4. কোথায় থামবেন? লাল দাগ দেওয়া প্রতিটি কাজ এখন এত ছোট যে একজন মানুষ আজই করে ফেলতে পারেন — আর কিছু ভাবার দরকার নেই। এটাই থামার নিয়ম: যখন প্রতিটি টুকরো সরাসরি করা যায়, তখন ভাঙা শেষ। প্রোগ্রামে এই ছোট টুকরোগুলোই পরে একেকটি ফাংশন হয়ে যায় (পর্ব ১৩-তে দেখবেন)।

বাকি তিনটি কৌশল কীভাবে কাজে আসে

Pattern recognition: পরীক্ষার রুটিন হাতে পেয়ে একজন বুদ্ধিমান ছাত্র খেয়াল করে — প্রতিটি বিষয়ের প্রস্তুতি আসলে একই ছাঁচে চলে: সিলেবাস দেখা → দুর্বল অধ্যায় চিহ্নিত করা → আগের বছরের প্রশ্ন সমাধান → রিভিশন। ছাঁচটি একবার বানালে দশটি বিষয়ে আলাদা করে ভাবতে হয় না। প্রোগ্রামিংয়ে এই একই চিন্তা থেকে আসে loop আর ফাংশন — একবার লিখে বারবার ব্যবহার।

Abstraction: আপনি যখন bKash-এ টাকা পাঠান, আপনার কাছে কাজটি তিনটি জিনিস — নম্বর, টাকার পরিমাণ, PIN. ভেতরে সার্ভার, ডেটাবেস, নিরাপত্তা যাচাই, ব্যাংকের হিসাব মেলানো — সবকিছু আপনার চোখের আড়ালে। Abstraction মানে জটিলতা মুছে ফেলা নয়, সঠিক জায়গায় লুকিয়ে রাখা। এজন্যই কোটি মানুষ কোনো প্রশিক্ষণ ছাড়াই মোবাইল ব্যাংকিং ব্যবহার করতে পারেন।

Algorithm: উপরের তিনটি কাজ শেষ হলে বাকি থাকে সমাধানটিকে নির্দিষ্ট ধাপে লেখা। পরের অংশে দেখব একটি নির্দেশের তালিকা ঠিক কখন “অ্যালগরিদম” হয়ে ওঠে।

৩ · অ্যালগরিদম আসলে কী — পাঁচটি শর্ত

সহজ কথায়, অ্যালগরিদম হলো একটি সমস্যা সমাধানের ধাপে ধাপে সাজানো নির্দেশের তালিকা। রান্নার রেসিপি, ATM থেকে টাকা তোলার নিয়ম, বোর্ড পরীক্ষার খাতা দেখার পদ্ধতি — সবই একরকম অ্যালগরিদম। কিন্তু প্রতিটি নির্দেশের তালিকাই অ্যালগরিদম নয়। কম্পিউটার বিজ্ঞানী Donald Knuth তাঁর বিখ্যাত বই The Art of Computer Programming-এ পাঁচটি শর্তের কথা বলেছেন:

শর্তমানে কীচা বানানোর উদাহরণে
ইনপুট (Input)কাজ শুরুর আগে কী কী লাগবেপানি, চা-পাতা, দুধ, চিনি
আউটপুট (Output)শেষে কী পাওয়া যাবে — অন্তত একটি ফলএক কাপ চা
নির্দিষ্টতা (Definiteness)প্রতিটি ধাপের মানে একটিই — দুইভাবে বোঝার সুযোগ নেই“একটু চিনি” নয়, “দুই চামচ চিনি”
সসীমতা (Finiteness)সীমিত সংখ্যক ধাপে কাজ শেষ হবেই“পানি ফুটলে থামো” — চিরকাল জ্বাল দেওয়া নয়
কার্যকারিতা (Effectiveness)প্রতিটি ধাপ আসলেই করা সম্ভব“পানি গরম করো” সম্ভব; “সবচেয়ে সুস্বাদু চা বানাও” মাপা যায় না

রোবটের চা-দুর্ঘটনার কথা মনে করুন। “কাপে চা-পাতা দাও” ধাপটি নির্দিষ্টতা ভেঙেছিল — কতটুকু, বলা হয়নি। আর পানি ভরতে থাকার কোনো থামার নিয়ম না থাকায় ভেঙেছিল সসীমতা। প্রোগ্রামে সসীমতা ভাঙলে তৈরি হয় এমন লুপ, যা কখনো শেষ হয় না — প্রোগ্রাম আটকে যায়, ফ্যান জোরে ঘুরতে থাকে।

“Algorithm” শব্দটি এসেছে একজন মানুষের নাম থেকে। নবম শতাব্দীতে বাগদাদে কাজ করতেন পারস্যের গণিতবিদ মুহাম্মদ ইবনে মুসা আল-খোয়ারিজমি। তাঁর বইয়ের মাধ্যমে ইউরোপ ভারতীয় দশমিক সংখ্যা-পদ্ধতিতে হিসাব করা শেখে। ল্যাটিন অনুবাদে তাঁর নাম লেখা হতো “Algoritmi” — সেখান থেকেই algorithm. মজার ব্যাপার, “algebra” শব্দটিও এসেছে তাঁর আরেকটি বইয়ের নামের “al-jabr” অংশ থেকে।

অ্যালগরিদম আর প্রোগ্রামের সম্পর্কটি রেসিপি আর রান্নার মতো। একই রেসিপি বাংলায় লেখা যায়, ইংরেজিতেও লেখা যায় — খাবার একই হবে। ঠিক তেমনি একই অ্যালগরিদম C-তে লেখা যায়, Python-এও লেখা যায়। ভাষা বদলায়, অ্যালগরিদম বদলায় না। এজন্যই অভিজ্ঞরা বলেন, ভাষা শেখা সহজ অংশ — কঠিন অংশ হলো অ্যালগরিদম ভাবা।

৪ · Pseudocode ও Flowchart — ভাবনাকে কাগজে আনা

মাথার ভেতরের ভাবনা ঝাপসা থাকে। কাগজে লিখতে গেলেই ফাঁকগুলো চোখে পড়ে। অ্যালগরিদম লেখার দুটি জনপ্রিয় উপায় আছে — কথায় লেখা (pseudocode) আর ছবিতে আঁকা (flowchart) পদ্ধতি।

Pseudocode — কোডের মতো, কিন্তু কোড নয়

Pseudocode-এর কোনো নির্দিষ্ট ব্যাকরণ নেই। শর্ত একটাই: প্রতিটি লাইন যেন একটিমাত্র স্পষ্ট কাজ বোঝায়। ধরুন ক্লাসের সব ছাত্রের নম্বর থেকে সর্বোচ্চ নম্বর বের করতে হবে। Pseudocode হতে পারে এমন:

শুরু
    ইনপুট: নম্বরের একটি তালিকা (অন্তত একটি নম্বর আছে)
    max ← তালিকার প্রথম নম্বর
    বাকি প্রতিটি নম্বর x-এর জন্য:
        যদি x > max হয়, তাহলে max ← x
    আউটপুট: max
শেষ

এখানে ← চিহ্নের মানে “এই বাক্সে এই মানটি রাখো।” max হলো একটি variable — নাম দেওয়া একটি বাক্স, যার ভেতরের মান বদলানো যায়। খেয়াল করুন, দ্বিতীয় লাইনেই একটি শর্ত লেখা আছে: “অন্তত একটি নম্বর আছে।” কেন লিখলাম, অংশ ৫-এ বুঝবেন।

Flowchart — অ্যালগরিদমের ছবি

যেখানে অনেকগুলো “যদি… তাহলে…” আছে, সেখানে ছবি আঁকলে পথগুলো পরিষ্কার দেখা যায়। Flowchart-এ প্রতিটি আকারের একটি নির্দিষ্ট অর্থ আছে:

আকারনামযা বোঝায়
⬭ ডিম্বাকারশুরু / শেষ (Terminal)অ্যালগরিদম কোথায় শুরু, কোথায় শেষ
▱ হেলানো চতুর্ভুজইনপুট / আউটপুটকিছু নেওয়া (PIN দেওয়া) বা দেখানো (রসিদ ছাপা)
▭ আয়তক্ষেত্রপ্রক্রিয়া (Process)একটি কাজ বা হিসাব — “ব্যালান্স থেকে টাকা বাদ দাও”
◇ রম্বস (ডায়মন্ড)সিদ্ধান্ত (Decision)হ্যাঁ/না প্রশ্ন — এখান থেকে দুটি পথ বের হয়
→ তীরপ্রবাহ (Flow)কোন কাজের পর কোন কাজ

এবার একটি পরিচিত কাজের flowchart বানাই — ATM থেকে টাকা তোলা। নিচের ডায়াগ্রামে একেকটি বাক্স একেক ধাপে যোগ হবে, আর নিচের কালো প্যানেলে একই অ্যালগরিদমের pseudocode একটু একটু করে তৈরি হবে। দুটি ◇ সিদ্ধান্তের বাক্স বিশেষভাবে খেয়াল করুন।

  1. শুরুকার্ড ঢোকানোTerminal
  2. PIN নিনinput PINইনপুট
  3. PIN ঠিক?হ্যাঁ / নাসিদ্ধান্ত ১
  4. পরিমাণ নিনinput amountইনপুট
  5. ব্যালান্স যথেষ্ট?হ্যাঁ / নাসিদ্ধান্ত ২
  6. টাকা দিনbalance −= amountপ্রক্রিয়া → শেষ
pseudocodeশুরুর বিন্দু
শুরু: গ্রাহক কার্ড ঢোকালেন
pseudocodeইনপুট যোগ হলো
শুরু: গ্রাহক কার্ড ঢোকালেন
PIN নাও
pseudocodeপ্রথম সিদ্ধান্ত — দুটি পথ
শুরু: গ্রাহক কার্ড ঢোকালেন
PIN নাও
যদি PIN ভুল হয়:
    "ভুল PIN" দেখাও, আবার PIN নাও
    নির্দিষ্ট সংখ্যকবার ভুল হলে কার্ড আটকে রাখো, শেষ
pseudocodePIN ঠিক হলেই এখানে আসা যায়
শুরু: গ্রাহক কার্ড ঢোকালেন
PIN নাও
যদি PIN ভুল হয়: … (আগের মতো)
টাকার পরিমাণ নাও
pseudocodeদ্বিতীয় সিদ্ধান্ত
শুরু: গ্রাহক কার্ড ঢোকালেন
PIN নাও
যদি PIN ভুল হয়: … (আগের মতো)
টাকার পরিমাণ নাও
যদি পরিমাণ > ব্যালান্স:
    "অপর্যাপ্ত ব্যালান্স" দেখাও, কার্ড ফেরত দাও, শেষ
pseudocodeসম্পূর্ণ অ্যালগরিদম
শুরু: গ্রাহক কার্ড ঢোকালেন
PIN নাও
যদি PIN ভুল হয়: … (আগের মতো)
টাকার পরিমাণ নাও
যদি পরিমাণ > ব্যালান্স: … (আগের মতো)
ব্যালান্স ← ব্যালান্স − পরিমাণ
টাকা দাও, রসিদ দাও, কার্ড ফেরত দাও
শেষ
হাতে চালিয়ে দেখাব্যালান্স ৫০০ টাকা, চাওয়া হলো ১০০০ টাকা
PIN নাও            → সঠিক PIN দেওয়া হলো
যদি PIN ভুল হয়     → না, তাই সামনে এগোই
টাকার পরিমাণ নাও    → 1000
যদি 1000 > 500     → হ্যাঁ!
    "অপর্যাপ্ত ব্যালান্স" দেখাও, কার্ড ফেরত দাও, শেষ
↑ ব্যালান্স 500-ই রইল — ঋণাত্মক হয়নি। অ্যালগরিদম ঠিক আচরণ করেছে।
  1. শুরু। প্রতিটি flowchart একটি ডিম্বাকার “শুরু” দিয়ে শুরু হয়। এখানে ঘটনাটি হলো গ্রাহক কার্ড ঢোকালেন। এটি না থাকলে বোঝা যায় না, অ্যালগরিদম কোথা থেকে পড়া শুরু করতে হবে।
  2. ইনপুট: PIN নেওয়া। হেলানো চতুর্ভুজ মানে বাইরের দুনিয়া থেকে কিছু আসছে। খেয়াল করুন, এখনো কোনো যাচাই হয়নি — শুধু নেওয়া হয়েছে।
  3. প্রথম সিদ্ধান্ত: PIN ঠিক কি না। ◇ বাক্স থেকে দুটি পথ বের হয়। “না” পথটি ভুলে গেলে চলবে না — ভুল PIN দিলে কী হবে, সেটাও অ্যালগরিদমের অংশ। বারবার চেষ্টার একটি সীমাও রাখতে হয়, নইলে কেউ বসে বসে সব PIN চেষ্টা করে দেখতে পারত।
  4. ইনপুট: টাকার পরিমাণ। এই বাক্সে শুধু তখনই আসা যায়, যখন PIN ঠিক। তীর দেখেই বোঝা যায় কোন কাজ কোন শর্তের পরে হয়।
  5. দ্বিতীয় সিদ্ধান্ত: ব্যালান্স যথেষ্ট কি না। আবার দুটি পথ। এই প্রশ্নটি বাদ দিলে কী হতো? গ্রাহকের ব্যালান্স ঋণাত্মক হয়ে যেত — ব্যাংক টাকা হারাত। ব্যবসার নিয়ম (business rule) অ্যালগরিদমে ঢোকে ঠিক এই ◇ বাক্সগুলো দিয়ে।
  6. প্রক্রিয়া: টাকা দেওয়া। আয়তক্ষেত্র মানে একটি কাজ বা হিসাব। ব্যালান্স থেকে পরিমাণ বাদ দিয়ে টাকা, রসিদ আর কার্ড দেওয়া হলো। ছয়টি বাক্সে পুরো অ্যালগরিদম।
  7. হাতে চালিয়ে যাচাই। শেষ ধাপটি সবচেয়ে গুরুত্বপূর্ণ: একটি কঠিন ইনপুট নিয়ে আঙুল দিয়ে flowchart ধরে চলুন। ব্যালান্স ৫০০, চাওয়া হলো ১০০০ — অ্যালগরিদম “না” পথে গিয়ে নিরাপদে থামল। এভাবে হাতে চালিয়ে দেখাকে বলে dry run. কোড লেখার আগেই বহু ভুল এভাবে ধরা পড়ে।

৫ · Edge case — যেখানে প্রোগ্রাম ভাঙে

ATM-এর flowchart-টি আরেকবার দেখুন। এবার কয়েকটি প্রশ্ন করি। গ্রাহক যদি ১২৩০ টাকা চান? ATM তো শুধু নির্দিষ্ট মানের নোট দিতে পারে। যদি ০ টাকা চান? যদি মেশিনে নোট শেষ হয়ে যায়? আমাদের অ্যালগরিদম এগুলোর কোনোটিই সামলায় না।

এমন সীমানার বা অস্বাভাবিক ইনপুটকে বলে edge case. সাধারণ ইনপুটে প্রায় সব অ্যালগরিদমই কাজ করে। পার্থক্য গড়ে দেয় edge case — আর বাস্তবের বেশিরভাগ bug লুকিয়ে থাকে এখানেই।

“সর্বোচ্চ নম্বর” সমস্যার edge case

অংশ ৪-এর pseudocode-টি মনে আছে? নিচের প্রতিটি পরিস্থিতিতে সেটি কী করবে, আগে নিজে ভাবুন, তারপর ডান কলাম দেখুন:

পরিস্থিতিউদাহরণযা ভাবতে হবে
তালিকা খালিনতুন সেকশন, এখনো কেউ পরীক্ষা দেয়নি“প্রথম নম্বর” বলে কিছু নেই! তাই pseudocode-এ আগেই শর্ত লিখেছিলাম। প্রোগ্রামে আলাদা করে সামলাতে হবে — বার্তা দেখানো বা একটি বিশেষ উত্তর ফেরত দেওয়া।
মাত্র একজন ছাত্র৭৮লুপ একবারও চলবে না, উত্তর ৭৮ — ঠিক আছে।
দুজনের সমান সর্বোচ্চ৯১, ৮৫, ৯১নম্বর ঠিকই আসবে (৯১)। কিন্তু প্রশ্ন যদি হয় “প্রথম কে হলো?” — তাহলে দুজনের নাম দেখাবেন, নাকি রোল নম্বরে আগে যে? সমস্যার বর্ণনাতেই এটা বলে দিতে হবে।
সবার নম্বর সমান৬০, ৬০, ৬০উত্তর ৬০ — ঠিক আছে, কারণ আমরা > দিয়ে তুলনা করছি।
সব মান ঋণাত্মকশীতের রাতের তাপমাত্রা: −৩, −৮, −১এখানেই সবচেয়ে বিখ্যাত ভুলটি ধরা পড়ে — নিচের সতর্কবার্তা দেখুন।
অসম্ভব মান১০০-এর মধ্যে ১০৫, বা −১০এটি কি টাইপের ভুল? প্রোগ্রাম কি থামবে, সতর্ক করবে, না উপেক্ষা করবে? সিদ্ধান্ত আপনার, কিন্তু সিদ্ধান্ত নিতেই হবে।

অনেকে, এমনকি AI-ও মাঝে মাঝে, শুরুতে max ← 0 লিখে ফেলেন — “নম্বর তো শূন্যের নিচে হয় না।” পরীক্ষার নম্বরে হয়তো কাজ চলে যায়। কিন্তু একই কোড তাপমাত্রার তালিকায় (−৩, −৮, −১) চালালে উত্তর আসবে ০ — যে মান তালিকাতেই নেই! এজন্যই আমরা max-এ রেখেছি তালিকার প্রথম মান. ছোট্ট সিদ্ধান্ত, কিন্তু এটিই সঠিক আর ভুল অ্যালগরিদমের পার্থক্য।

edge case খোঁজার অভ্যাস

যেকোনো অ্যালগরিদম লিখে নিজেকে এই প্রশ্নগুলো করুন: খালি হলে? একটি হলে? সব সমান হলে? সবচেয়ে ছোট আর সবচেয়ে বড় মান হলে? ঋণাত্মক বা শূন্য হলে? ভুল ধরনের ইনপুট হলে? প্রতিটির জন্য একটি test case বানান — ইনপুট আর তার প্রত্যাশিত সঠিক আউটপুট আগে থেকে লিখে রাখুন।

৬ · উত্তর ঠিক কি না — loop invariant ও ডমিনোর যুক্তি

পাঁচটি test case-এ প্রোগ্রাম ঠিক উত্তর দিল। তাহলে কি নিশ্চিত হওয়া গেল? না। পাঁচটিতে চলেছে মানে শুধু ওই পাঁচটিতে চলেছে। লাখ রকম ইনপুটের সবগুলো তো আর চালিয়ে দেখা সম্ভব নয়। তাহলে উপায়? উপায় হলো এমন একটি যুক্তি খোঁজা, যা সব ইনপুটের জন্য খাটে।

যে কথাটি কখনো মিথ্যা হয় না

“সর্বোচ্চ নম্বর” অ্যালগরিদমের লুপটি নিয়ে ভাবুন। লুপ যতবারই ঘুরুক, প্রতিবার ঘোরার শেষে একটি কথা সবসময় সত্য থাকে:

“এখন পর্যন্ত যতগুলো নম্বর দেখা হয়েছে,
তাদের মধ্যে সবচেয়ে বড়টি max-এ আছে।”

লুপের ভেতরে প্রতিবার সত্য থাকা এমন কথাকে বলে loop invariant — “invariant” মানে যা বদলায় না। লুপ শেষ হলে “এখন পর্যন্ত দেখা নম্বর” মানে সব নম্বর। তাই তখন max-এ আছে পুরো তালিকার সবচেয়ে বড় নম্বর। ব্যাস, প্রমাণ হয়ে গেল!

নিচের debugger-এ একটি ছোট তালিকা নিয়ে কথাটি ধাপে ধাপে যাচাই করুন। তালিকায় ইচ্ছা করেই দুটি 85 রাখা হয়েছে — সমান নম্বরের edge case পরীক্ষা করতে। প্রতিটি ধাপে ডান দিকের শেষ সারিটি খেয়াল করুন — invariant কখনো ভাঙছে কি?

1#include <stdio.h>23int main(void) {4    int marks[5] = {72, 85, 64, 85, 91};5    int max = marks[0];6    for (int i = 1; i < 5; i++) {7        if (marks[i] > max)8            max = marks[i];9    }10    printf("max = %d\n", max);11    return 0;12}
ভ্যারিয়েবলের মান (Watch)
max
i
marks[i]
max = প্রথম কয়টির সর্বোচ্চ?
  1. তালিকা তৈরি। পাঁচজন ছাত্রের নম্বর একটি array-তে রাখা হলো — পাশাপাশি পাঁচটি ঘর, ঘরের নম্বর 0 থেকে 4. max এখনো তৈরিই হয়নি (?)।
  2. শুরুর মান। max-এ রাখা হলো প্রথম নম্বর 72. Invariant কি সত্য? এখন পর্যন্ত দেখা হয়েছে মাত্র একটি নম্বর, আর সেটিই max-এ — হ্যাঁ, সত্য। এটিই ডমিনোর প্রথম গুটি।
  3. i = 1: তুলনা। marks[1] হলো 85. 85 > 72? হ্যাঁ — তাই পরের লাইনে max বদলাতে হবে। এই মুহূর্তে max এখনো 72, আর তা প্রথম ১টির সর্বোচ্চ — invariant অটুট।
  4. max বদলাল। max হলো 85. এখন প্রথম দুটি নম্বর (72, 85) দেখা হয়েছে, আর তাদের সর্বোচ্চ 85 — max-এই আছে। Invariant সত্য রইল।
  5. i = 2: তুলনা। 64 > 85? না। কিছুই বদলাতে হলো না। প্রথম তিনটির (72, 85, 64) সর্বোচ্চ এখনো 85 — invariant সত্য।
  6. i = 3: সমান নম্বরের পরীক্ষা। আবার 85! 85 > 85? না — সমান, বড় নয়। max বদলায় না, আর বদলানোর দরকারও নেই, কারণ মান একই। প্রথম চারটির সর্বোচ্চ 85 — সত্য। (যদি জানতে হতো কোন ছাত্র, তাহলে > রাখলে প্রথমজন থাকবে, >= দিলে শেষজন। এটাই সেই সিদ্ধান্ত, যা সমস্যার বর্ণনায় লেখা থাকা দরকার।)
  7. i = 4: তুলনা। 91 > 85? হ্যাঁ। পরের লাইনে max বদলাবে।
  8. max হলো 91. পাঁচটি নম্বরই দেখা হয়েছে, আর তাদের সর্বোচ্চ 91 — max-এ আছে। Invariant এখনো সত্য।
  9. লুপ শেষ, ফল ছাপা। i হলো 5, আর 5 < 5 মিথ্যা — লুপ থামল। এখন “দেখা নম্বর” মানে সবগুলো নম্বর, তাই invariant নিজেই বলে দিচ্ছে: max-এ পুরো তালিকার সর্বোচ্চ আছে। স্ক্রিনে এলো max = 91. খেয়াল করুন, এই যুক্তি শুধু এই পাঁচটি নম্বরের জন্য নয় — যেকোনো তালিকার জন্যই খাটে।

এবার একই প্রোগ্রাম নিজে চালিয়ে দেখুন। চালানোর পর নম্বরগুলো বদলে দিন — যেমন সব ঋণাত্মক করে দিন ({-3, -8, -1, -5, -9}) — এবং দেখুন উত্তর ঠিক আসে কি না।

#include <stdio.h>

int main(void) {
    int marks[5] = {72, 85, 64, 85, 91};
    int max = marks[0];              // first value, not 0
    for (int i = 1; i < 5; i++) {
        if (marks[i] > max)
            max = marks[i];
    }
    printf("max = %d\n", max);
    return 0;
}

প্রতিটি লাইন pseudocode-এর হুবহু অনুবাদ: “max ← প্রথম নম্বর” হলো max = marks[0], আর “বাকি প্রতিটি নম্বরের জন্য” হলো i = 1 থেকে শুরু হওয়া লুপ। এখনই সব syntax বোঝার দরকার নেই — for, if আর array নিয়ে পরের পর্বগুলোতে বিস্তারিত আসছে। এখানে দেখার বিষয় একটাই: ভাবনাটি আগে ঠিক হলে, কোড শুধু অনুবাদ। আর খেয়াল করুন, এই কোড খালি তালিকা সামলায় না — সংখ্যা 5 ধরে নেওয়া হয়েছে। বাস্তব প্রোগ্রামে সেটি আলাদা করে যাচাই করতে হবে।

ডমিনোর যুক্তি — induction

লাইন ধরে দাঁড় করানো এক হাজার ডমিনোর গুটি কল্পনা করুন। দুটি কথা যদি নিশ্চিত হয় — (১) প্রথম গুটিটি পড়বে, আর (২) যেকোনো একটি গুটি পড়লে তার পরেরটিও পড়বে — তাহলে প্রতিটি গুটি আলাদা করে না দেখেই বলা যায়: সব গুটিই পড়বে। এক হাজার হোক বা দশ লাখ।

গণিতে এই যুক্তির নাম mathematical induction, আর loop invariant আসলে এরই প্রয়োগ। Debugger-এর ধাপ ২-এ আমরা দেখিয়েছি invariant শুরুতে সত্য — প্রথম গুটি পড়েছে। তারপর প্রতিটি ধাপে দেখিয়েছি: আগের ধাপে সত্য থাকলে লুপের এক ঘোরার পরেও সত্য থাকে — নতুন নম্বরটি বড় হলে সেটি max-এ যায়, না হলে পুরোনো max-ই সর্বোচ্চ থাকে। এই দুটি কথা মিলে প্রমাণ হয় যে তালিকা যত বড়ই হোক, শেষে উত্তর ঠিক আসবে।

প্রোগ্রাম সঠিক কি না, তা গণিতের মতো যুক্তি দিয়ে প্রমাণ করার ধারণাটি জোরালোভাবে আসে ১৯৬০-এর দশকের শেষে — Robert Floyd ও Tony Hoare-এর কাজে। তখন থেকেই প্রোগ্রামাররা বুঝতে শুরু করেন: শুধু চালিয়ে দেখা যথেষ্ট নয়, প্রোগ্রাম কেন ঠিক, সেটাও বলতে পারা চাই। আজ বিমান, রেল সিগন্যাল বা চিকিৎসা যন্ত্রের মতো জায়গায় এই ধরনের প্রমাণ-ভিত্তিক পদ্ধতি সত্যিই ব্যবহার হয়।

৭ · কোনটি দ্রুত — ১ থেকে ১০০-এর খেলা

ঠিক উত্তর পাওয়াই শেষ কথা নয়। একই কাজ দুটি অ্যালগরিদম করতে পারে, কিন্তু একটি নিতে পারে এক সেকেন্ড, আরেকটি এক ঘণ্টা। একটি খেলা দিয়ে দেখি।

বন্ধু মনে মনে ১ থেকে ১০০-এর মধ্যে একটি সংখ্যা ভাবল। আপনি অনুমান বলবেন, আর সে শুধু বলবে “আরও বড়”, “আরও ছোট” বা “মিলে গেছে।” কীভাবে খেলবেন?

  • পদ্ধতি ক — একটি একটি করে: “১? ২? ৩? …” এভাবে এগোলে সংখ্যাটি ১০০ হলে আপনার লাগবে ১০০ বার। একে বলে linear search.
  • পদ্ধতি খ — প্রতিবার মাঝখানে: প্রথমে ৫০ বলুন। “আরও বড়” শুনলে ১–৫০ সবগুলো একবারেই বাদ! এবার বাকি অর্ধেকের মাঝখানে বলুন। প্রতিবার বাকি সংখ্যা অর্ধেক হয়ে যায়। একে বলে binary search.

বন্ধু ভেবেছে ৭৩. নিচের ডায়াগ্রামে পদ্ধতি খ ধাপে ধাপে দেখুন। শেষ সারিতে পাশাপাশি দেখানো হয়েছে, একই সময়ে পদ্ধতি ক কতদূর এগোত।

প্রতিটি ঘর = ৫টি সংখ্যা · █ = এখনো সম্ভব · ░ = বাদ

খেলার অবস্থা
কততম অনুমান
আপনার অনুমান (মাঝখানের সংখ্যা)
বন্ধুর উত্তর
বাকি পরিসর
বাকি কয়টি সংখ্যা
পদ্ধতি ক হলে এতক্ষণে
  1. শুরু। ১০০টি সংখ্যার যেকোনোটি হতে পারে। একটি নিয়ম ঠিক করি: সবসময় বাকি পরিসরের মাঝখানের সংখ্যা বলব (দুটি মাঝখান থাকলে ছোটটি)।
  2. ১ম অনুমান: ৫০. উত্তর “আরও বড়।” মাত্র একটি প্রশ্নে ১ থেকে ৫০ — পুরো পঞ্চাশটি সংখ্যা — বাদ পড়ে গেল। পদ্ধতি ক-তে একটি প্রশ্নে বাদ যায় মাত্র একটি সংখ্যা।
  3. ২য় অনুমান: ৭৫. ৫১ থেকে ১০০-এর মাঝখান ৭৫. উত্তর “আরও ছোট” — তাই ৭৫ থেকে ১০০ বাদ। বাকি থাকল ৫১ থেকে ৭৪, মাত্র ২৪টি।
  4. ৩য় অনুমান: ৬২. “আরও বড়।” বাকি ৬৩ থেকে ৭৪, ১২টি। লক্ষ করুন, প্রতি ধাপে বাকি সংখ্যা প্রায় অর্ধেক হচ্ছে: ১০০ → ৫০ → ২৪ → ১২।
  5. ৪র্থ অনুমান: ৬৮. “আরও বড়।” বাকি ৬৯ থেকে ৭৪ — মাত্র ৬টি।
  6. ৫ম অনুমান: ৭১. “আরও বড়।” বাকি ৭২, ৭৩, ৭৪ — তিনটি।
  7. ৬ষ্ঠ অনুমান: ৭৩ — মিলে গেছে! মাত্র ৬ বারে। ১ থেকে ১০০-এর যেকোনো সংখ্যা এই পদ্ধতিতে সর্বোচ্চ ৭ বারে পাওয়া যায়, কারণ ১০০-কে ৭ বার অর্ধেক করলে ১-এর নিচে নেমে যায়। অথচ পদ্ধতি ক এখনো ৭-এ দাঁড়িয়ে — ৭৩-এ পৌঁছাতে তার লাগবে ৭৩ বার।

বড় তালিকায় পার্থক্যটা কত বড়

১০০টি সংখ্যায় পার্থক্য ১০০ বনাম ৭. কিন্তু তালিকা যত বড় হয়, পার্থক্য তত নাটকীয় হয়:

সাজানো তালিকায় কতগুলো নামএকটি একটি করে (সবচেয়ে খারাপ ক্ষেত্রে)মাঝখান থেকে অর্ধেক করে (সবচেয়ে খারাপ ক্ষেত্রে)
১০০ (একটি ক্লাসের রোল)১০০ বার৭ বার
১,০০০ (একটি স্কুল)১,০০০ বার১০ বার
১০ লাখ (একটি বোর্ডের পরীক্ষার্থী)১০ লাখ বার২০ বার
প্রায় ১৭ কোটি (বাংলাদেশের মানুষ)প্রায় ১৭ কোটি বার২৮ বার

অভিধানে “নদী” শব্দটি খুঁজতে আপনি কখনো প্রথম পাতা থেকে একটি একটি করে পড়েন না। মাঝামাঝি খোলেন, দেখেন “ন” আগে না পরে, তারপর সেই অর্ধেকে যান। আপনি না জেনেই binary search করছেন! শর্ত একটাই — অভিধান বর্ণানুক্রমে সাজানো. এলোমেলো তালিকায় মাঝখান দেখে কিছুই বোঝা যায় না, তখন একটি একটি করে দেখা ছাড়া উপায় নেই।

মূল ধারণা

ইনপুট বড় হলে একটি অ্যালগরিদমের কাজ কত দ্রুত বাড়ে, তার হিসাবকে বলে time complexity. Linear search-এ তালিকা দ্বিগুণ হলে কাজও দ্বিগুণ হয় — একে লেখা হয় O(n). Binary search-এ তালিকা দ্বিগুণ হলে কাজ বাড়ে মাত্র একটি ধাপ — একে লেখা হয় O(log n). এই চিহ্নগুলো নিয়ে বিস্তারিত পড়বেন পর্ব ৩৯-এ; আপাতত মনে রাখুন: ছোট ইনপুটে সব অ্যালগরিদমই দ্রুত, বড় ইনপুটে অ্যালগরিদমের পছন্দই সব।

খেলাটি C-তে লিখলে কেমন দেখায়, চালিয়ে দেখুন। secret-এর মান বদলে ১ বা ১০০ দিয়ে চেষ্টা করুন — কখনো ৭-এর বেশি লাগে কি?

#include <stdio.h>

int main(void) {
    int secret = 73, lo = 1, hi = 100, tries = 0;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;     // middle of what is left
        tries++;
        printf("Try %d: guess %d\n", tries, mid);
        if (mid == secret) break;
        if (mid < secret) lo = mid + 1;   // too small: drop left half
        else              hi = mid - 1;   // too big: drop right half
    }
    printf("Found %d in %d tries\n", secret, tries);
    return 0;
}

lo আর hi হলো বাকি পরিসরের দুই প্রান্ত, আর mid তাদের মাঝখান। অনুমান ছোট হলে বাঁ দিকের অর্ধেক বাদ (lo = mid + 1), বড় হলে ডান দিকের অর্ধেক বাদ (hi = mid - 1) হয়ে যায়। আউটপুটে দেখবেন হুবহু ডায়াগ্রামের ক্রম: 50, 75, 62, 68, 71, 73. এই লুপেরও একটি invariant আছে — “গোপন সংখ্যাটি সবসময় lo থেকে hi-এর মধ্যেই আছে।” দেখতে সহজ হলেও binary search ঠিকঠাক লেখা কুখ্যাতভাবে কঠিন; + 1 বা - 1 একটু ভুল হলেই লুপ আর থামে না। ভাবনার প্রশ্ন ২-এ এর মজার ইতিহাস আছে।

৮ · AI-এর যুগে — সমস্যা ভেঙে নির্ভুল নির্দেশ লেখা

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

অস্পষ্ট প্রশ্ন → AI-এর অনুমান → লুকানো bug.
নির্ভুল প্রশ্ন → যাচাই করা যায় এমন উত্তর।

সমাধান হলো AI-কে কোড চাওয়ার আগে নিজে সমস্যাটি ভাঙা, তারপর একটি পরিষ্কার specification লেখা — কী ইনপুট, কী আউটপুট, কোন নিয়ম, আর কোন edge case-এ কী হবে। তুলনা করে দেখুন:

❌ অস্পষ্ট নির্দেশ

“ছাত্রদের সর্বোচ্চ নম্বর বের করার C প্রোগ্রাম লিখে দাও।”

AI নিজে ঠিক করবে: কতজন ছাত্র, খালি হলে কী, সমান হলে কে

✅ নির্ভুল নির্দেশ

ইনপুট, আউটপুট, নিয়ম, edge case আর পরীক্ষার উদাহরণ — সবকিছু লিখে দেওয়া, যেমন নিচের prompt-এ।

যাচাই করা যায়, কারণ “ঠিক” মানে কী, তা লেখা আছে

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

আমি C শিখছি। একটি ফাংশন দরকার যা ছাত্রদের নম্বরের তালিকা থেকে সর্বোচ্চ নম্বর আর সেই ছাত্রের রোল নম্বর বের করবে। ইনপুট: নম্বরের একটি int array আর তার দৈর্ঘ্য n. নম্বর 0 থেকে 100 এর মধ্যে। আউটপুট: সর্বোচ্চ নম্বর ও প্রথম যে ছাত্র সেটি পেয়েছে তার রোল (রোল মানে array-র ঘরের নম্বরের চেয়ে ১ বেশি)। Edge case: - n = 0 হলে কোনো নম্বর ফেরত দেবে না, বরং বোঝাবে যে তালিকা খালি। - দুজনের নম্বর সমান সর্বোচ্চ হলে, রোল নম্বরে যে আগে তাকে দেখাবে। - 0 থেকে 100 এর বাইরের কোনো নম্বর থাকলে সেটি জানাবে। সীমাবদ্ধতা: শুধু standard C, কোনো বাইরের library নয়। কোড লেখার আগে: ১. অ্যালগরিদমটি সহজ বাংলায় ধাপে ধাপে লেখো। ২. লুপের invariant কী, এক লাইনে বলো। ৩. তারপর কোড দাও, আর উপরের প্রতিটি edge case-এর জন্য একটি করে test case দাও (ইনপুট ও প্রত্যাশিত আউটপুট)।

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

ভুলকীভাবে ধরবেন
শুরুতে max = 0 ধরে নেওয়াসব ঋণাত্মক মান দিয়ে চালান
খালি তালিকা সামলানো হয়নিn = 0 দিয়ে চালান — crash বা আবর্জনা মান আসে কি না দেখুন
এক ঘর কম বা বেশি গোনা (< বনাম <=)একটিমাত্র উপাদানের তালিকা আর শেষ উপাদানটি খুঁজে দেখুন
সাজানো নয় এমন তালিকায় binary searchপ্রশ্ন করুন: “এই অ্যালগরিদম কোন শর্ত ধরে নিয়েছে?”
সমান মানের ক্ষেত্রে ভুল জনকে বেছে নেওয়াদুটি সমান সর্বোচ্চ মান দিয়ে চালিয়ে দেখুন কার রোল আসে

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

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

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

প্র ০১“আমার উদাহরণে তো ঠিক চলছে” — এটা কেন প্রমাণ নয়? তাহলে test করার মানে কী?

কম্পিউটার বিজ্ঞানের অন্যতম পথিকৃৎ Edsger Dijkstra একটি বিখ্যাত কথা বলেছিলেন: “Program testing can be used to show the presence of bugs, but never to show their absence.” অর্থাৎ পরীক্ষা করে দেখানো যায় যে bug আছে, কিন্তু কখনো দেখানো যায় না যে bug নেই।

কারণটা সংখ্যার হিসাবে। ধরুন একটি ফাংশন দুটি 32-bit পূর্ণসংখ্যা নেয়। সম্ভাব্য ইনপুট-জোড়া প্রায় ১.৮ × ১০১৯টি — প্রতি সেকেন্ডে একশো কোটি পরীক্ষা চালালেও সবগুলো শেষ করতে কয়েকশো বছর লাগবে। আপনার পাঁচটি উদাহরণ এই বিশাল সমুদ্রে পাঁচ ফোঁটা পানি। আর সমস্যা হলো, আমরা স্বভাবতই এমন উদাহরণ বাছি যেগুলো আমাদের মাথায় থাকা “স্বাভাবিক” ছবির সাথে মেলে — ঠিক সেগুলোতেই কোড চলবে, কারণ সেগুলো মাথায় রেখেই লেখা।

তাহলে test অর্থহীন? একদমই না। ভালো test হলো পরিকল্পিত test — এলোমেলো নয়, বরং edge case লক্ষ্য করে বানানো: খালি, একটি, সীমানার মান, সমান মান। প্রতিটি test এক একটি সম্ভাব্য ভুলের “প্রশ্ন।” তার সাথে লাগে যুক্তি — loop invariant-এর মতো একটি কথা, যা সব ইনপুটের জন্য খাটে।

বাস্তবে দুটো মিলিয়ে চলে। সাধারণ সফটওয়্যারে প্রচুর পরিকল্পিত test আর code review. আর যেখানে একটি bug-এর দাম মানুষের জীবন — বিমানের নিয়ন্ত্রণ, রেলের সিগন্যাল, চিকিৎসা যন্ত্র — সেখানে গাণিতিক প্রমাণ-ভিত্তিক পদ্ধতিও (formal verification) ব্যবহার হয়।

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

প্র ০২Binary search এত দ্রুত, তাহলে সবসময় এটাই ব্যবহার হয় না কেন? “সাজানো তালিকা” শর্তের দাম কত?

Binary search-এর গতি বিনামূল্যে আসে না — দামটা আগেই দিতে হয় সাজানোর মাধ্যমে. এলোমেলো তালিকা সাজাতে ভালো অ্যালগরিদমেরও একটি একটি করে খোঁজার চেয়ে অনেক বেশি কাজ লাগে। তাই প্রশ্নটি দাঁড়ায়: কতবার খুঁজবেন?

  • একবার খুঁজবেন: সাজাতে যে সময় যাবে, তার চেয়ে সরাসরি linear search দ্রুত। সাজানো লাভজনক নয়।
  • লাখবার খুঁজবেন: একবার সাজিয়ে রাখলে প্রতিটি খোঁজা প্রায় তাৎক্ষণিক। অভিধান, ফোনবুক, বোর্ডের ফলাফলের ডেটাবেস — সব এই কারণে সাজানো থাকে।
  • তালিকা প্রায়ই বদলায়: প্রতিবার নতুন নাম ঢোকালে সাজানো ক্রম বজায় রাখার খরচ আছে। এজন্যই ডেটাবেস সাধারণ সাজানো array-র বদলে B-tree-এর মতো বিশেষ কাঠামো ব্যবহার করে — খোঁজা আর যোগ করা, দুটোই দ্রুত রাখতে।
  • তালিকা খুব ছোট: ১০-২০টি জিনিসে linear search প্রায়ই সমান বা বেশি দ্রুত — কোড সরল, আর আধুনিক CPU পাশাপাশি থাকা ডেটা খুব দ্রুত পড়ে।

আরেকটি দাম — সঠিকভাবে লেখা কঠিন। Donald Knuth তাঁর বইয়ে উল্লেখ করেছেন, প্রথম binary search প্রকাশিত হয় ১৯৪৬ সালে, কিন্তু সম্পূর্ণ নির্ভুল সংস্করণ প্রকাশ পেতে লেগে যায় আরও প্রায় ষোলো বছর। আরও মজার ঘটনা: ২০০৬ সালে Google-এর প্রকৌশলী Joshua Bloch দেখান যে Java-র standard library-র binary search-এ বহু বছর ধরে একটি bug ছিল — (lo + hi) / 2 হিসাবে, খুব বড় array-তে lo + hi যোগফল int-এর সীমা ছাড়িয়ে যায়। নিরাপদ রূপ হলো lo + (hi - lo) / 2. আমাদের ১ থেকে ১০০-এর খেলায় সমস্যা নেই, কিন্তু কোটি কোটি উপাদানে আছে।

মূল উপলব্ধি: প্রতিটি দ্রুত অ্যালগরিদম কোনো না কোনো শর্ত বা প্রস্তুতির ওপর দাঁড়িয়ে থাকে। প্রশ্ন “কোনটা দ্রুত?” নয় — প্রশ্ন হলো “আমার পরিস্থিতিতে মোট খরচ কোনটায় কম?”

প্র ০৩Abstraction জটিলতা লুকিয়ে রাখে — কিন্তু লুকানো জটিলতা কি কখনো বেরিয়ে আসে?

আসে, এবং প্রায়ই সবচেয়ে খারাপ সময়ে। ২০০২ সালে সফটওয়্যার লেখক Joel Spolsky একে নাম দেন “Law of Leaky Abstractions” — যেকোনো অর্থবহ abstraction-ই কোনো না কোনো মাত্রায় “ফুটো” হয়; ভেতরের জটিলতা কোথাও না কোথাও চুইয়ে বাইরে আসে।

  • Google Maps: ম্যাপ বলছে ধানমন্ডি থেকে গুলশান ২৫ মিনিট। কিন্তু বর্ষায় রাস্তায় হাঁটুপানি জমলে সেই হিসাব ভেঙে পড়ে। ম্যাপ রাস্তাকে একটি রেখা হিসেবে দেখায় — পানি জমা, খোঁড়াখুঁড়ি বা হঠাৎ ব্যারিকেড সেই রেখায় নেই।
  • bKash-এর “Send Money”: আপনার কাছে এটি একটি বোতাম। কিন্তু নেটওয়ার্ক দুর্বল হলে হঠাৎ ভেতরের জটিলতা সামনে চলে আসে — টাকা কেটেছে কি না, পৌঁছেছে কি না, আবার চেষ্টা করলে দুবার কাটবে কি না। ভেতরে কী হয় না বুঝলে এই অবস্থায় সঠিক সিদ্ধান্ত নেওয়া কঠিন।
  • C-এর int: আমরা ভাবি এটি একটি “সংখ্যা।” কিন্তু এর সীমা আছে; সীমা ছাড়ালে অদ্ভুত ফল আসে — ঠিক যেমন binary search-এর lo + hi bug-এ দেখলাম। অঙ্কের সংখ্যা আর কম্পিউটারের সংখ্যা এক জিনিস নয় (পর্ব ০৫-এ বিস্তারিত)।

এর মানে এই নয় যে abstraction খারাপ — abstraction ছাড়া আধুনিক সফটওয়্যার বানানোই অসম্ভব। এর মানে হলো, ভালো প্রকৌশলী অন্তত এক স্তর নিচে কী হচ্ছে, তা জানেন। সাধারণ দিনে ওপরের স্তরে কাজ করেন; কিছু ভাঙলে নিচে নেমে খুঁজতে পারেন। C শেখার একটি বড় কারণও এটাই — C আপনাকে মেমরি আর হার্ডওয়্যারের কাছাকাছি নিয়ে যায়, যেখানে ওপরের ভাষাগুলোর অনেক abstraction শেষ হয়।

মূল উপলব্ধি: Abstraction ব্যবহার করুন, কিন্তু অন্ধভাবে বিশ্বাস করবেন না। “এটি ভেতরে কীভাবে কাজ করে, আর কখন ভাঙতে পারে?” — এই প্রশ্নটি করার অভ্যাসই দক্ষ প্রকৌশলীকে আলাদা করে।

প্র ০৪AI যখন কোড লেখে, তখন সমস্যার নির্ভুল বর্ণনা (specification) কেন আগের চেয়েও বেশি জরুরি?

আগে একজন প্রোগ্রামার নিজের হাতে কোড লিখতে গিয়ে প্রতিটি সিদ্ধান্তের মুখোমুখি হতেন — “খালি তালিকা হলে কী করব?” প্রশ্নটি লেখার মুহূর্তেই মাথায় আসত, কারণ কোড না লিখে এগোনো যেত না। অর্থাৎ কোড লেখার কষ্টই অনেক অস্পষ্টতা ধরিয়ে দিত।

AI এই কষ্টটা সরিয়ে দিয়েছে — আর তার সাথে সরিয়ে দিয়েছে সেই স্বাভাবিক সতর্কসংকেতও। অস্পষ্ট নির্দেশ পেলে AI থামে না, প্রশ্নও সবসময় করে না; সে সবচেয়ে “সম্ভাব্য” অনুমানটি নিয়ে এগিয়ে যায় এবং পরিপাটি, আত্মবিশ্বাসী কোড দেয়। ফলে সিদ্ধান্তগুলো তখনো নেওয়া হচ্ছে — কিন্তু আপনি নিচ্ছেন না, আর জানতেও পারছেন না কী সিদ্ধান্ত নেওয়া হলো।

উদাহরণ: “সর্বোচ্চ নম্বর পাওয়া ছাত্রের নাম দাও।” সমান নম্বর হলে AI হয়তো শেষজনকে দেখাবে। স্কুলের নিয়ম হয়তো রোল নম্বরে আগে যে, তাকে প্রথম ধরা। Test-এ সমান নম্বরের উদাহরণ না থাকলে ভুলটা ধরা পড়বে ফল প্রকাশের দিন — অভিভাবকের অভিযোগে।

এখানে একটি গভীর কথা আছে: specification লেখা আর অ্যালগরিদম ভাবা আসলে একই দক্ষতা. Edge case চিহ্নিত করা, ইনপুট-আউটপুট ঠিক করা, নিয়ম লেখা — এগুলোই computational thinking. এগুলো স্পষ্ট করে লিখতে পারলে AI-এর উত্তর যাচাই করাও সহজ হয়, কারণ আপনার হাতে একটি মাপকাঠি থাকে। না লিখলে “ঠিক উত্তর” কী, তা আপনি নিজেও জানেন না — তাহলে AI ঠিক করল কি না বুঝবেন কীভাবে?

এজন্যই পেশাদার দলে AI ব্যবহারের ভালো পদ্ধতি হলো আগে test case লেখা (প্রত্যাশিত ইনপুট-আউটপুট), তারপর AI-কে কোড লিখতে দেওয়া, তারপর সেই test দিয়ে যাচাই করা।

মূল উপলব্ধি: AI কোড লেখার খরচ প্রায় শূন্যে নামিয়ে এনেছে। তাই এখন দুর্লভ ও দামি দক্ষতা হলো “ঠিক কী বানাতে হবে” তা নির্ভুলভাবে বলা, আর “যা বানানো হলো তা ঠিক কি না” প্রমাণ করা।

প্র ০৫একটি সমস্যা কতটা ভাঙবেন? ভুলভাবে ভাঙলে কী সমস্যা হয়?

Decomposition সবচেয়ে কাজের কৌশল, কিন্তু এরও ভুল পথ আছে। দুটি চরম অবস্থা ভাবুন:

  • খুব কম ভাঙা: একটি টুকরোয় অনেক কাজ — “খাবার ও সাজসজ্জা” একজনের দায়িত্বে। কাজটি এখনো এত বড় যে কোথা থেকে শুরু, বোঝা যায় না। প্রোগ্রামে এটি হয় কয়েকশো লাইনের একটি বিশাল ফাংশন, যা কেউ পুরোপুরি বোঝে না।
  • খুব বেশি ভাঙা: “প্লেট আনা”, “গ্লাস আনা”, “চামচ আনা” — তিনজন আলাদা মানুষ। এখন সমন্বয়েই সব সময় চলে যায়। প্রোগ্রামে এটি হয় এক-দুই লাইনের অসংখ্য ফাংশন, যাদের মধ্যে ঘুরে ঘুরে মূল যুক্তিটাই হারিয়ে যায়।

ভালো ভাঙার মাপকাঠি: প্রতিটি টুকরোর একটি স্পষ্ট দায়িত্ব থাকবে, যা এক বাক্যে বলা যায়। টুকরোগুলোর মধ্যে আদান-প্রদান থাকবে কম ও পরিষ্কার — “খাবার-দল শুধু অতিথির সংখ্যাটা জানতে চায়, অতিথি-তালিকার বাকি খুঁটিনাটি নয়।” সফটওয়্যার প্রকৌশলে একে বলে high cohesion, low coupling — ভেতরে ঘনিষ্ঠ, বাইরে ঢিলেঢালা সংযোগ।

Unix অপারেটিং সিস্টেমের দর্শন এর চমৎকার উদাহরণ। Doug McIlroy-এর নামে প্রচলিত নীতি: প্রতিটি প্রোগ্রাম একটি কাজ করবে, এবং ভালোভাবে করবে; আর প্রোগ্রামগুলো একে অপরের সাথে মিলে কাজ করবে। ছোট ছোট টুল জোড়া দিয়ে বড় কাজ — পঞ্চাশ বছর পরও এই নকশা টিকে আছে।

আরেকটি গভীর দিক: কিছু অ্যালগরিদম নিজেই decomposition-এর ওপর দাঁড়িয়ে। Binary search সমস্যাকে অর্ধেক করে, তারপর শুধু একটি অর্ধেক নিয়ে কাজ করে। Merge sort তালিকাকে দুই ভাগ করে, আলাদা সাজায়, তারপর জোড়া দেয়। এই কৌশলের নাম divide and conquer — পর্ব ৪১-এ এটি বিস্তারিত দেখবেন।

মূল উপলব্ধি: ভাঙার লক্ষ্য টুকরো বাড়ানো নয় — লক্ষ্য হলো প্রতিটি টুকরোকে আলাদাভাবে বোঝা, বানানো ও যাচাই করা যায় এমন করা।

অনুশীলন

এগুলোর জন্য কোড লিখতে হবে না — খাতা-কলম আর ভাবনাই আসল কাজ।

  1. রোবটের জন্য চা: এক কাপ দুধ-চায়ের অ্যালগরিদম লিখুন, যেন একটি রোবট হুবহু মেনে বানাতে পারে। তারপর অংশ ৩-এর পাঁচটি শর্তের সাথে মিলিয়ে দেখুন — কোথাও “একটু”, “মতো” বা থামার নিয়মহীন ধাপ রয়ে গেছে কি?

    একটি সম্ভাব্য উত্তর: (১) পাতিলে ২০০ মিলি পানি নিয়ে চুলায় বসাও। (২) পানি ফুটতে শুরু করলে ১ চামচ চা-পাতা দাও, ২ মিনিট অপেক্ষা করো। (৩) ১০০ মিলি দুধ দিয়ে আবার ফুটে ওঠা পর্যন্ত অপেক্ষা করো। (৪) আগুন বন্ধ করে ছেঁকে কাপে ঢালো। (৫) ২ চামচ চিনি দিয়ে নাড়ো। শেষ।

    সাধারণ ভুলগুলো: “পানি গরম করো” (কতক্ষণ? — সসীমতা নেই), “চিনি দাও” (কতটুকু? — নির্দিষ্টতা নেই), আগুন বন্ধ করতে ভুলে যাওয়া। আর edge case? চা-পাতা শেষ হয়ে গেলে কী হবে, অ্যালগরিদম কিছুই বলে না!

  2. গ্রেডের flowchart: বাংলাদেশের SSC/HSC-র গ্রেডিং অনুযায়ী (৮০–১০০ A+, ৭০–৭৯ A, ৬০–৬৯ A−, ৫০–৫৯ B, ৪০–৪৯ C, ৩৩–৩৯ D, ০–৩২ F) নম্বর থেকে গ্রেড বের করার flowchart আঁকুন। তারপর এই ইনপুটগুলো দিয়ে dry run করুন: ৮০, ৭৯, ৩৩, ০, ১০৫, −৫.

    সবচেয়ে পরিষ্কার পদ্ধতি হলো ওপর থেকে নিচে ◇ সিদ্ধান্তের একটি শিকল: “নম্বর ≥ ৮০?” হ্যাঁ হলে A+, না হলে “নম্বর ≥ ৭০?” — এভাবে চলতে থাকবে। এতে প্রতিটি সীমা মাত্র একবার লিখতে হয়।

    Edge case-এ যা দেখবেন: ৮০ আর ৭৯ ঠিক সীমানায় — > আর >=-এর পার্থক্য এখানেই ধরা পড়ে। ৩৩ পাস (D), ৩২ ফেল — এটিও সীমানা। আর ১০৫ বা −৫? শুধু শিকল থাকলে ১০৫ পাবে A+ আর −৫ পাবে F — দুটোই ভুল আচরণ, কারণ এগুলো সম্ভবই নয়। তাই শিকলের একদম শুরুতে একটি ◇ দরকার: “নম্বর ০ থেকে ১০০-এর মধ্যে?” না হলে “ভুল ইনপুট” দেখিয়ে শেষ।

  3. রোল খাতায় নাম খোঁজা: একটি স্কুলের ১,০০০ জন ছাত্রের নাম বর্ণানুক্রমে সাজানো আছে। (ক) binary search-এ “রাফি” নামটি খুঁজতে সর্বোচ্চ কতবার তুলনা লাগবে? (খ) তালিকা যদি ভর্তির তারিখ অনুযায়ী সাজানো থাকে, তাহলে কী হবে?

    (ক) সর্বোচ্চ ১০ বার। ১,০০০-কে বারবার অর্ধেক করলে: ৫০০, ২৫০, ১২৫, ৬৩, ৩২, ১৬, ৮, ৪, ২, ১ — দশ ধাপে একটি নামে নেমে আসে।

    (খ) Binary search আর কাজ করবে না। তালিকা নামের ক্রমে নয়, তারিখের ক্রমে সাজানো। মাঝখানের নামটি “ম” দিয়ে শুরু হলেও “রাফি” কোন দিকে আছে, তা বলা যায় না। হয় linear search করতে হবে (সর্বোচ্চ ১,০০০ বার), নয়তো আগে নাম অনুযায়ী সাজিয়ে নিতে হবে। এটাই সেই লুকানো শর্ত — binary search সেই ক্রমেই কাজ করে, যে ক্রমে আপনি খুঁজছেন।

  4. AI-এর অ্যালগরিদম যাচাই: যেকোনো AI-কে শুধু এটুকু বলুন: “একটি তালিকা থেকে দ্বিতীয় সর্বোচ্চ সংখ্যা বের করার অ্যালগরিদম দাও।” তারপর তার অ্যালগরিদম কাগজে এই তিনটি ইনপুটে dry run করুন: [90, 90, 80], [50], [-5, -2, -9].

    প্রায়ই যা দেখা যায়:

    • [90, 90, 80]: অনেক অ্যালগরিদম উত্তর দেয় 90. কিন্তু “দ্বিতীয় সর্বোচ্চ” মানে কি দ্বিতীয় অবস্থান (90) নাকি দ্বিতীয় আলাদা মান (80)? প্রশ্নটিই অস্পষ্ট ছিল — AI চুপচাপ একটি বেছে নিয়েছে।
    • [50]: দ্বিতীয় কিছু নেই। অ্যালগরিদম কি কিছু একটা আবর্জনা মান ফেরত দিচ্ছে, নাকি জানাচ্ছে যে উত্তর নেই?
    • [-5, -2, -9]: শুরুতে 0 ধরে নিলে উত্তর আসবে 0, অথচ সঠিক উত্তর −5.

    এবার অংশ ৮-এর কাঠামো মেনে নির্ভুল prompt লিখে আবার চান। পার্থক্যটা নিজের চোখেই দেখবেন।

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

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

ক্রমএকটি নতুন সমস্যা সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🎯 সমস্যাটি বোঝা — ইনপুট কী, আউটপুট কী
  2. 🧩 বড় সমস্যাকে ছোট টুকরোয় ভাঙা
  3. 📋 Pseudocode বা flowchart-এ অ্যালগরিদম লেখা
  4. 🔍 Edge case খুঁজে হাতে dry run করা
  5. 💻 কোড লেখা (নিজে বা AI দিয়ে)
  6. ✅ Test case দিয়ে যাচাই করা

নিচের কোনটি অ্যালগরিদমের পাঁচটি শর্তের মধ্যে পড়ে না?

  • সীমিত ধাপে শেষ হওয়া (সসীমতা)
  • প্রতিটি ধাপ স্পষ্ট হওয়া (নির্দিষ্টতা)
  • সম্ভাব্য সবচেয়ে দ্রুত হওয়া
  • অন্তত একটি আউটপুট দেওয়া

সর্বোচ্চ মান বের করার কোডে শুরুতে max = 0 লেখা হয়েছে। কোন ইনপুটে এটি ভুল উত্তর দেবে?

  • 72, 85, 91
  • 60, 60, 60
  • 78
  • −3, −8, −1

১ থেকে ১,০০০-এর মধ্যে একটি সংখ্যা binary search দিয়ে খুঁজলে সবচেয়ে খারাপ ক্ষেত্রে কতবার অনুমান লাগবে?

  • ১,০০০ বার
  • ৫০০ বার
  • ১০ বার
  • ১ বার

“এখন পর্যন্ত দেখা নম্বরগুলোর মধ্যে সবচেয়ে বড়টি max-এ আছে” — এই কথাটিকে কী বলে, আর এটি কেন কাজের?

  • Loop invariant — এটি দিয়ে প্রমাণ করা যায় যে লুপ শেষে উত্তর ঠিক হবে
  • Edge case — এটি দিয়ে খালি তালিকা ধরা যায়
  • Pseudocode — এটি কোড লেখার খসড়া
  • Test case — এটি দিয়ে একটি ইনপুট পরীক্ষা করা যায়

AI-কে একটি অ্যালগরিদম লিখতে বলার সবচেয়ে ভালো উপায় কোনটি?

  • যত ছোট করে সম্ভব প্রশ্ন করা, AI বাকিটা বুঝে নেবে
  • “কোনো ভুল ছাড়া লিখবে” কথাটি যোগ করা
  • সবসময় সবচেয়ে দ্রুত অ্যালগরিদম চাওয়া
  • ইনপুট, আউটপুট, edge case ও test case লিখে দিয়ে, কোডের আগে অ্যালগরিদম ব্যাখ্যা চাওয়া

এলোমেলোভাবে রাখা ৫০০টি নামের তালিকায় একটি নাম একবারই খুঁজতে হবে। কোনটি বুদ্ধিমানের কাজ?

  • সরাসরি binary search চালানো
  • একটি একটি করে দেখা (linear search)
  • আগে পুরো তালিকা সাজিয়ে নিয়ে তারপর binary search
  • অনুমান করা যে নামটি তালিকায় নেই

শব্দকোষ

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

মনে রাখুন

  1. কম্পিউটার শুধু যা বলা হয় তাই করে — তাই কোডের আগে সমস্যাকে নির্ভুলভাবে ভাবতে হয়।
  2. চার কৌশল: ভাঙা, মিল খোঁজা, অদরকারি বাদ দেওয়া, ধাপ সাজানো (decomposition, pattern recognition, abstraction, algorithm).
  3. অ্যালগরিদমের পাঁচ শর্ত: ইনপুট, আউটপুট, নির্দিষ্টতা, সসীমতা, কার্যকারিতা; লিখুন pseudocode বা flowchart-এ।
  4. সবসময় edge case খুঁজুন — খালি, একটি, সমান, ঋণাত্মক। আর loop invariant দিয়ে বুঝুন উত্তর কেন ঠিক।
  5. সাজানো তালিকায় binary search (১০০-তে ৭ বার) অনেক দ্রুত; AI-কে দিন নির্ভুল specification, আর তার উত্তর নিজে যাচাই করুন।

পরের পর্বে: আজ আমরা ভাবনা সাজালাম, আর একটি অ্যালগরিদমকে C কোডে লিখলাম। কিন্তু সেই লেখা কোড কম্পিউটার বোঝে কীভাবে? পরের পর্বে দেখব .c ফাইলটি কোন চার ধাপ পেরিয়ে চালানো যায় এমন প্রোগ্রামে পরিণত হয়।

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

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