Java Stream: Filter mit mehreren Bereichen

9

Ich versuche, eine Ressource zu filtern und einige Elemente basierend auf einem Feld auszuschließen. Zum Ausschließen habe ich einen Satz (der eine ID enthält, die ausgeschlossen werden muss) und eine Liste (die mehrere Bereiche von IDs enthält, die ausgeschlossen werden müssen). Ich habe die folgende Logik geschrieben und bin mit der 2. Filterlogik nicht zufrieden. Gibt es eine bessere Möglichkeit, dies mit Java 8 zu tun? Ich muss das Gleiche tun, um auch Bereiche einzuschließen.

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());

Vielen Dank :)

Yadvendra Rathore
quelle
3
Verwenden Sie keine BooleanObjekte, wenn Sie nur einen booleanWert benötigen . Hier ist die Variable includejedoch völlig veraltet. Wenn die einzig mögliche Änderung von ist truezu false, können Sie ersetzen include = false;mit return false;als das Endergebnis bereits ermittelt wurde. Dann kann das return include;am Ende durch ersetzt return true;und die Variablendeklaration entfernt werden. Und da directoryRecordnie in der Schleife ändert, können Sie die Bewegung Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());vor der Schleife (und ändern Integerzu int).
Holger

Antworten:

9

Ich würde es mit einer benutzerdefinierten RangeKlasse machen, so etwas wie:

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;
    }
}

Was so etwas möglich macht:

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());

Ich persönlich finde Ihren ersten Filter gut genug, um so zu bleiben, wie er ist.

ernest_k
quelle
2
Sollte es nicht sein, noneMatchwenn wir darüber reden rangesToExclude? Und ich nehme an, es könnte eine noch elegantere Lösung mit einem TreeSet<Range>
Holger
Es sollte in der Tat, ich muss schläfrig gewesen sein.
ernest_k
@ernest_k Danke für die Lösung. Ich finde es sehr elegant.
Yadvendra Rathore
4

Ich würde ähnlich wie ernest_ks Antwort mit vorschlagen Range.

Bei diesem Ansatz können Sie jedoch sowohl eine Sammlung zum Erstellen List<Range>(dies "20"kann als behandelt werden "20-20") als auch die Filterbedingung ändern, mit der die Negation verwendet werden soll 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;
    }
}

AKTUALISIEREN

Die Erstellung von List<Range> rangeskann geändert werden, um Punkte aus Set<String> extensionsToExcludedem Bereich zu entfernen, aus dem erstellt wurde List<String> rangesToExclud. Dann werden keine unnötigen Bereiche erstellt.

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
quelle
0

Sie können eine frühe Pause einlegen, wenn die Bereichsbedingung erfüllt ist, anstatt darauf zu warten, dass alle Einträge ausgewertet werden.

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

Andernfalls geben Sie nach der for-Schleife einfach false zurück.

Engel Koh
quelle