:: Enseignements :: Master :: M1 :: 2021-2022 :: Java Avancé ::
[LOGO]

Examen de Java Avancé


Le but de ce TP noté est d'implanter une structure de données appelée NumericSeq qui est une séquence de valeurs numériques d'entiers, d'entiers long ou de nombres flottants.
L'intérêt de cette structure de données est d'avoir une représentation très compacte en mémoire des données sans pour autant dupliquer le code pour chaque type primitif.

Vos sources Java produites pendant l'examen devront être placées sous le répertoire EXAM de votre compte ($HOME) (qui est vide dans l'environnement de TP noté). Sinon, elles ne seront pas récupérées.

Tout document papier est proscrit.

La javadoc 17 est là, https://igm.univ-mlv.fr/~juge/javadoc-17/api/index.html.
Les seuls documents électroniques autorisés sont les supports de cours à l'url http://igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

Vous avez le droit de lire le sujet jusqu'au bout, cela vous donnera une bonne idée de là où on veut aller !

Exercice 1 - NumericSeq

La classe NumericSeq est un conteneur de valeurs numériques homogènes de type int, long ou double mais qui en interne stocke les valeurs dans un tableau de long.
En effet, un entier long 64 bits (long) peut contenir un int 32 bits ; il suffit de faire cast en int si on veut obtenir la valeur comme un int. Un entier long 64 bits (long) peut aussi contenir un nombre flottant 64 bits (double) car c'est la même taille en mémoire, il faut être capable pour cela de voir un double comme un long sans changer la représentation bit à bit. La méthode Double.doubleToRawLongBits(double valeur) renvoie le long correspondant bit à bit au double passé en paramètre et la méthode Double.longBitsToDouble(long value) fait l'opération inverse.
La classe NumericSeq permet de créer des séquences d'int, de long ou de double de la façon suivante.
  NumericSeq<Integer> seq1 = NumericSeq.ints(1, 4, 7);  // séquence d'entiers 32 bits
  NumericSeq<Long> seq2 = NumericSeq.longs(123L, -45L);  // séquence d'entiers 64 bits
  NumericSeq<Double> seq3 = NumericSeq.doubles(48.0, 123.25);  // séquence de flottants 64 bits
     
La classe NumericSeq possède, de plus,
  • les méthodes add(value), get(index) et size qui permettent respectivement d'ajouter un élément, d'obtenir un élément à un index donné et d'obtenir le nombre de valeurs dans la séquence.
  • un mécanisme qui permet d'afficher une séquence : les valeurs sont séparées par des virgules suivies d'un espace, et le tout entre '[' et ']'
      var seq1 = NumericSeq.ints(1, 4, 7);
      System.out.println(seq1);   // affiche [1, 4, 7]
           
  • un mécanisme qui permet de parcourir la séquence avec une boucle, comme dans l'exemple ci-dessous :
      var seq1 = NumericSeq.ints(1, 4, 7);
      for(var value: seq1) {
        System.out.println("value: " + value);
      }
           
  • une méthode addAll(anotherSequence) qui permet d'ajouter les valeurs d'une autre séquence à la séquence courante si elles ont le même type de valeurs numériques
  • une méthode map(function, factory) qui renvoie une nouvelle séquence créée en utlisant la méthode de création factory et qui contient les valeurs de retour de l'appel à la fonction prise en premier paramètre pour chaque valeur de la séquence courante.
      var seq1 = NumericSeq.ints(1, 4, 7);
      var seq4 = seq1.map(v -> v * 2.5, NumericSeq::double);
      System.out.println(seq4);  // affiche [2.5, 10.0, 17.5]
           
  • les méthodes toNumericSeq(factory) et stream(), qui permettent respectivement de collecter des valeurs dans un NumericSeq et de renvoyer un Stream.

Des tests unitaires correspondant à l'implantation sont ici : NumericSeqTest.java.

  1. On souhaite écrire une méthode longs sans paramètre qui permet de créer un NumericSeq vide ayant pour l'instant une capacité fixe de 16 valeurs. Cela doit être la seule façon de pouvoir créer un NumericSeq.
    Écrire la méthode longs puis ajouter les méthodes add(value), get(index) et size.
    Vérifier que les tests unitaires marqués "Q1" passent.

  2. On veut maintenant que le tableau utilisé par NumericSeq puisse s'agrandir pour permettre d'ajouter un nombre arbitraire de valeurs.
    On veut, de plus, que NumericSeq soit économe en mémoire, donc la capacité du tableau d'un NumericSeq vide doit être 0 (si vous n'y arrivez pas, faites la suite).
    Vérifier que les tests unitaires marqués "Q2" passent.
    Note: agrandir un tableau une case par une case est très inefficace !

  3. Faire en sorte d'utiliser un Stream pour que l'on puisse afficher un NumericSeq avec le format attendu.
    Vérifier que les tests unitaires marqués "Q3" passent.

  4. On veut maintenant ajouter les 3 méthodes ints, longs et doubles qui permettent respectivement de créer des NumericSeq d'int, de long ou de double en prenant en paramètre des valeurs séparées par des virgules.
    En termes d'implantation, l'idée est de convertir les int ou les double en long avant de les insérer dans le tableau. Et dans l'autre sens, lorsque l'on veut lire une valeur, c'est à dire quand on prend un long dans le tableau, on le convertit en le type numérique attendu. Pour cela, l'idée est de stocker dans chaque NumericSeq une fonction into qui sait convertir une valeur en long, et une fonction from qui sait convertir un long vers la valeur attendue.
    Vérifier que les tests unitaires marqués "Q4" passent.

  5. On souhaite maintenant pouvoir parcourir un NumericSeq avec une boucle for(:). Dans le cas où l'on modifie un NumericSeq avec la méthode add lors de l'itération, les valeurs ajoutées ne sont pas visibles pour la boucle.
    Modifier la classe NumericSeq pour implanter le support de la boucle for(:).
    Vérifier que les tests unitaires marqués "Q5" passent.

  6. On souhaite ajouter une méthode addAll qui permet d'ajouter un NumericSeq à un NumericSeq déjà existant.
    Ecrire le code de la méthode addAll.
    Vérifier que les tests unitaires marqués "Q6" passent.
    Note : on peut remarquer qu'il y a une implantation efficace car les deux NumericSeq utilisent en interne des tableaux de long.

  7. On souhaite maintenant écrire une méthode map(function, factory) qui prend en paramètre une fonction qui peut prendre en paramètre un élément du NumericSeq et renvoie une nouvelle valeur ainsi qu' une référence de méthode permettant de créer un nouveau NumericSeq qui contiendra les valeurs renvoyées par la fonction.
    Écrire la méthode map.
    Vérifier que les tests unitaires marqués "Q7" passent.

  8. On souhaite écrire une méthode toNumericSeq(factory) qui prend en paramètre une référence de méthode permettant de créer un NumericSeq et renvoie un Collector qui peut être utilisé pour collecter des valeurs numériques dans un/des NumericSeq créés par la référence de méthode.
    Écrire la méthode toNumericSeq.
    Vérifier que les tests unitaires marqués "Q8" passent.

  9. Enfin, écrire une méthode stream() qui renvoie un Stream qui voit l'ensemble des valeurs par ordre d'insertion dans le NumericSeq courant.
    Le Stream renvoyé devra être parallélisable.
    Vérifier que les tests unitaires marqués "Q9" passent.
    Note : pour que le Stream parallèle soit un peu efficace, on ne coupera pas en deux les Stream ayant moins de 1024 valeurs.