web-dev-qa-db-fra.com

Comment obtenir une gamme d’articles du flux en utilisant Java 8 lambda?

Dans une question précédente [ Comment faire un filtrage dynamique dans Java 8? ] Stuart Marks a donné une réponse formidable et a fourni plusieurs utilitaires utiles pour gérer la sélection de topN et de topPercent de flux.

Je vais les inclure ici de sa réponse originale:

@FunctionalInterface
public interface Criterion {
    Stream<Widget> apply(Stream<Widget> s);
}

Criterion topN(Comparator<Widget> cmp, long n) {
    return stream -> stream.sorted(cmp).limit(n);
}

Criterion topPercent(Comparator<Widget> cmp, double pct) {
    return stream -> {
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .limit((long)(temp.size() * pct));
    };
}

Mes questions sont les suivantes:

[1] Comment obtenir les éléments supérieurs de 3 à 7 d'un flux contenant un certain nombre d'éléments, donc si le flux contient des éléments de A1, A2 .. A10, l'appel de

topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L)

retournera {A3, A4, A5, A6, A7}

Le moyen le plus simple auquel je puisse penser est d'obtenir le top 7 [T7] de l'original, le top 3 [T3] de l'original, puis le T7 - ​​T3.

[2] Comment obtenir les éléments les plus importants des 10% aux 30% les plus élevés d’un flux contenant un certain nombre d’articles, ainsi, si le flux contient des éléments de X1, X2 .. X100, l’appel à

topPercentFromRange(Comparator<Widget> cmp, double from, double to) = topNFromRange(comparing(Widget::length), 0.10, 0.30)

retournera {X10, X11, X12, ..., X29, X30}

Le moyen le plus simple auquel je puisse penser est d’obtenir les 30% supérieurs [TP30] de l’original, les 10% supérieurs [TP10] de l’original, puis les types TP30 - TP10.

Quels sont les meilleurs moyens d'utiliser Java 8 Lambda pour exprimer de manière concise les situations ci-dessus?

55
Frank

Utilisateur skiwi déjà réponse la première partie de la question. La deuxième partie est:

(2) Comment obtenir les meilleurs éléments du top 10% au maximum 30% d'un flux avec une certaine quantité d'éléments ....

Pour ce faire, vous devez utiliser une technique similaire à topPercent dans mon réponse à l'autre question. En d’autres termes, vous devez rassembler les éléments dans une liste pour pouvoir les compter, éventuellement après le filtrage en amont.

Une fois que vous avez le nombre, vous calculez les bonnes valeurs pour skip et limit en fonction du nombre et des pourcentages souhaités. Quelque chose comme ça pourrait marcher:

Criterion topPercentFromRange(Comparator<Widget> cmp, double from, double to) {
    return stream -> {
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .skip((long)(temp.size() * from))
                   .limit((long)(temp.size() * (to - from)));
    };
}

Bien sûr, vous devrez vérifier les erreurs sur from et to. Un problème plus subtil consiste à déterminer le nombre d'éléments à émettre. Par exemple, si vous avez dix éléments, ils se trouvent aux index [0..9], ce qui correspond à 0%, 10%, 20%, ..., 90%. Mais si vous demandiez une fourchette de 9% à 11%, le code ci-dessus n'émettrait aucun élément, pas celui à 10% comme vous pourriez vous y attendre. Il est donc probablement nécessaire de bricoler les calculs de pourcentage pour s’adapter à la sémantique de ce que vous essayez de faire.

39
Stuart Marks

Pour obtenir une plage allant de Stream<T>, Vous pouvez utiliser skip(long n) pour ignorer tout d'abord un nombre défini d'éléments, puis vous pouvez appeler limit(long n) pour ne prendre qu'un montant spécifique. des articles.

Considérons un flux avec 10 éléments. Pour obtenir les éléments 3 à 7, appelez normalement à partir de List:

list.subList(3, 7);

Maintenant, avec un Stream, vous devez d'abord sauter 3 éléments, puis prendre 7 - 3 = 4 éléments, de sorte qu'il devient:

stream.skip(3).limit(4);

En guise de variante de la solution de @StuartMarks à la deuxième réponse, je vous propose la solution suivante, qui laisse la possibilité de chaîner une chaîne intacte. Elle fonctionne de la même manière que @StuartMarks:

private <T> Collector<T, ?, Stream<T>> topPercentFromRangeCollector(Comparator<T> comparator, double from, double to) {
    return Collectors.collectingAndThen(
        Collectors.toList(),
        list -> list.stream()
            .sorted(comparator)
            .skip((long)(list.size() * from))
            .limit((long)(list.size() * (to - from)))
    );
}

et

IntStream.range(0, 100)
        .boxed()
        .collect(topPercentFromRangeCollector(Comparator.comparingInt(i -> i), 0.1d, 0.3d))
        .forEach(System.out::println);

Ceci imprimera les éléments 10 à 29.

Cela fonctionne en utilisant un Collector<T, ?, Stream<T>> Qui prend vos éléments du flux, les transforme en un List<T>, Puis obtient un Stream<T>, Le trie et applique les limites (correctes) à cela.

45
skiwi