Home > Software design >  How to use an accumulator with Java Streams with a default value?
How to use an accumulator with Java Streams with a default value?

Time:12-30

Class Coordinate properties x and y of type double, and a method to compute the distance from this point to another given point:

public record Coordinate(double x, double y) {
    public double distanceTo(Coordinate point) {
        // implementation
    }
}

I want to write a method furthestCoordinate that determines the farthest point from the point base in a given stream and return the base itself if the stream is empty. I have written an Comparator which compares Coordinate Objects with the base, but it throws an exception when given an empty stream:

static Comparator<Coordinate> WayPointComparator = new Comparator<>() {
    @Override
    public int compare(Coordinate o1, Coordinate o2) {
        return Double.compare(o1.distanceTo(basePoint),o2.distanceTo(basePoint));
    }
};

public static Coordinate furthestCoordinate(Stream<Coordinate> coordinateStream, Coordinate base) {
    basePoint = base; // safes the base to be accessible for the Comparator 

    return coordinateStream.max(WayPointComparator).get();
}

How can I use an accumulator, which gets updated whenever the next Coordinate in the Stream is farther away and returns 'base' itself if the Stream is empty?

CodePudding user response:

You can use Java 8 method Comparator.comparingDouble() to define a comparator based on the method distanceTo() of your Coordinate class.

And to base coordinate if the given stream was empty, apply Optional.orElse() on the Optional returned by Stream.max() operation.

public static Coordinate furthestCoordinate(Stream<Coordinate> coordinateStream,
                                            Coordinate base) {
    
    return coordinateStream
        .max(Comparator.comparingDouble(base::distanceTo)) // produces Optional<Coordinate>
        .orElse(base);
}

CodePudding user response:

Here is one way to do it. I am using the JDK classes Math and Point2D.Double

Some data

List<Point2D> list = new ArrayList<>(
        List.of(
                new Point2D.Double(2.0, 10.0),
        new Point2D.Double(5.0, 6.0),
        new Point2D.Double(4.0, 9.3)));
        
Point2D basePoint = new Point2D.Double(0., 0.);

A defined comparator.

Comparator<Point2D> distanceComp = Comparator
        .comparingDouble(point -> Math.hypot(basePoint.getX() - point.getX(),
                basePoint.getY() - point.getY()));

And the stream.

list.stream().collect(Collectors.maxBy(distanceComp))
        .ifPresentOrElse(point->System.out.println("Max point is "   point),
                () -> System.out.println("Empty stream"));

prints

Max point is Point2D.Double[2.0, 10.0]

Collectors.maxBy returns an Optional<Point2D.Double>. So using IfPresentOrElse can handle the answer or report an empty stream.

If you prefer to use your own classes and methods for the point and distance then the above should still work just fine.

Here is a function to get a comparator for a specific basePoint.

static Function<Point2D, Comparator<Point2D>> getComparator = base -> Comparator
            .comparingDouble(point -> Math.hypot(base.getX() - point.getX(),
                    base.getY() - point.getY()));

You can then call your method with a stream and the basePoint.

public static Point2D furthestCoordinate(Stream<Point2D> coordinateStream,
         Point2D basePoint) {

     return coordinateStream
             .collect(Collectors.maxBy(getComparator.apply(basePoint)))
             .orElse(basePoint);
}

Note: In this case I returned the basePoint for an empty stream. But any point could be a valid answer. You may want to just return the optional and the use ifPresent or someother supported method to best determine how to handle an empty stream.

  • Related