Big Data Passion

Big Data Passion

Strona tworzona przez pasjonatów i praktyków Big Data

Radosław Szmit

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

}
comments powered by Disqus

Ostatnie wpisy

Zobacz więcej

Kategorie

About