1912: Design-Movie-Rental-System
Hard
table of contents
I need to dissect this new template and
hash functionality that I copied off of Reddit
(it technically should go under the “learnings” section but this is too
important to miss):
template<class A, class B>
struct hash<pair<A, B>>{
size_t operator() (const pair<A, B>& p) const {
return rotl(hash<A>{}(p.first),1) ^
hash<B>{}(p.second);
}
};template<> is used to simplify creating
functions that work with multiple data types. In our case, we
use template<class A, class B> to eventually specify
a hash function that can work with two independent
types.
template<class A, class B>For example, std::unordered_map and
std::unordered_set uses std::hash to hash many
different types of values. Unfortunately,
hash<pair<A, B>> is not defined by default, so
we have to define a new hash in this format:
struct hash<pair<A, B>>{
// function call operator: allows you to call `hash<pair<A, B>>(p);`
size_t operator() (const pair<A, B>& p) const {
return ... // returns the platform-independent hash value
}
};Note, that the second const before the function body
enforces read-only behaviour, helping guarantee thread
safety for example.
Finally, for the hash function itself, we have:
return rotl(hash<A>{}(p.first),1) ^ hash<B>{}(p.second);
// note: using hash<A>(p.first) as an example
// hash<A>{} initialises the hash object for type A
// we can then use the object as a function and pass `p.first` as a parameter
// to get the corresponding hash valueFor an ideal hash function, we want:
- fast calculations
- we use a
XORoperation
- we use a
- minimal collisions
- use the
std::rotlfunction, which applies a bitwise rotation left on the hash value ofp.firstby 1 bit- spread out hash values more uniformly
- e.g.
hash<A>(p.first)andhash<B>(p.second)might have similar hash values, potentially leading to more hash collisions when both values areXORtogether
- use the
Similar to most design questions, we need initialise multiple data structures to make our functions as efficient as possible.
- we need to have two
priority_queue; one to track rented movies, and another to track unrented movies- however, we have no way of adding and removing
specific movies from a
priority_queue - a
priority_queuealso gives us no way of iterating through the first 5 elements - solution?
- since each shop can only have one copy of a movie,
we can use a sorted
setto handle bothpriority_queue. Therefore, we have:unrentedMovies(unordered_map<int, set<pair<int, int>>>) to keep track of the stores that own the cheapest unrented copies of a specific movierentedMovies(set<tuple<int, int, int>>) to keep track of cheapest rented movies
- since each shop can only have one copy of a movie,
we can use a sorted
- however, we have no way of adding and removing
specific movies from a
- for both the
rentanddropfunctions, we need a way of retrieving themovie’spricewhen given theshopandmoviemoviePrice(unordered_map<pair<int, int>, int>) allows you to find theprice, making use of the hash function defined above
unordered_map<pair<int, int>, int> moviePrice; // {shop, movie} -> price
unordered_map<int, set<pair<int, int>>> unrentedMovies; // movie -> {price, shop}
set<tuple<int, int, int>> rentedMovies; // {price, shop, movie}Now onto some real code!
When we initialise our MovieRentingSystem, we want to
populate our moviePrice and unrentedMovies
data structures:
MovieRentingSystem(int n, vector<vector<int>>& entries) {
for(vector<int>& entry : entries) {
moviePrice[{entry[0], entry[1]}] = entry[2];
unrentedMovies[entry[1]].insert({entry[2], entry[0]});
}
}Now, for the search function, we simply iterate through
the first 5 elements of the set
unrentedMovies[movie], as it contains the unrented
movies sorted by price in ascending order (using the
smaller shop as a tiebreaker):
vector<int> search(int movie) {
vector<int> ans;
auto& m : unrentedMovies[movie];
int i = 0;
for (auto it = m.begin(); it != m.end() && i < 5; ++it, ++i) {
ans.push_back(it->second);
}
return ans;
}Both rent and drop functions are very
similar; you simply just movie the movie and its
corresponding values from unrentedMovies to
rentedMovies or vice versa:
void rent(int shop, int movie) {
int price = moviePrice[{shop, movie}];
unrentedMovies[movie].erase({price, shop});
rentedMovies.insert({price, shop, movie});
}
void drop(int shop, int movie) {
int price = moviePrice[{shop, movie}];
unrentedMovies[movie].insert({price, shop});
rentedMovies.erase({price, shop, movie});
}Finally, for the report function, it follows a similar
format to search, where you simply iterate through the
first 5 elements:
vector<vector<int>> report() {
vector<vector<int>> ans;
int i = 0;
for (int it = rentedMovies.begin(); it != rentedMovies.end() && i < 5; ++it, ++i) {
auto& [price, shop, movie] = *it;
ans.emplace_back(shop, movie);
}
return ans;
}code
template<class A, class B>
struct hash<pair<A, B>>{
size_t operator() (const pair<A, B>& p) const {
return rotl(hash<A>{}(p.first),1) ^
hash<B>{}(p.second);
}
};
class MovieRentingSystem {
public:
MovieRentingSystem(int n, vector<vector<int>>& entries) {
for(vector<int>& entry : entries) {
moviePrice[{entry[0], entry[1]}] = entry[2];
unrentedMovies[entry[1]].insert({entry[2], entry[0]});
}
}
vector<int> search(int movie) {
vector<int> ans;
auto& m = unrentedMovies[movie];
int i = 0;
for (auto it = m.begin(); it != m.end() && i < 5; ++it, ++i) {
ans.push_back(it->second);
}
return ans;
}
void rent(int shop, int movie) {
int price = moviePrice[{shop, movie}];
unrentedMovies[movie].erase({price, shop});
rentedMovies.insert({price, shop, movie});
}
void drop(int shop, int movie) {
int price = moviePrice[{shop, movie}];
unrentedMovies[movie].insert({price, shop});
rentedMovies.erase({price, shop, movie});
}
vector<vector<int>> report() {
vector<vector<int>> ans;
int i = 0;
for (auto it = rentedMovies.begin(); it != rentedMovies.end() && i < 5; ++it, ++i) {
auto &[price, shop, movie] = *it;
ans.emplace_back(shop, movie);
}
return ans;
}
private:
unordered_map<pair<int, int>, int> moviePrice;
unordered_map<int, set<pair<int, int>>> unrentedMovies;
set<tuple<int, int, int>> rentedMovies;
};
/**
* Your MovieRentingSystem object will be instantiated and called as such:
* MovieRentingSystem* obj = new MovieRentingSystem(n, entries);
* vector<int> param_1 = obj->search(movie);
* obj->rent(shop,movie);
* obj->drop(shop,movie);
* vector<vector<int>> param_4 = obj->report();
*/