I have following model representing a Snack bar SnackBarStat – This has a start date for a booking, number of customers in booking and number of Days(customer can set a start date and say for how many days they want to have table)
public class SnackBarStat { LocalDate startDate; int numberOfDays; int customers; }
Now given a list of such stats I am trying to find for each date how many customer are there in snack bar
For example if input is
Start date 13-9-2021, Customers: 2 , numberOfDays: 3 Start date 12-9-2021, Customers: 3 , numberOfDays: 2 Start date 13-9-2021, Customers: 1 , numberOfDays: 1
Expected output is
{2021-09-12=3, 2021-09-13=6, 2021-09-14=2, 2021-09-15=2}
What I have tried so far. I have created a simple logic to iterate over each startDate, expand the dates based on numberOfDays and then for each date add them to a map with summing customers on that date
public class SnackBarOccupanceTest { public static void main(String args[]){ SnackBarStat snackBarStat1 = new SnackBarStat(); snackBarStat1.setStartDate(LocalDate.of(2021, 9, 13)); snackBarStat1.setCustomers(2); snackBarStat1.setNumberOfDays(3); SnackBarStat snackBarStat2 = new SnackBarStat(); snackBarStat2.setStartDate(LocalDate.of(2021, 9, 12)); snackBarStat2.setCustomers(3); snackBarStat2.setNumberOfDays(2); SnackBarStat snackBarStat3 = new SnackBarStat(); snackBarStat3.setStartDate(LocalDate.of(2021, 9, 13)); snackBarStat3.setCustomers(1); snackBarStat3.setNumberOfDays(1); Map<LocalDate, Integer> occupancePerDate = new HashMap<>(); List<SnackBarStat> snackBarStats = List.of(snackBarStat1, snackBarStat2, snackBarStat3); for(SnackBarStat snackBarStat:snackBarStats){ var expandedDates = expandDates(snackBarStat.getStartDate(), snackBarStat.getNumberOfDays()); for(LocalDate eachDate : expandedDates){ if(occupancePerDate.get(eachDate) != null){ var existingCustomers = occupancePerDate.get(eachDate); occupancePerDate.put(eachDate, existingCustomers + snackBarStat.getCustomers()); }else{ occupancePerDate.put(eachDate, snackBarStat.getCustomers()); } } } System.out.println(occupancePerDate); } public static List<LocalDate> expandDates (LocalDate startDate, int numberOfDays){ List<LocalDate> dateRanges = new ArrayList<>(); for(int i = 0; i < numberOfDays; i++){ dateRanges.add(startDate.plusDays(i)); } return dateRanges; } }
This code works. I was wondering if there was shorter way with stream api to do the same. Note the code is just for demo purpose and I have made use of better code practice in actual code.
Advertisement
Answer
You can use flatMap
to “expand” the dates, and then you can use groupingBy
to group the expanded dates:
Map<LocalDate, Integer> occupancePerDate = snackBarStats.stream() .flatMap(x -> // expand the dates IntStream.range(0, x.getNumberOfDays()) // for each date, we create a new SnackBarStat with // that date, numberOfDays=1, and the same number of customers .mapToObj(n -> new SnackBarStat(x.getStartDate().plusDays(n), 1, x.getCustomers())) ).collect(Collectors.groupingBy( SnackBarStat::getStartDate, // group by start date Collectors.summingInt(SnackBarStat::getCustomers) // for each group, sum the customers ));
Though this is shorter, note that it is probably slower than your loops.