Reputation: 140
I have a class Module which contains
private LocalDate startDate;
private LocalDate endDate;
private Map<Teacher, Integer> workedHoursPerDay;
Each teacher has an attribute
private int hourlyWage;
The request is to get the monthly budget for all teachers grouped by month. The return expected is Map. I searched but I do not have a clue how to do it using startDate and endDate.
The monthly spend of a single module is the accumulated cost of all teacher assigned to the module across all working days in the month.
The logic will be to : -Get from all the modules (Set) the teacher that teaching that module and the number of hours (Map workedHoursPerDay) . Then multiply the number of worked hours for each teacher by the hourlyWage. Finally, group the cost by month using startDate and endDate of each module.
I have done something like that but I am not getting the expected results
public Map calculateCumulativeMonthlySpends() {
//Get the min startDate of all the modules
LocalDate localDateStart = modules.stream()
.flatMap(modules -> module.getWorkingDays().stream())
.min(LocalDate::compareTo).get();
//Get the max endDate of all the modules
LocalDate localDateEnd = modules.stream()
.flatMap(module -> module.getWorkingDays().stream())
.max(LocalDate::compareTo).get();
//Iterate from the start date to the end date, calculate the daily cost for all the module
//and sum the cost of all the days for the all modules
//Then create a map with moth as a key and the cost of all the modules for each month
//Finally sort the map by key
return Stream.iterate(localDateStart, d -> d.plusDays(1))
.limit(ChronoUnit.DAYS.between(localDateStart, localDateEnd) + 1)
.collect(Collectors.toMap(LocalDate::getMonth, day -> dailyCost(day, modules), Integer::sum))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldValue, newValue) -> oldValue, LinkedHashMap::new));
}
/**
* Calculate the daily cost for all the modules
* Check if the modules's working days contain the attribute day
* If yes sum(), add the daily cost of this module to the other modules' daily cost
*
* @param day
* @param modules
* @return
*/
private Integer dailyCost(LocalDate day, Set<Module> modules) {
return modules.stream()
.filter(module -> module.getWorkingDays().contains(day))
.flatMap(p -> p.getCommittedHoursPerDay().entrySet().stream())
.mapToInt(e -> e.getKey().getHourlyWage() * e.getValue())
.sum();
}
The code is modified. I have the expected results now but I hope there is a better way to do this.
Expected: {JANUARY=19529, FEBRUARY=26909, MARCH=35593, APRIL=60243, MAY=79172, JUNE=70135, JULY=72470, AUGUST=40161, SEPTEMBER=21750, OCTOBER=8636, NOVEMBER=1344}
Upvotes: 0
Views: 306
Reputation: 140
After investigation, I figure it out. The working days include the week-end which is wrong.
public Map<Month, Integer> calculateCumulativeMonthlySpends() {
//Get the min startDate of all the modules
LocalDate localDateStart = modules.stream()
.flatMap(modules -> module.getWorkingDays().stream())
.min(LocalDate::compareTo).get();
//Get the max endDate of all the modules
LocalDate localDateEnd = modules.stream()
.flatMap(module -> module.getWorkingDays().stream())
.max(LocalDate::compareTo).get();
//Iterate from the start date to the end date, calculate the daily cost for all the module
//and sum the cost of all the days for the all modules
//Then create a map with moth as a key and the cost of all the modules for each month
//Finally sort the map by key
return Stream.iterate(localDateStart, d -> d.plusDays(1))
.limit(ChronoUnit.DAYS.between(localDateStart, localDateEnd) + 1)
.collect(Collectors.toMap(LocalDate::getMonth, day -> dailyCost(day, modules), Integer::sum))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldValue, newValue) -> oldValue, LinkedHashMap::new));
}
/**
* Calculate the daily cost for all the modules
* Check if the modules's working days contain the attribute day
* If yes sum(), add the daily cost of this module to the other modules' daily cost
*
* @param day
* @param modules
* @return
*/
private Integer dailyCost(LocalDate day, Set<Module> modules) {
return modules.stream()
.filter(module -> module.getWorkingDays().contains(day))
.flatMap(p -> p.getCommittedHoursPerDay().entrySet().stream())
.mapToInt(e -> e.getKey().getHourlyWage() * e.getValue())
.sum();
}
I have now the excepted values but I think there is a better way to do this. I hope that you have any suggestions.
Note:getWorkingDays() working days without week end
Upvotes: 0
Reputation: 27976
It's pretty hard to find the bug in your code without a lot more information. But it's likely going to be more helpful to you to explain how to find it yourself.
Your single expression in dailyCost
is way too complicated to debug. Java streams are fantastic but they have one very large problem: they are incredibly hard to debug using an interactive debugger (see Problems using interactive debuggers with Java 8 streams which unfortunately has no useful answers).
The secret is to keep your chain of operators short and obvious. Where possible split into separate methods that represent a single responsibility and that can be individually unit tested. Then you can build those methods into your solution with confidence.
For example:
.filter(p -> (p.getStartDate().isBefore( day) || p.getStartDate().equals(day)))
.filter(p -> p.getEndDate().isAfter(day) || p.getEndDate().equals(day))
Could become:
.filter(p -> p.wasActiveOnDay(day))
And:
.mapToInt(p -> p.getCommittedHoursPerDay().entrySet().stream()
.mapToInt(e -> e.getKey().getHourlyWage() * e.getValue()).sum())
Could become:
.mapToInt(Module:getTotalWages)
Each of those could be unit tested as part of your Module
tests. And that would leave you with:
return modules.stream()
.filter(m -> m.wasActiveOnDay(day))
.mapToInt(Module::getTotalWages).sum();
That statement is much more straightforward to debug.
Upvotes: 1