Parcours
/
Go
Go
/
Exercices
/
Maître de la lasagne
Maître de la lasagne

Maître de la lasagne

Exercice d'apprentissage

Introduction

Une fonction permet de regrouper du code dans une unité réutilisable. Elle se compose du mot-clé func, du nom de la fonction, et d'une liste, séparée par des virgules, de zéro ou plusieurs paramètres et types entre parenthèses.

Paramètres de fonction

Tous les paramètres doivent être typés explicitement ; il n'y a pas d'inférence de type pour les paramètres. Les paramètres n'ont pas de valeur par défaut, donc tous les paramètres d'une fonction sont obligatoires.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

Les paramètres de même type peuvent être déclarés ensemble, suivis d'une seule déclaration de type.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

Paramètres vs arguments

Voyons rapidement deux termes que l'on confond souvent : parameters et arguments. Les paramètres de fonction sont les noms définis dans la signature de la fonction, comme greeting et name dans la fonction PrintGreetingName ci-dessus. Les arguments de fonction sont les valeurs concrètes passées aux paramètres de la fonction quand on appelle la fonction. Par exemple, dans l'exemple ci-dessous, "Hello" et "Katrina" sont les arguments passés aux paramètres greeting et name :

PrintGreetingName("Hello", "Katrina")

Valeurs de retour

Les paramètres de la fonction sont suivis de zéro ou plusieurs valeurs de retour, qui doivent elles aussi être typées explicitement. Une valeur de retour unique s'écrit sans parenthèses, plusieurs valeurs de retour sont entourées de parenthèses. Les valeurs sont renvoyées au code appelant depuis les fonctions à l'aide du mot-clé return. Une fonction peut contenir plusieurs instructions return. L'exécution de la fonction se termine dès qu'elle rencontre l'une de ces instructions return. Si une fonction doit renvoyer plusieurs valeurs, celles-ci sont séparées par des virgules.

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

Appeler des fonctions

Pour appeler une fonction, on précise le nom de la fonction et on passe des arguments pour chacun de ses paramètres entre parenthèses.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

Valeurs de retour nommées et retour « nu »

Comme les paramètres, les valeurs de retour peuvent être nommées, de façon facultative. Si l'on utilise des valeurs de retour nommées, une instruction return sans arguments renvoie ces valeurs. C'est ce que l'on appelle un retour « nu ».

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

Passage par valeur et passage par référence

Il est aussi important de clarifier les notions de passage par valeur et de passage par référence.

Commençons par clarifier le passage par valeur. Dans l'exemple ci-dessous, quand on passe la variable val à la fonction MultiplyByTwo, c'est une copie de val que l'on passe. Pour cette raison, newVal a la valeur mise à jour 4, mais la variable d'origine val vaut toujours 2. En coulisses, Go fait essentiellement une copie de la valeur d'origine, de sorte que seule cette copie, alias v, est modifiée par la fonction.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

À proprement parler, en Go, tous les arguments sont passés par valeur, c'est-à-dire qu'une copie est faite de la valeur ou des données fournies à la fonction. Mais si tu ne veux pas faire de copie des données passées à une fonction et que tu veux modifier ces données dans la fonction, tu dois alors utiliser des pointers comme arguments, autrement dit le passage par référence.

Pointeurs

On utilise un pointer pour réaliser le passage par référence. En passant des arguments pointer à une fonction, on peut modifier les données sous-jacentes transmises à la fonction au lieu de n'opérer que sur une copie de ces données.

Pour l'instant, il suffit de savoir que les types pointeur se reconnaissent au * placé devant le type dans la signature de la fonction.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

Si la notion de pointer te paraît déroutante, pas de panique. Une section dédiée plus loin dans le programme t'aidera à maîtriser les pointeurs.

Exceptions

Note que les slices et les maps font exception à la règle mentionnée ci-dessus. Quand on passe un slice ou une map comme arguments à une fonction, ils sont traités comme des types pointeur, même s'il n'y a pas de * explicite dans le type. Cela signifie que si l'on passe un slice ou une map à une fonction et que l'on modifie leurs données sous-jacentes, les changements se répercutent sur le slice ou la map d'origine.

Instructions

Dans cet exercice, tu vas écrire un peu plus de code pour préparer et cuisiner ta fameuse lasagne, tirée de ton livre de recettes préféré.

Tu as quatre tâches à accomplir. La première concerne la cuisson elle-même, les trois autres portent sur la préparation parfaite.

1. Estime le temps de préparation

Pour la prochaine lasagne que tu vas préparer, tu veux être sûr de réserver assez de temps afin de profiter de la cuisson. Tu as déjà prévu les couches de ta lasagne. Maintenant, tu veux estimer la durée de la préparation en fonction de ces couches.

Implémente une fonction PreparationTime qui prend en paramètres une slice de couches sous forme d'un []string et le temps de préparation moyen par couche, en minutes, sous forme d'un int. La fonction doit renvoyer une estimation du temps de préparation total, sous forme d'un int, en fonction du nombre de couches. Go ne gère pas de valeurs par défaut pour les fonctions. Si le temps de préparation moyen vaut 0 (la valeur initiale par défaut d'un int), c'est la valeur par défaut de 2 qu'il faut utiliser.

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. Calcule les quantités de nouilles et de sauce nécessaires

En plus de réserver le temps nécessaire, tu veux t'assurer d'avoir assez de sauce et de nouilles pour cuisiner la lasagne de tes rêves. Pour chaque couche de nouilles de ta lasagne, il te faudra 50 grammes de nouilles. Pour chaque couche de sauce de ta lasagne, il te faudra 0,2 litre de sauce.

Définis la fonction Quantities qui prend en paramètre une slice de couches sous forme d'un []string. La fonction détermine ensuite la quantité de nouilles et de sauce nécessaire pour préparer ton plat. Le résultat doit être renvoyé sous la forme de deux valeurs : noodles en int et sauce en float64.

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. Ajoute l'ingrédient secret

Il y a quelque temps, tu as rendu visite à un ami et tu as mangé de la lasagne chez lui. Elle était délicieuse et avait un petit quelque chose de spécial. Ton ami t'a envoyé la liste des ingrédients et t'a dit que le dernier élément de la liste était l'« ingrédient secret » qui rendait ce plat si spécial. Maintenant, tu veux ajouter cet ingrédient secret à ta recette, toi aussi.

Écris une fonction AddSecretIngredient qui prend en paramètres deux slices d'ingrédients de type []string. Le premier paramètre est la liste que ton ami t'a envoyée, le second est la liste d'ingrédients de ta propre recette. Le dernier élément de ta liste d'ingrédients est toujours "?". La fonction doit le remplacer par le dernier élément de la liste de ton ami. Remarque : AddSecretIngredient ne renvoie rien, tu dois modifier directement la liste de tes ingrédients. La liste contenant les ingrédients de ton ami ne doit pas être modifiée. De plus, comme les slice sont passées aux fonctions sous forme de pointeurs, les modifications apportées aux deux arguments de type []string passés à AddSecretIngredient seront appliquées directement.

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. Adapte la recette

Les quantités indiquées dans ton livre de recettes ne permettent de préparer qu'une lasagne pour deux portions. Comme tu veux cuisiner pour plus de personnes la prochaine fois, tu veux calculer les quantités pour différents nombres de portions.

Implémente une fonction ScaleRecipe qui prend deux paramètres.

  • Une slice de float64 correspondant aux quantités nécessaires pour 2 portions.
  • Le nombre de portions que tu veux préparer.

La fonction doit renvoyer une slice de float64 correspondant aux quantités nécessaires pour le nombre de portions souhaité. Tu veux toutefois conserver la recette d'origine. Cela signifie que l'argument quantities ne doit pas être modifié dans cette fonction.

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Go Exercism

Prêt à commencer Maître de la lasagne ?

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