Programmation orientée objet TP no 8 : Collections

publicité
Programmation orientée objet
TP no 8 : Collections
Calculatrice en notation polonaise inverse
La notation polonaise inverse est une façon d'écrire les expressions arithmétiques de manière nonambigüe sans parenthèse. On peut implémenter une calculatrice basée sur cette notation en utilisant
uniquement une pile comme structure de données. Le but de cet exercice est de réaliser une telle
calculatrice qui lira ses arguments sur l'entrée standard.
En notation polonaise inverse, l'opérateur suit ses opérandes. Par exemple, l'opération 5 + 12 s'écrit
5 12 +. A quelles expressions en notation classique correspondent les expressions en notation polonaise
inverse qui suivent. Remarquez que les parenthèses deviennent nécessaires.
• 5 12 4 + 3 − ×
• 12 4 + 5 3 − ×
L'implémentation de la calculatrice sera basée sur une pile. On rappelle qu'une pile est représentée
par l'interface Deque. On se limite aux quatre méthodes push, pop, peek et isEmpty. Utilisez
l'algorithme qui suit pour réaliser la calculatrice.
Pour chaque argument de la ligne de commande, on appelle une méthode entreeSuivante. La
méthode entreeSuivante(String arg) agit sur la pile :
Si arg est un nombre, on l'empile.
Si arg est un opérateur :
On dépile autant de valeurs que l'arité de l'opérateur.
On applique l'opérateur (attention à l'ordre des opérandes).
On empile le résultat.
Sinon arg est un symbole inconnu et on lance une exception.
Une fois tous les arguments traités, on ache le résultat.
Les opérations suivantes doivent être implémentées :
Addition, symbole "+",
Soustraction, symbole "-",
Multiplication, symbole "x", "X" ou "*",
Division, symbole "/",
Passage à l'opposé (moins unaire), symbole "~".
Parcours en largeur
Classes Noeud et Arbre
Un noeud d'un arbre quelconque est caractérisé par l'étiquette qui lui est associée (pour nous, ce
sera une chaîne de caractères) et par ses ls (en nombre quelconque). Proposez une classe Noeud qui
devra disposer :
d'un constructeur prenant son étiquette et un nombre variable de noeud ls,
d'accesseurs pour l'étiquette et les ls,
d'une méthode estFeuille() qui renvoie true si et seulement si le noeud n'a pas de ls,
et d'une méthode ajouteFils(Noeud) qui ajoute un noeud ls au noeud courant.
1
Quelle structure de données choisir pour stocker les ls d'un noeud? Assurez qu'ils ne soient pas
modiables en dehors de la méthode ajouteFils().
Un arbre est de manière usuelle représenté par son noeud racine. Donnez la classe Arbre.
Le parcours
Ajoutez une méthode parcoursEnLargeur() dans la classe Arbre. On rappelle que le parcours
en largeur nécessite l'usage d'une le. Quel est le type de retour de la méthode parcoursEnLargeur() ?
Exemple d'utilisation :
Noeud racine = new Node("AbstractCollection",
new Node("AbstractSet",
new Node("HashSet",
new Node("LinkedHashSet")),
new Node("AbstractSortedSet",
new Node("TreeSet"))),
new Node("AbstractList",
new Node("ArrayList"),
new Node("AbstractSequentialList",
new Node("LinkedList"))),
new Node("ArrayDeque"));
Arbre arbre = new Arbre(racine);
for (String etiquette : arbre.parcoursEnLargeur()) {
System.out.println(etiquette);
}
L'exécution de ce code ache à l'écran :
AbstractCollection
AbstractSet
AbstractList
ArrayDeque
HashSet
AbstractSortedSet
ArrayList
AbstractSequentialList
LinkedHashSet
TreeSet
LinkedList
Calculatrice programmable
On reprend la calculatrice développée en première partie, le but est de la rendre programmable.
Initialement, la calculatrice ne supportera aucune opération. On représente une opération par une
instance de l'interface Operation.
public interface Operation {
int arite();
double calcule(double... args);
}
Pour programmer la calculatrice, il faudra passer par une méthode ajouterOperation qui prend
une Operation en argument suivie d'un nombre quelconque de symboles qui lui sont associés. Quelle
structure de données faut-il mettre en place pour retrouver l'opération associée à un symbole? Implémentez la méthode ajouterOperation.
2
Maintenant que la calculatrice est programmable, il faut généraliser la méthode entreeSuivante
pour qu'elle supporte n'importe quelle opération et donc des opérations d'arité quelconque. Apportez
les modications nécessaires.
Pour retrouver la calculatrice initiale, il faudra la paramétrer de la manière suivante :
Calculatrice calc = new Calculatrice();
calc.ajouterOperation(new Operation() {
public int arite() { return 2; }
public double calcule(double... args)
}, "+");
calc.ajouterOperation(new Operation() {
public int arite() { return 2; }
public double calcule(double... args)
}, "-");
calc.ajouterOperation(new Operation() {
public int arite() { return 2; }
public double calcule(double... args)
}, "*", "x", "X");
calc.ajouterOperation(new Operation() {
public int arite() { return 2; }
public double calcule(double... args)
}, "/");
calc.ajouterOperation(new Operation() {
public int arite() { return 1; }
public double calcule(double... args)
}, "~");
{ return args[0] + args[1]; }
{ return args[0] - args[1]; }
{ return args[0] * args[1]; }
{ return args[0] / args[1]; }
{ return -args[0]; }
Vers Java 8
L'exemple précédent montre qu'il est fastidieux de paramétrer notre calculatrice. Il faut créer
des classes (éventuellement de manière anonyme comme dans l'exemple) qui implémentent l'interface
Operation pour chaque opération. On souhaite que l'utilisateur puisse paramétrer la calculatrice de
manière plus naturelle à l'aide des lambda-expressions de Java 8.
Calculatrice calc = new Calculatrice();
calc.ajouterOperation((x, y) -> x + y, "+");
calc.ajouterOperation((x, y) -> x - y, "-");
calc.ajouterOperation((x, y) -> x * y, "*", "x", "X");
calc.ajouterOperation((x, y) -> x / y, "/");
calc.ajouterOperation(x -> -x, "~");
Apportez les modications nécessaires à votre code. Pour cela, vous devrez spécialiser l'interface
en fonction de l'arité de l'opération (limitez-vous aux cas unaires et binaires). Il vous
faudra également surcharger judicieusement la méthode ajouterOperation.
Operation
3
Téléchargement