multiple ejemplo conditions java collections lambda java-8 java-stream

ejemplo - java stream filter multiple conditions



¿Cómo forzar max() para devolver TODOS los valores máximos en una secuencia Java? (5)

Creo que el OP está utilizando un Comparador para dividir la entrada en clases de equivalencia, y el resultado deseado es una lista de miembros de la clase de equivalencia que es el máximo según ese Comparador.

Desafortunadamente, el uso de valores int como un problema de muestra es un ejemplo terrible. Todos los valores int iguales son fungibles, por lo que no existe la noción de preservar el orden de los valores equivalentes. Quizás un mejor ejemplo es usar longitudes de cadena, donde el resultado deseado es devolver una lista de cadenas de una entrada que tengan la longitud más larga dentro de esa entrada.

No conozco ninguna forma de hacer esto sin almacenar al menos resultados parciales en una colección.

Dada una colección de entrada, digamos

List<String> list = ... ;

es lo suficientemente simple como para hacer esto en dos pasadas, la primera para obtener la longitud más larga y la segunda para filtrar las cadenas que tienen esa longitud:

int longest = list.stream() .mapToInt(String::length) .max() .orElse(-1); List<String> result = list.stream() .filter(s -> s.length() == longest) .collect(toList());

Si la entrada es una secuencia, que no puede atravesarse más de una vez , es posible calcular el resultado en una sola pasada utilizando un recopilador. Escribir un coleccionista de este tipo no es difícil, pero es un poco tedioso ya que hay varios casos que deben manejarse. Una función auxiliar que genera dicho recopilador, dado un comparador, es la siguiente:

static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) { return Collector.of( ArrayList::new, (list, t) -> { int c; if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) { list.add(t); } else if (c > 0) { list.clear(); list.add(t); } }, (list1, list2) -> { if (list1.isEmpty()) { return list2; } if (list2.isEmpty()) { return list1; } int r = comp.compare(list1.get(0), list2.get(0)); if (r < 0) { return list2; } else if (r > 0) { return list1; } else { list1.addAll(list2); return list1; } }); }

Esto almacena resultados intermedios en una ArrayList . Lo invariable es que todos los elementos dentro de cualquier lista son equivalentes en términos del Comparador. Al agregar un elemento, si es menor que los elementos en la lista, se ignora; si es igual, se agrega; y si es mayor, la lista se vacía y se agrega el nuevo elemento. La fusión tampoco es demasiado difícil: se devuelve la lista con los elementos más grandes, pero si sus elementos son iguales, se añaden las listas.

Dado un flujo de entrada, esto es bastante fácil de usar:

Stream<String> input = ... ; List<String> result = input.collect(maxList(comparing(String::length)));

He probado un poco la función max () en Java 8 lambdas y streams, y parece que en caso de que max () se ejecute, incluso si más de un objeto se compara con 0, devuelve un elemento arbitrario dentro de los candidatos vinculados sin para mayor consideracion.

¿Existe algún truco o función evidente para un comportamiento esperado máximo de modo que se devuelvan todos los valores máximos? No veo nada en la API, pero estoy seguro de que debe existir algo mejor que compararlo manualmente.

Por ejemplo:

//myComparator is an IntegerComparator Stream.of(1,3,5,3,2,3,5).max(myComparator).forEach(System.out::println); //Would print 5,5 in any order.


Implementé una solución de recopilador más genérica con un recopilador descendente personalizado. Probablemente algunos lectores lo encuentren útil:

public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator, Collector<? super T, A, D> downstream) { Supplier<A> downstreamSupplier = downstream.supplier(); BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator(); BinaryOperator<A> downstreamCombiner = downstream.combiner(); class Container { A acc; T obj; boolean hasAny; Container(A acc) { this.acc = acc; } } Supplier<Container> supplier = () -> new Container(downstreamSupplier.get()); BiConsumer<Container, T> accumulator = (acc, t) -> { if(!acc.hasAny) { downstreamAccumulator.accept(acc.acc, t); acc.obj = t; acc.hasAny = true; } else { int cmp = comparator.compare(t, acc.obj); if (cmp > 0) { acc.acc = downstreamSupplier.get(); acc.obj = t; } if (cmp >= 0) downstreamAccumulator.accept(acc.acc, t); } }; BinaryOperator<Container> combiner = (acc1, acc2) -> { if (!acc2.hasAny) { return acc1; } if (!acc1.hasAny) { return acc2; } int cmp = comparator.compare(acc1.obj, acc2.obj); if (cmp > 0) { return acc1; } if (cmp < 0) { return acc2; } acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc); return acc1; }; Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc); return Collector.of(supplier, accumulator, combiner, finisher); }

Entonces, por defecto, se puede recopilar para enumerar:

public static <T> Collector<T, ?, List<T>> maxAll(Comparator<? super T> comparator) { return maxAll(comparator, Collectors.toList()); }

Pero también puede usar otros colectores posteriores:

public static String joinLongestStrings(Collection<String> input) { return input.stream().collect( maxAll(Comparator.comparingInt(String::length), Collectors.joining(",")))); }


No estoy realmente seguro de si estás tratando de

  • (a) encuentre el número de ocurrencias del artículo máximo, o
  • (b) Encuentre todos los valores máximos en el caso de un Comparator que no sea consistente con equals .

Un ejemplo de (a) sería [1, 5, 4, 5, 1, 1] -> [5, 5] .

Un ejemplo de (b) sería:

Stream.of("Bar", "FOO", "foo", "BAR", "Foo") .max((s, t) -> s.toLowerCase().compareTo(t.toLowerCase()));

que desea dar [Foo, foo, Foo] , en lugar de solo FOO u Optional[FOO] .

En ambos casos, hay formas inteligentes de hacerlo en una sola pasada. Pero estos enfoques tienen un valor dudoso porque necesitaría realizar un seguimiento de la información innecesaria en el camino. Por ejemplo, si comienza con [2, 0, 2, 2, 1, 6, 2] , solo cuando llegue a 6 se dará cuenta de que no es necesario hacer un seguimiento de todos los 2 s.

Creo que el mejor enfoque es el obvio; use max , y luego repita los elementos nuevamente poniendo todos los lazos en una colección de su elección. Esto funcionará tanto para (a) como para (b).


Si entendí bien, desea la frecuencia del valor max en la secuencia.

Una forma de lograrlo sería almacenar los resultados en un TreeMap<Integer, List<Integer> cuando recopile elementos del Stream. Luego, toma la última clave (o la primera según el comparador que proporcione) para obtener el valor que contendrá la lista de valores máximos.

List<Integer> maxValues = st.collect(toMap(i -> i, Arrays::asList, (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()), TreeMap::new)) .lastEntry() .getValue();

Recogerlo de la Stream(4, 5, -2, 5, 5) le dará una List [5, 5, 5] .

Otro enfoque con el mismo espíritu sería utilizar un grupo por operación combinado con el colector counting() :

Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i, TreeMap::new, counting())).lastEntry(); //5=3 -> 5 appears 3 times

Básicamente, primero obtienes un Map<Integer, List<Integer>> . Luego, el recopilador de counting() descendente counting() devolverá el número de elementos en cada lista asignada por su clave, lo que dará como resultado un Mapa. Desde allí tomas la entrada máxima.

Los primeros enfoques requieren almacenar todos los elementos de la secuencia. El segundo es mejor (ver el comentario de Holger) ya que la List intermedia no está construida. En ambos abordados, el resultado se calcula en una sola pasada.

Si obtiene la fuente de una colección, es posible que desee usar Collections.max una vez para encontrar el valor máximo seguido de Collections.frequency para encontrar cuántas veces aparece este valor.

Requiere dos pases pero usa menos memoria ya que no tiene que construir la estructura de datos.

El flujo equivalente sería coll.stream().max(...).get(...) seguido de coll.stream().filter(...).count() .


TreeMap por valor y almacenaría los valores en un TreeMap para ordenar mis valores, luego obtendría el valor máximo obteniendo la última entrada como la siguiente:

Stream.of(1, 3, 5, 3, 2, 3, 5) .collect(groupingBy(Function.identity(), TreeMap::new, toList())) .lastEntry() .getValue() .forEach(System.out::println);

Salida:

5 5