tipos - polimorfismo java
Transmisión Java 8+: compruebe si la lista está en el orden correcto para dos campos de mis instancias de objeto (7)
Aquí está la solución por pairMap
en StreamEx
StreamEx.of(1, 2, 3, 5).pairMap((a, b) -> a <= b).allMatch(e -> e); // true
StreamEx.of(1, 2, 5, 3).pairMap((a, b) -> a <= b).allMatch(e -> e); // false
// your example:
StreamEx.of(myList)
.pairMap((a, b) -> a.getPercentage().compareTo(b.getPercentage()) <= 0 && !a.getDate().after(b.getDate()))
.allMatch(e -> e);
El título puede ser un poco vago, pero esto es lo que tengo (en código privatizado):
Una clase con algunos campos, incluyendo un BigDecimal y una Fecha:
class MyObj{
private java.math.BigDecimal percentage;
private java.util.Date date;
// Some more irrelevant fields
// Getters and Setters
}
En otra clase tengo una lista de estos objetos (es decir, java.util.List<MyObj> myList
). Lo que quiero ahora es un flujo de Java 8 para verificar si la lista está en el orden correcto de fechas y porcentajes para mi validador.
Por ejemplo, la siguiente lista sería veraz:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
Pero esta lista sería falsey porque el porcentaje no está en el orden correcto:
[ MyObj { percentage = 25, date = 01-01-2018 },
MyObj { percentage = 20, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
Y esta lista también sería falsey porque las fechas no están en el orden correcto:
[ MyObj { percentage = 25, date = 10-03-2018 },
MyObj { percentage = 50, date = 01-02-2018 },
MyObj { percentage = 100, date = 15-04-2019 } ]
Una posible solución podría ser crear Pairs
como este y luego usar un !
y .anyMatch
revisando cada Pair<MyObj>
individual Pair<MyObj>
. Pero realmente no quiero crear una clase Pair
solo para este propósito si es posible.
¿Hay tal vez una forma de usar .reduce
o algo para recorrer en pares de MyObj
para verificarlos? ¿Cuál sería el mejor enfoque aquí para verificar si todas las fechas y porcentajes de MyObj
en mi lista están en el orden correcto utilizando Java 8 stream?
Otra posibilidad es quizás ordenar la lista por fecha y luego verificar si están todas en orden de porcentaje, si eso es más fácil que verificar que ambos campos estén al mismo tiempo. El mismo problema con la comparación de pares de MyObj
para el porcentaje aún permanece, sin embargo.
(PD: lo com.vaadin.server.SerializablePredicate<MyObj> validator
para un com.vaadin.server.SerializablePredicate<MyObj> validator
, y prefiero un Java 8 lambda porque también he usado algunos para los otros validadores, por lo que estaría más en línea con el resto del código. El Java 8 lambda es más una preferencia que un requisito en mi pregunta, sin embargo.)
Bueno, si desea una operación de cortocircuito, no creo que exista una solución fácil utilizando stream-api ... Propongo una más simple, primero defina un método que le dirá si su Lista es ordenado o no, basado en algún parámetro:
private static <T, R extends Comparable<? super R>> boolean isSorted(List<T> list, Function<T, R> f) {
Comparator<T> comp = Comparator.comparing(f);
for (int i = 0; i < list.size() - 1; ++i) {
T left = list.get(i);
T right = list.get(i + 1);
if (comp.compare(left, right) >= 0) {
return false;
}
}
return true;
}
Y llamándolo a través de:
System.out.println(
isSorted(myList, MyObj::getPercentage) &&
isSorted(myList, MyObj::getDate));
Como mencionó que no querría crear una clase de Pairs
separada para esto, puede usar una clase incorporada para tal propósito: AbstractMap.SimpleEntry
.
Puedes hacer un BiPredicate
que verifique tus condiciones de comparación y usarlo para comparar todos los pares.
BiPredicate<MyObj,MyObj> isIncorrectOrder = (o1,o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? wrongOrder : o1.getPercentage().compareTo(o2.getPercentage()) > 0;
};
boolean isNotSorted = IntStream.range(1,myObjs.size())
.anyMatch(i -> isIncorrectOrder.test(myObjs.get(i-1),myObjs.get(i)));
La solución anterior con comparador:
Comparator<MyObj> comparator = (o1, o2) -> {
boolean wrongOrder = o1.getDate().after(o2.getDate());
return wrongOrder ? 1 : o1.getPercentage().compareTo(o2.getPercentage());
};
Predicate<AbstractMap.SimpleEntry<MyObj,MyObj>> isIncorrectOrder = pair -> comparator.compare(pair.getKey(),pair.getValue()) > 0;
boolean isNotSorted = IntStream.range(1,myObjs.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<>(myObjs.get(i-1),myObjs.get(i)))
.anyMatch(isIncorrectOrder);
Creo que casi has Stream.anyMatch
a intentar usar Stream.anyMatch
. Puedes lograrlo así:
private static boolean isNotOrdered(List<MyObj> myList) {
return IntStream.range(1, myList.size()).anyMatch(i -> isNotOrdered(myList.get(i - 1), myList.get(i)));
}
private static boolean isNotOrdered(MyObj before, MyObj after) {
return before.getPercentage().compareTo(after.getPercentage()) > 0 ||
before.getDate().compareTo(after.getDate()) > 0;
}
Podemos usar IntStream.range
para iterar sobre los elementos de la lista usando un índice. De esta manera podemos referirnos a cualquier elemento de la lista, por ejemplo, el anterior para compararlo.
EDITAR añadiendo una versión más genérica:
private static boolean isNotOrderedAccordingTo(List<MyObj> myList, BiPredicate<MyObj, MyObj> predicate) {
return IntStream.range(1, myList.size()).anyMatch(i-> predicate.test(myList.get(i - 1), myList.get(i)));
}
Esto se puede llamar de la siguiente manera usando el predicado anterior:
isNotOrderedAccordingTo(myList1, (before, after) -> isNotOrdered(before, after));
O usando referencia de método en una clase ListNotOrdered
:
isNotOrderedAccordingTo(myList1, ListNotOrdered::isNotOrdered)
De manera similar a la respuesta de @luis g., También puede usar reduce
en combinación con Optional
(vacío significa sin clasificar) y un MyObj
"mínimo" como la identidad:
boolean isSorted = list.stream()
.map(Optional::of)
.reduce(Optional.of(new MyObj(BigDecimal.ZERO, Date.from(Instant.EPOCH))),
(left, right) -> left.flatMap(l -> right.map(r -> l.date.compareTo(r.date)<= 0 && l.percentage.compareTo(r.percentage) <= 0 ? r : null)))
.isPresent();
Tenga en cuenta que la función de acumulación ( BinaryOperator
) debe ser asociativa, que no es en este caso. Además tampoco es cortocircuito.
No creo que este sea un problema que deba resolverse utilizando streams. Los flujos aplican asignaciones y filtros a los elementos de una colección de forma independiente (probablemente incluso distribuyendo el tratamiento de diferentes elementos a diferentes núcleos de CPU) antes de recopilarlos en una nueva colección de nuevo o reducirlos a algún tipo de valor acumulado. Su problema implica relaciones entre diferentes elementos de una colección que contradice el propósito de una transmisión. Si bien puede haber soluciones que involucren corrientes, sería como clavar un clavo en la pared con unos alicates. Un bucle clásico estará perfectamente bien para usted: ¡encuentre la primera aparición de un elemento que rompe el orden y devuelva el resultado deseado! Por lo tanto, ni siquiera necesitarías crear un par.
Sí, puede usar reduce
para comparar dos elementos (aunque no es la mejor opción). Solo necesita crear un nuevo artículo "vacío" como resultado cuando encuentre un artículo fuera de orden, como este:
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> {
if ((a.percentage == null && a.date == null) || // previous item already failed
a.percentage.compareTo(b.percentage) > 0 || // pct out of order
a.date.after(b.date)) { // date out of order
return new MyObj(null, null); // return new empty MyObj
} else {
return b;
}
})
.filter(a -> a.percentage != null || a.date != null)
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
Esto se imprimirá true
solo si se satisfacen ambas condiciones, false
contrario
Por supuesto, puede hacer que el código sea más agradable si incluye algunos métodos auxiliares en MyObj
:
class MyObj {
// ...
public MyObj() {}
public static MyObj EMPTY = new MyObj();
public boolean isEmpty() {
return percentage == null && date == null;
}
public boolean comesAfter(MyObj other) {
return this.percentage.compareTo(other.percentage) > 0 ||
this.date.after(other.date);
}
}
// ...
boolean fullyOrdered = myList.stream()
.reduce((a, b) -> (a.isEmpty() || a.comesAfter(b)) ? MyObj.EMPTY : b)
.filter(b -> !b.isEmpty())
.isPresent();
System.out.println("fullyOrdered = " + fullyOrdered);
Tenga en cuenta que esto no es un cortocircuito, es decir, recorrerá toda la lista incluso después de que encuentre un elemento sin ordenar. La única manera de salir del flujo temprano mientras usa reduce
sería lanzar una excepción RuntimeException
el momento en que encuentre el primer elemento desordenado, y eso sería usar excepciones para controlar el flujo del programa, lo que se considera una mala práctica. Aún así, quería mostrarte que puedes usar reduce
para este propósito.
Para un enfoque de cortocircuito que terminará tan pronto como encuentre el primer elemento fuera de lugar, eche un vistazo a la respuesta de @ LuCio.