diff --git a/.idea/modules.xml b/.idea/modules.xml index c0e0ef5..aaf6953 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -6,6 +6,7 @@ + \ No newline at end of file diff --git a/Week4/Week4.iml b/Week4/Week4.iml new file mode 100644 index 0000000..5173e2f --- /dev/null +++ b/Week4/Week4.iml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Week4/src/AllFilters.java b/Week4/src/AllFilters.java new file mode 100644 index 0000000..3f5b388 --- /dev/null +++ b/Week4/src/AllFilters.java @@ -0,0 +1,24 @@ +import java.util.ArrayList; + +public class AllFilters implements Filter { + ArrayList filters; + + public AllFilters() { + filters = new ArrayList<>(); + } + + public void addFilter(Filter f) { + filters.add(f); + } + + @Override + public boolean satisfies(String id) { + for (Filter f : filters) { + if (!f.satisfies(id)) { + return false; + } + } + + return true; + } +} diff --git a/Week4/src/DirectorsFilter.java b/Week4/src/DirectorsFilter.java new file mode 100644 index 0000000..d0b7bf4 --- /dev/null +++ b/Week4/src/DirectorsFilter.java @@ -0,0 +1,25 @@ +/** + * A class for filter movies by directors + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class DirectorsFilter implements Filter { + String directors; + + public DirectorsFilter(String directors) { + this.directors = directors; + } + + @Override + public boolean satisfies(String id) { + String movieDirectors = MovieDatabase.getDirector(id); + String[] filterDirectors = directors.split(","); + for (String direcor : filterDirectors) { + if (movieDirectors.contains(direcor)) { + return true; + } + } + return false; + } +} diff --git a/Week4/src/EfficientRater.java b/Week4/src/EfficientRater.java new file mode 100644 index 0000000..20388ca --- /dev/null +++ b/Week4/src/EfficientRater.java @@ -0,0 +1,51 @@ +import java.util.ArrayList; +import java.util.HashMap; + +/** + * The class EfficientRater keeps track of one rater and all their ratings. + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class EfficientRater implements Rater { + private final String myID; + private final HashMap myRatings; + + public EfficientRater(String id) { + myID = id; + myRatings = new HashMap<>(); + } + + public void addRating(String movieID, double rating) { + myRatings.put(movieID, new Rating(movieID, rating)); + } + + public boolean hasRating(String movieID) { + return myRatings.containsKey(movieID); + } + + public String getID() { + return myID; + } + + public double getRating(String movieID) { + Rating rating = myRatings.get(movieID); + if (rating != null) { + return rating.getValue(); + } else { + return -1; + } + } + + public int numRatings() { + return myRatings.size(); + } + + public ArrayList getItemsRated() { + return new ArrayList<>(myRatings.keySet()); + } + + public HashMap getMyRatings() { + return myRatings; + } +} diff --git a/Week4/src/Filter.java b/Week4/src/Filter.java new file mode 100644 index 0000000..7e799b2 --- /dev/null +++ b/Week4/src/Filter.java @@ -0,0 +1,3 @@ +public interface Filter { + boolean satisfies(String id); +} diff --git a/Week4/src/FirstRatings.java b/Week4/src/FirstRatings.java new file mode 100644 index 0000000..3931505 --- /dev/null +++ b/Week4/src/FirstRatings.java @@ -0,0 +1,366 @@ +import edu.duke.FileResource; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * A class to process the movie and ratings data and to answer questions about them. + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class FirstRatings { + private ArrayList movieArrayList; + private ArrayList raterArrayList; + private HashMap> directorsAndItsMovies; + private HashMap ratersWithIds; + private HashMap ratersAndCountOfRatings; + private HashMap> moviesAndRatersMap; + // The constrictor for only Movies + public FirstRatings(String fileName) { + String fullFileName = getFullFileName(fileName); + this.movieArrayList = loadMovies(fullFileName); + this.directorsAndItsMovies = getDirectorsAndMovies(); + } + // The constructor for Movies and Raters + public FirstRatings(String fileNameMovies, String fileNameRaters) { + this(fileNameMovies); + String fullFileNameRaters = getFullFileName(fileNameRaters); + this.raterArrayList = loadRaters(fullFileNameRaters); + this.ratersWithIds = loadRatersWithIDs(raterArrayList); + this.ratersAndCountOfRatings = getAllRatersIdAndItsRatingsCounterMap(); + this.moviesAndRatersMap = getAllMoviesAndRatesMap(this.movieArrayList); + } + + public FirstRatings() { + // Maybe later.. + } + + // Make full filename for any OS types + private static String getFullFileName(String fileName) { + if (!(new File(fileName).exists())) { + fileName = + System.getProperty("user.dir") + File.separator + "data" + File.separator + fileName; + } + return fileName; + } + + /* + * This method should process every record from the CSV file whose name is + * filename, a file of raters and their ratings + */ + static ArrayList loadRaters(String fileName) { + ArrayList ratersList = new ArrayList<>(); + ArrayList idsList = new ArrayList<>(); + + String fullFileName = getFullFileName(fileName); + + // Get Duke's FileResource. + FileResource fileResource = new FileResource(fullFileName); + + // Get Apache CSV + CSVParser csvRecords = fileResource.getCSVParser(); + + // Proceed every CSVRecord + for (CSVRecord record : csvRecords) { + // rater_id,movie_id,rating,time + String rater_id = record.get(0); + String movie_id = record.get(1); + double rating = Double.parseDouble(record.get(2)); + if (!idsList.contains(rater_id)) { + Rater rater = new EfficientRater(rater_id); + ratersList.add(rater); + rater.addRating(movie_id, rating); + idsList.add(rater_id); + } else { + for (Rater r : ratersList) { + if (r.getID().equals(rater_id)) { + r.addRating(movie_id, rating); + } + } + } + } + + return ratersList; + } // loadRaters + + private HashMap> getAllMoviesAndRatesMap(ArrayList moviesList) { + HashMap> allMoviesAndRatesTempMap = new HashMap<>(); + // fulfill map with all MoviesID with null ArrayList + for (Movie movie : moviesList) { + allMoviesAndRatesTempMap.putIfAbsent(movie.getID(), new ArrayList<>()); + } + + for (Rater rater : raterArrayList) { + ArrayList ratedMovies = rater.getItemsRated(); + for (String ratedMovieID : ratedMovies) { + allMoviesAndRatesTempMap.get(ratedMovieID).add(rater.getID()); + } + } + + return allMoviesAndRatesTempMap; + } + + private HashMap loadRatersWithIDs(ArrayList ratersList) { + HashMap map = new HashMap<>(); + for (Rater rater : ratersList) { + map.put(rater.getID(), rater); + } + return map; + } + + // Proceed map of all directors and its movies + private HashMap> getDirectorsAndMovies() { + HashMap> map = new HashMap<>(); + for (Movie movie : movieArrayList) { + String movieName = movie.getTitle(); + String[] directors = movie.getDirector().split(","); + for (String director : directors) { + director = director.trim(); + HashSet set = new HashSet<>(); + if (map.containsKey(director)) { + set = map.get(director); + set.add(movieName); + } else { + set.add(movieName); + map.put(director, set); + } + } + } + return map; + } + + // Process every record from the CSV file. + ArrayList loadMovies(String filename) { + + // Create empty list of Movie's + ArrayList moviesList = new ArrayList<>(); + + // Get Duke's FileResource. + FileResource fileResource = new FileResource(filename); + + // Get Apache CSV + CSVParser csvRecords = fileResource.getCSVParser(); + + // Proceed every CSVRecord + for (CSVRecord record : csvRecords) { + moviesList.add(parseMovieCSVRecord(record)); + } + return moviesList; + } + + // Creates new Movie object + private Movie parseMovieCSVRecord(CSVRecord record) { + /* id(0),title(1),year(2),country(3),genre(4),director(5),minutes(6),poster(7) + * As it shows in CSV file + */ + String id = record.get(0); + String title = record.get(1); + String year = record.get(2); + String country = record.get(3); + String genre = record.get(4); + String director = record.get(5); + // The constructor takes minutes as int + int minutes = Integer.parseInt(record.get(6)); + String poster = record.get(7); + + // return new Movie object + return new Movie(id, title, year, genre, director, country, poster, minutes); + } + + /** + * A void method for tests cases. + * + *
    + *
  • determine how many movies in total. + *
  • determine how many movies include the Comedy genre. + *
  • determine how many movies are greater than 150 minutes in length. + *
  • determine the maximum number of movies by any director, and who the directors are that + * directed that many movies. + *
+ */ + public void testLoadMovies() { + int totalMoviesCount = movieArrayList.size(); + int totalComedyCount = totalComedy(); + int totalMoviesLonger150MinutesCount = total150(); + String directorWithMaxFilms = maxFilmsOneDirectorName()[0]; + String oneDirectorMaxFilmsCounter = maxFilmsOneDirectorName()[1]; + + System.out.printf("Note there are %d movies in this file.\n", totalMoviesCount); + System.out.println("How many of the movies include the Comedy genre? > " + totalComedyCount); + System.out.println( + "How many of these movies run longer than 150 minutes? > " + + totalMoviesLonger150MinutesCount); + System.out.println( + "What is the maximum number of films directed by one director? > " + + oneDirectorMaxFilmsCounter); + System.out.println( + "What is the name of the director who directed more films than any other " + + "director? " + + directorWithMaxFilms); + System.out.println("---"); + } + + // Determine the Director with max movies and its count + private String[] maxFilmsOneDirectorName() { + String director = ""; + int counter = 0; + for (Map.Entry> pair : directorsAndItsMovies.entrySet()) { + if (pair.getValue().size() > counter) { + counter = pair.getValue().size(); + director = pair.getKey(); + } + } + String stringCounter = String.valueOf(counter); + return new String[] {director, stringCounter}; + } + + // determine how many movies are greater than 150 minutes in length. + private int total150() { + int counter = 0; + for (Movie movie : movieArrayList) { + if (movie.getMinutes() > 150) { + counter++; + } + } + return counter; + } + + // determine how many movies include the Comedy genre + private int totalComedy() { + int counter = 0; + for (Movie movie : movieArrayList) { + if (movie.getGenres().toLowerCase().contains("comedy")) { + counter++; + } + } + return counter; + } + + public void testLoadRaters(String raterID, String movieID) { + + // Use for test purposes + testPrintAllRaterWithRatings(); + + // find the number of ratings for a particular rater + printNumberOfRatings(raterID); + + // Find the maximum number of ratings by any rater + printMaxNumOfRatings(); + + // Print the number of ratings a particular movie has + // String movieID = "1798709"; + System.out.printf( + "How many ratings does the movie %s have? > %d\n", + movieID, getNumberOfRatingsByMovie(movieID)); + // And how many different movies have been rated by all these raters + + // total number of unique movies that have been rated + printTotalNumberMoviesRated(); + } + + // Q10. What is the total number of unique movies that have been rated? + private void printTotalNumberMoviesRated() { + int total = 0; + for (Map.Entry> entry : moviesAndRatersMap.entrySet()) { + if (entry.getValue() != null) { + total++; + } + } + System.out.println( + "What is the total number of unique movies that have been rated? > " + total); + } + + // Get number of Ratings by MovieID + private int getNumberOfRatingsByMovie(String movieID) { + return moviesAndRatersMap.get(movieID).size(); + } + + // Determine how many raters have this maximum number of ratings and who those raters are. + private void printMaxNumOfRatings() { + + // Find max rating value + int maxRatings = getMaxRatings(); + System.out.println("What is the maximum number of ratings by any rater? > " + maxRatings); + + for (Map.Entry entry : ratersAndCountOfRatings.entrySet()) { + if (entry.getValue() == maxRatings) { + System.out.println("Which rater rated the most number of movies? > " + entry.getKey()); + } + } + } + + // find the maximum number of ratings by any rater + private int getMaxRatings() { + int maxRatings = 0; + for (int counter : ratersAndCountOfRatings.values()) { + if (counter > maxRatings) { + maxRatings = counter; + } + } + return maxRatings; + } + + // Get map with number of rates for every Rater + private HashMap getAllRatersIdAndItsRatingsCounterMap() { + HashMap map = new HashMap<>(); + for (String id : ratersWithIds.keySet()) { + int count = getNumberOfRatingsByRaterID(id); + map.put(id, count); + } // for + return map; + } + + // Find the number of ratings for a particular rater + private void printNumberOfRatings(String id) { + int ratingsCount = getNumberOfRatingsByRaterID(id); + System.out.printf("How many ratings does the rater number %s have? > %s\n", id, ratingsCount); + // System.out.printf("Rater id: %s has %d ratings\n", id, ratingsCount); + } + + // Get total number of rates for specific rater's id + private int getNumberOfRatingsByRaterID(String id) { + return ratersWithIds.get(id).numRatings(); + } + + /* + Print the total number of raters. Then for each rater, print the rater’s ID and the number of + ratings they did on one line, followed by each rating (both the movie ID and the rating given) + on a separate line. If you run your program on the file ratings_short.csv you will see there + are five raters. + */ + private void testPrintAllRaterWithRatings() { + // Print the total number of raters. + int totalRaterCount = raterArrayList.size(); + System.out.printf("Note there are %d raters in this file.\n", totalRaterCount); + + // Then for each rater, print the rater’s ID + // and the number of ratings they did on one line + // printAllRaterRatings(); + } + + // Print out number o rates for all raters + private void printAllRaterRatings() { + for (Map.Entry entry : ratersAndCountOfRatings.entrySet()) { + String raterId = entry.getKey(); + int ratingsCount = entry.getValue(); + System.out.printf("Rater id: %s has %d ratings\n", raterId, ratingsCount); + printAllRatingsByRater(raterId); + } + } + + // Print all ratings for specific Rater ID + private void printAllRatingsByRater(String raterID) { + Rater rater = ratersWithIds.get(raterID); + ArrayList allRatedMoviesIDs = rater.getItemsRated(); + for (String id : allRatedMoviesIDs) { + double rating = rater.getRating(id); + System.out.printf("Movie ID: %s, rating: %f\n", id, rating); + } + } +} // class diff --git a/Week4/src/FourthRatings.java b/Week4/src/FourthRatings.java new file mode 100644 index 0000000..aa7e2b4 --- /dev/null +++ b/Week4/src/FourthRatings.java @@ -0,0 +1,171 @@ +import java.util.ArrayList; +import java.util.HashMap; + +import static java.util.Collections.reverseOrder; +import static java.util.Collections.sort; + +/** + * The week 4 class + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class FourthRatings { + + private Double getAverageByID(String movieID, Integer minimalRaters) { + RaterDatabase.initialize("ratings.csv"); + ArrayList myRaters = RaterDatabase.getRaters(); + long numOfRatings = myRaters.stream().filter(rater -> rater.hasRating(movieID)).count(); + + if (numOfRatings >= minimalRaters) { + return myRaters.stream() + .filter(rater -> rater.hasRating(movieID)) + .mapToDouble(rating -> rating.getRating(movieID)) + .average() + .orElse(0.0); + } + return 0.0; + } + + public ArrayList getAverageRatings(int minimalRaters) { + RaterDatabase.initialize("ratings.csv"); + ArrayList list = new ArrayList<>(); + ArrayList allMoviesIDs = MovieDatabase.filterBy(new TrueFilter()); + for (String movieID : allMoviesIDs) { + double averageRating = getAverageByID(movieID, minimalRaters); + if (averageRating != 0.0) { + list.add(new Rating(movieID, averageRating)); + } + } + return list; + } + + public ArrayList getAverageRatingsByFilter(Integer minimalRaters, Filter filterCriteria) { + ArrayList ratingsList = new ArrayList<>(); + ArrayList allMoviesIDs = MovieDatabase.filterBy(filterCriteria); + Rating rating; + for (String movie_id : allMoviesIDs) { + if (getAverageByID(movie_id, minimalRaters) != 0) { + rating = new Rating(movie_id, getAverageByID(movie_id, minimalRaters)); + ratingsList.add(rating); + } + } + + sort(ratingsList); + return ratingsList; + } + + public double dotProduct(Rater meRater, Rater otherRater) { + HashMap myRatings = meRater.getMyRatings(); + double result = 0.0; + for (String id : myRatings.keySet()) { + double scale = meRater.getRating(id) - 5; + if (otherRater.hasRating(id)) { + result += scale * (otherRater.getRating(id) - 5); + } + } + return result; + } + + private ArrayList getSimilarities(String id) { + ArrayList list = new ArrayList<>(); + Rater me = RaterDatabase.getRater(id); + for (Rater rater : RaterDatabase.getRaters()) { + if (!me.getID().equals(rater.getID())) { + double dotProduct = dotProduct(me, rater); + if (dotProduct > 0) { + Rating rating = new Rating(rater.getID(), dotProduct); + list.add(rating); + } + } + } + list.sort(reverseOrder()); + return list; + } + + public ArrayList getSimilarRatings( + String id, Integer numSimilarRaters, Integer minimalRaters) { + ArrayList ratingMoive = new ArrayList<>(); + ArrayList ratingRater = getSimilarities(id); + ArrayList movies = MovieDatabase.filterBy(new TrueFilter()); + for (String movie_id : movies) { + int md_id = Integer.parseInt(movie_id); + if (hasMinRaters(movie_id, minimalRaters, numSimilarRaters, ratingRater)) { + double sum = 0.0; + double ave; + double num = 0.0; + for (int i = 0; i < numSimilarRaters; i++) { + Rater rater = RaterDatabase.getRater(ratingRater.get(i).getItem()); + HashMap movieRated = rater.getMyRatings(); + for (String mo_id : movieRated.keySet()) { + int rm_id = Integer.parseInt(mo_id); + if (rm_id == md_id) { + sum += ratingRater.get(i).getValue() * rater.getRating(mo_id); + num += 1; + } + } + } + if (num != 0.0) { + ave = sum / num; + Rating rating = new Rating(movie_id, ave); + ratingMoive.add(rating); + } + } + } + ratingMoive.sort(reverseOrder()); + return ratingMoive; + } + + private boolean hasMinRaters( + String movie_id, + Integer minimalRaters, + Integer numSimilarRaters, + ArrayList ratingRater) { + int numOfRaters = 0; + int md_id = Integer.parseInt(movie_id); + for (int i = 0; i < numSimilarRaters; i++) { + Rater rater = RaterDatabase.getRater(ratingRater.get(i).getItem()); + HashMap movieRated = rater.getMyRatings(); + for (String mo_id : movieRated.keySet()) { + int rm_id = Integer.parseInt(mo_id); + if (rm_id == md_id) { + numOfRaters += 1; + } + } + } + return numOfRaters >= minimalRaters; + } + + public ArrayList getSimilarRatingsByFilter( + String id, Integer numSimilarRaters, Integer minimalRaters, Filter filterCriteria) { + ArrayList ratingMoive = new ArrayList<>(); + ArrayList ratingRater = getSimilarities(id); + ArrayList movies = MovieDatabase.filterBy(filterCriteria); + for (String movie_id : movies) { + int md_id = Integer.parseInt(movie_id); + if (hasMinRaters(movie_id, minimalRaters, numSimilarRaters, ratingRater)) { + double sum = 0.0; + double ave; + double num = 0.0; + for (int i = 0; i < numSimilarRaters; i++) { + Rater rater = RaterDatabase.getRater(ratingRater.get(i).getItem()); + HashMap movieRated = rater.getMyRatings(); + for (String mo_id : movieRated.keySet()) { + int rm_id = Integer.parseInt(mo_id); + if (rm_id == md_id) { + sum += ratingRater.get(i).getValue() * rater.getRating(mo_id); + num += 1.0; + } + } + } + if (num != 0.0) { + ave = sum / num; + Rating rating = new Rating(movie_id, ave); + ratingMoive.add(rating); + } + } + } + ratingMoive.sort(reverseOrder()); + return ratingMoive; + } +} diff --git a/Week4/src/GenreFilter.java b/Week4/src/GenreFilter.java new file mode 100644 index 0000000..7913b96 --- /dev/null +++ b/Week4/src/GenreFilter.java @@ -0,0 +1,22 @@ +/** + * A class for filter movies by genre + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class GenreFilter implements Filter { + + private final String genre; + + // The constructor should have one parameter named genre representing one genre, + // and the satisfies method should return true if a movie has this genre. + // Note that movies may have several genres. + public GenreFilter(String genre) { + this.genre = genre.toLowerCase(); + } + + @Override + public boolean satisfies(String id) { + return MovieDatabase.getGenres(id).toLowerCase().contains(genre); + } +} diff --git a/Week4/src/MinutesFilter.java b/Week4/src/MinutesFilter.java new file mode 100644 index 0000000..18a51c0 --- /dev/null +++ b/Week4/src/MinutesFilter.java @@ -0,0 +1,26 @@ +/** + * A class for filter movies by time + * + * @author Stanislav Rakitov + * @version 1.0 + */ +public class MinutesFilter implements Filter { + private final int minMinutes; + private final int maxMinutes; + + public MinutesFilter(int minMinutes, int maxMinutes) { + this.minMinutes = minMinutes; + this.maxMinutes = maxMinutes; + } + + // No max minutes given + public MinutesFilter(int minMinutes) { + this.minMinutes = minMinutes; + this.maxMinutes = Integer.MAX_VALUE; + } + + @Override + public boolean satisfies(String id) { + return MovieDatabase.getMinutes(id) >= minMinutes && MovieDatabase.getMinutes(id) <= maxMinutes; + } +} diff --git a/Week4/src/Movie.java b/Week4/src/Movie.java new file mode 100644 index 0000000..ae38dc3 --- /dev/null +++ b/Week4/src/Movie.java @@ -0,0 +1,82 @@ +// An immutable passive data object (PDO) to represent item data +public class Movie { + private final String id; + private final String title; + private final int year; + private final String genres; + private String director; + private String country; + private String poster; + private int minutes; + + public Movie(String anID, String aTitle, String aYear, String theGenres) { + // just in case data file contains extra whitespace + id = anID.trim(); + title = aTitle.trim(); + year = Integer.parseInt(aYear.trim()); + genres = theGenres; + } + + public Movie( + String anID, + String aTitle, + String aYear, + String theGenres, + String aDirector, + String aCountry, + String aPoster, + int theMinutes) { + // just in case data file contains extra whitespace + id = anID.trim(); + title = aTitle.trim(); + year = Integer.parseInt(aYear.trim()); + genres = theGenres; + director = aDirector; + country = aCountry; + poster = aPoster; + minutes = theMinutes; + } + + // Returns ID associated with this item + public String getID() { + return id; + } + + // Returns title of this item + public String getTitle() { + return title; + } + + // Returns year in which this item was published + public int getYear() { + return year; + } + + // Returns genres associated with this item + public String getGenres() { + return genres; + } + + public String getCountry() { + return country; + } + + public String getDirector() { + return director; + } + + public String getPoster() { + return poster; + } + + public int getMinutes() { + return minutes; + } + + // Returns a string of the item's information + public String toString() { + String result = "Movie [id=" + id + ", title=" + title + ", year=" + year; + result += ", genres= " + genres + "]"; + return result; + } +} diff --git a/Week4/src/MovieDatabase.java b/Week4/src/MovieDatabase.java new file mode 100644 index 0000000..c86fa82 --- /dev/null +++ b/Week4/src/MovieDatabase.java @@ -0,0 +1,89 @@ +import java.util.ArrayList; +import java.util.HashMap; + +public class MovieDatabase { + private static HashMap ourMovies; + + public static void initialize(String moviefile) { + if (ourMovies == null) { + ourMovies = new HashMap<>(); + loadMovies("data/" + moviefile); + } + } + + private static void initialize() { + if (ourMovies == null) { + ourMovies = new HashMap<>(); + loadMovies("data/ratedmoviesfull.csv"); + } + } + + private static void loadMovies(String filename) { + FirstRatings fr = new FirstRatings(); + ArrayList list = fr.loadMovies(filename); + for (Movie m : list) { + ourMovies.put(m.getID(), m); + } + } + + public static boolean containsID(String id) { + initialize(); + return ourMovies.containsKey(id); + } + + public static int getYear(String id) { + initialize(); + return ourMovies.get(id).getYear(); + } + + public static String getGenres(String id) { + initialize(); + return ourMovies.get(id).getGenres(); + } + + public static String getTitle(String id) { + initialize(); + return ourMovies.get(id).getTitle(); + } + + public static Movie getMovie(String id) { + initialize(); + return ourMovies.get(id); + } + + public static String getPoster(String id) { + initialize(); + return ourMovies.get(id).getPoster(); + } + + public static int getMinutes(String id) { + initialize(); + return ourMovies.get(id).getMinutes(); + } + + public static String getCountry(String id) { + initialize(); + return ourMovies.get(id).getCountry(); + } + + public static String getDirector(String id) { + initialize(); + return ourMovies.get(id).getDirector(); + } + + public static int size() { + return ourMovies.size(); + } + + public static ArrayList filterBy(Filter f) { + initialize(); + ArrayList list = new ArrayList<>(); + for (String id : ourMovies.keySet()) { + if (f.satisfies(id)) { + list.add(id); + } + } + + return list; + } +} diff --git a/Week4/src/MovieRunnerSimilarRatings.java b/Week4/src/MovieRunnerSimilarRatings.java new file mode 100644 index 0000000..82c863b --- /dev/null +++ b/Week4/src/MovieRunnerSimilarRatings.java @@ -0,0 +1,115 @@ +import java.util.ArrayList; +import java.util.Collections; + +/** @author Stanislav Rakitov */ +public class MovieRunnerSimilarRatings { + + /** + * Print a list of movies and their average ratings sorted by averages + * + * @param minimalRatings int specified number of ratings + */ + public void printAverageRatings(int minimalRatings) { + FourthRatings fourthRatings = new FourthRatings(); + ArrayList ratedList = fourthRatings.getAverageRatings(minimalRatings); + + Collections.sort(ratedList); + // Print the number of raters after creating a ThirdsRating object. + System.out.printf("Total movies with %d ratings is %d\n", minimalRatings, ratedList.size()); + + // Print the number of movies in the database. + System.out.println("The number of movies in the database is " + MovieDatabase.size()); + + // You will call getAverageRatings with a minimal number of raters to return an ArrayList of + // type Rating. + + ArrayList averageRatings = fourthRatings.getAverageRatings(minimalRatings); + + // Print out how many movies with ratings are returned, + // then sort them, and print out the rating and title of each movie + System.out.printf( + "How many movies with ratings %d are returned: %d%n", + minimalRatings, averageRatings.size()); + + printRatingsList(averageRatings); + } + + public void printAverageRatingsByYearAfterAndGenre(int minimalRatings, int year, String genre) { + FourthRatings fourthRatings = new FourthRatings(); + AllFilters filters = new AllFilters(); + filters.addFilter(new GenreFilter(genre)); + filters.addFilter(new YearAfterFilter(year)); + System.out.println(fourthRatings.getAverageRatingsByFilter(minimalRatings, filters).size()); + } + + private void printRatingsList(ArrayList averageRatingList) { + System.out.printf("Found %d movie(s)%n", averageRatingList.size()); + averageRatingList.stream() + .sorted() + .forEach( + rating -> { + String movieID = rating.getItem(); + System.out.printf("%-4s %s%n", rating.getValue(), MovieDatabase.getTitle(movieID)); + System.out.println(" Year: " + MovieDatabase.getYear(movieID)); + System.out.println(" Time: " + MovieDatabase.getMinutes(movieID)); + System.out.println(" Genre(s): " + MovieDatabase.getGenres(movieID)); + System.out.println(" Director(s): " + MovieDatabase.getDirector(movieID)); + }); + System.out.println("-------"); + } + + public void printSimilarRatings() { + FourthRatings fourthR = new FourthRatings(); + MovieDatabase.initialize("ratedmoviesfull.csv"); + RaterDatabase.initialize("ratings.csv"); + ArrayList ratingList = fourthR.getSimilarRatings("71", 20, 5); + System.out.println(MovieDatabase.getTitle(ratingList.get(0).getItem())); + } + + public void printSimilarRatingsByGenre() { + FourthRatings fourthR = new FourthRatings(); + MovieDatabase.initialize("ratedmoviesfull.csv"); + RaterDatabase.initialize("ratings.csv"); + ArrayList ratingList = + fourthR.getSimilarRatingsByFilter("964", 20, 5, new GenreFilter("Mystery")); + System.out.println(MovieDatabase.getTitle(ratingList.get(0).getItem())); + } + + public void printSimilarRatingsByDirector() { + FourthRatings fourthR = new FourthRatings(); + MovieDatabase.initialize("ratedmoviesfull.csv"); + RaterDatabase.initialize("ratings.csv"); + ArrayList ratingList = + fourthR.getSimilarRatingsByFilter( + "120", + 10, + 2, + new DirectorsFilter( + "Clint Eastwood,J.J. Abrams,Alfred Hitchcock,Sydney Pollack,David Cronenberg,Oliver Stone,Mike Leigh")); + System.out.println(ratingList.size()); + System.out.println(MovieDatabase.getTitle(ratingList.get(0).getItem())); + } + + public void printSimilarRatingsByGenreAndMinutes() { + FourthRatings fourthR = new FourthRatings(); + MovieDatabase.initialize("ratedmoviesfull.csv"); + RaterDatabase.initialize("ratings.csv"); + AllFilters allFilters = new AllFilters(); + allFilters.addFilter(new MinutesFilter(80, 160)); + allFilters.addFilter(new GenreFilter("Drama")); + ArrayList ratingList = fourthR.getSimilarRatingsByFilter("168", 10, 3, allFilters); + System.out.println(ratingList.size()); + System.out.println(MovieDatabase.getTitle(ratingList.get(0).getItem())); + } + + public void printSimilarRatingsByYearAfterAndMinutes() { + FourthRatings fourthR = new FourthRatings(); + MovieDatabase.initialize("ratedmoviesfull.csv"); + RaterDatabase.initialize("ratings.csv"); + AllFilters allFilters = new AllFilters(); + allFilters.addFilter(new MinutesFilter(70, 200)); + allFilters.addFilter(new YearAfterFilter(1975)); + ArrayList ratingList = fourthR.getSimilarRatingsByFilter("314", 10, 5, allFilters); + System.out.println(MovieDatabase.getTitle(ratingList.get(0).getItem())); + } +} diff --git a/Week4/src/Rater.java b/Week4/src/Rater.java new file mode 100644 index 0000000..65d5a3a --- /dev/null +++ b/Week4/src/Rater.java @@ -0,0 +1,19 @@ +import java.util.ArrayList; +import java.util.HashMap; + +public interface Rater { + // void addRating(String item, double rating); + void addRating(String movieID, double rating); + + boolean hasRating(String item); + + String getID(); + + double getRating(String item); + + int numRatings(); + + ArrayList getItemsRated(); + + HashMap getMyRatings(); +} diff --git a/Week4/src/RaterDatabase.java b/Week4/src/RaterDatabase.java new file mode 100644 index 0000000..4696bf1 --- /dev/null +++ b/Week4/src/RaterDatabase.java @@ -0,0 +1,71 @@ +/** + * Write a description of RaterDatabase here. + * + * @author (your name) + * @version (a version number or a date) + */ +import edu.duke.FileResource; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.util.ArrayList; +import java.util.HashMap; + +public class RaterDatabase { + private static HashMap ourRaters; + + private static void initialize() { + // this method is only called from addRatings + if (ourRaters == null) { + ourRaters = new HashMap(); + } + } + + public static void initialize(String filename) { + if (ourRaters == null) { + ourRaters = new HashMap(); + addRatings("data/" + filename); + } + } + + public static void addRatings(String filename) { + initialize(); + FileResource fr = new FileResource(filename); + CSVParser csvp = fr.getCSVParser(); + for (CSVRecord rec : csvp) { + String id = rec.get("rater_id"); + String item = rec.get("movie_id"); + String rating = rec.get("rating"); + addRaterRating(id, item, Double.parseDouble(rating)); + } + } + + public static void addRaterRating(String raterID, String movieID, double rating) { + initialize(); + Rater rater = null; + if (ourRaters.containsKey(raterID)) { + rater = ourRaters.get(raterID); + } else { + rater = new EfficientRater(raterID); + ourRaters.put(raterID, rater); + } + rater.addRating(movieID, rating); + } + + public static Rater getRater(String id) { + initialize(); + + return ourRaters.get(id); + } + + public static ArrayList getRaters() { + initialize(); + ArrayList list = new ArrayList(ourRaters.values()); + + return list; + } + + public static int size() { + return ourRaters.size(); + } +} diff --git a/Week4/src/Rating.java b/Week4/src/Rating.java new file mode 100644 index 0000000..68a4502 --- /dev/null +++ b/Week4/src/Rating.java @@ -0,0 +1,29 @@ +// An immutable passive data object (PDO) to represent the rating data +public class Rating implements Comparable { + private final String item; + private final double value; + + public Rating(String anItem, double aValue) { + item = anItem; + value = aValue; + } + + // Returns item being rated + public String getItem() { + return item; + } + + // Returns the value of this rating (as a number so it can be used in calculations) + public double getValue() { + return value; + } + + // Returns a string of all the rating information + public String toString() { + return "[" + getItem() + ", " + getValue() + "]"; + } + + public int compareTo(Rating other) { + return Double.compare(value, other.value); + } +} diff --git a/Week4/src/RecommendationRunner.java b/Week4/src/RecommendationRunner.java new file mode 100644 index 0000000..4a8f4af --- /dev/null +++ b/Week4/src/RecommendationRunner.java @@ -0,0 +1,35 @@ +import java.util.ArrayList; + +/** @author Stanislav Rakitov */ +public class RecommendationRunner implements Recommender { + @Override + public ArrayList getItemsToRate() { + ArrayList movies = MovieDatabase.filterBy(new TrueFilter()); + ArrayList toRet = new ArrayList<>(); + for (int i = 0; i < 25; i++) { + toRet.add(movies.get(i)); + } + return toRet; + } + + @Override + public void printRecommendationsFor(String webRaterID) { + FourthRatings fr = new FourthRatings(); + ArrayList movies = fr.getSimilarRatingsByFilter(webRaterID, 50, 2, new TrueFilter()); + if (movies.size() == 0) { + System.out.println("

Sorry, there are no recommendations for you.

"); + System.exit(1); + } + System.out.println(""); + System.out.println(""); + for (int i = 0; i < 15; i++) { + System.out.println( + ""); + } + System.out.println("
RankMovie Title
" + + (i + 1) + + "" + + MovieDatabase.getTitle(movies.get(i).getItem()) + + "
"); + } +} diff --git a/Week4/src/Recommender.java b/Week4/src/Recommender.java new file mode 100644 index 0000000..836e9c8 --- /dev/null +++ b/Week4/src/Recommender.java @@ -0,0 +1,49 @@ +import java.util.ArrayList; + +/** + * Implement this interface to allow your code to be integrated with our web site. + * + *

When users first visit the recommender website, our code will call the method + * getItemsToRate() to get a list of movies to display on the web page for users to rate. + * + *

When a user submits their ratings, our code will call the method + * printRecommendationsFor to get your recommendations based on the user's ratings. The ID + * given to this method is for a new Rater that we have already added to the RaterDatabase with + * ratings for the movies returned by the first method. Whatever is printed from that method will be + * displayed on the web page: HTML, plain text, or debugging information. + */ +public interface Recommender { + /** + * This method returns a list of movie IDs that will be used to look up the movies in the + * MovieDatabase and present them to users to rate. + * + *

The movies returned in the list will be displayed on a web page, so the number you choose + * may affect how long the page takes to load and how willing users are to rate the movies. For + * example, 10-20 should be fine, 50 or more would be too many. + * + *

There are no restrictions on the method you use to generate this list of movies: the most + * recent movies, movies from a specific genre, randomly chosen movies, or simply your favorite + * movies. + * + *

The ratings for these movies will make the profile for a new Rater that will be used to + * compare to for finding recommendations. + */ + ArrayList getItemsToRate(); + + /** + * This method returns nothing, but prints out an HTML table of the movies recommended for the + * given rater. + * + *

The HTML printed will be displayed on a web page, so the number you choose to display may + * affect how long the page takes to load. For example, you may want to limit the number printed + * to only the top 20-50 movies recommended or to movies not rater by the given rater. + * + *

You may also include CSS styling for your table using the <style> tag before you print + * the table. There are no restrictions on which movies you print, what order you print them in, + * or what information you include about each movie. + * + * @param webRaterID the ID of a new Rater that has been already added to the RaterDatabase with + * ratings for the movies returned by the method getItemsToRate + */ + void printRecommendationsFor(String webRaterID); +} diff --git a/Week4/src/TrueFilter.java b/Week4/src/TrueFilter.java new file mode 100644 index 0000000..9a5ae07 --- /dev/null +++ b/Week4/src/TrueFilter.java @@ -0,0 +1,6 @@ +public class TrueFilter implements Filter { + @Override + public boolean satisfies(String id) { + return true; + } +} diff --git a/Week4/src/Week4.java b/Week4/src/Week4.java new file mode 100644 index 0000000..e85208a --- /dev/null +++ b/Week4/src/Week4.java @@ -0,0 +1,36 @@ +/** @author Stanislav Rakitov */ +public class Week4 { + public static void main(String[] args) { + FourthRatings fourthRatings = new FourthRatings(); + Rater meRater = new EfficientRater("15"); + Rater otherRater = new EfficientRater("20"); + + meRater.addRating("2354", 10.0); + meRater.addRating("3285", 6.0); + meRater.addRating("1297", 2.0); + meRater.addRating("5804", 8.0); + + otherRater.addRating("3285", 4.0); + otherRater.addRating("1297", 7.0); + otherRater.addRating("6574", 10.0); + otherRater.addRating("2354", 9.0); + + System.out.print( + "Consider the method dotProduct in the FourthRatings class. What should the call " + + "dotProduct(“15”, “20”) return? "); + System.out.println(fourthRatings.dotProduct(meRater, otherRater)); + + MovieRunnerSimilarRatings quiz = new MovieRunnerSimilarRatings(); + quiz.printSimilarRatings(); + System.out.println("---"); + quiz.printSimilarRatingsByGenre(); + System.out.println("---"); + quiz.printSimilarRatings(); + System.out.println("---"); + quiz.printSimilarRatingsByDirector(); + System.out.println("---"); + quiz.printSimilarRatingsByGenreAndMinutes(); + System.out.println("---"); + quiz.printSimilarRatingsByYearAfterAndMinutes(); + } +} diff --git a/Week4/src/YearAfterFilter.java b/Week4/src/YearAfterFilter.java new file mode 100644 index 0000000..ecb3e3a --- /dev/null +++ b/Week4/src/YearAfterFilter.java @@ -0,0 +1,12 @@ +public class YearAfterFilter implements Filter { + private final int myYear; + + public YearAfterFilter(int year) { + myYear = year; + } + + @Override + public boolean satisfies(String id) { + return MovieDatabase.getYear(id) >= myYear; + } +}