Wyzwanie Java #7: Rozwiązanie

Radosław Szmit 4 czerwca 2018
 

Czas na rozwiązanie ostatniego wyzwania z programowania w języku Java. Tym razem chcieliśmy byście użyli funkcjonalności dostępnych od JDK w wersji 8 to przetworzenia zbioru filmów z portalu MovieLens (zbiór MovieLens 20M Dataset, plik movies.csv).

Programowanie funkcyjne dla wielu początkujących osób bywa bardzo trudne oraz wymaga praktyki, dlatego pozostawiliśmy tą część języka Java na sam koniec. Gdybyście chcieli rozszerzyć wiedzę z tego zakresu, oprócz dokumentacji i materiałów dostępnych w internecie, na rynku można znaleźć wiele książek dotyczących programowania funkcyjnego w Javie 8, jak choćby Functional Programming in Java, której autorem jest Venkat Subramaniam - bardzo popularny prezenter na konferencjach programistycznych w Polsce i na świecie, lub Java 8. Przewodnik doświadczonego programisty Cay S. Horstmanna - autora wielu cenionych książek do nauki języka Java.

Poniżej możliwe rozwiązanie naszego ostatniego wyzwania:

package pl.kodolamacz.func;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.text.MessageFormat.format;

public class Challenge7 {

    public static void main(String[] args) {

        Path sourcePath = Paths.get("/tmp/ml-20m/movies.csv");

        List<String> lines;
        try {
            lines = Files.readAllLines(sourcePath);
        } catch (Exception e) {
            System.out.println("Błąd dostępu do pliku: " + sourcePath.getFileName());
            return;
        }

        // Parsowanie pliku CSV
        List<String[]> movies = lines.stream()
                .skip(1) // pomijamy nagłówek
                .map(Challenge7::parseCSV)
                .collect(Collectors.toList());

        // Łączną ilość filmów w pliku (celowo użycie strumieni a nie lines.count)
        long count = movies.stream().count();

        System.out.println(format("Łączna ilość filmów w pliku: {0}", count));

        // Przedział lat w jakim te filmy wyszły (daty są w nawiasie w tytule)
        IntSummaryStatistics statistics = movies.stream()
                .map(movie -> movie[1].trim())
                // rok filmu jest zawsze na końcu
                .map(title -> title.substring(title.length() - 5, title.length() - 1))
                .filter(Challenge7::isNumeric)
                .mapToInt(Integer::parseInt)
                .summaryStatistics();

        System.out.println(format("Filmy zostały wyprodukowane w latach {0} - {1}", statistics.getMin(), statistics.getMax()));

        // Ile filmów znajduje się w każdym gatunku filmowym
        Map<String, Long> genresCollect = movies.stream()
                .map(movie -> movie[2])
                // Znak | jest wyrażeniem specjalnym, musimy użyć \\ by potraktować go jak zwykły znak
                .flatMap(genres -> Arrays.stream(genres.split("\\|")))
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        genresCollect.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEach(entry -> System.out.println("Gatunek: " + entry.getKey() + " ilość filmów: " + entry.getValue()));

        // Najczęstszy gatunek filmowy
        genresCollect.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .ifPresent(element -> System.out.println(format("Naczęstrszy gatunek filmowy to: {0}, liczba filmów: {1}", element.getKey(), element.getValue())));
    }

    /**
     * Do parsowania pliku można użyć np. własnego kodu, wyrażeń regularnych lub pliku
     * W tej metodzie zostały użyte wyrażenia regularne
     */
    private static String[] parseCSV(String line) {
        String regex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)";
        String[] movie = line.split(regex);
        String title = movie[1].trim();
        if (title.charAt(0) == '"' && title.charAt(title.length() - 1) == '"') {
            movie[1] = title.substring(1, title.length() - 1).trim();
        }
        return movie;
    }

    /**
     * Sprawdzam czy String zawiera w sobie liczbę (jeden z filmów w zbiorze nie ma roku podanego)
     */
    private static boolean isNumeric(String str) {
        for (char c : str.toCharArray()) {
            if (!Character.isDigit(c)) return false;
        }
        return true;
    }

}

To już koniec naszych wspólnych zmagań z podstawami Javy. Wszystkie zadania możecie wciąż znaleźć tu na blogu lub na Facebooku.

Lista zadań z całego wyzwania:

Wprowadzenie

Wyzwanie 1 - Hello world!

Wyzwanie 2 - Podstawowe instrukcje

Wyzwanie 3 - Programowanie obiektowe

Wyzwanie 4 - Algorytmy i struktury danych w języku Java

Wyzwanie 5 - Interfejsy i dziedziczenie

Wyzwanie 6 - Operacje wejścia - wyjścia

Wyzwanie 7 - Programowanie funkcyjne

Materiały dodatkowe:

Wprowadzenie do świata języka Java

Czego się uczyć by zostać programistą?

Java od środka, czyli jak to wszystko działa?

Wprowadzenie do Apache Maven, czyli jak tworzy się projekty w świecie Java

Wprowadzenie do testowania aplikacji w środowisku Java

6 książek, które powinien przeczytać każdy programista Java

Radosław Szmit

Opiekun bootcampu Full-stack w Kodołamacz.pl. Inżynier oprogramowania, specjalista Big Data, trener IT. Absolwent Politechniki Warszawskiej aktualnie pracujący nad rozprawą doktorską z zakresu Big Data i NLP. Twórca polskiej wyszukiwarki internetowej NEKST stworzonej przez Instytut Podstaw Informatyki Polskiej Akademii Nauk oraz Otwartego Systemu Antyplagiatowego realizowanego przez Międzyuniwersyteckie Centrum Informatyzacji. Zawodowo konsultant IT specjalizujący się w rozwiązaniach Java Enterprise Edition, Big Data oraz Business Intelligence, trener IT w firmie Sages.
Komentarze
Ostatnie posty
Data Science News #204
Data Science News #203
Data Science News #202
Data Science News #201