:: Enseignements :: ESIPE :: E4INFO :: 2012-2013 :: Java Avancé ::
[LOGO]

foreach, filter, map, reduce


Le but de ce TD est d'implanter les méthodes ditew fonctionnelles que l'on trouve habituellement sur les collections de langage comme Python ou Ruby.
L'interface FunIterable est un Iterable qui ajoute les méthodes forEach, toList et filter.
Les méthodes de FunIterable sont définies comme ceci:
  • La méthode forEach() appelle la méthode apply d'un Block pour chaque élement.
  • La méthode toList() copie l'ensemble des élements de l'Iterable dans la liste passée en paramètre.
  • La méthode filter() renvoie un FunIterable qui filtre les élements du FunIterable courant.
Avec les interfaces Block et Filter définies comme ceci:

Exercice 1 - FunUtils

Le but de cet exercice est de founir une implantation de l'interface FunIterable. Pour cela, la classe FunUtils contiendra un ensemble de méthodes statiques aidant à l'implantation de l'interface FunIterable.

  1. On souhaite écrire une méthode filterIterator qui prend en paramètre un Iterable et un filtre Filter et renvoie un Iterator qui parcourt l'ensemble des éléments de l'iterable pour lequel la méthode accept du filter renvoie vrai.
    Expliquer pourquoi la méthode remove ne peut pas être implantée.
    Implanter la méthode filterIterator.
  2. Vérifier que le code suivant compile en écrivant un main:
              Iterable<String> iterable = Arrays.asList("foo", "bar");
              Iterator<String> iterator = FunUtils.filterIterator(iterable,
                new Filter<Object>() {
                  public boolean accept(Object o) {
                    return o.toString().length() % 2 == 0;
                  }
                });
            
    Si le code ne compile pas, modifier la signature de la méthode filterIterator pour qu'il compile.
  3. Modifier le code du main pour que l'appel à filterIterator renvoie un Iterator<CharSequence>. Pourquoi le compilateur n'est pas capable de trouver tout seul E ?
  4. De la même façon, les signatures des méthodes forEach, toList et filter de l'interface FunIterable ne sont pas les bonnes, modifier-les en conséquence.
  5. Ecrire une méthode and qui prend deux Filter en paramètre et qui renvoie un nouveau Filter dont la méthode accept renverra vrai si les deux filtres sont vrais.
    On cherche, de plus, à ce que l'implantation soit lazy comme le 'et' booleéen en C (ou en Java).
  6. Ecrire une méthode trueFilter qui renvoie un filtre dont la méthode accept renvoie toujours vrai.
    Noter que ce filtre ne dépend pas du type d'élement passé en paramètre, donc il est possible d'utiliser un même objet filtre que cela soit un Filter<String> ou un Filter<Object>. On stockera donc cet unique filtre comme une constante;
  7. Ecrire une méthode asFunIterable qui prend un Iterable et un Filter et qui renvoie un FunIterable dont les élements sont ceux de l'itérable filtrés par le Filter.
  8. Modifier le code de filter de la classe implantant FunIterable en remarquant qu'il est possible de détecter si le FunIterable a été construit avec le filtre trueFilter, et donc qu'il n'est alors pas nécessaire d'utiliser and.
  9. Le test Junit est FunUtilsTest.java. Si certaines méthodes ne compilent pas, c'est que vous vous êtes trompé dans la signature de la méthode.

Exercice 2 - FunUtil2

On cherche maintenant à écrire un Itérateur qui associe à un élement un autre élément en utilisant une fonction définie par l'interface Mapper.

  1. Dans une classe FunUtils2, écrire une méthode mapperIterator qui prend en paramètre un Iterable et un Mapper et qui renvoie un Iterateur dont chaque élement renvoyé est le résultat de l'appel à la méthode map sur les élement de l'iterable passé en paramétre.
    Que dire de la méthode remove ?
  2. Ecrire une méthode compose qui prend deux Mapper en paramètre qui renvoie un mapper qui agit comme une composition de fonction.
  3. Ecrire une méthode identityMapper qui renvoie un mapper qui renvoie le même objet que celui qu'on lui passe en paramètre.
    Noter que ici aussi, il n'est pas nécessaire d'allouer l'objet correspondant au mapper identité à chaque fois que l'on appelle la méthode identityMapper.
  4. Ajouter à l'interface FunIterable la méthode map qui prend un Mapper en paramètre et qui renvoie un nouveau FunIterable dont l'iterateur renvoie les images des élements du FunIterable courant par la fonction de mapping.
    Noter que la signature en commentaire dans l'interface n'est pas la bonne. Quelle doit être la bonne signature?

  5. Ecrire une méthode asFunIterable dans la classe FunUtils2 qui prend un iterable et un mapper en paramètre et renvoie un FunIterable.
    La méthode map devra être optimisée pour éviter de créer des FunIterable si cela n'est pas nécessaire.
    Noter qu'il vous faudra aussi changer l'implantation dans FunUtils car on vient de changer l'interface.
Le test Junit correspondant est FunUtils2Test.java.

Exercice 3 - FunUtil3

On cherche enfin à avoir une implantation de FunIterable qui permette de filtrer et mapper en une seule opération, i.e sans créer des FunIterable de FunIterable.

  1. Ecrire, dans la classe FunUtils3, une méthode mapFilterIterator qui prend en paramètre un iterable, un filter puis un mapper et renvoie un itérateur des éléments filtrés puis mappés.
    Que peut-on dire sur la méthode remove de l'itérateur?
  2. Ecrire la méthode asFunIterable qui prend en paramètre un Iterable, un Filter et un Mapper et qui renvoie une classe implantant FunIterable capable d'appliquer un filtre puis un mapper en une seule opération.
    Les méthodes filter et map devront être optimisées pour ne créer des FunIterable de FunIterable que si nécessaire.
Le test Junit correspondant est FunUtils3Test.java.

Exercice 4 - Reduce [A la maison]

On souhaite ajouter une méthode reduce appelée aussi fold, lfold suivant les langages qui permet d'obtenir une valeur en itérant sur un itérable, voici par exemple la définition en Python reduce.
En fait il existe deux sortes de reduce, le reduce homogéne qui pour un iterable sur un type va fournir un résultat du même type en utilisant une fonction qui, à deux élements de même type, fait correspondre un élement de ce même type et le reduce hétérogène qui va retourner une valeur dont le type peut être différent du type des objets de l'iterable et qui va, pour chaque élément, appeler une fonction qui prendra l'ancienne valeur et l'élément courant pour retourner une nouvelle valeur.

  1. Ecrire l'interface Reducer qui correspondra à un reduce homogène et ajouter une méthode reduce à l'interface FunIterable. Vous changerez les implantations en conséquence.
  2. Ecrire l'interface Folder qui correspondra à un reduce hétérogène et ajouter une méthode fold à l'interface FunIterable. Vous changerez les implantations en conséquence.
  3. Fournissez les tests Junit qui valident vos implantations.