按对象值分组,计数,然后按最大对象属性设置组键
Grouping by object value, counting and then setting group key by maximum object attribute
我设法使用 Java 8 Streams API 编写了一个解决方案,该解决方案首先按其值对对象 Route 列表进行分组,然后计算每个组中的对象数。它returns一个映射Route -> Long。这是代码:
Map<Route, Long> routesCounted = routes.stream()
.collect(Collectors.groupingBy(gr -> gr, Collectors.counting()));
路线class:
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 但我不知道如何结合计数和按值分组以及路由最大 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
));
将等号和哈希码更改为仅依赖于起始单元格和结束单元格。
@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。
您可以定义一个抽象 "library" 方法,将两个收集器合二为一:
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()));
更新:我的 StreamEx library: MoreCollectors.pairing()
. Also similar collector is implemented in jOOL 库中提供了此类收集器,因此您可以使用 Tuple.collectors
而不是 pairing
。
原则上这似乎应该一次就可以完成。通常的问题是这需要一个特殊的元组或对,在本例中是 Route
和一个计数。由于 Java 缺少这些,我们最终使用长度为 2 的对象数组(如 所示),或 AbstractMap.SimpleImmutableEntry
,或假设的 Pair<A,B>
class .
另一种方法是写入一个小值 class,其中包含一个 Route
和一个计数。当然,这样做会有些痛苦,但在这种情况下,我认为这是值得的,因为它提供了放置组合逻辑的地方。这反过来又简化了流操作。
这是包含 Route
和计数的值 class:
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
值。现在我们有了这个值class,我们可以写一个单程流来得到我们想要的结果:
Map<Route, RouteCount> counted = routes.stream()
.collect(groupingBy(route -> route,
collectingAndThen(
mapping(RouteCount::fromRoute, reducing(RouteCount::combine)),
Optional::get)));
与其他答案一样,这会根据起始单元格和结束单元格将路由分组为等价的 classes。用作键的实际 Route
实例并不重要;它只是其 class 的代表。该值将是单个 RouteCount
,其中包含最近更新的 Route
个实例,以及等效 Route
个实例的计数。
其工作方式是每个 Route
具有相同开始和结束单元格的实例然后被送入 groupingBy
的下游收集器。这个 mapping
收集器将 Route
实例映射到 RouteCount
实例,然后将其传递给 reducing
收集器,该收集器使用上述组合逻辑减少实例。 collectingAndThen
的 and-then 部分从 reducing
收集器生成的 Optional<RouteCount>
中提取值。
(通常一个空 get
是危险的,但我们根本不会访问这个收集器,除非至少有一个可用值。所以 get
在这种情况下是安全的。)
我设法使用 Java 8 Streams API 编写了一个解决方案,该解决方案首先按其值对对象 Route 列表进行分组,然后计算每个组中的对象数。它returns一个映射Route -> Long。这是代码:
Map<Route, Long> routesCounted = routes.stream()
.collect(Collectors.groupingBy(gr -> gr, Collectors.counting()));
路线class:
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 但我不知道如何结合计数和按值分组以及路由最大 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
));
将等号和哈希码更改为仅依赖于起始单元格和结束单元格。
@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。
您可以定义一个抽象 "library" 方法,将两个收集器合二为一:
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()));
更新:我的 StreamEx library: MoreCollectors.pairing()
. Also similar collector is implemented in jOOL 库中提供了此类收集器,因此您可以使用 Tuple.collectors
而不是 pairing
。
原则上这似乎应该一次就可以完成。通常的问题是这需要一个特殊的元组或对,在本例中是 Route
和一个计数。由于 Java 缺少这些,我们最终使用长度为 2 的对象数组(如 AbstractMap.SimpleImmutableEntry
,或假设的 Pair<A,B>
class .
另一种方法是写入一个小值 class,其中包含一个 Route
和一个计数。当然,这样做会有些痛苦,但在这种情况下,我认为这是值得的,因为它提供了放置组合逻辑的地方。这反过来又简化了流操作。
这是包含 Route
和计数的值 class:
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
值。现在我们有了这个值class,我们可以写一个单程流来得到我们想要的结果:
Map<Route, RouteCount> counted = routes.stream()
.collect(groupingBy(route -> route,
collectingAndThen(
mapping(RouteCount::fromRoute, reducing(RouteCount::combine)),
Optional::get)));
与其他答案一样,这会根据起始单元格和结束单元格将路由分组为等价的 classes。用作键的实际 Route
实例并不重要;它只是其 class 的代表。该值将是单个 RouteCount
,其中包含最近更新的 Route
个实例,以及等效 Route
个实例的计数。
其工作方式是每个 Route
具有相同开始和结束单元格的实例然后被送入 groupingBy
的下游收集器。这个 mapping
收集器将 Route
实例映射到 RouteCount
实例,然后将其传递给 reducing
收集器,该收集器使用上述组合逻辑减少实例。 collectingAndThen
的 and-then 部分从 reducing
收集器生成的 Optional<RouteCount>
中提取值。
(通常一个空 get
是危险的,但我们根本不会访问这个收集器,除非至少有一个可用值。所以 get
在这种情况下是安全的。)