resueltos programación programacion orientada objetos metodos herencia ejercicios ejemplos clases atributos java java-8 grouping java-stream

java - programación - Agrupando por valor de objeto, contando y luego configurando la clave de grupo por atributo de objeto máximo



programacion orientada a objetos ejemplos c++ (4)

Me las arreglé para escribir una solución usando la API de Java 8 Streams que primero agrupa una lista de ruta de objetos por su valor y luego cuenta el número de objetos en cada grupo. Devuelve una ruta de mapeo -> Long. Aquí está el código:

Map<Route, Long> routesCounted = routes.stream() .collect(Collectors.groupingBy(gr -> gr, Collectors.counting()));

Y la clase de ruta:

public class Route implements Comparable<Route> { private long lastUpdated; private Cell startCell; private Cell endCell; private int dropOffSize; public Route(Cell startCell, Cell endCell, long lastUpdated) { this.startCell = startCell; this.endCell = endCell; this.lastUpdated = lastUpdated; } public long getLastUpdated() { return this.lastUpdated; } public void setLastUpdated(long lastUpdated) { this.lastUpdated = lastUpdated; } public Cell getStartCell() { return startCell; } public void setStartCell(Cell startCell) { this.startCell = startCell; } public Cell getEndCell() { return endCell; } public void setEndCell(Cell endCell) { this.endCell = endCell; } public int getDropOffSize() { return this.dropOffSize; } public void setDropOffSize(int dropOffSize) { this.dropOffSize = dropOffSize; } @Override /** * Compute hash code by using Apache Commons Lang HashCodeBuilder. */ public int hashCode() { return new HashCodeBuilder(43, 59) .append(this.startCell) .append(this.endCell) .toHashCode(); } @Override /** * Compute equals by using Apache Commons Lang EqualsBuilder. */ public boolean equals(Object obj) { if (!(obj instanceof Route)) return false; if (obj == this) return true; Route route = (Route) obj; return new EqualsBuilder() .append(this.startCell, route.startCell) .append(this.endCell, route.endCell) .isEquals(); } @Override public int compareTo(Route route) { if (this.dropOffSize < route.dropOffSize) return -1; else if (this.dropOffSize > route.dropOffSize) return 1; else { // if contains drop off timestamps, order by last timestamp in drop off // the highest timestamp has preceding if (this.lastUpdated < route.lastUpdated) return -1; else if (this.lastUpdated > route.lastUpdated) return 1; else return 0; } } }

Lo que me gustaría lograr adicionalmente es que la clave para cada grupo sea la que tenga el mayor valor LastUpdated. Ya estaba buscando esta solución, pero no sé cómo combinar el recuento y la agrupación por valor y el valor de Última actualización máxima de ruta. Aquí están los datos de ejemplo de lo que quiero lograr:

EJEMPLO:

List<Route> routes = new ArrayList<>(); routes.add(new Route(new Cell(1, 2), new Cell(2, 1), 1200L)); routes.add(new Route(new Cell(3, 2), new Cell(2, 5), 1800L)); routes.add(new Route(new Cell(1, 2), new Cell(2, 1), 1700L));

DEBE SER CONVERTIDO EN:

Map<Route, Long> routesCounted = new HashMap<>(); routesCounted.put(new Route(new Cell(1, 2), new Cell(2, 1), 1700L), 2); routesCounted.put(new Route(new Cell(3, 2), new Cell(2, 5), 1800L), 1);

Observe que la clave para el mapeo, que contó 2 Rutas, es la que tiene el mayor valor LastUpdated .


Aquí hay un enfoque. Primero agrupe en listas y luego procese las listas en los valores que realmente desea:

import static java.util.Comparator.comparingLong; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toMap; Map<Route,Integer> routeCounts = routes.stream() .collect(groupingBy(x -> x)) .values().stream() .collect(toMap( lst -> lst.stream().max(comparingLong(Route::getLastUpdated)).get(), List::size ));


Cambió igual y hashcode para depender solo de la celda inicial y la celda final.

@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Cell cell = (Cell) o; if (a != cell.a) return false; if (b != cell.b) return false; return true; } @Override public int hashCode() { int result = a; result = 31 * result + b; return result; }

Mi solución se ve así:

Map<Route, Long> routesCounted = routes.stream() .sorted((r1,r2)-> (int)(r2.lastUpdated - r1.lastUpdated)) .collect(Collectors.groupingBy(gr -> gr, Collectors.counting()));

Por supuesto, el envío a int debe reemplazarse con algo más apropiado.


En principio, parece que esto debería ser posible en una sola pasada. La arruga habitual es que esto requiere una tupla o par ad-hoc, en este caso con una Route y un conteo. Como Java carece de estos, terminamos usando una matriz de objetos de longitud 2 (como se muestra en la respuesta de Tagir Valeev ), o AbstractMap.SimpleImmutableEntry , o una hipotética clase Pair<A,B> .

La alternativa es escribir una pequeña clase de valor que contenga una Route y un conteo. Por supuesto, hay algo de dolor en hacer esto, pero en este caso creo que vale la pena porque proporciona un lugar para colocar la lógica de combinación. Eso a su vez simplifica la operación de transmisión.

Aquí está la clase de valor que contiene una Route y un recuento:

class RouteCount { final Route route; final long count; private RouteCount(Route r, long c) { this.route = r; count = c; } public static RouteCount fromRoute(Route r) { return new RouteCount(r, 1L); } public static RouteCount combine(RouteCount rc1, RouteCount rc2) { Route recent; if (rc1.route.getLastUpdated() > rc2.route.getLastUpdated()) { recent = rc1.route; } else { recent = rc2.route; } return new RouteCount(recent, rc1.count + rc2.count); } }

Bastante sencillo, pero observe el método combine . Combina dos valores de RouteCount eligiendo la Route que se ha actualizado más recientemente y utilizando la suma de los recuentos. Ahora que tenemos esta clase de valor, podemos escribir una secuencia de un paso para obtener el resultado que queremos:

Map<Route, RouteCount> counted = routes.stream() .collect(groupingBy(route -> route, collectingAndThen( mapping(RouteCount::fromRoute, reducing(RouteCount::combine)), Optional::get)));

Al igual que otras respuestas, esto agrupa las rutas en clases de equivalencia basadas en la celda inicial y final. La instancia de Route real utilizada como clave no es significativa; Es solo un representante de su clase. El valor será un solo RouteCount que contiene la instancia de Route que se actualizó más recientemente, junto con el recuento de instancias de Route equivalentes.

La forma en que esto funciona es que cada instancia de Route que tiene las mismas celdas de inicio y final se alimenta al recopilador aguas abajo de groupingBy . Este recopilador de mapping asigna la instancia de Route a una instancia de RouteCount , luego la pasa a un recopilador reducing que reduce las instancias utilizando la lógica de combinación descrita anteriormente. La porción y luego de Optional<RouteCount> extrae el valor del Optional<RouteCount> que produce el recopilador reducing .

(Normalmente, un get desnudo es peligroso, pero no llegamos a este recopilador en absoluto a menos que haya al menos un valor disponible. Así que get es seguro en este caso).


Puede definir un método abstracto de "biblioteca" que combine dos recopiladores en uno:

static <T, A1, A2, R1, R2, R> Collector<T, ?, R> pairing(Collector<T, A1, R1> c1, Collector<T, A2, R2> c2, BiFunction<R1, R2, R> finisher) { EnumSet<Characteristics> c = EnumSet.noneOf(Characteristics.class); c.addAll(c1.characteristics()); c.retainAll(c2.characteristics()); c.remove(Characteristics.IDENTITY_FINISH); return Collector.of(() -> new Object[] {c1.supplier().get(), c2.supplier().get()}, (acc, v) -> { c1.accumulator().accept((A1)acc[0], v); c2.accumulator().accept((A2)acc[1], v); }, (acc1, acc2) -> { acc1[0] = c1.combiner().apply((A1)acc1[0], (A1)acc2[0]); acc1[1] = c2.combiner().apply((A2)acc1[1], (A2)acc2[1]); return acc1; }, acc -> { R1 r1 = c1.finisher().apply((A1)acc[0]); R2 r2 = c2.finisher().apply((A2)acc[1]); return finisher.apply(r1, r2); }, c.toArray(new Characteristics[c.size()])); }

Después de eso, la operación real puede verse así:

Map<Route, Long> result = routes.stream() .collect(Collectors.groupingBy(Function.identity(), pairing(Collectors.maxBy(Comparator.comparingLong(Route::getLastUpdated)), Collectors.counting(), (route, count) -> new AbstractMap.SimpleEntry<>(route.get(), count)) )) .values().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

Actualización: dicho recopilador está disponible en mi biblioteca MoreCollectors.pairing() : MoreCollectors.pairing() . También se implementa un recopilador similar en la biblioteca jOOL , por lo que puede usar Tuple.collectors lugar de pairing .