有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java按对象值分组,计数,然后按最大对象属性设置组键

我已经成功地使用Java8StreamsAPI编写了一个解决方案,该解决方案首先根据对象路由的值对对象路由列表进行分组,然后统计每个组中的对象数。它返回一个映射路由->;长的代码如下:

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值的组。我已经在看this solution,但我不知道如何结合计数和分组(按值)以及路由最大LastUpdate值。以下是我想要实现的示例数据:

示例:

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值的键


共 (4) 个答案

  1. # 1 楼答案

    将equals和hashcode更改为仅依赖于开始单元格和结束单元格

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

    我的解决方案如下所示:

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

    当然,对int的转换应该用更合适的东西来代替

  2. # 2 楼答案

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

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

    更新:此类收集器在myStreamEx库中提供:^{}。在jOOL库中也实现了类似的收集器,因此您可以使用Tuple.collectors而不是pairing

  3. # 3 楼答案

    从原则上讲,这似乎应该是一次性的。通常的缺点是,这需要一个特别的元组或元组对,在本例中是一个Route和一个计数。由于Java缺少这些,我们最终使用了长度为2的对象数组(如Tagir Valeev's answer)、或AbstractMap.SimpleImmutableEntry、或假设的Pair<A,B>

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

    下面是包含Route和计数的value类:

    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方法。它通过选择最近更新的Route并使用计数总和来组合两个RouteCount值。现在我们有了这个值类,我们可以编写一个单通道流来获得我们想要的结果:

        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实例,然后将其传递给reducing收集器,该收集器使用上述组合逻辑减少实例。collectingAndThen的and then部分从Optional<RouteCount>收集器生成的reducing中提取值

    (通常裸get是危险的,但除非至少有一个值可用,否则我们根本无法到达该收集器。因此get在这种情况下是安全的。)

  4. # 4 楼答案

    这里有一种方法。首先将列表分组,然后将列表处理为实际需要的值:

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