Java Stream: filtro con múltiples rangos

9

Estoy tratando de filtrar un recurso y excluir algunos elementos basados ​​en un campo. Para excluir, tengo un conjunto (que contiene una identificación que debe excluirse) y una lista (contiene múltiples rangos de identificadores que deben excluirse). Escribí la lógica a continuación y no estoy satisfecho con la lógica del segundo filtro. ¿Hay alguna manera mejor de hacerlo con Java 8? Necesito hacer lo mismo para incluir rangos también.

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

Gracias :)

Yadvendra Rathore
fuente
3
No use Booleanobjetos cuando solo necesite un booleanvalor. Aunque aquí, la variable includees completamente obsoleta. Cuando el único cambio posible es de truea false, puede reemplazar include = false;con return false;ya que el resultado final ya se ha determinado. Entonces, return include;al final puede ser reemplazado por return true;y la declaración de variable eliminada. Y como directoryRecordnunca cambia en el bucle, puede mover Integer extension = Integer.parseInt(directoryRecord.getExtensionNumber());antes del bucle (y cambiar Integera int).
Holger

Respuestas:

9

Lo haría con una Rangeclase personalizada , algo como:

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

Lo que hará posible algo como esto:

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

Personalmente, creo que su primer filtro es lo suficientemente bueno como para preservarlo tal como está.

ernest_k
fuente
2
¿No debería ser noneMatchcuando estamos hablando rangesToExclude? Y supongo que podría haber una solución aún más elegante con un TreeSet<Range>...
Holger
De hecho, debería haber tenido sueño.
ernest_k
@ernest_k Gracias por la solución. Me parece muy elegante.
Yadvendra Rathore
4

Sugeriría una respuesta similar a la de ernest_k con Range.

Pero en este enfoque puede usar tanto la colección para crear List<Range>(esto "20"se puede tratar como "20-20") y cambiar la condición del filtro para usar la negación 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;
    }
}

ACTUALIZAR

La creación de List<Range> rangesse puede cambiar para eliminar puntos de los Set<String> extensionsToExcludeque están en el rango creado a partir de List<String> rangesToExclud. Entonces no se crearán rangos innecesarios.

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

puede hacer un descanso temprano si la condición del rango es verdadera, en lugar de esperar a que se evalúen todas las entradas.

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

de lo contrario, solo devuelve false después del ciclo for.

Angel Koh
fuente