Design a Food Rating System

Updated on 22 May, 2025
Design a Food Rating System header image

Problem Statement

The goal is to design and implement a food rating system that effectively manages two primary functionalities:

  • Modification of Food Ratings: Allows for the updating of the rating of any listed food item.
  • Retrieval of the Highest-Rated Food Item by Cuisine: Retrieves the name of the food item that has the highest rating within a specific cuisine type. In cases where there are multiple items with the same highest rating, the food item with the lexicographically smallest name is chosen.

To achieve this, a class FoodRatings is to be implemented with the following components:

  • Constructor - FoodRatings(String[] foods, String[] cuisines, int[] ratings): This initializes the system with arrays describing the food items, their corresponding cuisines, and their initial ratings.
  • Method - void changeRating(String food, int newRating): This method allows updating the rating of a food item specified by its name.
  • Method - String highestRated(String cuisine): This method returns the name of the food item with the highest rating for the given cuisine. Ties in ratings are resolved by selecting the lexicographically smaller name.

Each foods[i] denotes a food item's name, cuisines[i] indicates the type of cuisine, and ratings[i] represents the initial rating of the corresponding food item.

Examples

Example 1

Input:

["FoodRatings", "highestRated", "highestRated", "changeRating", "highestRated", "changeRating", "highestRated"]
[[["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]], ["korean"], ["japanese"], ["sushi", 16], ["japanese"], ["ramen", 16], ["japanese"]]

Output:

[null, "kimchi", "ramen", null, "sushi", null, "ramen"]

Explanation:

FoodRatings foodRatings = new FoodRatings(["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]);
foodRatings.highestRated("korean"); // return "kimchi"
// "kimchi" is the highest rated korean food with a rating of 9.
foodRatings.highestRated("japanese"); // return "ramen"
// "ramen" is the highest rated japanese food with a rating of 14.
foodRatings.changeRating("sushi", 16); // "sushi" now has a rating of 16.
foodRatings.highestRated("japanese"); // return "sushi"
// "sushi" is the highest rated japanese food with a rating of 16.
foodRatings.changeRating("ramen", 16); // "ramen" now has a rating of 16.
foodRatings.highestRated("japanese"); // return "ramen"
// Both "sushi" and "ramen" have a rating of 16.
// However, "ramen" is lexicographically smaller than "sushi".

Constraints

  • 1 <= n <= 2 * 104
  • n == foods.length == cuisines.length == ratings.length
  • 1 <= foods[i].length, cuisines[i].length <= 10
  • foods[i], cuisines[i] consist of lowercase English letters.
  • 1 <= ratings[i] <= 108
  • All the strings in foods are distinct.
  • food will be the name of a food item in the system across all calls to changeRating.
  • cuisine will be a type of cuisine of at least one food item in the system across all calls to highestRated.
  • At most 2 * 104 calls in total will be made to changeRating and highestRated.

Approach and Intuition

Given the requirements and constraints of the FoodRatings system, the following approach can be employed:

  1. Data Structures Utilized:

    • HashMaps for Efficient Retrieval:
      • To keep track of the linkage between foods and their respective properties (cuisine and rating), a HashMap can be utilized.
      • A separate HashMap can also be maintained to map every cuisine to a max-heap (or priority queue) that holds food items in descending order of their ratings and secondarily by lexicographical order for resolving ties.
  2. Initialization:

    • As the system is initialized, iterate over the provided arrays and populate the above mentioned HashMaps.
    • Initialize the max heap for each cuisine type using a comparator that first compares ratings and then the lexicographical order of the food names.
  3. Rating Modification:

    • On receiving a request to update a rating, update the potentially complex data structures (like the HashMap and max-heap) appropriately so as to accommodate this change.
    • Since each update requires reflecting changes in the max-heap (used for fast retrieval of the highest-rated food), adjustments in the heap might entail removing the old entry and re-inserting it with the new rating.
  4. Retrieval of the Highest-Rated Food:

    • Fetching the highest-rated food for a cuisine can be efficiently conducted by accessing the max-heap of that particular cuisine type and fetching the top element.
    • Given that the inherent properties of a max-heap are utilized, this retrieval operation is swift and prevents the need for a complete traversal of all items.

Given the constraints and requirements, the noted techniques aim to ensure operations are optimized and suited for scenarios with high frequency of updates and queries.

Solutions

  • C++
  • Java
  • Python
cpp
class RestaurantRatings {
    unordered_map<string, int> foodRatings;
    unordered_map<string, string> foodTypes;
    unordered_map<string, set<pair<int, string>>> typeRatings;

public:
    RestaurantRatings(vector<string>& foods, vector<string>& types, vector<int>& scores) {
        for (int i = 0; i < foods.size(); ++i) {
            foodRatings[foods[i]] = scores[i];
            foodTypes[foods[i]] = types[i];
            typeRatings[types[i]].insert({ -scores[i], foods[i] });
        }
    }

    void modifyRating(string food, int newScore) {
        string type = foodTypes[food];
        auto it = typeRatings[type].find({ -foodRatings[food], food });
        typeRatings[type].erase(it);

        foodRatings[food] = newScore;
        typeRatings[type].insert({ -newScore, food });
    }

    string topRated(string type) {
        return typeRatings[type].begin()->second;
    }
};

This solution discusses the implementation of a food rating system using C++. The main class, RestaurantRatings, manages ratings for different foods, grouped by their types.

  • Initialization occurs in the constructor where three vectors are passed: foods, types, and scores.

    • Each food item is associated with a type and a score. The food ratings and types are stored using two unordered_map objects, foodRatings and foodTypes.
    • The ratings are also stored in a set within an unordered_map typeRatings, where they are ordered by score (from highest to lowest) for each type.
  • The modifyRating function is crucial for updating the rating of a specific food.

    • First, locate and remove the current rating of the food from typeRatings.
    • Update the foodRatings with the new score.
    • Reinsert the updated rating into the typeRatings to maintain order.
  • To fetch the top-rated food for a specific type, use the topRated function.

    • Retrieve the food with the highest rating from the typeRatings map for the given type.

This system effectively keeps track of food ratings and allows for quick updates and retrieval of the highest rated food within any given type, demonstrating efficient use of data structures such as hash maps and sets to achieve fast insertions, deletions, and lookups.

java
class CuisineRatings {
    private Map<String, Integer> ratingsMap = new HashMap<>();
    private Map<String, String> cuisineTypeMap = new HashMap<>();
    private Map<String, TreeSet<Pair<Integer, String>>> sortedCuisineFoods = new HashMap<>();

    public CuisineRatings(String[] foods, String[] cuisines, int[] ratings) {
        for (int i = 0; i < foods.length; i++) {
            ratingsMap.put(foods[i], ratings[i]);
            cuisineTypeMap.put(foods[i], cuisines[i]);

            sortedCuisineFoods
                .computeIfAbsent(cuisines[i], k -> new TreeSet<>((p1, p2) -> {
                    int rateCompare = Integer.compare(p1.getKey(), p2.getKey());
                    if (rateCompare == 0) {
                        return p1.getValue().compareTo(p2.getValue());
                    }
                    return rateCompare;
                }))
                .add(new Pair(-ratings[i], foods[i]));
        }
    }

    public void updateRating(String food, int newRating) {
        String cuisine = cuisineTypeMap.get(food);
        TreeSet<Pair<Integer, String>> foodsSet = sortedCuisineFoods.get(cuisine);

        Pair<Integer, String> existing = new Pair<>(-ratingsMap.get(food), food);
        foodsSet.remove(existing);

        ratingsMap.put(food, newRating);
        foodsSet.add(new Pair<>(-newRating, food));
    }

    public String getTopRated(String cuisine) {
        Pair<Integer, String> topRated = sortedCuisineFoods.get(cuisine).first();
        return topRated.getValue();
    }
}

This solution involves designing a food rating system with Java, using a class named CuisineRatings. Here is a breakdown of how it addresses the problem:

  • Data Structures Used:

    • ratingsMap handles the mapping between food items and their ratings, using Map<String, Integer>.
    • cuisineTypeMap links each food to its respective cuisine type using Map<String, String>.
    • sortedCuisineFoods organizes foods by cuisine and sorts them based on ratings using Map<String, TreeSet<Pair<Integer, String>>>.
  • Constructor Implementation:

    • It initializes the class with arrays of foods, cuisines, and their corresponding ratings.
    • For each food item, the constructor performs the following actions:
      • Maps food to its rating.
      • Maps food to its cuisine.
      • Adds the food to a treeset in sortedCuisineFoods, ensuring it's sorted first by negative ratings (for descending order) and then alphabetically in case of ties.
  • Updating Ratings:

    • The updateRating method allows the rating of a specific food to be changed.
    • This method adjusts both ratingsMap and sortedCuisineFoods to reflect the updated rating while maintaining order.
  • Retrieving Top Rated Food:

    • The getTopRated method provides the top-rated food for a requested cuisine by accessing the first element in the sorted set for that cuisine.

This Java solution effectively manages the complexity of maintaining a dynamically changeable rating system and ensures efficient retrieval of the top-rated foods within various cuisines. Additionally, it uses Java's TreeSet and Pair utilities to handle sorting and pair management naturally, contributing to both the correctness and efficiency of the system.

python
from sortedcontainers import SortedSet
from collections import defaultdict

class RestaurantRatings:
    def __init__(self, menu_items: List[str], meal_types: List[str], scores: List[int]):
        self.menu_rating = {}
        self.menu_type = {}
        self.type_menu_sorted = defaultdict(SortedSet)

        for index in range(len(menu_items)):
            self.menu_rating[menu_items[index]] = scores[index]
            self.menu_type[menu_items[index]] = meal_types[index]
            self.type_menu_sorted[meal_types[index]].add((-scores[index], menu_items[index]))

    def updateRating(self, item: str, new_score: int) -> None:
        meal_category = self.menu_type[item]
        old_score_tuple = (-self.menu_rating[item], item)
        self.type_menu_sorted[meal_category].remove(old_score_tuple)
        self.menu_rating[item] = new_score
        self.type_menu_sorted[meal_category].add((-new_score, item))

    def bestRated(self, meal_type: str) -> str:
        top_scored = self.type_menu_sorted[meal_type][0]
        return top_scored[1]

This solution designs a food rating system using Python3, focusing on management and retrieval of restaurant menu item ratings associated with different meal types. The implementation leverages the sortedcontainers and collections libraries to handle data efficiently.

Key Components of the System:

  • Data Structures:

    • self.menu_rating: A dictionary that maps each menu item to its current rating score.
    • self.menu_type: A dictionary associating each menu item with its meal type.
    • self.type_menu_sorted: A of default dictionary of SortedSet from sortedcontainers, where for each meal type, menu items are sorted by their negative scores (to maintain a max heap structure).
  • Constructor (__init__):

    • Initializes dictionaries and populates them using parameters menu_items, meal_types, and scores.
    • For each menu item, it inserts a tuple of negative score and item name into the SortedSet associated with its meal type.
  • Rating Update (updateRating):

    • Given a menu item and a new score, updates the item's score.
    • Efficiently manages the sorted order of scores within the relevant meal type by removing the old score and inserting the new score (as a negative value).
  • Retrieve Best Rated Item (bestRated):

    • Returns the name of the highest rated menu item within a specified meal type.
    • The top of the sorted set for each meal type gives the item with the maximum score, due to negative score storage.

This system is especially efficient for update and query operations, crucial for dynamic environments like restaurant rating systems where ratings frequently change and quick retrieval of top-rated items is necessary. Its use of SortedSet ensures that both updates and queries can be performed in logarithmic time complexity relative to the number of items within each meal type category, making the solution scalable and efficient.

Comments

No comments yet.