Using Stream API to count stats

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.

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.