Треки
/
Go
Go
/
Вправи
/
Витрати
Витрати

Витрати

Навчальна вправа

Вступ

У 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!

Значення функцій дають змогу параметризувати функції не лише даними, а й поведінкою. У наступному прикладі ми передаємо поведінку функції dialog через параметр greetingFunc:

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-те число послідовності Фібоначчі, починаючи з 0. Анонімна внутрішня функція має доступ до локальних змінних (n1 і n2) зовнішньої функції fib. Це чудовий спосіб дати значенням функцій змогу зберігати стан між викликами. Кажемо, що анонімна функція - це замикання змінних n1 і n2. Замикання широко використовують у програмуванні, і їхню підтримку можна побачити і в інших мовах.

Вказівки

Боб працює фінансовим радником і допомагає людям керувати своїми витратами. Клієнти Боба надсилають йому записи про витрати для аналізу. У Боба є записи за попередні періоди, тож він бачить, як змінюються витрати. Боб не любить календарі й використовує епоху Боба замість дат. Епоха Боба - це кількість днів, що минули від початку активності клієнта Боба.

У цій вправі ми створимо програму, яка допоможе Бобові керувати витратами його клієнтів і аналізувати їх.

Боб працює з 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. Реалізуйте загальний фільтр записів

Щодня Боб має справу з великою кількістю записів, але не всі з них цікаві залежно від того, який аналіз робить Боб. Допоможімо Бобові виконати базове фільтрування записів.

Реалізуйте загальну функцію 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. Відфільтруйте записи в межах періоду часу

Бобові часто потрібно фільтрувати записи, що належать до заданого періоду часу.

Реалізуйте функцію ByDaysPeriod, яка допоможе Бобові створювати такі фільтри. Ця функція приймає 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. Відфільтруйте записи за категорією

Окрім фільтрування записів за періодом часу, Бобові також потрібно фільтрувати записи за категорією.

Реалізуйте функцію ByCategory, яка допоможе Бобові створювати такі фільтри. Ця функція приймає категорію і повертає функцію, яка бере запис і повідомляє, чи збігається категорія цього запису з категорією, переданою як аргумент.

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. Обчисліть загальну суму витрат за період

Боб також хоче знати загальну суму витрат за період часу.

Реалізуйте функцію 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. Обчисліть загальну суму витрат для записів певної категорії за період

Для найскладніших звітів, які Боб робить для своїх клієнтів, йому потрібно фільтрувати записи одночасно за категорією та періодом часу. Тобто Боб хоче знати загальну суму витрат для записів певної категорії за заданий період часу.

Реалізуйте функцію 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 концепції166 вправ та справжнє наставництво від людей, і все це безкоштовно.