UP | HOME

Un module de calculs génériques pour diverses structures algébriques

Table of Contents

Session 4 - Travaux pratiques - 22 juin 2015 - 13h30-16h15 - 2h15

  • A préparer à l'avance (temps de travail : quarante-cinq minutes).

Ce TP initie à la généricité. On commence par définir un nouveau type de données permettant de représenter des matrices carrées, puis on développe un module générique de calculs fournissant des fonctions d'exponentiation puissance. On termine en appliquant ces fonctions à quatre structures algébriques formées respectivement :

  • d'entiers naturels,
  • d'entiers relatifs,
  • de matrices carrées,
  • de rationnels.

Comme d'habitude, les implémentations des types de données seront immutables.

Une hiérarchie de structures algébriques

Le paquet tp4.hierarchie est fourni. Il définit une hiérarchie d'interfaces représentant les structures algébriques classiques. C'est un raffinement de la hiérarchie déjà vue, produisant une décomposition maximale. Voir les nouvelles interface UnifereAddition, UnifereMultiplication et BiUnifere relatives à la présence des éléments neutres, ainsi que SymetriqueAddition SymetriqueMultiplication et BiSymetrique relatives à la présence d'opposés ou d'inverses.

Voici le schéma des types.

medias/progMod15_tp4_hierarchie.png

Hiérarchie des structures algébriques - Décomposition maximale

Des entiers

Le paquet tp4.entiers est fourni. Il définit deux interfaces, Nat et Z, pour représenter des entiers naturels et des entiers relatifs respectivement, ainsi que deux classes associées d'implémentation, NatParInt et ZParInt. Deux fabriques sont aussi fournies sous la forme d'interfaces génériques (représentant chacune une propriété), FabriqueNaturels et FabriqueRelatifs.

Voici le schéma des types.

medias/progMod15_tp4_entiers.png

Des entiers naturels et relatifs

Des rationnels

Le paquet tp4.rationnels est fourni. Il définit une interface, Quotient, pour représenter des rationnels, ainsi qu'une classe associée d'implémentation, QuotientParDouble. Une fabrique est aussi fournie sous la forme d'une interface générique, FabriqueRationnels.

Voici le schéma des types.

medias/progMod15_tp4_rationnels.png

Des rationnels

Des matrices carrées

Le paquet tp4.matrices fournit quelques définitions. Il doit être complété.

On considère l'ensemble des matrices carrées d'ordre n sur un anneau unitaire A. On suppose n strictement positif. Cet ensemble est donc paramétré par un entier naturel, l'ordre, et par un autre ensemble, l'anneau. Comment représenter l'ensemble des matrices carrées en Java ? On représente cet ensemble par un type abstrait, une interface. La paramétrisation par un ensemble se traduit par la généricité, la paramétrisation par un type. En revanche, il est impossible de paramétrer un type par une valeur. La solution est alors de représenter une valeur par une classe particulière, un singleton, possédant une seule valeur, celle à représenter. Toutes les classes singletons implémentent la même interface, qui représente alors l'ensemble des valeurs utilisées comme paramètre. Cette solution permet de transformer une paramétrisation par une valeur en une paramétrisation par un type.

L'interface représentant cet ensemble de valeurs, nommée Dimension, est définie ainsi.

public interface Dimension {
        Nat norme();
}

La classe d'implémentation représentant une dimension de norme deux est définie ainsi.

public enum C2 implements Dimension {
        VAL;
        @Override
        public Nat norme() {
                return new NatParInt(2);
        }
}

Les classes enum sont utilisées pour implémenter des classes dont le nombre d'instances est fixé statiquement (dès la compilation). Ici, la seule instance s'appelle VAL, qu'on désignera dans le code par son nom complet : C2.VAL. On obtient bien ainsi un singleton, une classe avec une seule instance. Il s'agit d'un patron classique de conception, appelé "Singleton".

En utilisant cette représentation par des classes singletons, on obtient finalement que l'interface MatriceCarree est paramétrée par deux types :

  • un type représentant l'ordre des matrices considérées,
  • un type ayant la propriété d'être un anneau unitaire.

Etant donnés deux types T et A sous-types de Dimension et de AnneauUnitaire<A> respectivement, l'interface MatriceCarree<T, A> possède quatre propriétés, définies par des interfaces :

  • avoir un ordre (celui donné par la dimension T),
  • être un tableau bidimensionnel d'éléments de l'anneau A,
  • être une fabrique de matrices carrées du même ordre et sur le même anneau,
  • être un anneau unitaire.

Voici les interfaces correspondantes, toutes fournies, sauf la fabrique FabriqueMatriceCarree.

Avoir un ordre

public interface Carre {
        Dimension ordre();
} 

Etre un tableau bidimensionnel d'éléments d'un ensemble A

public interface Tableau<A> {
        A val(int l, int c);
}

Etre une fabrique de matrices carrées étant donnés une dimension T et un anneau A

public interface FabriqueMatriceCarree<T ..., A ...> {
        AssembleurMatriceCarree<T, A> getAssembleur();
        MatriceCarree<T, A> creerMatriceZero();
        MatriceCarree<T, A> creerMatriceUnite();
}

Un assembleur d'une matrice carrée est un objet mutable : initialement, il contient une matrice remplie de zéros. On peut ensuite ajouter des éléments un par un, en spécifiant leur position (ligne, colonne). Lorsque l'assemblage est terminé, on peut appeler la méthode terminer, qui renvoie la matrice carrée ainsi définie. L'assembleur ne peut plus être utilisé ensuite, afin d'éviter toute modification de la matrice produite : il faut en créer un nouveau, avec sa propre matrice. C'est un patron classique de conception, appelé "Assembleur" ou "Monteur" ("Builder" en anglais) et utilisé pour initialiser des objets complexes.

Etre un anneau unitaire étant donné un type T

public interface AnneauUnitaire<T> extends SemiAnneauUnitaire<T>, Anneau<T> {}

Pour implémenter l'interface générique MatriceCarre<T, A>, l'héritage suivant une approche descendante est utilisé, ce qui permet de factoriser l'implémentation des calculs algébriques, réalisée par la classe abstraite AnneauUnitaireDesMatricesCarrees. Cette approche est ici optimale dans le sens où on n'envisage pas d'autres implémentations des calculs algébriques.

Voici le schéma récapitulatif des types.

medias/progMod15_tp4_matrices.png

Des matrices

Un module de calculs algébriques

Le paquet tp4.algebre contiendra une classe Calculs. C'est une classe dite module car elle ne fournit que des fonctions (méthodes static) utiles. En l'occurrence, on s'intéresse à différentes fonctions d'exponentiation. Elles diffèrent non seulement par l'exposant, un entier naturel ou un entier relatif, mais aussi par l'algorithme utilisé. Le premier algorithme est récursif, reposant sur l'identité suivante.

\[ x^n = x . x^{n-1}. \]

Le second algorithme est aussi récursif, mais bien plus efficace : il repose sur une accélération logarithmique, fondée sur les identités suivantes.

\[ x^{2.n} = (x^{2})^{n} \quad\text{et}\quad x^{2.n+1} = (x^{2})^{n}.x. \]

Travail à faire

  • Mettre à jour le projet des corrections en important l'archive des corrections.
  • Dans le projet dédié au développement :
    • Recopier le paquet tp4, avec ses sous-paquets tp4.hierarchie, tp4.entiers, tp4.matrices et tp4.rationnels.

Les développements suivants seront réalisés dans le paquet tp4.

Etude des paquets fournis et premiers tests

Etudier les paquets fournis suivants : tp4.entiers et tp4.rationnels.

Créer une classe de test, tp4.Test, et réaliser le scénario suivant dans la fonction principale. Pour créer ces différents nombres, utiliser des fabriques.

  • Créer quatre entiers relatifs valant -3, 3, 2 et 1 et les stocker dans quatre variables.
  • Afficher leur valeur à l'écran, ainsi que la valeur attendue (l'oracle) suivant le format suivant : oracle ? valeur. On procédera toujours ainsi pour l'affichage.
  • Créer un entier naturel valant 3.
  • Afficher sa valeur.
  • Créer deux rationnels, valant 2.0 et 0.5.
  • Afficher leur valeur.

Etudier le paquet fourni tp4.matrices.

  • Afficher les valeurs des deux dimensions fournies.

Le module de calculs algébriques

Nous allons construire un module générique de calculs. Ce module comportera des fonctions réalisant des calculs sur les éléments de différentes structures algébriques, de manière générique. On travaille dans le paquet tp4.algebre.

  • Définir une classe module Calculs.
  • Y déclarer la fonction d'exponentiation puissance la plus générale possible, avec pour exposant un Nat.
  • L'implémenter récursivement en utilisant l'identité \(x^n = x . x^{n-1}\).
  • Définir une seconde fonction puissance, avec pour exposant un Z.
  • Tester les deux fonctions en réalisant les calculs suivants.
    • Entier naturel trois au cube
    • Moins trois au cube
    • Rationnel deux au cube
    • Rationnel deux au cube inversé
    • Rationnel un-demi au cube
  • Définir des variantes des fonctions puissance utilisant l'accélération logarithmique : elles reposent sur les deux identités \(x^{2.n} = (x^{2})^{n}\) et \(x^{2.n+1} = (x^{2})^{n}.x\). On les nommera puissanceLog.
  • Tester les deux nouvelles fonctions comme précédemment.

Matrices carrées - Définition des interfaces génériques

On travaille dans le paquet tp4.matrices.

  • Définir trois interfaces génériques
    • FabriqueMatriceCarree
    • AssembleurMatriceCarree et
    • MatriceCarree.

    Elles doivent être quantifiées universellement ainsi : pour tout type T sous-type de Dimension et pour tout type A qui est un AnneauUnitaire.

  • Préciser les interfaces parentes de l'interface MatriceCarree, conformément à la spécification ci-dessus.
  • Déclarer les méthodes de l'interface FabriqueMatriceCarree, suivant la spécification donnée ci-dessus.
  • Déclarer les deux méthodes suivantes dans l'interface AssembleurMatriceCarree.
    • void placer(int l, int c, A x)
    • MatriceCarree<T, A> terminer()

Matrices carrées - Factorisation des calculs algébriques

On travaille dans le paquet tp4.matrices.

  • Définir une classe abstraite générique AnneauUnitaireDesMatricesCarrees implémentant partiellement l'interface MatriceCarree. La quantifier universellement comme l'interface implémentée.
  • Implémenter les cinq méthodes correspondant aux calculs algébriques, zero, somme, oppose, produit et un, en utilisant les méthodes abstraites, soit les fabriques et les accesseurs. Pour les méthodes somme, produit et oppose, utiliser un assembleur pour produire la matrice résultat à partir de la matrice initialement nulle.

Matrices carrées - Implémentation par tableaux unidimensionnels

On travaille dans le paquet tp4.matrices. On implémente les matrices carrées en utilisant un tableau de type ArrayList, sous-type de List.

  • Définir une classe concrète générique MatriceCarreeParArrayList implémentant l'interface MatriceCarree par héritage de la classe abstraite AnneauUnitaireDesMatricesCarrees. La quantifier universellement comme l'interface implémentée.
  • Définir l'état ainsi.
    private List<A> tableau;
    private Dimension ordre;
    private int ordreInt; // Calculé à partir de this.ordre
    
  • Définir les méthodes protected suivantes.
    protected void initialiser(A x) {
            for(int l = 0; l < this.ordreInt; l++){
                    for(int c = 0; c < this.ordreInt; c++){
                            this.tableau.add(x); // Ajout en fin de liste
                    }
            }       
    }
    protected void setVal(int l, int c, A x) {
            this.tableau.set(position(l, c), x); // Pré-condition : position dans la liste
    }
    protected int position(int l, int c) {
            return c + l * this.ordreInt; // Par ligne
    }
    

    Elles sont à utiliser par la suite.

  • Définir le constructeur suivant à l'aide des commentaires.
    public MatriceCarreeParArrayList(Dimension ordre, UnifereAddition<A> genZero) {
            // Initialiser les attributs. 
            ...
            // Initialiser à zéro les éléments de la matrice.
            ...
    }
    
  • Définir les accesseurs ordre et val.
  • Définir les deux fabriques creerMatriceZero et creerMatriceUnite. Utiliser un assembleur de type AssembleurMatriceCarree, qui initialement contient une matrice remplie de zéros.
  • Définir la méthode getAssembleur en renvoyant null : on la définira après avoir défini une implémentation de AssembleurMatriceCarree.
  • Définir la méthode toString ainsi.
    public String toString(){
            StringBuilder r = new StringBuilder("");
            for(int l = 0; l < this.ordreInt; l++){
                    r.append('[');
                    for(int c = 0; c < this.ordreInt; c++){
                            r.append(this.val(l,  c).toString());
                            r.append(' ');
                    }
                    r.append(']');
            }
            return r.toString();
    }
    

Assembleurs - Implémentation adjointe

On travaille dans le paquet tp4.matrices. On implémente les assembleurs associés aux matrices carrées représentées par des tableaux de type ArrayList.

  • Définir une classe concrète générique AssembleurMatriceCarreeParArrayList implémentant l'interface AssembleurMatriceCarree. La quantifier universellement comme l'interface implémentée.
  • Définir un attribut privé de type MatriceCarreeParArrayList<T, A>. On utilise comme type la classe d'implémentation et non l'interface pour pouvoir accéder à l'accesseur en écriture setVal, qui ne fait pas partie de l'interface. Cet accesseur n'est utilisé que lors de l'assemblage.
  • Définir le constructeur associé à l'attribut.
  • Définir la méthode placer, qui place un élément dans la matrice sous-jacente à la position indiquée.
  • Définir la méthode terminer de manière à renvoyer la matrice sous-jacente. Pour éviter que cette matrice puisse être modifiée après son assemblage, mettre à null l'attribut avant de retourner le résultat.
  • Dans la classe MatriceCarreeParArrayList, implémenter la méthode getAssembleur en appelant le constructeur de la classe AssembleurMatriceCarreeParArrayList. Passer en argument une nouvelle matrice de type MatriceCarreeParArrayList.

Test des matrices

On travaille dans la classe tp4.Test.

  • Compléter la fonction principale en suivant les commentaires. Tous les objets créés sont à stocker dans des variables. On veillera à utiliser des noms significatifs.
public static void main(String[] args) {
      // Tests avec les nombres faits précédemment.

      // Créer une fabrique de matrices carrées d'ordre deux sur Z. 

      // Créer la matrice (d'ordre deux) nulle. 
      // Afficher son ordre.
      // Afficher sa valeur.

      // Récupérer un assembleur d'une matrice carrée.
      // Placer sur la diagonale deux (entier relatif).
      // Produire la matrice ainsi initialisée.
      // Afficher sa valeur.
      // Calculer son cube en utilisant les fonctions puissance 
      //   et afficher le résultat.

      // Créer la matrice (d'ordre deux) unité. 
      // Afficher sa valeur.
      // Afficher le résultat de l'appel de zero.
      // Afficher le résultat de l'appel de un.
      // Calculer la somme de un et zéro.
      // Afficher le résultat.
      // Calculer le produit de un par un.
      // Afficher le résultat.
      // Calculer l'opposé de un.
      // Afficher le résultat.

      // Créer une fabrique de matrices carrées d'ordre trois sur Z. 
      // Créer la matrice (d'ordre trois) nulle. 
      // Afficher son ordre.
      // Afficher le résultat de l'appel de un.
      // Faire si possible la somme d'une matrice d'ordre deux et 
      //    d'une matrice d'ordre trois.
}

Last Updated 2015-06-19T17:31+0200. Comments or questions: Send a mail.