খরচ

খরচ

লার্নিং অনুশীলনী

ভূমিকা

Go-তে ফাংশন প্রথম শ্রেণির মান। এর মানে হলো, অন্য সব মানের সাথে যা যা করা যায়, ফাংশনের সাথেও ঠিক সেগুলোই করা যায়: ফাংশনকে ভ্যারিয়েবলে অ্যাসাইন করা, সেগুলোকে অন্য ফাংশনের আর্গুমেন্ট হিসেবে পাঠানো, এমনকি অন্য ফাংশন থেকে ফাংশন রিটার্ন করা।

নিচে আমরা দুটি ফাংশন তৈরি করছি, engGreeting আর espGreeting, এবং সেগুলো greeting ভ্যারিয়েবলে অ্যাসাইন করছি:

import "fmt"

func engGreeting(name string) string {
	return fmt.Sprintf("Hello %s, nice to meet you!", name)
}

func espGreeting(name string) string {
	return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}

greeting := engGreeting			// greeting is a variable of type func(string) string
fmt.Println(greeting("Alice"))	// Hello Alice, nice to meet you!

greeting = espGreeting
fmt.Println(greeting("Alice")) 	// ¡Hola Alice, mucho gusto!

ফাংশন মান শুধু ডেটা দিয়েই নয়, আচরণ দিয়েও ফাংশন প্যারামিটারাইজ করার সুযোগ দেয়। পরের উদাহরণে আমরা greetingFunc প্যারামিটারটির মাধ্যমে dialog ফাংশনে আচরণ পাঠাচ্ছি:

func dialog(name string, greetingFunc func(string) string) {
	fmt.Println(greetingFunc(name))
	fmt.Println("I'm a dialog bot.")
}

func espGreeting(name string) string {
	return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}

greeting := espGreeting
dialog("Alice", greeting)
// =>
// ¡Hola Alice, mucho gusto!
// I'm a dialog bot.

ইনিশিয়ালাইজ না করা ফাংশন টাইপের ভ্যারিয়েবলের মান nil। তাই nil ফাংশন মান কল করলে প্যানিক হয়।

var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function

ফাংশন মান nil-এর সাথে তুলনা করা যায়। অপ্রয়োজনীয় প্রোগ্রাম প্যানিক এড়াতে এটি কাজে লাগতে পারে।

var dutchGreeting func(string) string
if dutchGreeting != nil {
	dutchGreeting("Alice") // safe to call dutchGreeting
}

ফাংশন টাইপ

Go-তে ফাংশন টাইপ থাকার কারণেই ফাংশন মান ব্যবহার করা সম্ভব। একটি ফাংশন টাইপ বলতে এমন সব ফাংশনের সেটকে বোঝায়, যাদের প্যারামিটার টাইপের ক্রম ও রেজাল্ট টাইপের ক্রম একই। ফাংশন টাইপের উপরে ভিত্তি করে ইউজার-ডিফাইন্ড টাইপ ডিক্লেয়ার করা যায়। যেমন, আগের উদাহরণগুলোর dialog ফাংশনটি এভাবে আপডেট করা যায়:

type greetingFunc func(string) string

func dialog(name string, f greetingFunc) {
	fmt.Println(f(name))
	fmt.Println("I'm a dialog bot.")
}

অ্যানোনিমাস ফাংশন

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

যেমন:

func fib() func() int {
	var n1, n2 int

	return func() int {
		if n1 == 0 && n2 == 0 {
			n1 = 1
		} else {
			n1, n2 = n2, n1 + n2
		}
		return n2
	}
}

next := fib()
for i := range N {
  fmt.Printf("F%d\t= %4d\n", i, next())
}

fib কল করলে n1 আর n2 ভ্যারিয়েবল ডিক্লেয়ার হয় এবং একটি অ্যানোনিমাস ফাংশন রিটার্ন হয়, যা আবার ফাংশনটি যতবার কল করা হয়, ততবারই এই ভ্যারিয়েবলগুলোর মান বদলে দেয়। অ্যানোনিমাস ফাংশনটি N-তম বার কল করলে ০ থেকে শুরু হওয়া ফিবোনাচি অনুক্রমের N-তম সংখ্যাটি রিটার্ন করে। ভেতরের অ্যানোনিমাস ফাংশনটি বাইরের ফাংশন fib-এর লোকাল ভ্যারিয়েবল (n1 আর n2) ব্যবহার করতে পারে। কল থেকে কলের মধ্যে ফাংশন মান দিয়ে স্টেট ধরে রাখার এটি একটি দারুণ উপায়। আমরা বলি, অ্যানোনিমাস ফাংশনটি n1 আর n2 ভ্যারিয়েবলের একটি ক্লোজার। প্রোগ্রামিংয়ে ক্লোজার ব্যাপকভাবে ব্যবহৃত হয়, এবং অন্য ভাষাগুলোতেও এটি সমর্থিত হতে দেখতে পারেন।

নির্দেশনা

Bob একজন আর্থিক উপদেষ্টা এবং মানুষকে তাদের খরচ সামলাতে সাহায্য করেন। Bob-এর ক্লায়েন্টরা বিশ্লেষণের জন্য তাঁকে খরচের রেকর্ড পাঠান। Bob-এর কাছে আগের সময়কালগুলোর রেকর্ড থাকে, যাতে তিনি খরচের পরিবর্তন দেখতে পারেন। Bob ক্যালেন্ডার পছন্দ করেন না এবং তারিখের বদলে Bob epoch ব্যবহার করেন। Bob epoch হলো Bob-এর ক্লায়েন্ট তাদের কাজ শুরু করার পর যত দিন পেরিয়েছে সেই সংখ্যা।

এই অনুশীলনীতে আপনি এমন একটি প্রোগ্রাম তৈরি করবেন, যা Bob-কে তাঁর ক্লায়েন্টদের খরচ সামলাতে ও বিশ্লেষণ করতে সাহায্য করবে।

Bob Record ও DaysPeriod নিয়ে কাজ করেন।

একটি Record হলো একটি খরচের রেকর্ড, যাতে থাকে খরচটি যে দিনে করা হয়েছে সেই দিন, খরচ করা টাকার পরিমাণ এবং খরচের ক্যাটাগরি।

// Record represents an expense record.
type Record struct {
	Day         int
	Amount      float64
	Category    string
}

একটি DaysPeriod হলো দিনের একটি পরিসর, যাতে From দিন থেকে To দিন পর্যন্ত সব দিন ধরা হয়। পরিসরে দুই প্রান্তই ধরা হয়।

// DaysPeriod represents a period of days.
type DaysPeriod struct {
	From int
	To   int
}

p := DaysPeriod{From: 1, To: 31}
// p represents all days from the day 1 to the day 31:
//  - days 1, 20, 16 and 31 are examples of days that are included
//    in the range of time specified by p
//  - days 50 and 40 are examples of days that are not included
//    in the range of time specified by p

1. সাধারণ রেকর্ড ফিল্টার তৈরি করুন

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

এমন একটি ফাংশন দিয়ে দেওয়া মানদণ্ড অনুযায়ী রেকর্ড ফিল্টার করার জন্য জেনেরিক Filter ফাংশনটি তৈরি করুন। এই ফিল্টার ফাংশনটি রেকর্ডের একটি সংগ্রহ ও একটি প্রেডিকেট ফাংশন নেয় এবং কেবল সেই রেকর্ডগুলো ফেরত দেয়, যেগুলো প্রেডিকেটটি সন্তুষ্ট করে।

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
}

// Day1Records only returns true for records that are from day 1
func Day1Records(r Record) bool {
  return r.Day == 1
}

Filter(records, Day1Records)
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"}
// ]

2. একটি সময়কালের মধ্যে রেকর্ড ফিল্টার করুন

Bob-কে প্রায়ই এমন রেকর্ড ফিল্টার করতে হয়, যেগুলো একটি নির্দিষ্ট সময়কালের মধ্যে আছে।

ByDaysPeriod ফাংশনটি তৈরি করুন, যা Bob-কে এমন ফিল্টার বানাতে সাহায্য করবে। এই ফাংশনটি একটি DaysPeriod নেয় এবং এমন একটি ফাংশন ফেরত দেয়, যা একটি রেকর্ড নিয়ে বলে দেয় রেকর্ডটি আর্গুমেন্ট হিসেবে দেওয়া DaysPeriod-এর নির্দিষ্ট সময়কালের মধ্যে আছে কি না।

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 26, Amount: 300, Category: "university"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

period := DaysPeriod{From: 1, To: 15}

Filter(records, ByDaysPeriod(period))
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"},
//   {Day: 11, Amount: 300, Category: "utility-bills"},
//   {Day: 12, Amount: 28, Category: "groceries"},
// ]

3. ক্যাটাগরি অনুযায়ী রেকর্ড ফিল্টার করুন

সময়কাল অনুযায়ী রেকর্ড ফিল্টার করার পাশাপাশি Bob-কে রেকর্ডের ক্যাটাগরি অনুযায়ীও ফিল্টার করতে হয়।

ByCategory ফাংশনটি তৈরি করুন, যা Bob-কে এমন ফিল্টার বানাতে সাহায্য করবে। এই ফাংশনটি একটি ক্যাটাগরি নেয় এবং এমন একটি ফাংশন ফেরত দেয়, যা একটি রেকর্ড নিয়ে বলে দেয় রেকর্ডটির ক্যাটাগরি আর্গুমেন্ট হিসেবে দেওয়া ক্যাটাগরির সঙ্গে একই কি না।

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

Filter(records, ByCategory("groceries"))
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"},
//   {Day: 12, Amount: 28, Category: "groceries"},
// ]

4. একটি সময়কালে মোট খরচের পরিমাণ হিসাব করুন

Bob একটি সময়কালের মধ্যে মোট খরচের পরিমাণও জানতে চান।

দিনের সময়কালে খরচের যোগফল ফেরত দিতে TotalByPeriod ফাংশনটি তৈরি করুন।

records := []Record{
  {Day: 15, Amount: 16, Category: "entertainment"},
  {Day: 32, Amount: 20, Category: "groceries"},
  {Day: 40, Amount: 30, Category: "entertainment"}
}

p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}

TotalByPeriod(records, p1)
// => 16

TotalByPeriod(records, p2)
// => 50

5. একটি সময়কালে একটি ক্যাটাগরির রেকর্ডের মোট খরচ হিসাব করুন

Bob তাঁর ক্লায়েন্টদের জন্য যে সবচেয়ে জটিল রিপোর্টগুলো তৈরি করেন, তার জন্য তাঁকে একইসঙ্গে ক্যাটাগরি ও সময়কাল অনুযায়ী রেকর্ড ফিল্টার করতে হয়। অর্থাৎ Bob একটি নির্দিষ্ট সময়কালে একটি ক্যাটাগরির রেকর্ডের মোট খরচ জানতে চান।

CategoryExpenses ফাংশনটি তৈরি করুন, যা একটি নির্দিষ্ট দিন-সময়কালে একটি ক্যাটাগরির মোট খরচের পরিমাণ ফেরত দেয়। ফাংশনটির উচিত দেওয়া ক্যাটাগরিটি খরচের রেকর্ডে না থাকার ক্ষেত্রটি এবং দেওয়া সময়কালে ওই ক্যাটাগরির কোনো খরচ না থাকার ক্ষেত্রটি আলাদা করে চেনা। যখন ক্যাটাগরিটি কোনো রেকর্ডেরই ক্যাটাগরি নয় (সময়কাল যাই হোক), তখন ফাংশনটির একটি এরর ফেরত দেওয়া উচিত।

p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 26, Amount: 300, Category: "university"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

CategoryExpenses(records, p1, "entertainment")
// => 0, error(unknown category entertainment)

CategoryExpenses(records, p1, "rent")
// => 1300, nil

CategoryExpenses(records, p2, "rent")
// => 0, nil
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
Go Exercism

খরচ শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, Go ট্র্যাকের 34টি কনসেপ্ট165টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।