¿Es posible ignorar algunos elementos durante Collectors.groupingBy o Collectors.partitioningBy ?
Por supuesto, sé que puedo colocar un .filter() en cualquier lugar de una secuencia. Pero mi problema es que tengo que ejecutar una evaluación bastante compleja y costosa para decidir en qué "grupo" se deben dividir mis objetos.
Además, siempre hay muchos objetos que me gustaría descuidar durante la recolección.
Ejemplo: imagina una List<Foo> , que quiero dividir en 2 listas. Eso es fácil, pero ¿cómo puedo ignorar adicionalmente todos los objetos que no encajan en mi condición evaluada?
var map = foos.stream().collect(Collectors.groupingBy( foo -> { int bar = complexEvaluation(foo); if (bar > 1000) return true; if (bar < 0) return false; //TODO how to neglect the rest between 0-1000 }, Collectors.mapping(foo -> foo.id, Collectors.toSet()) ));Para reutilizar el resultado de complexE Evaluation tanto en filter como en groupingBy , puede llamar antes de filtrar y almacenar el resultado en una clase contenedora.
foos.stream() .map(foo -> { int bar = complexEvaluation(foo); if (bar > 1000) Pair.of(foo, true); if (bar < 0) Pair.of(foo, false); return Pair.of(foo, null); ) .filter(fooPair -> fooPair.getRight() != null) .collect(Collectors.groupingBy( Pair::getRight(), Collectors.mapping(fooPair -> fooPair.getLeft().id, Collectors.toSet() ); Sin embargo, esto es solo si insiste en usar groupingBy por alguna razón.
Una alternativa con foreach sería mucho más fácil de leer:
Map<Boolean, Set<Foo>> groups = new HashMap<>(); foos.stream() .forEach(foo -> { int bar = complexEvaluation(foo); if (bar > 1000) groups.computeIfAbsent(true, k->new HashSet<>()).add(foo); if (bar < 0) groups.computeIfAbsent(false, k->new HashSet<>()).add(foo); })Si entiendo su intención correctamente, desea filtrar algunos valores mientras los recopila. Es factible con Collectors.filtering() .
Tenga en cuenta que el filtering() puede eliminar todos los valores de algunos depósitos, pero no hará que se eliminen los depósitos vacíos. En el siguiente ejemplo, los cubos para 1, 2, 3, 4, 5 estarán vacíos.
public static void main(String[] args) { var foos = List.of(-100, 1, 2, 3, 4, 5, 1001, 1002, 1003); var map = foos.stream() .collect(Collectors.groupingBy( UnaryOperator.identity(), Collectors.filtering(foo -> foo < 0 || foo > 1000, Collectors.toSet()))); System.out.println(map); }mapa
{1=[], 2=[], 3=[], -100=[-100], 4=[], 5=[], 1001=[1001], 1002=[1002], 1003=[1003]}ACTUALIZAR
Revisé este problema y la solución proporcionada a continuación calcula los valores de antemano.
En esta versión, las entradas no deseadas se filtran en la secuencia, lo que hace que el código dentro de Collector sea más fácil de leer. Espero que en lugar de foo objects se use un número entero no sea un problema.
public static void main(String[] args) { List<Integer> foos = List.of(-100, 1, 2, 3, 4, 5, 101, 102, 103); Map<Integer, Integer> fooToValue = getFooToValueMap(foos); Map<Boolean, Set<Integer>> map = getFoosMap(fooToValue); System.out.println(map); } private static Map<Boolean, Set<Integer>> getFoosMap(Map<Integer, Integer> fooToValue) { return fooToValue.entrySet().stream() .filter(entry -> entry.getValue() < 0 || entry.getValue() > 1000) .collect(Collectors.partitioningBy( entry -> entry.getValue() > 1000, Collectors.mapping(Map.Entry::getKey, Collectors.toSet()) )); } private static Map<Integer, Integer> getFooToValueMap(List<Integer> list) { return list.stream() .collect(Collectors.toMap(UnaryOperator.identity(), foo -> complexEvaluation(foo))); } private static int complexEvaluation(int foo) { return (int) Math.signum(foo) * foo * foo; }mapa
{false=[-100], true=[101, 102, 103]}Simplemente use una enum para definir sus 3 casos:
enum Categories { HIGH, LOW, NEGATIVE } var map = foos.stream().collect(Collectors.groupingBy( foo -> { int bar = complexEvaluation(foo); if (bar > 1000) return HIGH; if (bar < 0) return NEGATIVE; return LOW; }, Collectors.mapping(foo -> foo.id, Collectors.toSet()) )); Luego ignore o elimine LOW si no lo necesita. También tiene los beneficios adicionales de dar más significado a sus categorías en lugar de simplemente nombrarlas true / false , y facilita la refactorización si necesita más categorías en el futuro.
El único inconveniente es que crea un conjunto LOW inútil, pero eso solo es un problema si es realmente grande en comparación con los otros conjuntos y la operación complexEvaluation() .
Si desea evitar el almacenamiento temporal, debe implementar su propio recopilador:
var map = foos.parallelStream().collect( () -> Map.of(true, new HashSet<ID>(), false, new HashSet<ID>()), (o, foo) -> { int bar = complexEvaluation(foo); ID id = foo.id; if (bar > 1000) o.get(true).add(id); else if (bar < 0) o.get(false).add(id); }, (a, b) -> { a.get(true).addAll(b.get(true)); a.get(false).addAll(b.get(false)); } ); Este ejemplo tiene el mismo comportamiento que partitioningBy , siempre creando entradas para true y false .
ID es un marcador de posición para el tipo de foo.id que no incluyó en la pregunta.