如何使用单个流操作从对象获取多个值?

MWB*_*MWB 9 java java-8 java-stream

我想确定显示点集合所需的最小区域.简单的方法是循环遍历集合,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我开始了解溪流.要做到这一点,您可以执行以下操作:

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);
Run Code Online (Sandbox Code Playgroud)

两者都给出了相同的结果.然而,尽管流方法很优雅,但速度要慢得多(如预期的那样).

有没有办法让minX,maxX,minYmaxY在一个单一的流操作?

dav*_*xxx 9

您可以summaryStatistics()在保持直接代码的同时将迭代除以2 :

IntSummaryStatistics stat = points.stream().mapToInt(point -> point.x).summaryStatistics();
int minX = stat.getMin();
int maxX = stat.getMax();
Run Code Online (Sandbox Code Playgroud)

并做同样的事情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);
Run Code Online (Sandbox Code Playgroud)

自定义收集器是可能的,但请注意,您应该实现组合器部分,这将使您的代码更难阅读.
所以,但是如果你需要使用并行流,你应该坚持不懈的方式.
虽然您可以通过依赖Math.minMath.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);
}
Run Code Online (Sandbox Code Playgroud)


Fed*_*ner 9

JDK 12将具有Collectors.teeing(webrevCSR),它收集到两个不同的收集器,然后将两个部分结果合并为最终结果.

你可以在这里使用它来收集两个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();
Run Code Online (Sandbox Code Playgroud)

这里第一个收集器收集统计信息,x第二个收集器收集统计信息y.然后,对于统计xy合并成一个List由JDK 9的装置List.of,其接受两个元件工厂方法.

List::of合并的替代方案是:

(xStats, yStats) -> Arrays.asList(xStats, yStats)
Run Code Online (Sandbox Code Playgroud)

如果您的计算机上没有安装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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在创建返回的收集器时,我没有考虑下游收集器的特性.


And*_*lko 8

通过类比IntSummaryStatistics,创建一个PointStatistics收集所需信息的类.它定义了两种方法:一种用于记录a的值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
}
Run Code Online (Sandbox Code Playgroud)

然后你可以收集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);
    }
}
Run Code Online (Sandbox Code Playgroud)

UPDATE

我完全被OP 得出的结论所困惑,所以我决定编写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
Run Code Online (Sandbox Code Playgroud)

对于每次迭代,我都生成了一个大小为100K,1M,10M 的随机Points(new Point(random.nextInt(), random.nextInt()))的共享列表.

结果是

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)