Java Stream: фильтр с несколькими диапазонами

9

Я пытаюсь отфильтровать ресурс и исключить некоторые элементы на основе поля. Для исключения у меня есть набор (который содержит идентификатор, который должен быть исключен) и список (он содержит несколько диапазонов идентификаторов, которые необходимо исключить). Я написал приведенную ниже логику, и меня не устраивает 2-я логика фильтра. Есть ли лучший способ сделать это с помощью Java 8? Мне нужно сделать то же самое для включения диапазонов.

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

Спасибо :)

Ядвендра Раторе
источник
3
Не используйте Booleanобъекты, когда вам просто нужно booleanзначение. Хотя здесь, переменная includeполностью устарела. Если единственное возможное изменение - от trueна false, вы можете заменить include = false;на, так return false;как конечный результат уже определен. Затем return include;в конце можно заменить return true;и объявление переменной удалить. И поскольку directoryRecordв цикле никогда не происходит изменений, вы можете переместить Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());цикл перед циклом (и перейти Integerк нему int).
Хольгер

Ответы:

9

Я бы сделал это с пользовательским Rangeклассом, что-то вроде:

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

Что сделает что-то подобное возможным:

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

Я лично считаю ваш первый фильтр достаточно хорошим, чтобы его можно было сохранить как есть.

ernest_k
источник
2
Разве это не должно быть, noneMatchкогда мы говорим о rangesToExclude? И я полагаю, что может быть еще более элегантное решение с TreeSet<Range>
Хольгер
Должно быть, я действительно был сонным.
ernest_k
@ernest_k Спасибо за решение. Я нахожу это действительно элегантным.
Ядвендра Раторе
4

Я бы предложил аналогично ответу ernest_k с Range.

Но в этом подходе вы можете использовать как коллекцию для создания List<Range>(это "20"можно рассматривать как "20-20"), так и изменить условие фильтра, чтобы использовать отрицание с 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;
    }
}

ОБНОВИТЬ

Создание List<Range> rangesможно изменить, чтобы удалить точки из Set<String> extensionsToExcludeдиапазона, созданного из List<String> rangesToExclud. Тогда ненужные диапазоны не будут созданы.

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
источник
0

Вы можете сделать ранний перерыв, если условие диапазона истинно, вместо того, чтобы ждать оценки всех записей.

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

в противном случае просто верните false после цикла for.

Ангел Ко
источник