我想确定显示点集所需的最小面积。简单的方法是像这样遍历整个集合:
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int minY = Integer.MAX_VALUE;
int maxY = Integer.MIN_VALUE;
for (Point point: points) {
if (point.x < minX) {
minX = point.x;
}
if (point.x > maxX) {
maxX = point.x;
}
if (point.y < minY) {
minY = point.y;
}
if (point.y > maxY) {
maxY = point.y;
}
}
我正在了解流。为此,您可以执行以下操作:
int minX = points.stream().mapToInt(point -> point.x).min().orElse(-1);
int maxX = points.stream().mapToInt(point -> point.x).max().orElse(-1);
int minY = points.stream().mapToInt(point -> point.y).min().orElse(-1);
int maxY = points.stream().mapToInt(point -> point.y).max().orElse(-1);
两者给出相同的结果。但是,尽管流方法很优雅,但速度却比预期慢得多。
是否有一种方法可以通过单个流操作来获取minX
,maxX
,minY
和maxY
?
答案 0 :(得分:9)
您可以使用summaryStatistics()
除以两次迭代,同时保持简单的代码:
IntSummaryStatistics stat = points.stream().mapToInt(point -> point.x).summaryStatistics();
int minX = stat.getMin();
int maxX = stat.getMax();
并使用point.y
做同样的事情。
您可以通过这种方式进行排除:
Function<ToIntFunction<Point>, IntSummaryStatistics> statFunction =
intExtractor -> points.stream()
.mapToInt(p -> intExtractor.applyAsInt(pp))
.summaryStatistics();
IntSummaryStatistics statX = statFunction.apply(p -> p.x);
IntSummaryStatistics statY = statFunction.apply(p -> p.y);
可以使用自定义收集器,但是请注意,您应该实现合并器部分,这将使您的代码难以阅读。
因此,但是如果您需要使用并行流,则应该遵循命令式的方法。
尽管您可以依靠Math.min
和Math.max
函数来改善实际代码:
for (Point p : points) {
minX = Math.min(p.x, minX);
minY = Math.min(p.y, minY);
maxY = Math.max(p.x, maxX);
maxY = Math.max(p.y, maxY);
}
答案 1 :(得分:8)
类似于IntSummaryStatistics
,创建一个类PointStatistics
,该类收集所需的信息。它定义了两种方法:一种用于记录Point
中的值,一种用于组合两个Statistics
。
class PointStatistics {
private int minX = Integer.MAX_VALUE;
private int maxX = Integer.MIN_VALUE;
private int minY = Integer.MAX_VALUE;
private int maxY = Integer.MIN_VALUE;
public void accept(Point p) {
minX = Math.min(minX, p.x);
maxX = Math.max(maxX, p.x);
minY = Math.min(minY, p.y);
maxY = Math.max(minY, p.y);
}
public void combine(PointStatistics o) {
minX = Math.min(minX, o.minX);
maxX = Math.max(maxX, o.maxX);
minY = Math.min(minY, o.minY);
maxY = Math.max(maxY, o.maxY);
}
// getters
}
然后,您可以将Stream<Point>
收集到PointStatistics
中。
class Program {
public static void main(String[] args) {
List<Point> points = new ArrayList<>();
// populate 'points'
PointStatistics statistics = points
.stream()
.collect(PointStatistics::new, PointStatistics::accept, PointStatistics::combine);
}
}
更新
我完全被OP绘制的the conclusion所迷惑,所以我决定编写JMH基准测试。
基准设置:
# JMH version: 1.21
# VM version: JDK 1.8.0_171, Java HotSpot(TM) 64-Bit Server VM, 25.171-b11
# Warmup: 1 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Benchmark mode: Average time, time/op
对于每次迭代,我都会生成一个随机列表Point
(new Point(random.nextInt(), random.nextInt())
)的共享列表,大小为100K,1M,10M。
结果是
100K
Benchmark Mode Cnt Score Error Units
customCollector avgt 10 6.760 ± 0.789 ms/op
forEach avgt 10 0.255 ± 0.033 ms/op
fourStreams avgt 10 5.115 ± 1.149 ms/op
statistics avgt 10 0.887 ± 0.114 ms/op
twoStreams avgt 10 2.869 ± 0.567 ms/op
1M
Benchmark Mode Cnt Score Error Units
customCollector avgt 10 68.117 ± 4.822 ms/op
forEach avgt 10 3.939 ± 0.559 ms/op
fourStreams avgt 10 57.800 ± 4.817 ms/op
statistics avgt 10 9.904 ± 1.048 ms/op
twoStreams avgt 10 32.303 ± 2.498 ms/op
10M
Benchmark Mode Cnt Score Error Units
customCollector avgt 10 714.016 ± 151.558 ms/op
forEach avgt 10 54.334 ± 9.820 ms/op
fourStreams avgt 10 699.599 ± 138.332 ms/op
statistics avgt 10 148.649 ± 26.248 ms/op
twoStreams avgt 10 429.050 ± 72.879 ms/op
答案 2 :(得分:6)
JDK 12将具有Collectors.teeing
(webrev和CSR),它们收集到两个不同的收集器,然后将两个部分结果合并为最终结果。
您可以在此处使用它收集IntSummaryStatistics
坐标和x
坐标的两个y
:
List<IntSummaryStatistics> stats = points.stream()
.collect(Collectors.teeing(
Collectors.mapping(p -> p.x, Collectors.summarizingInt()),
Collectors.mapping(p -> p.y, Collectors.summarizingInt()),
List::of));
int minX = stats.get(0).getMin();
int maxX = stats.get(0).getMax();
int minY = stats.get(1).getMin();
int maxY = stats.get(1).getMax();
这里,第一个收集器收集x
的统计信息,第二个收集器收集y
的统计信息。然后,通过接受两个元素的JDK 9 List.of
工厂方法,将x
和y
的统计信息合并到List
中。
List::of
的合并替代品是:
(xStats, yStats) -> Arrays.asList(xStats, yStats)
如果您的计算机上未安装JDK 12,请使用teeing
方法的简化的通用版本,您可以放心地将其用作实用程序方法:
public static <T, A1, A2, R1, R2, R> Collector<T, ?, R> teeing(
Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
class Acc {
A1 acc1 = downstream1.supplier().get();
A2 acc2 = downstream2.supplier().get();
void accumulate(T t) {
downstream1.accumulator().accept(acc1, t);
downstream2.accumulator().accept(acc2, t);
}
Acc combine(Acc other) {
acc1 = downstream1.combiner().apply(acc1, other.acc1);
acc2 = downstream2.combiner().apply(acc2, other.acc2);
return this;
}
R applyMerger() {
R1 r1 = downstream1.finisher().apply(acc1);
R2 r2 = downstream2.finisher().apply(acc2);
return merger.apply(r1, r2);
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::applyMerger);
}
请注意,创建返回的收集器时,我没有考虑下游收集器的特征。
答案 3 :(得分:1)
您可以使用Stream::reduce
使用2个流,以得到最小的点和最大的点。我不建议将结果连接到一个流,因为可能很难区分最小,最大和坐标之间的差异。
Point min = points
.stream()
.reduce((l, r) -> new Point(Math.min(l.y, r.y), Math.min(l.y, r.y))
.orElse(new Point(-1, -1));
Point max = points
.stream()
.reduce((l, r) -> new Point(Math.max(l.y, r.y), Math.max(l.y, r.y))
.orElse(new Point(-1, -1));
作为BinaryOperator<Point>
时,请使用两个后续的Points
和三元运算符来找出传递给新对象Point
并使用Optional::orElse
返回的最小值/最大值使用默认的-1, -1
坐标。
答案 4 :(得分:1)
感谢大家的所有建议和答案。这非常有帮助,我学到了很多东西!
我决定尝试大多数解决方案(JDK12解决方案除外)。对于其中一些,您已经为我提供了代码。另外,我制作了自己的Collector
。
class extremesCollector implements Collector<Point, Map<String, Integer>, Map<String , Integer>> {
@Override
public Supplier<Map<String, Integer>> supplier() {
Map<String, Integer> map = new HashMap<>();
map.put("xMin", Integer.MAX_VALUE);
map.put("yMin", Integer.MAX_VALUE);
map.put("xMax", Integer.MIN_VALUE);
map.put("yMax", Integer.MIN_VALUE);
return () -> map;
}
@Override
public BiConsumer<Map<String, Integer>, Point> accumulator() {
return (a, b) -> {
a.put("xMin", Math.min(a.get("xMin"), b.x));
a.put("yMin", Math.min(a.get("yMin"), b.y));
a.put("xMax", Math.max(a.get("xMax"), b.x));
a.put("yMax", Math.max(a.get("yMax"), b.y));
};
}
@Override
public Function<Map<String, Integer>, Map<String, Integer>> finisher() {
return Function.identity();
}
@Override
public BinaryOperator<Map<String, Integer>> combiner() {
return (a, b) -> {
a.put("xMin", Math.min(a.get("xMin"), b.get("xMin")));
a.put("yMin", Math.min(a.get("yMin"), b.get("yMin")));
a.put("xMax", Math.max(a.get("xMax"), b.get("xMax")));
a.put("yMax", Math.max(a.get("yMax"), b.get("yMax")));
return a;
};
}
@Override
public Set<Characteristics> characteristics() {
Set<Characteristics> characteristics = new HashSet<>();
characteristics.add(Characteristics.UNORDERED);
characteristics.add(Characteristics.CONCURRENT);
characteristics.add(Characteristics.IDENTITY_FINISH);
return characteristics;
}
}
结果
我尝试了所有方法并比较了结果。好消息:就所有这些值而言,我得到的结果都是相同的!
关于速度,这是排名:
数字2和3实际上在速度上非常接近。并行版本可能较慢,因为我的数据集太小了。