corrige

Transcription

corrige
Introduction à la Programmation 1
Séance 10 de cours/TD
Université Paris-Diderot
Objectifs:
— Principe des enregistrements
— Problème d’aliasing
— Clonage
1
— Champs immuables
— Enregistrements récursifs
Principe des enregistrements
Un premier exemple
[Cours]
On considère la fonction suivante :
1
2
3
4
5
6
7
8
9
10
11
12
13
public static char mo st Fre qu en tL et te r ( String s ) {
int [] freq = new int [26];
for ( int i =0; i < stringLength ( s ) ; i ++) {
int k = letterRank ( s . charAt ( i ) ) ;
if ( k != -1) { freq [ i ]++; }
}
int j =0;
for ( int i =1; i <26; i ++) {
if ( freq [ i ] > freq [ j ]) { j = i ;}
}
String alphabet = " a b c d e f g h i j k l m n o p q r s t u v w x y z " ;
return alphabet . charAt ( j ) ;
}
Cette fonction renvoie la lettre la plus fréquente dans la chaine de caractère. On aimerait aussi savoir combien
de fois cette lettre apparait dans la chaîne de caractère ; on peut écrire une autre fonction, très proche de
celle-ci, mais alors on refera deux fois le calcul. On voudrait plutôt regrouper les deux calculc dans une seule
fonction qui renverrait les deux informations (la lettre et son nombre d’occurrences) en une seule fois.
Avec ce que vous connaissez, rien ne vous permet de le faire. On ne peut pas renvoyer la lettre et sa fréquence
sous forme d’un tableau de taille 2 (pourquoi ?).
De manière générale, le résultat d’un calcul est souvent une donnée qui comporte plusieurs composantes.
Pour représenter ce résultat, on utilise un enregistrement. On peut penser un enregistrement comme une
boîte contenant plusieurs données regroupées ensembles et portant chacune une étiquette.
La structure d’un enregistrement est spécifiée par son type – on parlera alors de type de données. Pour définir
un type de données en Java, on définit une classe 1 . Pour ce qui nous intéresse, nous voulons regrouper dans
un enregistrement le caractère de la lettre la plus fréquente et l’entier de son nombre d’occurrences. On doit
donc déclarer le type de données suivant.
1
1
2
3
4
5
6
7
8
class LetterFreq {
public char letter ;
public int freq ;
public LetterFreq ( char let , int fre ) {
letter = let ;
freq = fre ;
}
}
Le code ci-dessus déclare une type de données dont le nom est LetterFreq. Ce type de données comporte plusieurs champs : le champs letter, qui contient un caractère, et le champs freq, qui contient un
entier.
La quatrième ligne contient le constructeur d’enregistrements. Son rôle est d’initialiser la valeur des champs
lorsque l’enregistrement est créé. Il n’est pas déclaré comme une fonction classique, puisque le nom de la
fonction et son type de retour sont confondus, et correspondent au nom du type de données. Dans ce cours,
le constructeur fera toujours la même chose : il recopiera ses arguments dans les champs pour leur affecter
une valeur initiale.
Dans la boîte
[Cours]
Pour créer un enregistrement, on utilise le mot-clé new. Lorsqu’on détient un enregistrement, on peut l’affecter
à une variable, une case de tableau, ou encore le renvoyer comme résultat de fonction.
1
2
3
4
5
6
7
8
9
10
11
LetterFreq x = new LetterFreq ( ’a ’ ,3) ;
LetterFreq y = new LetterFreq ( ’b ’ ,6) ;
LetterFreq [] t = new LetterFreq [3];
t [0] = x ;
t [1] = y ;
t [2] = new LetterFreq ( ’a ’ ,3) ;
...
class LetterFreqPair {
public LetterFreq fst ;
public LetterFreq snd ;
}
On constate que LetterFreq a le même statut que n’importe quel autre type. En particulier, le champs d’un
enregistrement peut contenir un autre enregistrement (“on peut mettre des boîtes dans des boîtes”).
Comme les tableaux et les chaînes de caractères, les enregistrements sont alloués dans le tas. À la fin du code
ci-dessus, la mémoire est dans l’état
x
$0
y
$1
t
$2
{ letter
freq
$0
→
→
’a’
3}
{letter
freq
$1
→
→
$2
’b’
6}
{$0,$1,$3}
{letter
freq
$3
→
→
’a’
3}
En particulier, le test x==t[0] renvoie vrai, mais le test x==t[2] renvoie faux.
Exercice 1 (Application, ?)
Modifiez la fonction mostFrequentLetter pour qu’elle renvoie un enregistrement contenant la lettre la plus
fréquente ainsi que sa fréquence.
Réponse
2
Accéder au contenu
[Cours]
De même qu’on peut lire et modifier le contenu d’un tableau à une case donnée, on peut lire et modifier le contenu d’un enregistrement à un champs donné. Il faut nommer le champs qui nous intéresse et le
préfixer par un point.
1
2
3
4
5
LetterFreq x = new LetterFreq ( ’a ’ ,3) ;
char c = x . letter ;
x . freq = 4;
LetterFreqPair p = new LetterFreqPair ( new LetterFreq ( ’b ’ ,1) ,x ) ;
p . fst . freq = p . snd . freq ;
Exercice 2 (Manipulation de nombres rationnels, ?)
1. Déclarez un type de données qui représente un nombre rationnel (un nombre qui s’écrit de la forme
pour a, b entiers).
a
b
2. Écrivez une fonction calcule le produit de deux nombres rationnels.
3. Écrivez une fonction calcule la somme de deux nombres rationnels.
4. Écrivez une fonction qui simplifie un nombre rationnel puis l’affiche.
Réponse
Exercice 3 (Le temps qui passe, ?)
1. Déclarez un type de données class Time qui contient l’heure qu’affiche votre montre.
2. Écrivez une fonction public static void tic(Time t) qui avance votre montre d’une seconde.
3. Écrivez une fonction public static Time next(Time t) qui renvoie le temps qu’affichera votre
montre à la prochaine seconde, sans avancer l’heure sur votre montre.
Réponse
2
Phénomène d’aliasing
Une métamorphose
[Cours]
On peut modifier le contenu d’un enregistrement dont l’adresse est stockée dans la variable x sans mentionner la variable x, il suffit qu’il exsite une autre façon de calculer l’adresse de cet enregistrement.
1
2
3
4
5
6
7
8
9
10
class Animal {
public String famille ;
public String nom ;
...
}
...
Animal chenille = new Animal ( " insecte " ," chenille " ) ;
Animal papillon = chenille ;
papillon . nom = " papillon " ;
// maintenant chenille.nom vaut "papillon"
Exercice 4 (Une fonction sournoise, ??)
On veut écrire une fonction Java qui calcule la fonction mathématique f (x) =
code suivant ?
3
1
1+x2 .
Quel problème pose le
1
2
3
4
5
6
public static Fraction f ( Fraction x ) {
int temp = x . num ;
x . num = x . den * x . den ;
x . den = x . den * x . den + temp * temp ;
return x
}
Réponse
2.1
Le clonage
Un dédoublement de personnalité
[Cours]
Il y a plusiers façons de se prémunir contre des erreurs dues à l’aliasing. La première approche est de cloner
un enregistrement avant de le modifier.
1
2
3
4
5
6
7
8
public static Animal cloneAnimal ( Animal a ) {
return new Animal ( a . famille , a . nom ) ;
}
...
Animal chenille = new Animal ( " insecte " ," chenille " ) ;
Animal papillon = cloneAnimal ( chenille ) ;
papillon . nom = " papillon " ;
// chenille.nom vaut toujours "chenille"
Lorsque certains champs de l’enregistrement que l’on clone sont des enregistrements, il faut les cloner euxmêmes.
Exercice 5 (Vivariums, ??)
Écrivez la fonction cloneVivarium qui assure dans le code ci-dessous que le premier vivarium contient
toujours deux chenilles à la fin.
1
2
3
4
5
6
7
8
9
class Vivarium {
public Animal a1 ;
public Animal a2 ;
}
Animal chenille1 = new Animal ( " insecte " ," chenille " ) ;
Animal chenille2 = cloneAnimal ( chenille1 ) ;
Vivarium v1 = new Vivarium ( chenille1 , chenille2 ) ;
Vivarium v2 = cloneVivarium ( v1 ) ;
v2 . a1 . nom = papillon ;
Réponse
2.2
Champs mutables et persistants
Constance
[Cours]
Une autre façon de se prémunir contre les erreurs liées à l’aliasing entre variables est d’utiliser des enregistrements dont les champs ne sont jamais modifiés au cours de l’exécution du programme ; de tels champs
sont dits immuables 2 ou persistants. On peut forcer un champs à être immuable en préfixant sa déclaration
avec le mot-clé final.
4
1
2
3
4
5
6
7
8
class PersistentRatio {
public final int num ;
public final int den ;
public Fraction ( int n , int d ) {
num = n ;
den = d ;
}
}
Lorsque tous les champs d’un enregistrement sont persistants, cet enregistrement représente une “constante”.
1
2
3
4
5
PersistentRatio unDemi = new PersistentRatio (1 ,2) ;
PersistentRatio x = unDemi ;
...
x . den = 3; // interdit par le compilateur
x = new PersistentRatio (1 ,4) ; // autorisé
Lorsque tous les champs d’un type de données sont persistants, les variables ayant ce type se comportent
comme les variables de type entiers, caractères, ou booléens : une telle variable ne peut changer de valeur que
si on redéfinit entièrement sa valeur. On peut donc souvent éviter de cloner un tel enregistrement.
Si seulement certains champs peuvent être rendus immuables, il faudra peut-être avoir recours au clonage,
mais le fait de savoir que certains champs sont persistants permet de cloner plus efficacement.
Exercice 6 (Clonage paresseux, ??)
Dans le type de donnée Animal ci-desuss, le champs famille était de type String. On se propose d’introduire
un nouveau type de donnée pour décrire une famille d’animaux, comportant deux champs booléens insecte
et aquatique. Pour créer une chenille, on fait donc désormais
1
2
Famille insecteTerrien = new Famille ( true , false ) ;
Animal chenille = new Animal ( insecteTerrien , " chenille " ) ;
1. Quels champs des types de données Famille et Animal sont persistants ? Donner les deux déclarations
de type de données de Famille et Animal
2. Écrivez une fonction de clonage pour le type de donnée Animal.
3. Partant de l’idée que les chenilles ne peuvent pas séchapper de leur vivarium, on propose le type de
donnée suivant :
1
2
3
4
class Vivarium {
public final a1 ;
public final a2 ;
}
Sera-t-il possible de cloner un vivarium ? Une chenille pourra-t-elle devenir un papillon ?
Réponse
Cloner ou ne pas cloner
[Cours]
Il ne convient pas toujours de cloner les enregistrements. Des fois, on cherche au contraire à ce qu’une
modification sur un enregistrement prenne effet du point de vue de toutes les variables ayant accès à cet
enregistrement. Par exemple, on peut avoir un vivarium contenant initialement deux chenilles, puis avoir
une chenille qui se métamorphose en papillon. Dans ce cas, on peut souhaiter que le vivarium contienne
effectivement une chenille et un papillon. C’est ce qui se passe dans le code ci-dessous.
5
1
2
3
4
5
Animal chenille1 = new Animal ( " prairie " ," oiseau " ," chenille " ) ;
Animal chenille2 = cloneAnimal ( chenille1 ) ;
Vivarium v = new Vivarium ( chenille1 , chenille2 ) ;
chenille2 . nom = " papillon " ;
// v contient une chenille et un papillon
3
Types de données récursifs
Arbre généalogique
[Cours]
Un type de données récursif est un type de données t dans lequel un ou plusieurs champs sont du type
de donnée t.
1
2
3
4
5
6
7
8
class Personne {
public String nom ;
public Personne mere ;
public Personne pere ;
public Personne ( String nom , Personne me , Personne pe ) {
nom = no ; mere = me ; pere = pe ;
}
}
Pour créer un enregistrement d’un type de données récursif, on a besoin de laisser certains champs indéfinis.
On utilise pour cela le mot-clé null.
1
2
3
Personne adam = new Personne ( " adam " , null , null ) ;
Personne sansNom = null ;
Personne eve = new Personne ( " eve " , sansNom , sansNom ) ;
Exercice 7 (Arbre généalogique à l’envers, ??)
1. Déclarez un type de donnée Personne qui contienne le nom et les enfants de la personne concernée.
2. Écrivez une fonction qui affiche tous les petits enfants d’une personne.
Réponse
Référencement mutuel
[Cours]
Deux enregistrements peuvent faire référence l’un à l’autre 3
1
2
3
4
5
6
7
class Personne {
public String nom ;
public Personne conjoint ;
}
Personne adam = new Personne ( " adam " , null ) ;
Personne eve = new Personne ( " eve " , adam ) ;
adam . conjoint = eve ;
Exercice 8 (Amis, ??)
On change encore le type de données Personne.
1
2
class Personne {
public String name ;
6
public Personne [] amis ;
3
4
}
1. Écrivez une fonction qui prend en paramètre un tableau de personnes sans amis et qui les rends amis
les uns des autres, en supposant qu’on est toujours ami avec soi-même.
2. On suppose que chaque personne peut trier le tableau de ses amis par ordre de préférence. Bien sûr,
chacun a son ordre de préférence. Vérifiez si votre réponse à la question 1 le permet, et le cas échéant
refaites la question 1.
3. Si une personne change de nom, est-ce que ses amis sont au courant ?
Réponse
Les structures de données récursives sont très utiles pour écrire des listes, des piles, des files, des arbres, et plein
d’autres structures de données abstraites qui sont au coeur de nombreux algorithmes. Maitriser l’usage des
structures de données prend du temps, malgré leur apparente simplicité. C’est tellement vrai que la définition
de nouvelles structures de données implémentant des structures de données abstraites de manière efficace fait
encore l’objet de recherche très poussées.
7
4
Réponses aux exercices
Exercice 1, page 2
On remplace le type de sortie dans l’entête, qui s’écrit alors public static LetterFreq mostFrequent(
String s), et on remplace la ligne 12 par les lignes suivantes
1
2
LetterFreq res = new LetterFreq ( alphabet . charAt ( j ) , freq [ j ]) ;
return res ;
Exercice 2, page 3
class Ratio public int num ; public int den ; public Ratio(int nu,int de) num = nu ; den = de ;
public static Ratio product(Ratio x,Ratio y) int nu = x.num*y.num ; int de = x.den*x.den ; Ratio res = new
Ratio(nu,de) ;
public static Ratio sum(Ratio x,Ratio y) int nu = x.num*y.den + x.den*y.num ; int de = x.den*y.den ; Ratio
res = new Ratio(nu,de) ;
public static void showRatio(Ratio x) int p = pgcd(x.num,x.den) ; x.num = x.num / p ; x.den = x.den / p ;
showString(x.num+"/"+x.den) ;
Exercice 3, page 3
1
2
3
4
5
6
7
8
class Time {
public int hour ;
public int min ;
public int sec ;
public Time ( int h , int m , int s ) {
hour = h ; min = m ; sec = s ;
}
}
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void tic ( Time t ) {
t . sec = t . sec + 1;
if ( t . sec ==60) {
t . sec = 0;
t . min = t . min + 1;
}
if ( t . min ==60) {
t . min = 0;
t . hour = t . hour + 1;
}
if ( t . hour ==24) {
t . hour = 0;
}
}
24
25
26
27
28
public static Time next ( Time t ) {
int h = t . hour ;
int m = t . min ;
int s = t . sec ;
8
s = s + 1;
if ( s ==60) {
s = 0;
m = m + 1;
}
if ( m ==60) {
m = 0;
h = h + 1;
}
if ( h ==24) {
h = 0;
}
Time res = new Time (h ,m , s ) ;
return res ;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
}
Exercice 4, page 4
La fonction renvoie bien le résultat attendu, mais a pour effet de bord de modifier le contenu de son argument.
Par exemple, si on exécute
1
2
3
Ratio un = new Ratio (1 ,1) ;
showRatio ( f ( un ) ) ;
showRatio ( f ( un ) ) ;
le programme affichera 1/2 puis 4/5.
Exercice 5, page 4
1
2
3
4
5
public static Vivarium cloneVivarium ( Vivarium v ) {
Animal ins1 = cloneAnimal ( v . a1 ) ;
Animal ins2 = cloneAnimal ( v . a2 ) ;
return new Vivarium ( ins1 , ins2 ) ;
}
Exercice 6, page 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Famille {
public final boolean insecte ;
public final boolean aquatique ;
public Famille ( boolean i , boolean a ) {
insecte = i ;
aquatique = a ;
}
}
class Animal {
public final Famille famille ;
public String nom ;
public Animal ( Famille f , String n ) {
famille = f ;
nom = n ;
9
15
16
17
18
19
20
21
22
23
24
}
}
class Vivarium {
public final Animal a1 ;
public final Animal a2 ;
public Vivarium ( Animal aa1 , Animal aa2 ) {
a1 = aa1 ;
a2 = aa2 ;
}
}
25
26
27
28
29
30
class Main {
public static Animal cloneAnimal ( Animal a ) {
return new Animal ( a . famille , a . nom ) ;
// on n’a pas besoin de cloner la famille
}
31
32
33
34
35
36
37
38
39
public static Vivarium cloneVivarium ( Vivarium v ) {
Animal a1 = cloneAnimal ( v . a1 ) ;
Animal a2 = cloneAnimal ( v . a2 ) ;
return new Vivarium ( a1 , a2 ) ;
// aucun probleme, on ne modifie pas v.a1 et v.a2
// par contre, on doit les cloner, même si ce sont
// des champs persistants
}
40
41
42
43
44
45
46
47
48
49
50
51
public static void main ( String [] args ) {
Famille insecteTerrien = new Famille ( true , false ) ;
Animal chenille1 = new Animal ( insecteTerrien , " chenille " ) ;
Animal chenille2 = cloneAnimal ( chenille1 ) ;
Animal chenille3 = cloneAnimal ( chenille1 ) ;
Vivarium v = new Vivarium ( chenille1 , chenille2 ) ;
v . a1 . nom = " papillon " ; // la chenille 1 peut devenir papillon
v . a2 = chenille3 ; // <- erreur du compilateur
// on ne peut pas mettre une chenille a la place d’une autre dans le vivarium
}
}
Exercice 7, page 6
1
2
3
4
5
6
7
8
9
10
11
12
13
class Personne {
public String nom ;
public Personne [] enfants ;
...
}
...
public static void petitsEnfants ( Personne p ) {
for ( int i =0; i < p . enfants . length ; i ++) {
for ( int j =0; j < p . enfants [ i ]. length ; j ++) {
System . out . println ( p . enfants [ i ]. enfants [ j ]) ;
}
}
}
10
Exercice 8, page 7
1
2
3
4
5
public static void faitAmisBugge ( Personne [] t ) {
for ( int i =0; i < t . length ; i ++) {
t [ i ]. amis = t ;
}
}
6
7
8
9
10
11
12
13
public cloneTab ( Personne [] t ) {
Personne res = new Personne [ t . length ];
for ( int i =0; i < t . length ; i ++) {
res [ i ] = t [ i ];
}
return res ;
}
14
15
16
17
18
19
20
public static void faitAmis ( Personne [] t ) {
for ( int i =0; i < t . length ; i ++) {
t [ i ]. amis = cloneTab ( t ) ;
}
}
// si une personne change de nom, ses amis sont au courant.
11