javadictionarysumhashmapjava-stream

Convert enhanced for loop with inner iterator to stream


I am trying to convert his piece of working code into a stream:

Map<Month,Integer> data = new HashMap<>();
for (Scooter scooter : scooters) {
    scooter.getRentalDays().iterator().forEachRemaining(e -> {

        int newVal = scooter.getMileage() * scooter.getMileageRate();
        int curVal = data.getOrDefault(e.getMonth(), 0);
        data.put(e.getMonth(), curVal + newVal);
    });
}

The added complexity from the iterator creates a problem for me where I don't know how to sum the value inside the hashmap. The example below is somewhat close to the code above, but doesn't support the sum of the value.

Map<Month,Integer> data = projects.stream()
        .flatMap(scooter -> scooter.getRentalDays().stream()
                .map(date -> Map.entry(date.getMonth(), scooter.getMileage() * scooter.getMileageRate())))
        .collect(Collectors.toMap(
                Map.Entry::getKey, 
                Map.Entry::getValue, // this value needs to be a sum of the previous one.
                (k1, k2) -> k1));

Any tips on how to solve this problem? The idea is to have a full solution with streams.


Solution

  • One of possible solutions can be introducing an intermediate class to store intermediate values (assuming Month is an enum):

    private static Map<Month, Integer> sumGroupingByMonth(final Collection<Scooter> scooters) {
        @AllArgsConstructor
        final class Intermediate {
            final int newVal;
            final Day rentalDay;
        }
    
        return scooters.stream()
                .flatMap(scooter -> {
                    final int newVal = scooter.getMileage() * scooter.getMileageRate();
                    return scooter.getRentalDays()
                            .stream()
                            .map(day -> new Intermediate(newVal, day));
                })
                .collect(Collectors.groupingBy(intermediate -> intermediate.rentalDay.getMonth(), Collectors.summingInt(intermediate -> intermediate.newVal)));
    }
    

    I find this code pretty complex: closures, explicit intermediate state, function scoping, intermediate objects, and probably the following could be both more simple and faster?

    private static int[] sumGroupingByMonth(final Iterable<Scooter> scooters) {
        final int[] data = new int[12];
        for ( final Scooter scooter : scooters ) {
            final int newVal = scooter.getMileage() * scooter.getMileageRate();
            for ( final Day rentalDay : scooter.getRentalDays() ) {
                data[rentalDay.getMonth().ordinal()] += newVal;
            }
        }
        return data;
    }