Search code examples
javajava-streamcollectors

Collectors group by characteristic and minimum value of a field of said characteristic


Title is sort of confusing but I'm not sure how to explain my problem in a simple sentence.

I have a homework task to group an arraylist of rectangles by length (same as perimeter in this case) using streams and collectors and to calculate minimum width for each group. I have tried the following:

public static Map<Double, Double> groupIndenticalPerimeterWidth(ArrayList<Rectangle> rectangles){
        return rectangles.stream().collect(Collectors.groupingBy(Rectangle::getLength, Collectors.minBy((rectangle1, rectangle2) -> Double.compare(rectangle1.getWidth(), rectangle2.getWidth()))));
    }

which gets me a Map<Double, Optional<Rectangle>> and I can't figure out how to get the width of minimum rectangle in the second argument of Collectors.groupingBy instead of Optional<Rectangle>

Any help appreciated


Solution

  • group an arraylist of rectangles by length (same as perimeter in this case) using streams and collectors and to calculate minimum width for each group.

    As you've noticed, collector minBy() produces Optional<Rectangle>.

    To get double property from the optional result, you can use collector collectingAndThen(). It expects a collector (minBy() in this case) as the first argument, and a function that transforms the result produced by the collector as the second argument.

    public static Map<Double, Double> groupIndenticalPerimeterWidth(ArrayList<Rectangle> rectangles){
        return rectangles.stream()
            .collect(Collectors.groupingBy(
                Rectangle::getPerimeter,
                Collectors.collectingAndThen(
                    Collectors.minBy(Comparator.comparingDouble(Rectangle::getWidth)),
                    result -> result.map(Rectangle::getWidth).orElseThrow() // `map` transforms Optional<Rectangle> into Optional<Double> and `orElseThrow` extracts the value from the optional
                )
            ));
    }
    

    Dummy Rectangle class:

    public static class Rectangle {
        private double height;
        private double width;
        
        public double getPerimeter() {
            return 2 * (height + width);
        }
    
        // getters
    }
    

    Sidenote: it's highly advisable to use Java 8 static methods when you need to define a compactor.