Сортировка, группировка и подсчет дат с использованием java Stream API - PullRequest
2 голосов
/ 17 февраля 2020

Как решить следующие примеры с помощью 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);

Мой пример решения:

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(???));

}

Какой код следует вставить в (Collectors.summingLong (??? ))) чтобы посчитать количество понедельников?

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());
}

Подскажите, как правильно писать.

1 Ответ

2 голосов
/ 17 февраля 2020

Я только помогу вам исправить первый, вы можете понять это и исправить второй тоже (это почти то же самое).

Вы сначала говорите:

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

Но вам действительно нужно повторять лет здесь? Вам нужно выяснить Mondays (что такое дни, нет?). В этом главная проблема, так как ваше "l oop" произойдет только столько раз, сколько эта разница в годах. Допустим, вы хотите 2018-2020 интервал - сколько раз вы собираетесь повторять то, что у вас есть сейчас?

Но теперь вам нужно подумать, когда «остановить» это l oop - и вот почему вы использовали limit:

 .limit(yearUntilInclusive)

Проблема в том, что если вы правильно итерируете days, вы не знаете, когда остановиться, не так ли? В идеале вы хотите остановиться, используя Predicate, например: я хочу начать с первого day из 2018 и остановиться на последнем day из 2020. limit на самом деле не может этого сделать (вы можете через разницу дней от этого last до этого first - я дам вам понять, как). Или мне проще использовать Predicate с takeWhile (требуется не менее java -9).

Тогда этот Collectors.summingLong можно сделать как Collectors.summingInt (вам потребуется много лет, чтобы перейти от 32 битов целых к 64 битам). И что тебе нужно там делать? Для каждого дня, когда у вас есть совпадение, просто добавьте его. Простое «добавление одного» также называется counting, и для этого Collectors.counting() есть коллектор, который вы также можете использовать.

И последнее, что EnumMap может принимать Map в качестве параметра для своего конструктора; как таковой:

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

Но можете ли вы быть более эффективными здесь? Вы можете найти первый понедельник в году, с которого вы начинаете, и повторять недели, а не дни:

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