Strumień Java: Filtruj z wieloma zakresami

9

Próbuję przefiltrować zasób i wykluczyć niektóre elementy na podstawie pola. Do wykluczenia mam zestaw (który zawiera identyfikator, który należy wykluczyć) i listę (zawiera wiele zakresów identyfikatorów, które należy wykluczyć). Napisałem poniższą logikę i nie jestem zadowolony z logiki drugiego filtra. Czy jest lepszy sposób, aby to zrobić w Javie 8? Muszę zrobić to samo, aby uwzględnić również zakresy.

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

Dzięki :)

Yadvendra Rathore
źródło
3
Nie używaj Booleanobiektów, gdy potrzebujesz tylko booleanwartości. Chociaż tutaj zmienna includejest całkowicie przestarzała. Gdy tylko ewentualna zmiana jest od truecelu false, można wymienić include = false;z return false;jako końcowy wynik został ustalony już. Następnie return include;na końcu można zastąpić return true;i usunąć deklarację zmiennej. A ponieważ directoryRecordnigdy nie zmienia się w pętli, możesz przenieść Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());przed pętlą (i zmienić Integerna int).
Holger

Odpowiedzi:

9

Zrobiłbym to z Rangeklasą niestandardową , na przykład:

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

Co sprawi, że coś takiego będzie możliwe:

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

Osobiście uważam, że twój pierwszy filtr jest wystarczająco dobry, aby go zachować.

ernest_k
źródło
2
Czy nie powinno tak być, noneMatchkiedy mówimy rangesToExclude? I przypuszczam, że mogłoby być jeszcze bardziej eleganckie rozwiązanie z TreeSet<Range>
Holger
Rzeczywiście powinienem był być senny.
ernest_k
@ernest_k Dziękujemy za rozwiązanie. Uważam to za bardzo eleganckie.
Yadvendra Rathore
4

Sugerowałbym podobną do odpowiedzi ernest_k z Range.

Ale w tym podejściu możesz użyć obu kolekcji do utworzenia List<Range>( "20"można to potraktować jako "20-20") i zmienić warunek filtru, aby użyć negacji 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;
    }
}

AKTUALIZACJA

Utworzenie List<Range> rangesmożna zmienić, aby usunąć punkty, Set<String> extensionsToExcludektóre znajdują się w utworzonym zakresie List<String> rangesToExclud. Wówczas niepotrzebne zakresy nie zostaną utworzone.

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);
Łczapski
źródło
0

możesz zrobić wczesną przerwę, jeśli warunek zakresu jest prawdziwy, zamiast czekać na ocenę wszystkich wpisów.

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

w przeciwnym razie po prostu zwróć false po pętli for.

Angel Koh
źródło