Spese

Spese

Esercizio di apprendimento

Introduzione

In Go, le funzioni sono valori di prima classe. Questo significa che con le funzioni puoi fare le stesse cose che fai con tutti gli altri valori: assegnare funzioni a variabili, passarle come argomenti ad altre funzioni o persino restituire funzioni da altre funzioni.

Qui sotto creiamo due funzioni, engGreeting e espGreeting, e le assegniamo alla variabile 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!

I valori di funzione offrono l'opportunità di parametrizzare le funzioni non solo con i dati, ma anche con il comportamento. Nell'esempio seguente passiamo il comportamento alla funzione dialog tramite il parametro 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.

Il valore di una variabile non inizializzata di tipo funzione è nil. Quindi, chiamare un valore di funzione nil provoca un panic.

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

I valori di funzione si possono confrontare con nil. Questo può essere utile per evitare inutili panic nel programma.

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

I tipi di funzione

Usare i valori di funzione è possibile grazie ai tipi di funzione in Go. Un tipo di funzione indica l'insieme di tutte le funzioni con la stessa sequenza di tipi di parametro e la stessa sequenza di tipi di risultato. Si possono dichiarare tipi definiti dall'utente a partire dai tipi di funzione. Ad esempio, la funzione dialog degli esempi precedenti si può aggiornare come segue:

type greetingFunc func(string) string

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

Le funzioni anonime

Grazie al supporto per le funzioni di prima classe è disponibile un altro strumento potente: le funzioni anonime. Le funzioni anonime si definiscono nel punto in cui vengono usate, senza un nome dopo la parola chiave func. Queste funzioni hanno accesso alle variabili della funzione che le contiene.

Per esempio:

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())
}

Una chiamata a fib dichiara le variabili n1 e n2 e restituisce una funzione anonima che, a sua volta, cambia i valori di queste variabili ogni volta che viene chiamata. La ennesima chiamata della funzione anonima restituisce l'ennesimo numero della sequenza di Fibonacci, partendo da 0. La funzione anonima interna ha accesso alle variabili locali (n1 e n2) della funzione che la contiene, fib. È un ottimo modo per far mantenere lo stato ai valori di funzione tra una chiamata e l'altra. Diciamo che la funzione anonima è una closure delle variabili n1 e n2. Le closure sono ampiamente usate nella programmazione e potresti vedere altri linguaggi che le supportano.

Istruzioni

Bob è un consulente finanziario e aiuta le persone a gestire le loro spese. I clienti di Bob gli inviano i record di spesa da analizzare. Bob ha i record dei periodi precedenti, così può vedere i cambiamenti nelle spese. A Bob non piacciono i calendari e usa Bob epoch al posto delle date. Bob epoch è il numero di giorni trascorsi da quando il cliente di Bob ha iniziato la sua attività.

In questo esercizio, costruirai un programma per aiutare Bob a gestire e analizzare le spese dei suoi clienti.

Bob lavora con Record e DaysPeriod.

Un Record rappresenta un record di spesa che contiene il giorno in cui è stata effettuata la spesa, il denaro speso e la categoria della spesa.

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

Un DaysPeriod rappresenta un intervallo di giorni e include tutti i giorni dal giorno From fino al giorno To. Entrambi gli estremi sono inclusi nell'intervallo.

// 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. Implementa un filtro generale per i record

Bob ha a che fare con molti record ogni giorno, ma non tutti sono interessanti a seconda dell'analisi che sta facendo. Aiutiamo Bob ad applicare un filtro di base ai record.

Implementa la funzione generica Filter per filtrare i record in base a un criterio fornito da una funzione. Questa funzione di filtro accetta una collezione di record e una funzione predicato e restituisce solo i record della collezione che soddisfano il predicato.

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. Filtra i record in un periodo di tempo

Bob ha spesso bisogno di filtrare i record che si trovano in un certo periodo di tempo.

Implementa la funzione ByDaysPeriod, che aiuterà Bob a creare filtri di questo tipo. Questa funzione accetta un DaysPeriod e restituisce una funzione che prende un record e indica se il record rientra nel periodo di tempo specificato dal DaysPeriod passato come argomento.

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. Filtra i record per categoria

Oltre a filtrare i record per periodo di tempo, Bob ha bisogno anche di filtrarli per categoria.

Implementa la funzione ByCategory, che aiuterà Bob a creare filtri di questo tipo. Questa funzione accetta una categoria e restituisce una funzione che prende un record e indica se la categoria di quel record è uguale alla categoria passata come argomento.

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. Calcola l'importo totale delle spese in un periodo

Bob vuole anche conoscere l'importo totale delle spese in un periodo di tempo.

Implementa la funzione TotalByPeriod per restituire la somma delle spese nel periodo di giorni.

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. Calcola le spese totali per i record di una categoria in un periodo

Per i report più complessi che Bob prepara per i suoi clienti, Bob ha bisogno di filtrare i record per categoria e periodo di tempo allo stesso tempo. In altre parole, Bob vuole conoscere le spese totali per i record di una categoria in un dato periodo di tempo.

Implementa la funzione CategoryExpenses, che restituisce l'importo totale delle spese di una categoria in un dato periodo di giorni. La funzione deve inoltre distinguere il caso in cui la categoria indicata non è presente nei record di spesa e il caso in cui non ci sono spese di quella categoria nel periodo indicato. Quando la categoria non è la categoria di nessuno dei record (indipendentemente dal periodo di tempo), la funzione deve restituire un errore.

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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Go Exercism

Vuoi iniziare Spese?

Iscriviti a Exercism per imparare e padroneggiare Go con 34 concetti165 esercizi e il mentoring di persone reali, tutto gratis.