Spis treści

Java 8: praktyczny przykład - przewidywanie kolejnej daty w serii

Java 8 - praktyczny przykład

To pierwszy wpis w serii “przykłady pisane na kolanie”. Dziś będzie o wyznaczaniu kolejnej daty.

Jak powszechnie wiadomo, bardzo przydatną umiejętnością jest umiejętność przewidzenia, jaka data wypada jako kolejna po serii dat, które są od siebie mniej więcej w podobnej odległości czasowej.

Napisałam w tym celu mały programik w javie 8. Cały program znajduje się w jednym pliku. Używam wyłącznie biblioteki standardowej.

Użycie:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
λ  java com.kamilachyla.DateTimeSeries
2020-01-02
2020-01-04
2020-01-06
2020-01-09
2020-01-12
Average period length: 2,50
Predicted next date: 2020-01-15
λ

Przewidywanie daty

Jednym z najprostszych sposobów, w jaki można to zrobić, polega na dodaniu do ostatniej daty w serii średniej odległości między wszystkimi datami. Zbudowałam sobie pomocniczą klasę RunningAverage przechowującą ostatnią widzianą datę, sumę odległości czasowych oraz ilość odległości czasowych. Na podstawie tych danych mogę użyć operacji reduce na serii dat wczytanych jako kolejne wiersze ze standardowego wejścia.

Wczytywanie danych

Zakładam, że kolejne wiersze z System.in będą zawierały daty w formacie ISO-8601, a dokładniej: spodziewam się napisów w postaci YYYY-MM-DD.

1
2
3
4
 Stream<String> lines = is.lines().filter(s -> !s.isEmpty());
 List<LocalDate> dates = lines
    .map(toLocalDate)
    .collect(Collectors.toList());

Redukcja dat do RunningAverage

Kolejny krok to redukcja dat do RunningAverage:

1
2
3
4
5
6
7
8
 private static  RunningAverage calculateAveragePeriod(List<LocalDate> dates) {
        return dates.stream().reduce(
          RunningAverage.initial(), 
          RunningAverage::updated, 
          RunningAverage::plus);
 }
 // ... a w main():
 RunningAverage average = calculateAveragePeriod(dates);

Wypisanie rezultatów

Napisałam dwie funkcje implementujące Consumer<RunningAverage>, których zadaniem jest wypisanie:

  • średniej odległości czasowej (averagePeriodPrinter) oraz
  • przewidywanej następnej daty (nextDatePredictor)

Użyłam ich do operowania na otrzymanym wyżej obiekcie average:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private static final Consumer<RunningAverage> averagePeriodPrinter =
            (RunningAverage avg) -> System.out.println(
                    String.format("Average period length: %,.2f", avg.average()));

private static final Consumer<RunningAverage> nextDatePredictor =
            (RunningAverage ra) -> System.out.println(
                    String.format("Predicted next date: %s", ra.previous.plusDays(Math.round(ra.average()))));

    
averagePeriodPrinter.andThen(nextDatePredictor).accept(average);

Cały kod

A tu cały kod:

Gist na GitHubie

Źródła:

The Running Average