< < E-NEF > >

Création de sites | Moniteurs | Chercher | Voyager | Cartes

()

Titre : Grands Nombres et Listes.

Auteur : Emmanuel PIERRE

 

Suite à l’article où je décrivais les grands nombres à travers les tableaux, voilà pour tous ceux qui sont obligés d’utiliser les listes, les fonctions correspondantes.

La grande différence avec les tableaux, est que les tableaux se traitent de manière linéaire, alors que les listes se traitent de manière récursive.

Qu’est-ce que la récursivité ? C’est la propriété qu’a une fonction de s’auto-appeler, c’est à dire de se rappeler elle-même plusieurs fois. On décrit ça en disant que l’on ouvre plusieurs feuilles de calcul en simultané.

Pratiquement, une fonction qui se rappelle stocke sur la pile ses données, et son point de retour à la fin de la fonction appelée sur le stack, et quand la sous-routine se termine, les paramètres de la précédente ( appelée mère ), sont restaurés.

Théoriquement, il y a deux types de récursivité, la récursivité terminale, et la récursivité non-terminale.

La récursivité non-terminale, c’est quand on rappelle la fonction avec les nouveaux paramètres, et qui se termine en renvoyant le résultat, c’est à dire que le résultat va remonter toutes les feuilles de calcul.

Pour la récursivité terminale, on rappelle la fonction en lui passant un accumulateur, c’est lui qui contiendra le retour, et évitera ainsi de tout désempiler lors du retour.

Les deux méthodes sont équivalentes en temps machine, mais il faut les connaître, c’est très utile suivant le type de calcul à effectuer.

Qu’est-ce qu’une liste ?

C’est un ensemble de cases qui se suivent, auxquelles on accède par la première, puis la suivante, et ainsi de suite jusqu’à la dernière.

Une cellule de la liste contient l’adresse de la suivante, c’est pour cela que l’on les appelle les listes chaînées.

Un exemple, en prenant pour convention le stockage à l’envers du nombre dans la liste, c’est à dire que le premier sera l’unité puissance zéro, on aura, pour 51987 :

7

8

9

1

5

La liste correspondant pointera sur le 7, plus facile à utiliser pour additionner deux listes, mais on peut choisir un autre sens.

L’avantage des listes étant que, à chaque fois que l’on a besoin d’une nouvelle case, on la crée. Voyons la déclaration d’une liste :

Type

Liste = ^Cellule;

Cellule = Record

Caract : Char;

Suivant : Liste;

End;

 

Il s’agit d’un pointeur sur un type record, contenant le caractère, ou un byte, suivant son goût personnel, et l’affichage. Ensuite, il faut des instructions pour initialiser une liste :

Function Init : Liste;

Begin

Init := Nil

end;

Et d’autres pour créer le premier élément. On procédera ainsi:

var A : liste;

 

begin

 

a := Init;

 

end;

 

La liste est initialisée, pour l’allocation, elle se fera toute seule, lors de la première opération, du genre A := CONS ( '0' , A );, puisque tout est traité avec des caractères par la suite. Voyons cette fonction :

 

Function Cons (Element : Char; L : Liste) : Liste;

Var

Nouv_Liste : Liste;

 

Begin

New(Nouv_Liste); (* on crée la cellule *)

Nouv_Liste^.Caract := Element; (* on met le caractère *)

Nouv_Liste^.Suivant := L; (* on prend l'adresse envoyée comme *)

Cons:=Nouv_Liste; (* nouvelle case vide potentielle*)

(* et on rattache le tout *);

End;

Bien évidemment, on ne peut rajouter une case qu’en tête de liste, d’où le besoin d’établir des conventions, comme stoker le nombre à l’envers, et la première case lue sera celle des unités puissance 0.

Une autre fonction nécessaire est savoir si la liste est vide, si c’est le cas, il n’y a rien à traiter :

 

Function Liste_Est_Vide(L : Liste) : Boolean;

Begin

Liste_Est_Vide := (L=Nil)

End;

 

Maintenant, pour extraire un nombre, on ne peut extraire que la queue, je m’explique, la tête de la liste pointe sur NIL, au cas où on voudrait rajouter une case, et donc, comme le montre le graphe, c’est la queue de la liste qui contient l’adresse de la cellule suivante et ainsi de suite jusqu’à la tête.

Pour parcourir la liste, on lira la queue, puis on passera à la case suivante ... Il n’y a pas de parcours en sens inverse possible, car on ne garde pas trace de la cellule précédente, ce qui est d’ailleurs inutile dans le travail sur les nombres par les listes.

Voici donc comment lire la queue de la liste :

Function Premier (L : Liste) : Char;

Begin

Premier := L^.Caract

End;

Et comment la retirer pour lire la case suivante :

Function Reste (L : Liste) : Liste;

Begin

Reste := L^.Suivant

End;

Maintenant, pour admirer vos essais, voici comment afficher une liste :

Procedure Affiche_Liste(L : Liste);

Begin

If Not (Liste_Est_Vide(L)) Then

Begin

Write (Premier(L));

Affiche_Liste(Reste(L))

End

End;

Il arrive qu’au bout d’un moment on veuille savoir le nombre d’éléments d’une liste, voici comment y arriver :

Function Longueur (L:Liste):integer;

Var

TAILLE : Integer;

Begin

TAILLE := 0;

Repeat

L := Reste(L);

TAILLE := TAILLE + 1;

Until Liste_est_vide (L);

LONGUEUR := TAILLE;

End;

Et à cause de la convention de stockage, il arrive parfois que, par la récursivité, que la liste soit renvoyée à l’envers, et donc il faut l’inverser :

Function Miroir (L: liste) : Liste;

Var

L_inv : Liste;

 

Begin

L_inv:=init;

While not (Liste_est_vide(L)) do

Begin

L_inv:=cons(premier(L),L_inv);

L:=reste (L);

End;

Miroir:=L_inv;

End;

Le renversement à été fait de manière linéaire, et ne pouvait pas être fait de manière récursive ici à cause des listes. Voyons maintenant comment comparer deux listes, ci-dessous ce test, on envoie A et B et on veut la comparaison :

Function Test_A_Plus_Grand_Que_B (NA,NB: Liste): Integer;

Var

B: Integer;

(* NB : Valeurs possibles de B:

0: Egalité de NA et NB

1: NA>NB

2: NA<NB *)

 

Begin

If Longueur(NA)>Longueur(NB) then

B:=1

Else

If Longueur(NA)<Longueur(NB) then

B:=2

Else If Longueur(NA)=Longueur(NB) Then

Begin

NA:=Miroir(NA);

NB:=Miroir(NB);

While (premier(NA)=premier(NB))

and (not Liste_est_vide(NA)) do

Begin

NA:=Reste(NA);

NB:=Reste(NB);

End;

If (Liste_est_vide(NA) and Liste_est_vide(NB)) then

B:=0

Else

If ( ord (premier(NA)) )> ( ord(premier(NB))) then

B:=1

Else

If ( ord (premier(NA)) ) <

( ord(premier(NB)) ) then

B:=2

End;

Test_A_plus_grand_que_B:=B;

End;

Plus compliqué qu’avec les tableaux, et plus lent oui, mais les inconvénients ont desfois des avantages. Ci-après, une fonction qui extrait p éléments de la liste N , ça peut servir desfois :

Function XTRACT (N:Liste; P: Integer) : Liste;

Var

RES: Liste;

Begin

RES:=Init;

N:=Miroir(N);

While (not(Liste_est_vide(N)) and (P>0)) do

Begin

RES:=Cons(Premier(N),RES);

N:=Reste(N);

P:=P-1;

End;

XTRACT:=RES;

End;

Après ces quelques amusements, le gros du boulot, l’addition de deux listes, ici c’est très simple et clair, grâce aux listes. En deux mots, la procédure principale reçoit les deux listes NA et NB, teste la nullité, dans le cas contraire additionne NA et NB dans NC, et quand une des deux listes est vide, envoie le traitement à la procédure imbriquée fin_d_add, qui finit le boulot, mais regardez plutôt :

Function Addition (NA,NB:liste):liste;

Var

NC : Liste;

retenue: Integer;

a,b : Integer;

 

Procedure fin_d_add(var ND:liste);

Begin

While not (liste_est_vide(ND)) do

Begin

NC:=cons(chr( ((ord(premier(ND))+retenue-zero) mod 10)+zero),NC);

retenue:=(ord(premier(ND))+retenue-zero) div 10;

ND:=reste(ND);

End

End;

 

Begin

If Liste_est_vide(NA) then NA:=cons('0',NA);

If Liste_est_vide(NB) then NB:=cons('0',NB);

NC:=init;

retenue:=0;

While not ((Liste_est_vide(NA)) and (Liste_est_vide(NB))) do

If ((Liste_est_vide(NA)) and (not(Liste_est_vide(NB)))) then

fin_d_add(NB)

Else If ((Liste_est_vide(NB)) and (not(Liste_est_vide(NA)))) then

fin_d_add(NA)

Else

Begin

A:=ord(premier(NA))-zero;

B:=ord(premier(NB))-zero;

NC:=cons( chr( ( (a+b+retenue) mod 10)+zero) , NC);

retenue:=( (a+b+retenue) div 10 );

NA:=reste(NA);

NB:=reste(NB);

End;

If retenue<>0 then

NC:=cons(chr(retenue+zero),NC);

Addition:=Miroir(NC);

End;

 

C’est quand même plus simple que les tableaux non ? Du moins c’est plus clair. Continuons dans le concret, voici la différence de deux listes. Pour simplifier la lecture, on en a fait 3 procédures imbriquées, la première principale, cherche la plus grande, car la différence est positive toujours, et traite la nullité. Après, elle appelle SOUSTRACT, qui fait la soustraction de cellule à cellule jusqu’à ce que une des deux listes soit vide, et c’est la procédure FIN_D_SOUS qui finit le boulot. Admirez :

 

Function SOUSTRACTION (NA,NB: liste) : liste;

 

Function SOUSTRACT (NA,NB:liste) : liste;

Var

NC: Liste;

Retenue : Integer;

a,b,ret : Integer;

 

(************** Fin de la soustraction quand liste vide *************)

Procedure FIN_D_SOUS(Var ND: liste);

Var

C: Integer;

Begin

While not (liste_est_vide(ND)) do

Begin

RET:=0;

C:=ord(premier(ND))-zero;

if C<retenue then

Begin

C:=C+10;

Ret:=1;

End;

NC:=cons(chr(c-retenue+zero),NC);

retenue:=ret;

ND:=reste(ND);

End

End;

(* Fin de FIN_D_SOUS *)

 

(* début de SOUSTRACT *)

Begin

NC:=Init;

Retenue:=0;

A:=0;

B:=0;

While not ((liste_est_vide(NA)) and (liste_est_vide(NB))) do

if liste_est_vide(NB) then

FIN_D_SOUS(NA)

else

Begin

A:=ord(premier(NA))-zero;

B:=ord(premier(NB))-zero;

ret:=0;

If A<(B+retenue) then

Begin

A:=A+10;

RET:=1;

End;

NC:=cons(chr(a-(b+retenue)+zero),NC);

retenue:=ret;

NA:=Reste(NA);

NB:=Reste(NB);

End;

Soustract:=Miroir(NC);

End;

(* Fin de SOUSTRACT *)

 

Var

T: Integer;

Begin

T:=Test_A_Plus_Grand_Que_B(NA,NB);

If T=0 then

Soustraction:=cons('0',nil)

Else If T=1 then

Soustraction:=Soustract(NA,NB)

Else If T=2 then

Soustraction:=Soustract(NB,NA);

End;

 

Puisque nous voilà lancés, maintenant voyons la multiplication d’une liste par une constante, chose fort peu aisée, et on remarque ici un retournement du résultat de l’opération :

Function MultC (L1:Liste; d: integer):Liste;

Var

Multiplic: Liste;

Retenue: Integer;

 

Begin

If (d<>0) then

Begin

Multiplic:=init;

Retenue:=0;

While not (Liste_est_vide(L1)) do

Begin

Multiplic:=cons( chr((((ord(premier(L1zero)*d+retenue) mod 10)+zero),Multiplic);

Retenue:=((Ord(premier(L1))-zero)*d+Retenue) div 10;

L1:=Reste(L1);

End;

If (Retenue<>0) then

Multiplic:=cons( chr (retenue+zero), multiplic);

MultC:=Miroir(Multiplic);

End

Else MultC:=cons('0',Nil);

End;

Voici ensuite la multiplication généralisée de deux listes, qui utilise la procédure précédente, et renvoie le tout dans une liste. On procède en prenant le dernier de la plus petite liste, que l’on multiplie par l’autre liste, on met ça dans L_partielle, puis ce nombre étant stocké à l’envers par convention, pour le rajouter à L_totale, il suffit de lui rajouter autant de zéros que son rang :

Function Multiplication (N1,N2: Liste) : Liste;

 

Function Multiplicat (L1,L2: Liste): Liste;

Var

L_totale : Liste;

L_partielle : Liste;

I : Integer;

N : Integer;

Puiss : Integer;

 

Begin

L_Totale:=Init;

L_partielle:=Init;

Puiss:=1;

For I:=1 to Longueur (L2) do

Begin

L_partielle:=(MultC(L1,( ord(premier(L2))) - zero));

For N:=1 to (Puiss-1) do

Begin

L_partielle:=Cons('0',L_partielle);

End;

Puiss:=Puiss+1;

L_totale:=Addition(L_totale,L_partielle);

L2:=Reste(L2);

End;

Multiplicat:=L_Totale;

End;

 

Begin

If longueur(N1)<Longueur(N2) then

Multiplication:=Multiplicat(N2,N1)

Else Multiplication:=Multiplicat(N1,N2);

End;

Ici plus bêtement, on multiplie p fois N par 10 pour le mettre à la puissance p voulue :

Function PUISS_10 (N:Liste; NB_ZEROS: Integer) : Liste;

Var

I: Integer;

 

Begin

For I:=NB_Zeros Downto 0 do

N:=Multiplication (N,Cons('1',Cons('0',nil)));

Puiss_10:=N;

End;

Ca c’était le digestif avant le second plat de résistance : la division. Essayez donc de comprendre tout seul, la mise en page me faire damner...

Function DIVISION (NA,NB:liste; Var RESTE: Liste):liste;

 

Function Divis (N1,N2: Liste; RESULT : Liste; Var RESTE: Liste) : Liste;

 

Begin

If TEST_A_PLUS_GRAND_QUE_B (Xtract(N1,LONGUEUR(N2)),N2)=2 then

If TEST_A_PLUS_GRAND_QUE_B (Xtract(N1,LONGUEUR(N2)+1),N2)=2 then

Begin

Divis:=Miroir(Result);

Reste:=N1;

End

Else Divis:=Divis(

Soustraction(N1,

Puiss_10(N2,

Longueur(N1)-Longueur(N2)-1

)

),

N2,

Miroir(Addition(miroir(Result),Cons('1',nil))),

reste)

Else

Divis:=Divis (Soustraction(N1,

Puiss_10(N2,

Longueur(N1)-Longueur(N2))

), N2,

Miroir(Addition(Miroir(Result),Cons('1',nil))),

reste)

 

End;

 

Begin

If TEST_A_PLUS_GRAND_QUE_B(NA,NB)=1

Then Division:=Divis(NA,NB,nil,reste)

Else If TEST_A_PLUS_GRAND_QUE_B(NA,NB)=2

Then Division:=Divis(NB,NA,nil,reste)

Else Division:=nil

End;

Vous venez d’admirer un grand moment de récurrence de listes, de rappels de fonctions. Ca serait vraiment très long à schématiser pour vous expliquer, mais je garde un grand espoir de vous faire un jour un cours de numération.

Et pour finir, le modulo de A par B, du gâteau :

Function MODULO(NA,NB:Liste):Liste;

Var

DIVI,RESTE:Liste;

 

Begin

Divi:=init;

Reste:=init;

Divi:=Division(NA,NB,RESTE);

Modulo:=reste;

End;

 

 

Conclusion

Vous voici tout clef en main, vous n’avez plus qu’à faire du copié-collé, comme moi avec l’article précédent. Dans un futur proche, je vous ferai peut-être une description des méthodes de tri avec listes et tableaux, si j’en trouve le courage.

Pour aller plus loin ... je signale qu’il existe une fonction pré-existante en C utilisable par LIST.H, et qui dans le même genre définit des arbres binaires... Si cela vous intéresse, je vous en parlerai. Sachez seulement qu’un arbre binaire est une cellule qui pointe sur deux autres cellules et ainsi de suite, ce qui en le représentant sur du papier donne une sorte d’arbre.

Autre chose, si vous faites un travail à bases de suites de Fibonnacci, contactez moi, j’ai un rapport de 40 pages au moins et des programmes à ce sujet.

Famous last words : Bien sûr, si certains bouts de programmes ne marchent pas, où si vous avez mieux, je suis prêt à tout accueillir pour améliorer ce document.