Java 8 streams. How to aggregate BigDecimal totals per enumerated type into a custom bean

Martin :

Its hard to word the question. Heres my code snippet:

Map<PassType, PassTypeRate> ratesPerType = new HashMap<>();

entries.stream().forEach((entry) -> {
    if (!ratesPerType.containsKey(entry.getPassType())) {
        ratesPerType.put(entry.getPassType(), new PassTypeRate(BigDecimal.ZERO, BigDecimal.ZERO));
    }

    if (AgeType.ADULT.equals(entry.getAgeType())) {
        PassTypeRate passTypeRate = ratesPerType.get(entry.getPassType());
        passTypeRate.setAdultRate(passTypeRate.getAdultRate().add(entry.getRate()));
    }
    if (AgeType.CHILD.equals(entry.getAgeType())) {
        PassTypeRate passTypeRate = ratesPerType.get(entry.getPassType());
        passTypeRate.setChildRate(passTypeRate.getChildRate().add(entry.getRate()));
    }
});

So entries is a list of beans that holds an ageType, passType and rate. I need to aggregate the total rates per 'ageType' and 'passType'.

The PassTypeRate bean holds an aggregated total of rate for adult or child per 'PassType'.

My basic question is....is it possible at all to rewrite the above snippet using Java 8 Collectors or similar? I can't figure it out.

Any advice would be appreciated.

Thanks

Adam Bukowiecki :

Yes, looks like a good candidate to implement with Collectors.toMap. Take a look at below implementation (I used lombok (https://projectlombok.org/) to make it more readable):

    Map<PassType, PassTypeRate> group(List<DataEntry> entries) {
        return entries.stream()
                .collect(Collectors.toMap(
                        DataEntry::getPassType,
                        entry -> PassTypeRate.builder()
                                .adultRate(entry.getAgeType() == ADULT ? entry.getRate() : BigDecimal.ZERO)
                                .childRate(entry.getAgeType() == CHILD ? entry.getRate() : BigDecimal.ZERO)
                                .build(),
                        (rate1, rate2) -> PassTypeRate.builder()
                                .childRate(rate1.getChildRate().add(rate2.getChildRate()))
                                .adultRate(rate1.getAdultRate().add(rate2.getAdultRate()))
                                .build()
                ));
    }

And short explanation:

  • Firstly, we need to define PassType as a map key

  • Secondly, we need to map our DataEntry to single PassTypeRate objects. If this is Adult entry, then new PassTypeRate has to have a value in adultRate field and zero in childRate. And vice versa.

  • But some entries may have the same PassType! So we need to define third function - the merge function. How do we merge PassTypeRate objects? By adding proper rates. Merge function returns new PassTypeRate as a result of addition of two PassTypeRate objects.

I've also prepared some test case to verify if solution works - and seems it works :) Test case below:

public class RateGroupingTest {

    private RateGrouping subject = new RateGrouping();

    @Test
    public void groups() {
        //given
        List<DataEntry> entries = List.of(
                new DataEntry(ADULT, X, new BigDecimal("3")),
                new DataEntry(ADULT, Y, new BigDecimal("5")),
                new DataEntry(ADULT, Z, new BigDecimal("7")),
                new DataEntry(CHILD, X, new BigDecimal("11")),
                new DataEntry(CHILD, Y, new BigDecimal("13")),
                new DataEntry(CHILD, Z, new BigDecimal("17")),

                new DataEntry(ADULT, X, new BigDecimal("13")),
                new DataEntry(ADULT, Y, new BigDecimal("25")),
                new DataEntry(ADULT, Z, new BigDecimal("37")),
                new DataEntry(CHILD, X, new BigDecimal("411")),
                new DataEntry(CHILD, Y, new BigDecimal("513")),
                new DataEntry(CHILD, Z, new BigDecimal("617"))
        );

        //when
        Map<PassType, PassTypeRate> actual = subject.group(entries);

        //then
        assertThat(actual.get(PassType.X))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("422"))
                                .adultRate(new BigDecimal("16"))
                                .build()
                );
        assertThat(actual.get(PassType.Y))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("526"))
                                .adultRate(new BigDecimal("30"))
                                .build()
                );
        assertThat(actual.get(PassType.Z))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("634"))
                                .adultRate(new BigDecimal("44"))
                                .build()
                );
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=327800&siteId=1