Correction prog fonc

par Tom
le 07/04/2024

1.

Expliquer ce que fait cette fonction, plus précisément en détaillant chacun des opérations qui sont invoquées

public static double mysteriousF(BasicStream<Integer> s) {
    Pair<Integer, Integer> r = s.filter(x -> x % 2 == 0)
             .map(x -> new Pair<>(1, x))
             .reduce((p, q) -> new Pair<>(p.key() + q.key(), p.value() + q.value()))
             .orElse(new Pair<>(1, 0));
    return (double) r.value() / r.key();
}

Réponse

  • s.filter(x -> x % 2 == 0) récupère les nombres pairs de s
  • .map(x -> new Pair<>(1, x)) pour chaque nombre $x$, en fait une paire $(1, x)$
  • .reduce((p, q) -> new Pair<>(p.key() + q.key(), p.value() + q.value())) pour chaque couple de paires successives p et q, remplace par une nouvelle paire contenant la somme de chaque membre de p et q
    • à la fin de ce parcours, on obtient une paire dont le premier membre contient la somme de tous les premiers membres des paires, et idem pour le second
    • comme le premier membre commençait à 1, en fait le premier membre final contient juste le nombre d'éléments
    • le second membre commence avec la valeur de chaque nombre pair, le second membre final contient la somme de tous ces nombres
  • .orElse(new Pair<>(1, 0)) s'il n'y avait aucun nombre pair, on utilise $(1, 0)$ comme valeur par défaut
  • au point où on en est, r contient le nombre de nombre pairs, et leur somme
  • (double) r.value() / r.key() donne donc la moyenne (arithmétique) de tous les nombres pairs de s

2.

Dans la classe SupplierStream, voici la fonction filter proposée comme correction (rappel: s est l'attribut déclaré dans SupplierStream de la manière suivante:

final Supplier<Optional<T>> s;

Expliquer pourquoi la fonction filter contient une boucle while (alors que filter sur un stream n'est pas considéré comme une opération terminale, mais, au contraire comme une opération intermédiaire)

public BasicStream<T> filter(Predicate<T> predicate) {
    return new SupplierStream<>( () -> {
         Optional<T> o = s.get();
         while (o.isPresent() && !predicate.test(o.get())) {
             o = s.get();
         }
         return o;
    });
}

Réponse

Le but de filter est de... filtrer un stream selon un prédicat. Donc à chaque itération on donne le prochain élément validant le critère.

Autrement dit, à chaque fois qu'on demande au stream résultat son prochain élément, il faut parcourir le stream entrant jusqu'à trouver un élément validant le critère.

Seulement là en peut renvoyer cet élément.

Le while dans la fonction filter modélise cet aspect de devoir chercher dans le stream d'entrée le prochain élément validant le critère.

3.

Etant donné la classe ValueHolder

static class ValueHolder<T> {
    private T inner;
    ValueHolder(T value) {
        this.inner = value;
    }
    T set(T value) {
        T old = inner;
        inner = value;
        return old;
    }
    T get() {
        return inner;
    }
}

expliquer ce que fait cette fonction mysteriousF2, et au passage, expliquer pourquoi la fonction renvoie un Supplier. Expliquer ensuite pourquoi la mysteriousF2 est une closure. Et expliquer pourquoi cette closure n'est pas safe

public static <T> Supplier<T> mysteriousF2(Lst<T> l) {
    ValueHolder<Lst<T>> cell = new ValueHolder<>(l);
    return () -> cell.get() == null ? null : cell.set(cell.get().cdr()).car();
}

Ensuite, donner une fonction qui permet de tester mysteriousF2 étant donné cette liste de String:

Lst<String> l = new Lst<>("pomme", new Lst<>("raisin", new Lst<>("poire", null)));
Supplier<String> monsupplier = mysteriousF2(l);
  • Comment déclencher la fonction reférencée par monsupplier
  • Que se passe-t-il si cette fonction référencée par monsupplier est invoquée deux fois: expliquer quelle sera la valeur renvoyée de type String à l'issue de chacune de ces deux invocations? Expliquer ce qu'il se passera si on invoque monsupplier 100 fois ?

Réponse

La classe ValueHolder sert de conteneur pour une valeur. En Java, une closure ne peut pas modifier la valeur d'une variable capturée. Cependant, modifier la valeur d'un membre d'un objet ne "compte pas" comme une modification. Mettre une valeur à l'intérieur d'un objet (ici ValueHolder) permet de contourner cette règle et de permettre à la closure de modifier une valeur extérieure.

Ici, mysteriousF2 prend initialement en entrée une liste liée, puis elle renvoie une fonction.

Cette fonction est de type Supplier puisqu'elle ne prend pas de paramètre et renvoie une valeur.

En particulier, c'est une closure car elle fait référence à cell, variable définie à l'extérieur.

Cette closure n'est pas safe car elle affecte mutablement la valeur d'une variable extérieure, ce qui fait qu'elle n'est en outre pas pure car son comportement dépend de la valeur de cell.

En pratique, cell contient initialement (dans le ValueHolder) la liste passée, puis à chaque appel, le car de la valeur de cell est renvoyé, puis cell est modifiée pour contenir le cdr.

Concrètement, mysteriousF2 renvoie un itérateur sur la liste passée ; c'est-à-dire une closure qui à chaque appel renvoie l'élément suivant dans la liste, puis renvoie null indéfiniment quand on est rendu à la fin de la liste.

Voici un exemple de test pour mysteriousF2. On utilise .get() pour déclencher la fonction référencée par monsupplier (cf la définition de l'interface Supplier<T>).

String current;
while ((current = monsupplier.get()) != null) {
    System.out.println(current);   
}
System.out.println("Liste terminée");

Comme on l'a dit, la fonction itère dans la liste. Donc si on invoque la fonction deux fois, on aura successivement le premier ("raisin") puis le deuxième ("poire") élément de la liste. Si on l'invoque 100 fois, on aura les trois premiers éléments de la liste, puis les 97 appels suivants renverront null car on est arrivé à la fin de la liste.

4.

Etant donné l'implémentation de limit fournie en correction pour SupplierStream

public BasicStream<T> limit(long maxSize) {
    AtomicInteger c = new AtomicInteger(0);
    return new SupplierStream<>(() ->
         s.get().filter(x -> c.getAndIncrement() < maxSize));
}

Etant donné que la fonction filter sur un Optional est définie ainsi (voir ci dessous), réécrire la fonction limit sans utiliser cette fonction filter:

Optional<T> filter(Predicate<? super T> predicate)
If a value is present, and the value matches the given predicate, returns an Optional describing the value, otherwise returns an empty Optional.
Parameters:
predicate the predicate to apply to a value, if present
Returns:
an Optional describing the value of this Optional, if a value is present and the value matches the given predicate, otherwise an empty Optional

Réponse

public BasicStream<T> limit(long maxSize) {
    AtomicInteger c = new AtomicInteger(0);
    return new SupplierStream<>(() -> {
        if (c.getAndIncrement() < maxSize) {
            return s.get();
        } else {
            return Optional.empty();
        }
    });
}