Java Stream: Filter dengan beberapa rentang

9

Saya mencoba memfilter sumber daya dan mengecualikan beberapa elemen berdasarkan bidang. Untuk mengecualikan saya memiliki satu set (yang berisi id yang perlu dikecualikan) dan daftar (itu berisi beberapa rentang id yang perlu dikecualikan). Saya menulis logika di bawah ini dan saya tidak puas dengan logika filter ke-2. Apakah ada cara yang lebih baik untuk melakukannya dengan Java 8? Saya perlu melakukan hal yang sama untuk memasukkan rentang juga.

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

Terima kasih :)

Yadvendra Rathore
sumber
3
Jangan gunakan Booleanobjek saat Anda hanya perlu booleannilai. Meskipun di sini, variabelnya includesepenuhnya usang. Ketika satu-satunya perubahan yang mungkin adalah dari trueke false, Anda dapat menggantinya include = false;dengan return false;karena hasil akhirnya telah ditentukan. Kemudian, return include;pada akhirnya dapat diganti dengan return true;dan deklarasi variabel dihapus. Dan karena directoryRecordtidak pernah berubah dalam loop, Anda dapat memindahkan Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());sebelum loop (dan mengubah Integerke int).
Holger

Jawaban:

9

Saya akan melakukannya dengan Rangekelas khusus , seperti:

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

Yang akan memungkinkan hal seperti ini:

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

Saya pribadi menemukan filter pertama Anda cukup baik untuk dilestarikan apa adanya.

ernest_k
sumber
2
Bukankah seharusnya noneMatchketika kita berbicara tentang rangesToExclude? Dan saya kira, mungkin ada solusi yang lebih elegan dengan TreeSet<Range>...
Holger
Seharusnya memang, aku pasti mengantuk.
ernest_k
@ernest_k Terima kasih atas solusinya. Saya merasa sangat elegan.
Yadvendra Rathore
4

Saya akan menyarankan mirip dengan jawaban ernest_k dengan Range.

Namun dalam pendekatan ini Anda dapat menggunakan kedua koleksi untuk membuat List<Range>(ini "20"dapat diperlakukan sebagai "20-20") dan mengubah kondisi filter untuk menggunakan negasi 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;
    }
}

MEMPERBARUI

Pembuatan List<Range> rangesdapat diubah untuk menghapus titik dari Set<String> extensionsToExcludeyang ada dalam rentang yang dibuat List<String> rangesToExclud. Maka rentang yang tidak perlu tidak akan dibuat.

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

Anda dapat melakukan jeda lebih awal jika kondisi rentang benar, daripada menunggu semua entri dievaluasi.

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

jika tidak, kembalikan saja false setelah for loop.

Angel Koh
sumber