Java Stream: filtre avec plusieurs plages

9

J'essaie de filtrer une ressource et d'exclure certains éléments en fonction d'un champ. Pour exclure, j'ai un ensemble (qui contient un identifiant qui doit être exclu) et une liste (il contient plusieurs plages d'identifiants qui doivent être exclus). J'ai écrit la logique ci-dessous et je ne suis pas satisfait de la logique du 2ème filtre. Y a-t-il une meilleure façon de le faire avec Java 8? Je dois également faire de même pour inclure les plages.

Set<String> extensionsToExclude = new HashSet<>(Arrays.asList("20","25","60","900"));
List<String> rangesToExclude = new ArrayList<>(Arrays.asList("1-10","20-25","50-70","1000-1000000"));
return directoryRecords.stream()
        .filter((directoryRecord) -> !extensionsToExclude.contains(directoryRecord.getExtensionNumber()))
        .filter((directoryRecord -> {
            Boolean include = true;
            for(String s : rangesToExclude) {
                String [] rangeArray = s.split("-");
                Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());
                if(extension <= Integer.parseInt(rangeArray[0]) && extension >= Integer.parseInt(rangeArray[1])) {
                    include = false;
                }
            }
            return include;
        }))
        .collect(Collectors.toList());

Merci :)

Yadvendra Rathore
la source
3
N'utilisez pas d' Booleanobjets lorsque vous avez juste besoin d'une booleanvaleur. Bien qu'ici, la variable includeest entièrement obsolète. Lorsque le seul changement possible est de trueà false, vous pouvez remplacer include = false;par return false;car le résultat final a déjà été déterminé. Ensuite, le return include;à la fin peut être remplacé par return true;et la déclaration de variable supprimée. Et puisque directoryRecordjamais la boucle ne change, vous pouvez déplacer l' Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());avant de la boucle (et passer Integerà int).
Holger

Réponses:

9

Je le ferais avec une Rangeclasse personnalisée , quelque chose comme:

class Range {
    private long start;
    private long end;

    Range(String start, String end) {
        this.start = Long.parseLong(start);
        this.end = Long.parseLong(end);
    }

    Range(String range) {
        this(range.split("-")[0], range.split("-")[1]);
    }

    boolean inRange(long n) {
        returns start <= n && n <= end;
    }
}

Ce qui rendra quelque chose comme ça possible:

List<Range> ranges = rangesToExclude.stream()
                     .map(Range::new).collect(Collectors.toList());
return directoryRecords.stream()
        .filter((directoryRecord) -> !extensionsToExclude
                                    .contains(directoryRecord.getExtensionNumber()))
        .filter(directoryRecord -> ranges.stream()
                                    .noneMatch(r -> r.isInRange(directoryRecord)))
        .collect(Collectors.toList());

Personnellement, je trouve que votre premier filtre est assez bon pour être conservé tel quel.

ernest_k
la source
2
Cela ne devrait-il pas être noneMatchlorsque nous parlons rangesToExclude? Et je suppose qu'il pourrait y avoir une solution encore plus élégante avec un TreeSet<Range>...
Holger
Ça aurait dû, en effet, je devais avoir sommeil.
ernest_k
@ernest_k Merci pour la solution. Je le trouve vraiment élégant.
Yadvendra Rathore
4

Je suggérerais similaire à la réponse de ernest_k avec Range.

Mais dans cette approche, vous pouvez utiliser les deux collections pour créer List<Range>(cela "20"peut être traité comme "20-20") et modifier la condition de filtre pour utiliser la négation avec anyMatch.

List<Range> ranges = Stream.concat(extensionsToExclude.stream(), rangesToExclude.stream())
        .map(Range::creatRange).collect(Collectors.toList());

return directoryRecords.stream()
        .filter(directoryRecord -> !ranges.stream()
                .anyMatch(r -> r.isInRange(
                        Integer.parseInt(directoryRecord.getExtensionNumber()))
                ))
        .collect(Collectors.toList());
class Range {
    private int start;
    private int end;

    Range(String start, String end) {
        this.start = Integer.parseInt(start);
        this.end = Integer.parseInt(end);
    }

    static Range creatRange(String range) {
        if (range.contains("-")) {
            return new Range(range.split("-")[0], range.split("-")[1]);
        }
        return new Range(range, range);
    }

    boolean isInRange(int n) {
        return start <= n && n <= end;
    }
}

MISE À JOUR

La création de List<Range> rangespeut être modifiée pour supprimer les points de Set<String> extensionsToExcludela plage créée à partir de List<String> rangesToExclud. Les plages inutiles ne seront alors pas créées.

List<Range> ranges = rangesToExclude.stream().map(Range::creatRange)
        .collect(Collectors.toCollection(ArrayList::new));
extensionsToExclude.stream()
        .filter(v -> !ranges.stream()
                .anyMatch(r -> r.isInRange(Integer.parseInt(v))))
        .map(Range::creatRange)
        .forEach(ranges::add);
lczapski
la source
0

vous pouvez effectuer une pause anticipée si la condition de plage est vraie, plutôt que d'attendre que toutes les entrées soient évaluées.

if(extension >= Integer.parseInt(rangeArray[0]) && extension <= Integer.parseInt(rangeArray[1])) {
                    return true;
                }

sinon, renvoyez simplement false après la boucle for.

Angel Koh
la source