Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Заменить лайки на систему рейтинга #30

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bad0cba
feat: Заменить лайки на рейтинг
vvbakhanovich Feb 3, 2024
a9868c4
feat: Заменить лайки на рейтинг
vvbakhanovich Feb 3, 2024
7ac9c30
Merge remote-tracking branch 'origin/add-test-rating' into add-test-r…
vvbakhanovich Feb 6, 2024
d16fb10
style
vvbakhanovich Feb 6, 2024
733bf7f
refactor: Изменить лайки на рейтинг
vvbakhanovich Feb 6, 2024
479ebaf
refactor: Возвращать void у setGenresForFilms
vvbakhanovich Feb 6, 2024
7701e94
refactor: Исправить like на rating в searchFilms
vvbakhanovich Feb 6, 2024
0163e4d
feat: Добавить сортировку по рейтингу
vvbakhanovich Feb 6, 2024
3038976
test: Добавить рейтинг в существующие тесты
vvbakhanovich Feb 6, 2024
a1c1a4d
Merge remote-tracking branch 'origin/develop' into add-test-rating
vvbakhanovich Feb 8, 2024
e451bbc
delete: Dao для связующих таблиц
vvbakhanovich Feb 8, 2024
4a6fbe8
refactor: Добавить параметр рейтинг
vvbakhanovich Feb 8, 2024
7f4f986
refactor: Удалить filmLikeStorage
vvbakhanovich Feb 8, 2024
a2e7ed5
refactor: Оптимизировать showRecommendations
vvbakhanovich Feb 9, 2024
b70889d
style: Исправить опечатку
vvbakhanovich Feb 9, 2024
be4cd81
refactor: Добавить positiveRating
vvbakhanovich Feb 9, 2024
22861d7
refactor: Добавить positiveRating
vvbakhanovich Feb 9, 2024
07a28a7
fix: Разрешить нулевой рейтинг
vvbakhanovich Feb 9, 2024
76e1f5b
fix: Изменить алгоритм рекомендаций
vvbakhanovich Feb 9, 2024
cd71423
refactor: Убрать лишний вложенный блок
vvbakhanovich Feb 9, 2024
fbc8e26
fix: Использовать новую сигнатуру getUsersAndFilmLikes
vvbakhanovich Feb 9, 2024
3724014
feat: Использовать mergre при добавлении оценки фильму
vvbakhanovich Feb 10, 2024
c3967b0
refactor: Использовать slope one для рекомендаций
vvbakhanovich Feb 10, 2024
8800893
test: Добавить тесты для рейтинга
vvbakhanovich Feb 10, 2024
09083bd
fix: Добавить проверку на количество оценок
vvbakhanovich Feb 10, 2024
6a83488
test: Написать тесты на получение рекомендаций
vvbakhanovich Feb 10, 2024
2485552
test: Написать тесты на получение рекомендаций
vvbakhanovich Feb 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.dto.FilmDto;
import ru.yandex.practicum.filmorate.dto.FilmSearchDto;
import ru.yandex.practicum.filmorate.service.FilmService;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Min;
import java.util.Collection;

@RestController
@RequestMapping("/films")
@Validated
@RequiredArgsConstructor
public class FilmController {

Expand Down Expand Up @@ -41,8 +45,11 @@ public FilmDto getFilmById(@PathVariable long id) {
}

@PutMapping("/{id}/like/{userId}")
public FilmDto likeFilm(@PathVariable long id, @PathVariable long userId) {
return filmService.likeFilm(id, userId);
public FilmDto likeFilm(@PathVariable long id, @PathVariable long userId,
@RequestParam(required = false, defaultValue = "0")
@PositiveOrZero(message = "Рейтинг может находиться в диапозоне от 1 до 10")
@Max(value = 10, message = "Рейтинг не должен превышать 10.") int rating) {
return filmService.likeFilm(id, userId, rating);
}

@DeleteMapping("/{id}/like/{userId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

public interface FilmStorage extends Dao<Film> {

void addLikeToFilm(long filmId, long userId);
void addLikeToFilm(long filmId, long userId, int rating);

void removeLikeFromFilm(long filmId, long userId);

Expand All @@ -24,4 +24,6 @@ public interface FilmStorage extends Dao<Film> {
Collection<Film> findMostLikedFilms(int count, Integer genreId, Integer year);

Collection<Film> findCommonFilms(long userId, long friendId);

Map<Long, Set<Film>> findAllFilmsLikedByUsers();
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void update(final Film film) {
@Override
public Collection<Film> findAll() {
final String sql = "SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
Expand All @@ -94,7 +94,7 @@ public Collection<Film> findAll() {
@Override
public Film findById(final long filmId) {
final String sql = "SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
Expand All @@ -116,7 +116,7 @@ public Film findById(final long filmId) {
@Override
public Collection<Film> findMostLikedFilms(final int count, final Integer genreId, final Integer year) {
final StringBuilder sql = new StringBuilder(
"SELECT f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"SELECT f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM FILM_GENRE " +
"RIGHT JOIN film f on FILM_GENRE.FILM_ID = f.ID " +
"LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
Expand All @@ -141,7 +141,7 @@ public Collection<Film> findFilmsFromDirectorOrderBy(final long directorId, fina
final String ids = String.join(",", Collections.nCopies(filmsByDirectorId.size(), "?"));
final String sql = String.format(
"SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
Expand All @@ -161,7 +161,7 @@ public Collection<Film> findFilmsByIds(Set<Long> filmIds) {
final String ids = String.join(",", Collections.nCopies(filmIds.size(), "?"));
final String sql = String.format(
"SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
Expand All @@ -177,7 +177,7 @@ public Collection<Film> findFilmsByIds(Set<Long> filmIds) {
@Override
public Collection<Film> searchFilms(FilmSearchDto search) {
StringBuilder sql = new StringBuilder("SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes, d.DIRECTOR_NAME " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating, d.DIRECTOR_NAME " +
"FROM FILM f " +
"LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
Expand Down Expand Up @@ -211,20 +211,20 @@ public Collection<Film> findCommonFilms(long userId, long friendId) {
"WHERE fl1.user_id = ? AND fl2.user_id = ? AND fl1.film_id = fl2.film_id";
final String sql = String.format(
"SELECT " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, COUNT(fl.USER_ID) AS likes " +
"f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, CAST (AVG (fl.RATING) AS DECIMAL(3,1)) AS rating " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"LEFT JOIN film_like fl on f.id = fl.film_id " +
"GROUP BY f.id, m.rating_name " +
"HAVING f.id IN (%s) " +
"ORDER BY likes DESC", commonFilmsIdsSql);
"ORDER BY COUNT(fl.USER_ID) DESC", commonFilmsIdsSql);
return jdbcTemplate.query(sql, this::mapToFilm, userId, friendId);
}

@Override
public void addLikeToFilm(final long filmId, final long userId) {
final String sql = "INSERT INTO film_like (film_id, user_id) VALUES (?, ?)";
jdbcTemplate.update(sql, filmId, userId);
public void addLikeToFilm(final long filmId, final long userId, final int rating) {
final String sql = "INSERT INTO film_like (film_id, user_id, rating) VALUES (?, ?, ?)";
jdbcTemplate.update(sql, filmId, userId, rating);
}

@Override
Expand All @@ -239,6 +239,15 @@ public Map<Long, Set<Long>> getUsersAndFilmLikes() {
return jdbcTemplate.query(filmsIdsSql, this::extractToUserIdLikedFilmsIdsMap);
}

public Map<Long, Set<Film>> findAllFilmsLikedByUsers() {
final String sql = "SELECT fl.USER_ID, f.ID, f.TITLE, f.DESCRIPTION, f.RELEASE_DATE, f.DURATION, f.MPA_ID, m.RATING_NAME, fl.RATING " +
"FROM " +
"FILM f LEFT JOIN MPA m ON f.MPA_ID = m.ID " +
"JOIN film_like fl on f.id = fl.film_id " +
"GROUP BY f.id, m.rating_name, fl.user_id";
return jdbcTemplate.query(sql, this::extractToUserFilmMap);
}

private void setGenresForFilms(Collection<Film> films) {
Map<Long, Film> filmMap = films.stream()
.collect(Collectors.toMap(Film::getId, identity()));
Expand Down Expand Up @@ -334,14 +343,14 @@ private List<Long> findFilmsByDirectorId(final long directorId) {

private Film mapToFilm(ResultSet rs, int rowNum) throws SQLException {
Film film = Film.builder()
.id(rs.getLong(1))
.id(rs.getLong("id"))
.name(rs.getString("title"))
.description(rs.getString("description"))
.releaseDate(rs.getDate("release_date").toLocalDate())
.duration(rs.getInt("duration"))
.mpa(new Mpa(rs.getInt("mpa_id"), rs.getString("rating_name")))
.build();
film.setLikes(rs.getLong("likes"));
film.setRating(rs.getDouble("rating"));
return film;
}

Expand Down Expand Up @@ -402,4 +411,19 @@ private Map<Long, Set<Long>> extractToUserIdLikedFilmsIdsMap(ResultSet rs) throw
}
return userFilmLikesMap;
}

private Map<Long, Set<Film>> extractToUserFilmMap(ResultSet rs) throws SQLException, DataAccessException {
final Map<Long, Set<Film>> userIdLikedFilmsMap = new HashMap<>();
while (rs.next()) {
Long userId = rs.getLong("user_id");
Set<Film> films = userIdLikedFilmsMap.get(userId);
if (films == null) {
films = new HashSet<>();
}
Film film = mapToFilm(rs, rs.getRow());
films.add(film);
userIdLikedFilmsMap.put(userId, films);
}
return userIdLikedFilmsMap;
}
}
6 changes: 2 additions & 4 deletions src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
import ru.yandex.practicum.filmorate.model.Mpa;
import ru.yandex.practicum.filmorate.validation.PastDate;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import javax.validation.constraints.Size;
import javax.validation.constraints.*;
import java.time.LocalDate;
import java.util.LinkedHashSet;

Expand All @@ -33,5 +31,5 @@ public class FilmDto {
private Mpa mpa; //возрастной рейтинг
private final LinkedHashSet<Genre> genres = new LinkedHashSet<>(); //жанры
private final LinkedHashSet<Director> directors = new LinkedHashSet<>(); //режиссеры
private long likes; //количество лайков от пользователей
private double rating; //рейтинг фильма
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static FilmDto toDto(Film film) {
.releaseDate(film.getReleaseDate())
.duration(film.getDuration())
.mpa(film.getMpa())
.likes(film.getLikes())
.rating(film.getRating())
.build();
filmDto.getGenres().addAll(film.getGenres());
filmDto.getDirectors().addAll(film.getDirectors());
Expand All @@ -30,7 +30,7 @@ public static Film toModel(FilmDto filmDto) {
.releaseDate(filmDto.getReleaseDate())
.duration(filmDto.getDuration())
.mpa(filmDto.getMpa())
.likes(filmDto.getLikes())
.rating(filmDto.getRating())
.build();
film.getGenres().addAll(filmDto.getGenres());
film.getDirectors().addAll(filmDto.getDirectors());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ public class Film {
private Mpa mpa; //возрастной рейтинг
private final LinkedHashSet<Genre> genres = new LinkedHashSet<>(); //жанры
private final LinkedHashSet<Director> directors = new LinkedHashSet<>(); //режиссеры
private long likes; //количество лайков от пользователей
private double rating; //количество лайков от пользователей
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
@Getter
public enum SortBy {
YEAR("f.release_date"),
LIKES("likes DESC");
LIKES("COUNT(fl.USER_ID) DESC"),
RATING("rating DESC");

private final String sql;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface FilmService {

FilmDto getFilmById(long filmId);

FilmDto likeFilm(long filmId, long userId);
FilmDto likeFilm(long filmId, long userId, int rating);

FilmDto removeLike(long filmId, long userId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ public FilmDto getFilmById(final long filmId) {
*/
@Override
@Transactional
public FilmDto likeFilm(final long filmId, final long userId) {
public FilmDto likeFilm(final long filmId, final long userId, final int rating) {
filmStorage.findById(filmId);
userStorage.findById(userId);
filmStorage.addLikeToFilm(filmId, userId);
filmStorage.addLikeToFilm(filmId, userId, rating);
log.info("Пользователь с id {} поставил лайк фильму с id {}", userId, filmId);
eventStorage.addEvent(EventType.LIKE.name(), Operation.ADD.name(), filmId, userId);
return toDto(filmStorage.findById(filmId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,27 +179,44 @@ public void removeUser(long userId) {
public Collection<FilmDto> showRecommendations(long id) {
log.info("Получение списка рекомендаций фильмов для пользователя с id {}.", id);
Map<Long, Set<Long>> usersLikes = filmStorage.getUsersAndFilmLikes();
Map<Long, Set<Film>> usersLikedFilms = filmStorage.findAllFilmsLikedByUsers();
int maxLikes = 0;
Set<Long> recommendations = new HashSet<>();
Set<Long> userLikedFilms = usersLikes.get(id);
for (Long user : usersLikes.keySet()) {
if (user != id) {
Set<Long> likedFilms = usersLikes.get(user);
Set<Long> currentUserLikedFilms = new HashSet<>(userLikedFilms);
currentUserLikedFilms.retainAll(likedFilms);
if (currentUserLikedFilms.size() > maxLikes && currentUserLikedFilms.size() < likedFilms.size()) {
Set<Film> recommendations = new HashSet<>();
Set<Film> userLikedFilms = usersLikedFilms.get(id);
for (Long userId : usersLikes.keySet()) {
if (userId != id) {
Set<Film> sameFilms = new HashSet<>();
Set<Film> anotherUserLikedFilms = usersLikedFilms.get(userId);
Set<Film> currentUserLikedFilms = new HashSet<>(userLikedFilms);
for (Film film : anotherUserLikedFilms) {
if (currentUserLikedFilms.stream().noneMatch(f -> f.getId() == film.getId())) {
continue;
}
Film sameFilm = currentUserLikedFilms.stream().filter(f -> f.getId() == film.getId()).findFirst().get();
Copy link
Collaborator

@ayzatmr ayzatmr Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findFirst() - у тебя вернет либо объект либо optional.emty() => ты можешь if наверху удалить, а if напсиать уже ниже, в виде если findFirst() пустой тогда continue, если нет, то сделай get(). Тут у тебя получается что ты 2 раза по циклу итерируешься чтобы сделать одно и тоже

upd:

по моему там вообще можно сделать getOrElse() и все в одну строчку уместить)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо, так и сделал)

if ((film.getRating() > 5 && sameFilm.getRating() > 5) || (film.getRating() < 6 && sameFilm.getRating() < 6)) {
sameFilms.add(film);
}
}
if (sameFilms.size() > maxLikes && sameFilms.size() < anotherUserLikedFilms.size()) {
recommendations.clear();
maxLikes = currentUserLikedFilms.size();
likedFilms.removeAll(currentUserLikedFilms);
recommendations.addAll(likedFilms);
maxLikes = sameFilms.size();
anotherUserLikedFilms.removeAll(sameFilms);
recommendations.addAll(anotherUserLikedFilms);
} else if (currentUserLikedFilms.size() == maxLikes) {
likedFilms.removeAll(currentUserLikedFilms);
recommendations.addAll(likedFilms);
anotherUserLikedFilms.removeAll(sameFilms);
recommendations.addAll(anotherUserLikedFilms);
}
}
}
Collection<Film> filmsRecommendation = filmStorage.findFilmsByIds(recommendations);
return filmsRecommendation.stream().map(FilmMapper::toDto).collect(Collectors.toList());
if (recommendations.isEmpty()) {
return Collections.emptyList();
}
Set<Long> recommendedFilmsId = recommendations.stream()
.filter(film -> film.getRating() > 5)
.map(Film::getId)
.collect(Collectors.toSet());

return filmStorage.findFilmsByIds(recommendedFilmsId).stream().map(FilmMapper::toDto).collect(Collectors.toList());
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ CREATE TABLE IF NOT EXISTS FRIENDSHIP(
CREATE TABLE IF NOT EXISTS FILM_LIKE(
FILM_ID INTEGER NOT NULL,
USER_ID INTEGER NOT NULL,
RATING INTEGER,
CONSTRAINT pk_film_like PRIMARY KEY (FILM_ID, USER_ID),
FOREIGN KEY (FILM_ID) REFERENCES FILM(ID) ON DELETE CASCADE,
FOREIGN KEY (USER_ID) REFERENCES FILMORATE_USER(ID)
);
Expand Down
Loading
Loading