Sorting, grouping, and counting dates using the java Stream API

Alexander :

How to solve the following examples using the Stream API.

/*
 * Calculate the total number of Mondays for the specified years grouped by month
 */
EnumMap<Month, Long> totalMondaysByMonth(int yearSince, int yearUntilInclusive);

/*
 * Issue a list of Fridays on the 13th for the specified years,
 * sorted in ascending order first by year, and second by name of the month
 */
List<LocalDate> friday13s(int yearFrom, int yearUntilInclusive);

My example solution:

public EnumMap<Month, Long> totalMondaysByMonth(int yearSince, int yearUntilInclusive) {
           return Stream.iterate(LocalDate.of(yearSince, 0, 0), date -> date.plusYears(1))
                   .limit(yearUntilInclusive)
                   .filter(x -> x.getDayOfWeek() == DayOfWeek.MONDAY)
                   .collect(Collectors.groupingBy(LocalDate::getMonth, Collectors.summingLong(???));

}

What code should I insert in (Collectors.summingLong(???))) to count the number of Mondays?

public List<LocalDate> friday13s(int yearFrom, int yearUntilInclusive) {
    return Stream.iterate(LocalDate.of(yearFrom, 0, 0), date -> date.plusYears(1))
            .limit(yearUntilInclusive)
            .filter(x -> x.getDayOfWeek() == DayOfWeek.FRIDAY || x.getDayOfMonth() == 13)
            .sorted(Comparator.comparing(LocalDate::getYear).thenComparing(LocalDate::getMonth))
            .collect(Collectors.toList());
}

Tell me how to write correctly.

Eugene :

I will only help you fix the first one, you can understand this and fix your second one too (it is almost the same).

You first say :

Stream.iterate(LocalDate.of(yearSince, 0, 0), date -> date.plusYears(1))

But do you really need to iterate years here? You need to find out Mondays (which are days, no?) That is the main problem here, since your "loop" is going to happen only as many times as that difference of years is. Let's say you want 2018-2020 interval - how many times are you going to iterate with what you have right now?

But now you need to think when to "stop" this loop - and this why you were using limit:

 .limit(yearUntilInclusive)

The problem is that if you would correctly iterate days, you do not really know when to stop, do you? Ideally, you want to stop by using a Predicate, like: I want to start on the first day of 2018 and stop on the last day of 2020. limit can't really do that (you could via a difference of days from that last to that first - I'll let you figure out how). Or, I find easier to use a Predicate with takeWhile (requires at least java-9).

Then that Collectors.summingLong can be made as an Collectors.summingInt (you would need a lot of years to jump from 32 bits integer to 64 bits long). And what do you need to do there? For each days that you have a match, simply add one. Simply "adding one" is also called counting and there is a collector for that Collectors.counting() that you can use also.

And the last point is that EnumMap can take a Map as a parameter to its constructor; as such:

public static EnumMap<Month, Integer> totalMondaysByMonth(int yearSince, int yearUntilInclusive) {

    return Stream.iterate(LocalDate.of(yearSince, 1, 1), date -> date.plusDays(1))
                 .takeWhile(date -> date.getYear() <= yearUntilInclusive)
                 .filter(x -> x.getDayOfWeek() == DayOfWeek.MONDAY)
                 .collect(
                     Collectors.collectingAndThen(
                         Collectors.groupingBy(LocalDate::getMonth, Collectors.summingInt(x -> 1)),
                         EnumMap::new
                     )
                 );
}

But can you be more effective here? You could find the first Monday in the year you start from and iterate weeks from that, not days:

 LocalDate firstMonday = LocalDate.of(yearSince, 1, 1).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
 EnumMap<Month, Integer> right =
        Stream.iterate(firstMonday, date -> date.plusWeeks(1))
              .takeWhile(date -> date.getYear() <= yearUntilInclusive)
              .collect(
                  Collectors.collectingAndThen(
                      Collectors.groupingBy(LocalDate::getMonth, Collectors.summingInt(x -> 1)),
                      EnumMap::new
                  )
              );

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=416612&siteId=1