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.
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
)
);