স্ট্রাকচার ইন সি প্রোগ্রামিং

আবছা ধারণা

আমাদের ভ্যারিয়েবলের দরকার পড়ত যখন আমরা প্রোগ্রামে কোন নাম বা সংখ্যা এমন কিছু স্টোর করে রাখতে চাইতাম, পরে কোন ক্যালকুলেশনের সময় লাগবে এটা ভেবে। ইন্টিজার, স্ট্রিং যেমন ডাটা টাইপ – সংখ্যা, নাম এসব মনে রাখার জন্য, স্ট্রাকচার কেও ঠিক তেমন ধরণের একটা ডাটা টাইপ এর মত করে কল্পনা করতে পারি আমরা। পার্থক্য হল, ইন্টিজার এ শুধু আমরা সংখ্যা ই রাখতে পারব, ক্যারেক্টার টাইপ ভ্যারিয়েবল এ শুধু ক্যারেক্টার ই রাখতে পারব, কারণ সি প্রোগ্রামিং ল্যাঙ্গুয়েজ এ এই ডাটা টাইপ গুলো প্রিডিফাইন্ড।
কিন্তু স্ট্রাকচার ব্যবহার করে আমরা আমাদের নিজেদের ইচ্ছা মত ডাটা টাইপ তৈরী করতে পারব, সেখানে কি টাইপ ডাটা রাখব আমাদের ইচ্ছার উপর নির্ভর করবে। যখন আমরা সি দিয়ে কোন প্রোজেক্ট তৈরী করব, তখন এই স্ট্রাকচার এর প্রচুর ব্যবহার দেখব। এখনো এই স্ট্রাকচার কেন প্রয়োজন তা খুব বেশি বুঝে উঠবার কথা না, আশা করি, নিচের কাল্পনিক উদাহরণ টা দেখলে কিছুটা হলেও পরিষ্কার হবে।

হসপিটাল ম্যানেজমেন্ট সিস্টেম

মনে কর, আমার একটা হসপিটাল আছে। তার মানে আমার মূল কাজ হল রোগীদের নিয়ে। আমি আমার হসপিটাল এর জন্য একটা সফটয়্যার বানাতে চাই, যার মূল কাজ আমার হসপিটালে যত রোগী আসে তাদের তথ্য জমা রাখা। খুব সোজা ভাবে চিন্তা করলে, আমার প্রতিটা রোগীর জন্য কী কী তথ্য জমা রাখা প্রয়োজন? ধর, আমরা প্রতিটা রোগীর নাম, বয়স, ঠিকানা আর ব্লাড গ্রুপ জমা রাখব। এই চারটি জিনিস, কোনটি কী টাইপ ভ্যারিয়েবলে জমা রাখব তা নিশ্চয়ই আন্দাজ করতে পারছ। এখানে নাম, ঠিকানা, আর ব্লাড গ্রুপ স্ট্রিং টাইপ। আর, বয়স ইন্টিজার। প্রথমে মনে করি, আমার হসপিটালে মাত্র একজন ই রোগী। এই একজনের তথ্য ইনপুট নিয়ে তা আবার আউটপুট হিসেবে দেখানোর সি প্রোগ্রাম নিচে দিচ্ছি আমি।

***কোডিং রিজিয়ন গুলো Horizontally Scrollable, আর ইমেজ না, তাই কপি করতে পারবে ।

                #include<stdio.h>
                int main()
                {
                    int age;
                    char name[20], addr[30], bg[4];
                    printf("Enter patient's info (Age, Name, Address and then Blood Group): \n");
                    scanf("%d %s %s %s", &age, name, addr, bg);
                    printf("Displaying patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.", name, age, addr, bg); 
                    return 0;
                }
            

মনে করিয়ে দেই, স্ট্রিং হচ্ছে ক্যারেক্টার টাইপ অ্যারে। কিন্তু ইন্টিজার অ্যারের মতন এর ইনপুট নিতে লুপ ব্যবহার করা লাগে না। %s হল স্ট্রিং এর টাইপ স্পেসিফায়ার, যেমন %d ইন্টিজার এর। আর থার্ড ব্র্যাকেটে স্ট্রিং এর সাইজ উল্লেখ করে দিতে হয়, মানে এর মধ্যে সর্বোচ্চ অতগুলি ক্যারেক্টার তুমি জমা রাখতে পারবে। ঠিক অতগুলি না, তার থেকে এক কম আসলে। যেমন, bg এর সাইজ নিয়েছি আমি ৪। তার মানে এর মধ্যে আমি ০ থেকে শুরু করে ৩ টা পর্যন্ত যে কোন সংখ্যক ক্যারেক্টার রাখতে পারব। ধর কারো ব্লাড গ্রুপ AB+, তখন এই bg স্ট্রিং এ আর কোন খালি জায়গা থাকবে না। কারণ, AB+ এ ৩ ক্যারেক্টার আর, সবসময় স্ট্রিং এর শেষে নাল ক্যারেক্টার( ‘ \0 ’ ) একা একাই বসে যায়। এই মোট ৪ টি ঘর দখল হয়ে গেল। আচ্ছা, আবার আগের কথায় ফিরে আসি। আমার এই প্রথম রোগীর তথ্য জমা রাখতে আমাকে মোট ৪ টা ভ্যারিয়েবল লাগল। এখন, আমার রোগী যদি দুইজন হত, তাহলে প্রোগ্রামটা দেখতে হত নিচের মত।

                #include<stdio.h>
                int main()
                {
                    int age_1,age_2;
                    char name_1[20], addr_1[30], bg_1[4], name_2[20], addr_2[30], bg_2[4];
                
                    printf("Enter first patient's info (Age, Name, Address and then Blood Group): \n");
                    scanf("%d %s %s %s", &age_1, name_1, addr_1, bg_1);
                
                    printf("Enter second patient's info (Age, Name, Address and then Blood Group): \n");
                    scanf("%d %s %s %s", &age_2, name_2, addr_2, bg_2);
                
                    printf("Displaying first patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.\n", name_1, age_1, addr_1, bg_1); 
                    
                    printf("Displaying second patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.", name_2, age_2, addr_2, bg_2); 
                    return 0;
                }
            

এবার আমার মোট ৮ টা ভ্যারিয়েবল লাগল। হসপিটালের জীবনকালে যত রোগী আসবে যাবে, সবার তথ্যই তো আমার এভাবে জমা করে রাখতে হবে। খুব ছোট হসপিটাল হলেও তো এতে ৫০০ রোগী থাকবেই এর ডাটাবেজে। তখন আমার প্রোগ্রামে ভ্যারিয়েবল সংখ্যা হবে ৪*৫০০ = ২০০০ টা। ভেবে দেখ, কত বিশ্রী হবে প্রোগ্রাম টা দেখতে আর কত বড় হবে। কোথাও কোন ভুল করলে তা খুঁজতে সারাজীবন পার হয়ে যাবে। তার মানে এটা বোঝা গেল যে, যখন আমরা রিয়াল লাইফ অবজেক্ট (অবজেক্ট বলতে এই ক্ষেত্রে রোগী) নিয়ে কাজ করব তখন প্রিডিফাইন্ড ডাটা টাইপ ব্যবহার করা কোন ফিজিবল সল্যুশন না। এজন্য আমাদের স্ট্রাকচার ব্যবহার করতে হবে। দুইজন রোগী নিয়ে লিখা প্রোগ্রাম টাতে যদি আমরা স্ট্রাকচার ব্যবহার করি তাহলে এটা দেখতে হত নিচের মত। প্রথম দেখায় হয়ত অনেক কিছুই বুঝতে পারবে না, একটু পরই আমি সব কিছু বুঝিয়ে দেওয়ার চেষ্টা করছি। আর একটা জিনিস, যখনই আমি কোন কোড লিখব, তোমরা সেটা নিজেদের কোডব্লকস এ কপি করে, রান করে আউটপুট চেক করবে। এজন্য শুধু কোড দিলাম, আউটপুট বের করার দায়িত্ব তোমাদের।

                #include<stdio.h>

                struct patient
                {
                    int age;
                    char name[20];
                    char addr[30];
                    char bg[4];
                };
                
                
                int main()
                {
                    struct patient p1, p2;
                
                    printf("Enter first patient's info (Age, Name, Address and then Blood Group): \n");
                    scanf("%d %s %s %s", &p1.age, p1.name, p1.addr, p1.bg);
                
                    printf("Enter second patient's info (Age, Name, Address and then Blood Group): \n");
                    scanf("%d %s %s %s", &p2.age, p2.name, p2.addr, p2.bg);
                
                    printf("Displaying first patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.\n", p1.name, p1.age, p1.addr, p1.bg); 
                    
                    printf("Displaying second patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.", p2.name, p2.age, p2.addr, p2.bg); 
                    
                
                }
            

আর একটা ছোট্ট জিনিস মনে করিয়ে দেই, সেটা হল, ক্লাসে বলছিলাম, অন্য সব ভ্যারিয়েবলে যেমন ইনপুট নেবার সময় অ্যাাম্পারস্যান্ড(‘&’) সাইন দিতে হয়, স্ট্রিং এর ক্ষেত্রে সেটা দেওয়া লাগে না। আচ্ছা, উপরের কোড টা তে তোমাদের কাছে ৩ টা জিনিস নতুন মনে হতে পারে।

প্রথমটা...

                struct patient
                {
                    int age;
                    char name[20];
                    char addr[30];
                    char bg[4];
                };

দ্বিতীয়টা...

struct patient p1, p2;

এবং তৃতীয়টা...

p1.age, p1.name, p1.addr, p1.bg

একটা একটা করে সবগুলোর ব্যাখ্যা দেওয়া যাক এখন। প্রথম কোড সেগমেন্টে আমি একটা স্ট্রাকচার ডিক্লিয়ার করেছি। এভাবে চিন্তা করলে সুবিধা হবে, মনে কর আমরা নতুন একটা ডাটা টাইপ বানালাম, ঠিক ইন্টিজার, ডাবল এসব এর মতন। এখানে আমার ডাটা টাইপ টা হল, patient যার কিছু অ্যাট্রিবিউট বা বৈশিষ্ট্য আছে। একটা ডাবল টাইপ ভ্যারিয়েবল এর অ্যাট্রিবিউট বা বৈশিষ্ট্য গুলো কী? যে এটা একটা দশমিক সংখ্যার মান ধরে রাখে। ঠিক এই patient ভ্যারিয়েবলের ও কাজ একজন রোগীর সব তথ্য ধরে রাখা। এই ক্ষেত্রে রোগীর নাম, বয়স, ঠিকানা আর ব্লাড গ্রুপ। তার মানে একটা স্ট্রাকচার তৈরী হয় অন্য সব ডাটা টাইপ এর সমন্বয়ে। এখন থেকে আমরা কোডের মধ্যে patient টাইপ এর ভ্যারিয়েবল ডিক্লিয়ার করতে পারব, ঠিক ইন্টিজার, ডাবল এর মত। আর কীভাবে এটা করব সেটা আছে দ্বিতীয় কোড সেগমেন্টে। আমরা ইন্টিজার ডিক্লিয়ার করি এভাবে –

int a;

কিন্তু স্ট্রাকচার ডিক্লিয়ার করতে গেলে শুরুতে struct কথাটা থাকা লাগবে। তারপর লাগবে অবজেক্ট টাইপ, এই ক্ষেত্রে patient এবং সবশেষে ভ্যারিয়েবল নেইম - p1, p2 যা ইচ্ছা। হুবুহু ইন্টিজার, ডাবল বা অন্য টাইপ ভ্যারিয়েবল এর মতই। আমি হসপিটাল এর নতুন একটা patient ডিক্লিয়ার করতে গেলে নিচের লাইনটা লিখা লাগবে।

struct patient p1;

আমার নতুন ভ্যারিয়েবল এর নাম p1. কিন্তু এটা আমার patient এর name না। কারণ, আমি এখনো রোগীর নাম, বয়স, ঠিকানা কিছুই সেট করিনি। একটা ইন্টিজার এর ভ্যালু খুব সহজেই আমরা এক লাইনে নিচের মত করে সেট করতে পারি কিন্তু স্ট্রাকচার এর ক্ষেত্রে আমরা তা পারব না।

a = 10;

আমাদেরকে প্রতিটা অ্যাট্রিবিউট এর ভ্যালু আলাদা আলাদা ভাবে সেট করা লাগবে বা ইউজার থেকে ইনপুট নেওয়া লাগবে। হার্ডকোড করবা না ইউজার থেকে নিবে তোমার ইচ্ছা, কিন্তু যত গুলো অ্যাট্রিবিউট সব গুলো আলাদা ভাবে সেট করতে হবে। স্ট্রাকচারের ক্ষেত্রে ভ্যালু হার্ড কোড করা আসলে একটু ঝামেলার, বিশেষ করে যদি অ্যাট্রিবিউটের টাইপ স্ট্রিং হয়। অন্য সব ক্ষেত্রে equal sign দিয়ে ভ্যালু অ্যাাসাইন করতে পারবে। কিন্তু স্ট্রিং এর ক্ষেত্রে তোমাকে আলাদা একটা ফাংশন (কোন সংখ্যার বর্গমূল বের করতে আমরা যেমন ‘sqrt( )’ ফাংশন ব্যবহার করতাম এটাও তেমন) ব্যবহার করতে হবে ‘strcpy( )’ নামে। এই ফাংশনে দুইটি প্যারামিটার পাস করতে হবে (‘sqrt( )’ ফাংশনের প্যারামিটার ছিল একটা, সেটা হল যার বর্গমূল বের করব সেই সংখ্যা টা, তাই ৯ এর বর্গমূল লিখতে আমরা এভাবে লিখতাম – ‘sqrt( 9 )’ )। প্রথমটি হল ভ্যারিয়েবল এর নাম, আর তারপর ভ্যারিয়েবল এর ভ্যালু। যেমন, patient p1 এর নাম “Abir” সেট করতে আমরা লিখব strcpy(p1.name, "Abir"); । আর যেহেতু strcpy নামে নতুন ফাংশন ব্যবহার করলাম যার তাই এর সংশ্লিষ্ট হেডার ফাইল (string.h) প্রোগ্রামে সংযুক্ত করে দিতে হবে।
জিনিসটা বিদ্ঘুটে হলেও তোমরা এভাবে মনে রাখ যে, p1.name = “Abir”; এর পরিবর্তে আমাদের লিখতে হচ্ছে strcpy(p1.name, “Abir”); -একটু অদ্ভুত, কিন্তু কি করার।

                #include<stdio.h>
                #include<string.h>
                struct patient
                {
                    int age;
                    char name[20];
                    char addr[30];
                    char bg[4];
                };
                
                
                int main()
                {
                    struct patient p1;
                    
                    strcpy(p1.name, "Abir");
                    p1.age = 20;
                    strcpy(p1.addr, "Rajshahi");
                    strcpy(p1.bg, "B+");
                
                    printf("Displaying patient's info: \n");
                    printf("Patient's name is %s.\nHe is %d years old and lives in %s.\nHis blood group is %s.\n", p1.name, p1.age, p1.addr, p1.bg); 
                
                }

এবার আসি তৃতীয় কোড সেগমেন্টে। আমরা যে patient ভ্যারিয়েবল ডিক্লিয়ার করেছি তার নাম p1 যার চারটি অ্যাট্রিবিউট আছে। নাম, বয়স, ঠিকানা এবং ব্লাডগ্রুপ। তাই, এখন থেকে p1.name দিয়ে আমরা p1 এর নাম বুঝব, p1.age দিয়ে আমরা p1 এর বয়স বুঝব। এভাবে বাকী অ্যাট্রিবিউট গুলো ও অ্যাক্সেস করা যাবে আশা করি। এবার আমরা একটু হসপিটাল থেকে বের হয়ে অন্য কিছু ম্যানেজ করে আসি।

বার্গার

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

                struct burger
                {
                    char name[20];
                    char patty_type[10];
                    int no_of_patty;
                    int cheese;
                };

আমাদের স্ট্রাকচারটা দেখতে হবে তাহলে এমন। যেহেতু স্ট্রাকচার ডিফাইন করা শেষ, আমরা এখন আমাদের প্রোগ্রামে এই স্ট্রাকচার ব্যবহার করে ভ্যারিয়েবল ডিক্লিয়ার করতে পারব। এখানে patty_type নিয়েছি স্ট্রিং অর্থাৎ এই এর ভ্যালু যদি “chicken” হয় তাহলে বুঝব এটা চিকেন বার্গার, আর “beef” হলে বীফ বার্গার। no_of_patty দিয়ে বুঝাচ্ছি বার্গারে কয়টা প্যাটি থাকবে সেটা। এর ভ্যালু হবে ১ বা ১ এর থেকে বড় যেকোন সংখ্যা। cheese এর ভ্যালু হতে পারবে ০ বা এর থেকে বড় যেকোন কিছু। যদি ০ হয় তাহলে বুঝব চীজ নেই, আর এর থেকে বড় হলে তত লেয়ার চীজ আছে। এপর্যন্ত সবাই বুঝছি আশা করি। খিলগাঁও তে Mr. Burger নামে একটা রেস্টুরেন্ট আছে, ওদের বার্গার বেশ ভালো! তো ওদের একটা বার্গারের নাম, Mr. Whopper, যাতে আছে ৩ টা চিকেন প্যাটি সাথে ৩ লেয়ার চীজ। এখন এই বার্গারটা আমাদের কোডে ডিফাইন করে ফেলি!

                #include<stdio.h>
                #include<string.h>
                
                struct burger
                {
                    char name[20];
                    char patty_type[10];
                    int no_of_patty;
                    int cheese;
                };
                
                int main()
                {
                    struct burger burg1;
                    strcpy(burg1.name, "Mr. Whopper");
                    strcpy(burg1.patty_type, "chicken");
                    burg1.no_of_patty = 3;
                    burg1.cheese = 3;
                
                    printf("%s is a %s burger with %d patty and %d layer of cheese.",burg1.name, burg1.patty_type, burg1.no_of_patty, burg1.cheese);
                }

বার্গারের প্রাইস ক্যালকুলেশন

এই অংশটা বেশ সহজ। প্রতিটা অ্যাট্রিবিউটের আলাদা আলদা একটা প্রাইস ধরে আমরা পুরা বার্গারটার একটা প্রাইস হিসাব করব। মনে কর, প্রতিটা প্যাটি ৮০ টাকা আর চীজ ২০ টাকা। প্রতিটা বার্গারের দামের সাথে একটা করে বেস প্রাইস যুক্ত থাকে, যা সব বার্গারের ক্ষেত্রে একই। এর মধ্যে থাকে, বান এর মূল্য, দোকানের খরচ, কর্মচারীর বেতন ইত্যাদি। ধরি, Mr. Burger এর ক্ষেত্রে প্রতিটা বার্গারের বের প্রাইস ৫০ টাকা। তাহলে, আমাদের Mr. Whopper এর প্রাইস হচ্ছে – (৮০*৩)+(২০*৩)+৫০ = ৩৫০ টাকা। এর সি প্রোগ্রামটা হবে নিচের মত।

                    #include<stdio.h>
                    #include<string.h>
                    
                    struct burger
                    {
                        char name[20];
                        char patty_type[10];
                        int no_of_patty;
                        int cheese;
                    };
                    
                    int main()
                    {
                        struct burger burg1;
                        strcpy(burg1.name, "Mr. Whopper");
                        strcpy(burg1.patty_type, "chicken");
                        burg1.no_of_patty = 3;
                        burg1.cheese = 3;
                    
                        int base_price = 50;
                        int burg1_price = burg1.no_of_patty*80 + burg1.cheese*20 + base_price;
                    
                        printf("%s costs BDT %d.",burg1.name, burg1_price);
                    }

স্ট্রাকচারের অ্যারে

এর আগে অ্যারে টপিকটা পড়ে নিতে পারো।

আবার ফিরে আসি আমাদের হসপিটাল এর প্রব্লেমটাতে। এতক্ষণ আমরা নতুন patient ডিক্লিয়ার করছিলাম নিচের মত করে।

struct patient p1, p2, p3;

এই পদ্ধতি দুই তিনটা patient এর জন্য ফিজিবল, কিন্তু এর বেশি হলে খুবই শ্রমসাধ্য হয়ে যায় বিষয়টা। এক্ষেত্রে আমরা অ্যারের সাহায্য নিব। ইন্টিজারের যেভাবে অ্যারে ডিক্লিয়ার করতাম, ঠিক একইভাবে আমরা স্ট্রাকচারেরও অ্যারে ডিক্লিয়ার করতে পারব। নিচের কোডে আমি ৩ জন patient এর জন্য অ্যারে ডিফাইন করলাম।

struct patient p[3];

এভাবে এক লাইন কোডে আমি ৩ টা patient টাইপ ভ্যারিয়েবল ডিক্লিয়ার করলাম, যাদের নাম যথাক্রমে p[0], p[1], এবং p[2]। এদের প্রত্যেকের আগের অ্যাট্রিবিউট গুলো আছে, যেমন p[0].name, p[0].age ইত্যাদি। স্ট্রাকচার অ্যারের ইনপুট আউটপুট দেখিয়ে একটি সি প্রোগ্রাম নিচে দেওয়া হল।

                        #include<stdio.h>
                        #include<string.h>
                        struct patient
                        {
                            int age;
                            char name[20];
                            char addr[30];
                            char bg[4];
                        };
                        
                        
                        int main()
                        {
                            struct patient p[3];
                        
                            //Input can also be taken with scanf and loop
                            strcpy(p[0].name, "Abir");
                            p[0].age = 20;
                            strcpy(p[0].addr, "Rajshahi");
                            strcpy(p[0].bg, "B+");
                        
                            strcpy(p[1].name, "Srijon");
                            p[1].age = 20;
                            strcpy(p[1].addr, "Dhaka");
                            strcpy(p[1].bg, "A+");
                        
                            strcpy(p[2].name, "Rohan");
                            p[2].age = 20;
                            strcpy(p[2].addr, "Kushtia");
                            strcpy(p[2].bg, "O+");
                        
                        
                            int i;
                            for(i = 0; i<3; i++)
                            {
                                printf("Patient Info:\n");
                                printf("Name: %s\n",p[i].name);
                                printf("Age: %d\n",p[i].age);
                                printf("Address: %s\n",p[i].addr);
                                printf("Blood Group: %s\n",p[i].bg);
                                printf("------------\n");
                            }
                        
                        }

স্ট্রাকচার নিয়ে মোটামোটি এই পর্যন্তই। আশা করি, এখান থেকে একটা ধারণা পেয়েছ যে, কীভাবে প্রোজেক্ট এর কোডিং শুরু করতে হবে। হয়ত, সামনের কোন আর্টিকেলে আমি স্ট্রাকচার দিয়ে হসপিটাল ম্যানেজমেন্ট সিস্টেম এর ফুল কোড করে দেখাব। স্ট্রাকচার টপিকটা প্রোজেক্ট এবং পরীক্ষা দুইটার জন্যই খুব গুরুত্বপূর্ণ। কোথাও বুঝতে সমস্যা হলে, Google Classroom এ জানাবে।