Parcours
/
Go
Go
/
Exercices
/
Dépenses
Dépenses

Dépenses

Exercice d'apprentissage

Introduction

En Go, les fonctions sont des valeurs de première classe. Cela signifie qu'on peut faire avec les fonctions la même chose qu'avec toutes les autres valeurs : les affecter à des variables, les passer en arguments à d'autres fonctions, ou même renvoyer des fonctions depuis d'autres fonctions.

Ci-dessous, on crée deux fonctions, engGreeting et espGreeting, et on les affecte à la variable 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!

Les valeurs de fonction offrent la possibilité de paramétrer des fonctions non seulement avec des données, mais aussi avec du comportement. Dans l'exemple suivant, on passe du comportement à la fonction dialog via le paramètre 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.

La valeur d'une variable non initialisée de type fonction est nil. Par conséquent, appeler une valeur de fonction nil provoque un panic.

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

Les valeurs de fonction peuvent être comparées à nil. Cela peut être utile pour éviter des panics inutiles dans le programme.

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

Types de fonction

Utiliser des valeurs de fonction est possible grâce aux types de fonction de Go. Un type de fonction désigne l'ensemble de toutes les fonctions ayant la même séquence de types de paramètres et la même séquence de types de résultats. On peut déclarer des types définis par l'utilisateur à partir de types de fonction. Par exemple, la fonction dialog des exemples précédents peut être mise à jour comme suit :

type greetingFunc func(string) string

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

Fonctions anonymes

La prise en charge des fonctions de première classe offre un autre outil puissant : les fonctions anonymes. Les fonctions anonymes sont définies au moment de leur utilisation, sans nom après le mot-clé func. De telles fonctions ont accès aux variables de la fonction englobante.

Par exemple :

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

Un appel à fib déclare les variables n1 et n2 et renvoie une fonction anonyme qui, à son tour, modifie les valeurs de ces variables à chaque appel de la fonction. Le N-ième appel de la fonction anonyme renvoie le N-ième nombre de la suite de Fibonacci en partant de 0. La fonction interne anonyme a accès aux variables locales (n1 et n2) de la fonction englobante fib. C'est un excellent moyen de faire conserver un état aux valeurs de fonction entre les appels. On dit que la fonction anonyme est une fermeture des variables n1 et n2. Les fermetures sont largement utilisées en programmation et tu pourrais voir d'autres langages les prendre en charge.

Instructions

Bob est conseiller financier et aide les gens à gérer leurs dépenses. Les clients de Bob lui envoient des relevés de dépenses à analyser. Bob dispose des relevés des périodes précédentes, ce qui lui permet de voir l'évolution des dépenses. Bob n'aime pas les calendriers et utilise l'Bob epoch au lieu de dates. L'Bob epoch est le nombre de jours écoulés depuis que le client de Bob a commencé son activité.

Dans cet exercice, tu vas écrire un programme pour aider Bob à gérer et analyser les dépenses de ses clients.

Bob travaille avec des Record et des DaysPeriod.

Un Record représente un relevé de dépenses qui contient le jour où la dépense a été faite, la somme dépensée et la catégorie de la dépense.

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

Un DaysPeriod représente une plage de jours et inclut tous les jours depuis le jour From jusqu'au jour To. Les deux bornes sont incluses dans la plage.

// 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. Implémente un filtre général de relevés

Bob traite beaucoup de relevés chaque jour, mais tous ne sont pas intéressants selon l'analyse qu'il fait. Aidons Bob à effectuer un filtrage simple des relevés.

Implémente la fonction générique Filter pour filtrer les relevés selon un critère donné par une fonction. Cette fonction de filtrage prend une collection de relevés et une fonction prédicat, et renvoie uniquement les relevés de la collection qui satisfont le prédicat.

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. Filtre les relevés sur une période de temps

Bob a souvent besoin de filtrer les relevés qui se trouvent dans une période donnée.

Implémente la fonction ByDaysPeriod qui aidera Bob à créer de tels filtres. Cette fonction prend un DaysPeriod et renvoie une fonction qui prend un relevé et indique si ce relevé se trouve dans la période spécifiée par le DaysPeriod donné en argument.

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. Filtre les relevés par catégorie

En plus de filtrer les relevés par période, Bob a aussi besoin de les filtrer par catégorie.

Implémente la fonction ByCategory qui aidera Bob à créer de tels filtres. Cette fonction prend une catégorie et renvoie une fonction qui prend un relevé et indique si la catégorie de ce relevé est la même que celle donnée en argument.

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. Calcule le montant total des dépenses sur une période

Bob veut aussi connaître le montant total des dépenses sur une période.

Implémente la fonction TotalByPeriod pour renvoyer la somme des dépenses de la période de jours.

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. Calcule le total des dépenses des relevés d'une catégorie sur une période

Pour les rapports les plus complexes qu'il remet à ses clients, Bob doit filtrer les relevés à la fois par catégorie et par période. Autrement dit, Bob veut connaître le total des dépenses des relevés d'une catégorie sur une période donnée.

Implémente la fonction CategoryExpenses qui renvoie le montant total des dépenses d'une catégorie sur une période de jours donnée. La fonction doit aussi distinguer le cas où la catégorie donnée n'apparaît dans aucun relevé de dépenses de celui où il n'y a pas de dépenses de cette catégorie sur la période fournie. Lorsque la catégorie n'est celle d'aucun des relevés (quelle que soit la période), la fonction doit renvoyer une erreur.

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
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Go Exercism

Prêt à commencer Dépenses ?

Inscris-toi sur Exercism pour apprendre et maîtriser Go avec 34 concepts166 exercices, et un vrai mentorat humain, le tout gratuitement.