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
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 keySecondly, we need to map our
DataEntry
to singlePassTypeRate
objects. If this isAdult
entry, then newPassTypeRate
has to have a value inadultRate
field and zero inchildRate
. And vice versa.But some entries may have the same
PassType
! So we need to define third function - the merge function. How do we mergePassTypeRate
objects? By adding proper rates. Merge function returns newPassTypeRate
as a result of addition of twoPassTypeRate
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()
);
}
}