• Aucun résultat trouvé

Mise en œuvre

Dans le document Apprenez à programmer avec Ada (Page 111-114)

Nous allons désormais implémenter notre programme Dichotomie(). Toutefois, vous aurez remarqué qu'il utilise des tableaux de tailles différentes. Nous avons donc un souci car nous ne savons déclarer que des tableaux de taille fixe. Deux solutions sont possibles :

Regarder le chapitre de la partie 4 sur la généricité pour pouvoir faire des tableaux dont on ne connaît pas préalablement la taille. Le problème, c'est que l'on risque de vite se mélanger les pinceaux, d'autant plus que ce chapitre est dans une partie encore plus dure.

Faire en sorte que notre programme manipule un tableau T de taille fixe mais aussi deux variables Min et Max qui indiqueront la «plage» du tableau qui nous intéresse («cherche dans le tableau T entre l'indice Min et l'indice Max»).

Nous retiendrons pour l'instant, vous vous en doutez, cette seconde solution. Toutefois, il sera judicieux de reprendre ce programme après avoir vu la généricité.

Autre question : que donne la division 15/2 en Ada ? Réponse : Code : Console

Reponse : 7_

Puisque nous divisons deux integer, Ada nous renvoie également un integer. Or vous savez bien que cette division ne «tombe pas juste» et qu'elle devrait donner 7,5 ! Mais Ada doit faire un choix : soit la valeur approchée à l'unité près par excès (8) soit la valeur approchée à l'unité près par défaut (7). Le choix a été fait de renvoyer la valeur par défaut car elle correspond à la partie entière du nombre décimal. Toutefois, si vous vouliez savoir durant la programmation de cet algorithme, si un nombre est pair ou impair, je vous rappelle qu'il existe l'opération MOD !

Exemple : 15 mod 2 = 1 (Donc 15 est impair) ; 18 mod 2 = 0 (Donc 18 est pair).

Dernier problème à régler : que faire s'il n'y a pas de 9 ? Renvoyer une valeur particulière ? Ce n'est pas la meilleure idée.

L'utilisateur final n'est pas sensé le savoir. Renvoyer l'indice de la valeur la plus proche ? Pourquoi pas mais si l'utilisateur ne connaît pas cette nuance, cela peut poser problème. Le mieux serait de renvoyer deux résultats : un booléen Existe indiquant si la valeur existe ou non dans le tableau et une variable Indice indiquant la valeur trouvée (seulement si Existe vaut true).

Dès lors, plusieurs solutions s'offrent à vous : vous pouvez créer un type structuré (avec RECORD) pour renvoyer deux résultats en même temps. Ou vous pouvez créer une procédure avec deux paramètres en mode OUT ou encore utiliser un pointeur sur booléen comme paramètre à votre fonction pour savoir savoir si le résultat existe. A vous de choisir.

Solution

Voici une solution possible pour le programme Dichotomie() : Code : Ada

Procedure Dichotomie is

type T_Tableau is array(1..50) of integer ; --à vous de choisir la taille du tableau

type T_Ptr_Bool is access boolean ; Insérer ici le code source de la fonction récursive ---T : ---T_---Tableau ;

Partie 3 : Ada, les types composites 111/312

Existe : T_Ptr_Bool ; Indice : integer ; begin

Init(T) ; --à vous d'initialiser ce tableau

Existe := new boolean ; --création et initialisation du pointeur

Existe.all := false ;

Indice := Dicho(T, 13, Existe, T'first, T'last) ; --on cherche le nombre 13 dans le tableau T

if Existe.all

then put("Le nombre 13 est à l'indice numero") ; put(Indice) ;

else put("Le nombre 13 n'est pas présent dans le tableau") ; end if ;

end Dichotomie ;

Dans le programme ci-dessus, on recherche le nombre 13 dans un tableau de 50 cases. La procédure d'initialisation n'est pas écrite, je vous laisse le faire (pensez que le tableau doit être ordonné !). Voici maintenant le code de la fonction Dicho() :

Code : Ada

function Dicho(T : T_Tableau ; N : integer ; Existe : T_Ptr_Bool ;

Min, Max : integer) return integer is Milieu : integer ;

begin

Milieu := (Min+Max) / 2 ; if T(Milieu) = N --Cas où on trouve le nombre N cherché then Existe.all := true ; return Milieu ; elsif Min = Max

--Cas où l'on n'a pas trouvé N et où on ne peut plus continuer then Existe.all := false ;

--On indique que N n'existe pas et on renvoie 0.

return 0 ;

elsif T(Milieu) > N --Cas où on peut continuer avec un sous-tableau then return Dicho(T,N,Existe,Min,Milieu-1) ; else return Dicho(T,N,Existe,Milieu+1,Max) ; end if ;

end Dicho ;

Nous avons cette fois réalisé un programme où l'utilisation de la récursivité est vraiment pertinente. À quoi reconnaît-on qu'un programme pourrait être traité de manière récursive ? Tout d'abord il faut avoir besoin de répéter certaines actions. La question est plutôt : pourquoi choisir un algorithme récursif plutôt que itératif ? La réponse est en grande partie dans la définition même de la récursivité. Il faut se trouver face à un problème qui ne se résolve qu'en réemployant les mêmes procédés sur un même objet. Les algorithmes récursifs nécessitent généralement de disjoindre deux cas : le cas simple, trivial et le cas complexe. Ce dernier peut être lui-même subdivisé en plusieurs autres cas (a<b et a>b ; n pair et n impair…).

Quelques exercices Exercice 1

Énoncé

Rédigez une fonction récursive Factorielle( ) qui calcule… la factorielle du nombre donné en argument. Pour exemple, la factorielle de 7 est donnée ainsi : . Ainsi, votre fonction Factorielle(7) renverra 5040.

Solution

Secret (cliquez pour afficher) Code : Ada

function factorielle(n : integer) return integer is begin

if n>0

then return n*factorielle(n-1) ; else return 1 ;

end if ; end factorielle ;

À partir de , des problèmes commencent à apparaître : des résultats sont négatifs ce qui est impossible puisqu'on ne multiplie que des nombres positifs. De plus, les résultats ne «grossissent» plus.

Alors pourquoi ces résultats ? C'est ce que l'on appelle l'overflow. Je vous ai souvent mis en garde : l'infini en informatique n'existe pas, les machines ont beau être ultra-puissantes, elles ont toujours une limite physique. Par exemple, les integer sont codés par le langage Ada sur 32 bits, c'est-à-dire que l'ordinateur ne peut retenir que des nombres formés de maximum 32 chiffres (ces chiffres n'étant que des 0 ou des 1, c'est ce que l'on appelle le code binaire, seul langage compréhensible par un ordinateur). Pour être plus précis même, le 1er chiffre (on dit le premier bit) correspond au signe : 0 pour positif et 1 pour négatif. Il ne reste donc plus que 31 bits pour coder le nombre. Le plus grand integer enregistrable est donc . Si jamais nous enregistrions un nombre plus grand encore, il nécessiterait plus de bits que l'ordinateur ne peut en fournir, c'est ce que l'on appelle un dépassement de capacité ou overflow. Du coup, le seul bit supplémentaire possible est celui du signe + ou -, ce qui explique les erreurs obtenues à partir de 17.

Exercice 2

Énoncé

Rédigez une fonction récursive Puissance(a,n) qui calcule la puissance n du nombre a (c'est-à-dire ), mais seulement en utilisant des multiplications.

Solution

Secret (cliquez pour afficher) Code : Ada

function puissance(a,n : integer) return integer is begin

if n > 0

then return a*puissance(a,n-1) ; else return 1 ;

end if ; end puissance ;

Si vous cherchez à calculer , vous obtiendrez là aussi un résultat négatif du à un overflow.

Exercice 3

Énoncé

Rédigez des fonction récursives Affichage( ) et Minimum( ). Chacune devra parcourir un tableau, soit pour l'afficher soit pour indiquer l'indice du plus petit élément du tableau.

Partie 3 : Ada, les types composites 112/312

Solution

Secret (cliquez pour afficher) Code : Ada

type T_Tableau is array(1..10) of integer ; Procedure Affichage(T : T_Tableau) is

procedure Afg(T : T_Tableau ; dbt : integer) is begin

put(T(dbt),1) ; put(" ; ") ; if dbt < T'last

then Afg(T,dbt+1) ; end if ;

end Afg ; begin

Afg(T,T'first) ; end Affichage ;

function Minimum(T:T_Tableau) return integer is

function minimum(T:T_Tableau ; rang, res, min : integer) return integer is

--rang est l'indice que l'on va tester

--res est le résultat temporaire, l'emplacement du minimum trouvé jusque là

--min est le minimum trouvé jusque là min2 : integer ;

res2 : integer ; begin

if T(rang) < min then min2 := T(rang) ; res2 := rang ; else min2 := min ; res2 := res ; end if ;

if rang = T'last then return res2 ;

else return minimum(T,rang+1,res2,min2) ; end if ;

end minimum ; begin

return minimum(T,T'first+1,T'first,T(T'first)) ; end Minimum ;

Il existe deux fonctions minimum, l'une récursive et l'autre non ! Mais cela ne pose pas de problème car le compilateur constate que le nombre de paramètres est différent. Il est donc facile de les distinguer. De plus, dans votre procédure principale, seule la fonction minimum non récursive sera accessible.

Exercice 4

Énoncé

Rédigez une fonction récursive Effectif(T,x) qui parcourt un tableau T à la recherche du nombre d'apparition de l'élément x. Ce nombre d'apparition est appelé effectif. En le divisant par le nombre total d'élément dans le tableau, on obtient la fréquence (exprimée en pourcentages si vous la multipliez par 100). Cela vous permettra ainsi de rédiger une seconde fonction appelée Fréquence(T,x). Le tableau T pourra contenir ce que vous souhaitez, il peut même s'agir d'un string.

Solution

Secret (cliquez pour afficher) Code : Ada

function effectif(T: T_Tableau ; x : integer) return integer is function effectif(T:T_Tableau ; x : integer ; rang, eff : integer) return integer is

eff2 : integer ; begin

if T(rang) = x then eff2 := eff +1 ; else eff2 := eff ; end if ; if rang=T'last then return eff2 ;

else return effectif(T,x,rang+1,eff2) ; end if ;

end effectif ; begin

return effectif(T,x,T'first,0) ; end effectif ;

function frequence(T : T_Tableau ; x : integer) return float is begin

return float(effectif(T,x))/float(T'length)*100.0 ; end frequence ;

Là encore, je crée deux fonctions effectif() de manière à ce que l'emploi de la première (non récursive) soit facilité (peu de paramètres). La seconde fonction effectif( ), récursive et «coincée» dans la première, nécessite plus de paramètres qui n'ont pas à être connus par l'utilisateur final de ce code.

Nous venons de créer plusieurs fonctions ou procédures liées aux tableaux. Peut-être pourriez-vous les ajouter à votre package P_Integer_Array.

Bien, nous avons désormais terminé ce chapitre sur la récursivité. Cette notion va nous être utile au prochain chapitre puisque nous allons aborder un nouveau type composite : les types abstraits de données. Ce chapitre fera appel aux pointeurs, aux types structurés et à la récursivité. Ce sera le dernier chapitre théorique de cette partie et probablement le plus compliqué, donc si vous avez encore des difficultés avec l'une de ces trois notions, je vous invite à relire les chapitres précédents et à vous entraîner car les listes sont des objets un peu plus compliqués à manipuler.

J'espère que ce chapitre vous aura permis d'avoir une idée assez précise de ce qu'est la récursivité et de l'intérêt qu'elle peut représenter en algorithmique. Pour disposer d'un second point de vue, je vous conseille également la lecture du tutoriel de bluestorm disponible sur le site du zéro en cliquant ici. Toutefois, s'il est plus complet concernant la notion de récursivité, ce tutoriel n'est pas rédigé en Ada, vous devrez donc vous contenter de ses explications.

En résumé :

Les boucles récursives permettent, comme les boucles itératives, de répéter une tâche. Il suffit pour cela qu'une procédure ou une fonction s'appelle elle-même.

Un programme récursif doit envisager plusieurs cas et notamment le cas trivial qui mettra fin à la boucle récursive. Une fois ce cas évident établi, vous devez réfléchir à l'opération à effectuer pour passer à l'étape supérieure.

Les algorithmes récursifs nécessitent plus d'entraînement et de réflexion que les algorithmes itératifs. Nous verrons au prochain chapitre que certaines structures s'adaptent mieux à ce type de raisonnement. C'est pourquoi vous devez malgré tout vous entraîner à la récursivité, malgré les difficultés qu'elle peut présenter.

Partie 3 : Ada, les types composites 113/312

Dans le document Apprenez à programmer avec Ada (Page 111-114)