按对象值分组,按最大对象属性进行计数,然后设置组密钥

我已经设法使用Java 8 Streams API编写一个解决scheme,首先将对象Route列表按其值分组,然后统计每个组中的对象数。 它返回一个映射Route – > Long。 这里是代码:

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

路线类:

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

我想另外实现的是,每个组的键都是lastUpdated值最大的键。 我已经在看这个解决scheme,但我不知道如何结合计数和分组的价值和路线最大lastUpdated值。 这是我想要实现的示例数据:

例:

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

应该被转换为:

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

请注意,计算2个路由的映射关键字是lastUpdated值最大的映射关键字。

这是一种方法。 第一组进入列表,然后将列表处理为您实际需要的值:

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

您可以定义一个将两个收集器合并为一个的抽象“库”方法:

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

之后,实际操作可能如下所示:

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

更新:这样的collections家可以在我的StreamEx库: MoreCollectors.pairing MoreCollectors.pairing() 。 jool库中也实现了类似的收集器,因此您可以使用Tuple.collectors而不是pairing

改变了等号和散列码只依赖于开始单元格和结束单元格。

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

我的解决scheme如下所示:

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

当然,将int转换为int应该replace为更多的东西。

原则上,这似乎应该是一回事。 通常的皱纹是这需要一个临时的元组或对,在这种情况下, Route和计数。 由于Java缺less这些,我们最终使用了长度为2的对象数组(如Tagir Valeev的答案中所示 )或AbstractMap.SimpleImmutableEntry或假设的Pair<A,B>类。

另一种方法是编写一个包含Route和Count的小值类。 当然,这样做会有一些痛苦,但是在这种情况下,我认为它是有好处的,因为它提供了一个放置组合逻辑的地方。 这又简化了stream操作。

下面是包含Route和count的值类:

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

很简单,但注意combine方法。 它通过select最近更新的Route并使用计数总和来组合两个RouteCount值。 现在我们有了这个值类,我们可以写一个单一的stream来获得我们想要的结果:

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

像其他答案一样,这将路线根据开始和结束单元划分为等价类。 用作密钥的实际Route实例并不重要; 它只是它的一个代表。 该值将是一个RouteCount ,其中包含最近更新的Route实例以及等效Route实例的计数。

这样做的方式是每个具有相同的开始和结束单元格的Route实例然后被馈送到groupingBy的下游收集器。 该mapping收集器将Route实例映射到RouteCount实例,然后将其传递给使用上述组合逻辑减less实例的reducing收集器。 Optional<RouteCount> -then部分然后从reducing收集器生成的Optional<RouteCount>中提取值。

(通常情况下,一个裸机是危险的,但是除非至less有一个可用的值,否则我们根本就没有find这个收集器,所以在这种情况下安全。