Au programme, section [E.2] : Langages et
programmation
Des exemples relevant de domaines variés sont à privilégier. 1
On peut écrire la somme des n premiers entiers de la forme :
Exemple : pour n=3, on a .
Comment créer une fonction somme(n) qui implémente cette
formule ? Il faut programmer la répétition (les
)
On peut utiliser une boucle for pour parcourir tous les
entiers i entre 0 et n. Il faut
donc aussi une variable intermédiaire t pour accumuler la
somme des entiers, et retourner la valeur de cette variable à la
fin.
def somme(n: int) -> int:
assert n >= 0, "n doit être positif"
t = 0
for i in range(0,n+1):
t += i
return t(le n+1 dans range est nécessaire car la
borne supérieure n’est pas incluse)
La formulation mathématique n’indique pas qu’il faut créer une
variable t intermédiaire. On peut voir ça de 2 façons :
Définition d’une fonction mathématique
somme(n) qui, pour tout
renvoie la somme des
premiers entiers :
Cette définition est récursive : c’est une définition de fonction qui fait appel à elle-même.
Exemple de calculs :
Un des avantages est que l’implémentation en python est très proche de la définition de la fonction :
def somme(n: int) -> int:
assert n >= 0, "n doit être positif"
if n == 0:
return 0
return n + somme(n-1)Exécution du programme pas à pas pour somme(3)
Pour calculer somme(3), il faut avoir le résultat de somme(2), qui a besoin du résultat de somme(1), qui a besoin du résultat de somme(0). Le calcul est fait en quelque sorte “à l’envers” : l’arbre d’appel est le suivant :
somme(3) =
return 3 + somme(2)
|
return 2 + somme(1)
|
return 1 + somme(0)
|
return 0
Utilisation de https://pythontutor.com/ pour visualiser l’exécution du code, en sélectionnant “show all frames (Python)” dans les options en dessous de “Visualize Execution”
Une formulation récursive d’une fonction est toujours constituée de plusieurs cas :
Exercice : donner une définition récursive de ( fois, si , rappel ), puis écrire son implémentation en python.
Plusieurs cas de base peuvent être donnés :
Cela peut être nécessaire, ou inutile (comme pour l’exemple ci-dessus)
Plusieurs cas récursifs peuvent être donnés. Exemple, pour la suite appelée suite de Syracuse : voir wikipedia fr.wikipedia.org/wiki/Conjecture_de_Syracuse et/ou cette vidéo de Veritasium.
On considère la suite définie par un entier (le point de départ) et (le nombre d’itérations) :
En mode TLDR pour la conjecture de Syracuse: on suppose qu’on part d’un entier ; on calcule les termes successifs () de la suite de Syracuse définie ci-dessous, et on observe que, quelle que soit la valeur de , on finit toujours par atteindre le nombre un pour lequel la suite atteint 1. Ce nombre est appelé le temps de vol de . %
Exercice : implémenter def sy(n,k),
vérifier son fonctionnement sur quelques valeurs. Puis écrire un
programme (non récursif) temps_de_vol(k) appelant
sy(n,k) qui détermine le temps de vol pour le nombre
k. Enfin, utilisez temps_de_vol sur les 100
premiers entiers pour vérifier la conjecture de Syracuse pour tous les
.
Les expressions peuvent dépendre de plusieurs appels à la fonction en cours de définition. Exemple, la suite de Fibonnacci : https://fr.wikipedia.org/wiki/Suite_de_Fibonacci
Exercice : écrire l’arbre d’appel de
;
que peut-on dire du nombre d’opérations nécessaires pour calculer fib(n)
? Implémenter fib(n) en python et chronométrer son
exécution pour différentes valeurs de n.
Cet exemple “explosif” montre qu’on peut rapidement se retrouver avec des temps de calcul monstrueux avec des implémentations récursives naïves.
Une optimisation possible est de mémoriser les résultats déjà calculés (la technique de la mémoïsation - ce n’est pas une erreur de frappe, c’est bien memoïsation, pas mémorisation). On verra ça plus tard.
Les appels à la fonction en cours de définition peuvent parfois être imbriqués. Exemple : une fonction complètement tordue : la fonction (article wikipedia si vous voulez en savoir plus).
Exercice : évaluer
Définition en même temps de deux fonctions récursives s’appelant l’une l’autre.
Exemple : les fonctions est_pair(n) et
est_impair(n) qui déterminent si un entier
est pair ou impair.
Les règles suivantes doivent être respectées pour qu’une fonction récursive (= avec au moins un cas de base et un cas récursif) soit bien écrite :
La vérification de ces règles permet de détecter des erreurs dans les définitions récursives.
Le cas de base n’est jamais atteint.
Exercice: expliquer pourquoi le cas de base n’est jamais atteint.
pose un petit problème…
Exercice : expliquer pourquoi le domaine n’est pas respecté.
Il manque un truc…
Exercice : expliquer pourquoi la définition est incomplète.
La fonction factorielle est définie par si et .
fact(n) qui
implémente cette définition.nbc(n) qui prend un entier positif ou nul
en argument et renvoie son nombre de chiffres. Par exemple,
doit renvoyer 5. Indice : penser à ce que fait la division entière par
10, et au fait que tous les entiers entre 0 et 9 ont un seul
chiffre.nbc(n) qui
implémente cette définition.L’algorithme d’Euclide est un des plus anciens algos connus : https://fr.wikipedia.org/wiki/Algorithme_d%27Euclide
Il permet de calculer le PGCD de deux nombres (plus grand commun diviseur). Rappel : le PGCD de deux entiers et est le plus grand entier qui divise à la fois et .
Exemple : le PGCD de 12 et 15 est 3, car 3 est le plus grand entier qui divise à la fois 12 () et 15 ().
Représentation visuelle du PGCD :
PGCD(a, b) = a si b = 0
= PGCD(b, a mod b) sinon
PGCD(351, 216)Ce que je ne vais pas forcément dans ce cours un peu “théorique”, où une bonne partie des exemples seront assez “mathématiques”, mais on en fera plein d’autres de plein de domaines dans les chapitres suivants, promis.↩︎