Best Reviews and Monthly Rewards
Design a review system where viewers can write reviews, rate reviews written by other viewers, and reward the best reviews for each calendar month.
Review System
Each viewer is identified by a unique viewer ID. A viewer can write multiple reviews, but every review must have a globally unique review ID.
A viewer can rate a review from 1 to 5. A viewer cannot rate their own review and can submit only one rating for a particular review. Submitting another rating for the same review replaces the previous rating.
Each review belongs to the calendar month represented by the currentYearMonth value supplied when the review is written.
A calendar month is represented as an integer in YYYYMM format.
202605 represents May 2026.
202611 represents November 2026.
After a calendar month has ended, the highest-ranked reviews that have received at least one rating can receive rewards.
Current Calendar Month
Every operation method receives a currentYearMonth parameter representing the calendar month in which that method call occurs.
Across all method calls made on the same ReviewRewardSystem object, currentYearMonth is monotonically non-decreasing. It may remain the same for multiple calls, but it will never be smaller than the value supplied in an earlier call.
For example, the following sequence is valid:
202605
202605
202606
202608
The following sequence is invalid because the month decreases from 202606 to 202605:
Constructor
ReviewRewardSystem(List<String> viewerIds)
Creates the review system with the given viewers.
Methods
Write Review
String writeReview(String reviewId, String viewerId, String reviewText, int currentYearMonth)
Adds a review written by the specified viewer during currentYearMonth.
The review permanently belongs to the calendar month represented by currentYearMonth.
The review text is stored as part of the review but does not affect its ratings, ranking, or reward eligibility.
- Return
"REVIEW_ADDED" when the review is added successfully.
- Return
"VIEWER_NOT_FOUND" when the viewer does not exist.
- Return
"REVIEW_ALREADY_EXISTS" when the review ID is already in use.
The validation order is:
- Check whether the viewer exists.
- Check whether the review ID is already in use.
- Add the review.
Rate Review
String rateReview(String viewerId, String reviewId, int rating, int currentYearMonth)
Adds or updates the viewer's rating for the specified review.
A review may continue receiving ratings after its calendar month has ended. Ratings are accepted until rewards are issued for that review's calendar month.
After rewards have been issued for a month, any new or updated ratings for reviews belonging to that month are ignored. An ignored rating does not affect the review's average rating or rating count.
- Return
"RATING_ADDED" when the viewer rates the review for the first time.
- Return
"RATING_UPDATED" when an existing rating is replaced before rewards are issued.
- Return
"RATING_IGNORED" when rewards have already been issued for the review's calendar month.
- Return
"VIEWER_NOT_FOUND" when the viewer does not exist.
- Return
"REVIEW_NOT_FOUND" when the review does not exist.
- Return
"CANNOT_RATE_OWN_REVIEW" when the review was written by the same viewer and rewards have not yet been issued for that month.
The validation order is:
- Check whether the viewer exists.
- Check whether the review exists.
- Check whether rewards have already been issued for the review's calendar month.
- Check whether the viewer wrote the review.
- Add or update the rating.
Because the reward-issued check occurs before the ownership check, any rating submitted after rewards have been issued returns "RATING_IGNORED", even when the viewer is the author of the review.
Get Best Reviews
List<String> getBestReviews(int yearMonth, int limit, int currentYearMonth)
Returns up to limit best reviews written during the specified yearMonth.
The requested yearMonth will not be later than currentYearMonth.
Each returned string must use the following format: "reviewId,viewerId,averageRating,ratingCount".
Average Rating Calculation
For a review with n current ratings, its exact average rating is calculated using:
averageRating = (rating1 + rating2 + ... + ratingN) / n
Equivalently:
averageRating = sumOfCurrentRatings / ratingCount
Only the latest rating submitted by each viewer is included in the calculation.
When a viewer updates a rating, the previous rating is removed from the sum and replaced by the new rating. Updating a rating does not change the rating count.
For example, suppose a review currently has ratings of 5, 4, and 2.
sumOfCurrentRatings = 5 + 4 + 2 = 11
ratingCount = 3
averageRating = 11 / 3 = 3.666...
The average rating returned in the formatted string is 3.67.
Reviews are ranked using their exact average ratings before rounding. Rounding is performed only when formatting the returned strings.
The formatted average rating must be rounded to exactly two decimal places using half-up rounding.
If ratingCount = 0, division is not performed. The review has an average rating of 0.00.
Reviews without any ratings must therefore have an average rating of 0.00 and a rating count of 0.
Reviews without ratings are included in the result returned by getBestReviews, but they are not eligible to receive rewards.
Reviews are ordered using the following rules:
- Higher exact average rating first.
- If exact averages are equal, higher rating count first.
- If both are equal, lexicographically smaller review ID first.
Calling this method does not issue rewards or finalize the requested month.
If rewards have already been issued for the requested month, ratings submitted afterward are ignored and do not affect the returned ranking.
Reward Best Reviews
List<String> rewardBestReviews(int yearMonth, int rewardCount, int currentYearMonth)
Rewards up to rewardCount best eligible reviews written during the specified yearMonth.
Rewards can be issued only after the specified calendar month has ended. Therefore, yearMonth will always be earlier than currentYearMonth when this method is called.
Each returned string must use the following format: "reviewId,viewerId".
Reviews are selected using the same ordering rules as getBestReviews.
Only reviews that have received at least one rating are eligible for rewards. A review with a rating count of 0 must never receive a reward.
Reviews with no ratings are excluded before applying rewardCount. The method then returns up to rewardCount highest-ranked eligible reviews.
If fewer than rewardCount eligible reviews exist, return all eligible reviews.
If the month contains no rated reviews, return an empty list.
The first call to this method for a particular yearMonth permanently finalizes rewards for that month, even when the returned list is empty.
Rewards can be issued only once for a particular yearMonth. Any later call for the same yearMonth is ignored and must return an empty list, regardless of the supplied rewardCount.
After the first reward call for a month, later ratings for reviews from that month are ignored, even if no review received a reward.
Multiple reviews written by the same viewer may receive rewards during the same calendar month.
Year-Month Format
Both yearMonth and currentYearMonth values must use the YYYYMM format:
- The first four digits represent the year.
- The final two digits represent a month from
01 to 12.
- January 2027 is represented as
202701.
- December 2027 is represented as
202712.
Constraints
1 ≤ viewerIds.size() ≤ 100,000
- All viewer IDs are unique and non-empty.
- Viewer IDs and review IDs do not contain commas.
1 ≤ reviewId.length() ≤ 100
- No two successfully added reviews can have the same review ID.
1 ≤ reviewText.length() ≤ 10,000
200001 ≤ yearMonth ≤ 999912
200001 ≤ currentYearMonth ≤ 999912
- The month portion of every year-month value is between
01 and 12.
currentYearMonth never decreases across method calls made on the same object.
- For
getBestReviews, yearMonth ≤ currentYearMonth.
- For
rewardBestReviews, yearMonth < currentYearMonth.
1 ≤ rating ≤ 5
1 ≤ limit ≤ 100,000
1 ≤ rewardCount ≤ 100,000
- No parameter will contain a null value.
Examples
Parameter names in the examples are included for readability and are not part of Java method-call syntax.
Example 1
ReviewRewardSystem(viewerIds = ["viewer1", "viewer2", "viewer3"])
writeReview(reviewId = "reviewA", viewerId = "viewer1", reviewText = "Clear and useful review", currentYearMonth = 202604)
Output: "REVIEW_ADDED"
rateReview(viewerId = "viewer2", reviewId = "reviewA", rating = 5, currentYearMonth = 202604)
Output: "RATING_ADDED"
rateReview(viewerId = "viewer3", reviewId = "reviewA", rating = 3, currentYearMonth = 202604)
Output: "RATING_ADDED"
getBestReviews(yearMonth = 202604, limit = 2, currentYearMonth = 202604)
Output: ["reviewA,viewer1,4.00,2"]
Example 2
ReviewRewardSystem(viewerIds = ["amy", "ben", "cara"])
writeReview(reviewId = "r20", viewerId = "amy", reviewText = "Helpful explanation", currentYearMonth = 202607)
Output: "REVIEW_ADDED"
writeReview(reviewId = "r10", viewerId = "ben", reviewText = "Detailed review", currentYearMonth = 202607)
Output: "REVIEW_ADDED"
rateReview(viewerId = "cara", reviewId = "r20", rating = 4, currentYearMonth = 202607)
Output: "RATING_ADDED"
rateReview(viewerId = "cara", reviewId = "r10", rating = 4, currentYearMonth = 202607)
Output: "RATING_ADDED"
rewardBestReviews(yearMonth = 202607, rewardCount = 1, currentYearMonth = 202608)
Output: ["r10,ben"]
Both reviews have the same exact average rating and rating count, so the lexicographically smaller review ID receives the reward.
rewardBestReviews(yearMonth = 202607, rewardCount = 2, currentYearMonth = 202608)
Output: []
Rewards were already issued for July 2026, so the duplicate call is ignored even though a different rewardCount was supplied.
Example 3
ReviewRewardSystem(viewerIds = ["userA", "userB"])
writeReview(reviewId = "monthlyReview", viewerId = "userA", reviewText = "A short review", currentYearMonth = 202611)
Output: "REVIEW_ADDED"
rateReview(viewerId = "userA", reviewId = "monthlyReview", rating = 5, currentYearMonth = 202611)
Output: "CANNOT_RATE_OWN_REVIEW"
rateReview(viewerId = "userB", reviewId = "monthlyReview", rating = 2, currentYearMonth = 202611)
Output: "RATING_ADDED"
rateReview(viewerId = "userB", reviewId = "monthlyReview", rating = 5, currentYearMonth = 202611)
Output: "RATING_UPDATED"
rewardBestReviews(yearMonth = 202611, rewardCount = 1, currentYearMonth = 202612)
Output: ["monthlyReview,userA"]
rateReview(viewerId = "userB", reviewId = "monthlyReview", rating = 3, currentYearMonth = 202612)
Output: "RATING_IGNORED"
The updated rating is ignored because rewards have already been issued for November 2026.
rateReview(viewerId = "userA", reviewId = "monthlyReview", rating = 1, currentYearMonth = 202612)
Output: "RATING_IGNORED"
The rating is ignored before checking whether the viewer wrote the review.
getBestReviews(yearMonth = 202611, limit = 5, currentYearMonth = 202612)
Output: ["monthlyReview,userA,5.00,1"]
The ignored ratings do not replace or change the previously stored rating of 5.
Example 4
ReviewRewardSystem(viewerIds = ["writer1", "writer2", "reader"])
writeReview(reviewId = "mayReview", viewerId = "writer1", reviewText = "Review written in May", currentYearMonth = 202605)
Output: "REVIEW_ADDED"
rateReview(viewerId = "reader", reviewId = "mayReview", rating = 4, currentYearMonth = 202605)
Output: "RATING_ADDED"
writeReview(reviewId = "juneReview", viewerId = "writer2", reviewText = "Review written in June", currentYearMonth = 202606)
Output: "REVIEW_ADDED"
rateReview(viewerId = "reader", reviewId = "juneReview", rating = 5, currentYearMonth = 202606)
Output: "RATING_ADDED"
getBestReviews(yearMonth = 202605, limit = 10, currentYearMonth = 202606)
Output: ["mayReview,writer1,4.00,1"]
Only reviews written in May 2026 are included, even though the June review has a higher rating.
Example 5
ReviewRewardSystem(viewerIds = ["author1", "author2", "reader"])
writeReview(reviewId = "ratedReview", viewerId = "author1", reviewText = "A rated review", currentYearMonth = 202701)
Output: "REVIEW_ADDED"
writeReview(reviewId = "unratedReview", viewerId = "author2", reviewText = "An unrated review", currentYearMonth = 202701)
Output: "REVIEW_ADDED"
rateReview(viewerId = "reader", reviewId = "ratedReview", rating = 1, currentYearMonth = 202702)
Output: "RATING_ADDED"
The January review can still be rated in February because rewards for January have not yet been issued.
getBestReviews(yearMonth = 202701, limit = 10, currentYearMonth = 202702)
Output: ["ratedReview,author1,1.00,1", "unratedReview,author2,0.00,0"]
Both reviews are included in the ranking returned by getBestReviews.
rewardBestReviews(yearMonth = 202701, rewardCount = 2, currentYearMonth = 202702)
Output: ["ratedReview,author1"]
Only the rated review receives a reward. The unrated review is never eligible for a reward, even though fewer than two rated reviews exist.