Skip to content

Commit c9d0182

Browse files
committed
Add problem 2353: Design a Food Rating System
1 parent 3fe4e71 commit c9d0182

File tree

3 files changed

+334
-0
lines changed

3 files changed

+334
-0
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,7 @@ pub mod problem_2348_number_of_zero_filled_subarrays;
17441744
pub mod problem_2349_design_a_number_container_system;
17451745
pub mod problem_2351_first_letter_to_appear_twice;
17461746
pub mod problem_2352_equal_row_and_column_pairs;
1747+
pub mod problem_2353_design_a_food_rating_system;
17471748

17481749
#[cfg(test)]
17491750
mod test_utilities;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// ------------------------------------------------------ snip ------------------------------------------------------ //
2+
3+
use std::cmp::Reverse;
4+
use std::collections::hash_map::Entry;
5+
use std::collections::HashMap;
6+
use std::convert;
7+
8+
fn encode_name(name: &str) -> u64 {
9+
let mut result = 0;
10+
let mut offset = 64;
11+
12+
for c in name.bytes() {
13+
offset -= 5;
14+
result |= u64::from(c & 0b_11111) << offset;
15+
}
16+
17+
result
18+
}
19+
20+
fn decode_name(mut name: u64, buffer: String) -> String {
21+
let mut result = buffer.into_bytes();
22+
23+
result.clear();
24+
25+
loop {
26+
result.push((name >> 59) as u8 | 0b_0110_0000);
27+
28+
name <<= 5;
29+
30+
if name == 0 {
31+
break;
32+
}
33+
}
34+
35+
String::from_utf8(result).unwrap()
36+
}
37+
38+
fn get_stat(stats: &[(u32, u64, u16)], index: u16) -> (u32, Reverse<u64>) {
39+
let (rating, food_name, _) = stats[usize::from(index)];
40+
41+
(rating, Reverse(food_name))
42+
}
43+
44+
fn heap_sift_down(
45+
heap: &mut [u16],
46+
stats: &mut [(u32, u64, u16)],
47+
stat_index: usize,
48+
rating: u32,
49+
food_name: u64,
50+
mut heap_index: usize,
51+
) {
52+
let stat = (rating, Reverse(food_name));
53+
54+
loop {
55+
let left_child_heap_index = heap_index * 2 + 1;
56+
57+
if let Some(&left_child_stat_index) = heap.get(left_child_heap_index) {
58+
let right_child_heap_index = left_child_heap_index + 1;
59+
60+
let child_heap_index = if let Some(&right_child_stat_index) = heap.get(right_child_heap_index) {
61+
if get_stat(stats, right_child_stat_index) > get_stat(stats, left_child_stat_index) {
62+
right_child_heap_index
63+
} else {
64+
left_child_heap_index
65+
}
66+
} else {
67+
left_child_heap_index
68+
};
69+
70+
let child_stat_index = heap[child_heap_index];
71+
72+
if get_stat(stats, child_stat_index) > stat {
73+
heap[heap_index] = child_stat_index;
74+
stats[usize::from(child_stat_index)].2 = heap_index as _;
75+
heap_index = child_heap_index;
76+
} else {
77+
break;
78+
}
79+
} else {
80+
break;
81+
}
82+
}
83+
84+
heap[heap_index] = stat_index as _;
85+
stats[stat_index].2 = heap_index as _;
86+
}
87+
88+
fn heap_sift_up(
89+
heap: &mut [u16],
90+
stats: &mut [(u32, u64, u16)],
91+
stat_index: usize,
92+
rating: u32,
93+
food_name: u64,
94+
mut heap_index: usize,
95+
) {
96+
let stat = (rating, Reverse(food_name));
97+
98+
loop {
99+
let parent_heap_index = heap_index.wrapping_sub(1) / 2;
100+
101+
if let Some(&parent_stat_index) = heap.get(parent_heap_index) {
102+
let parent_stat = get_stat(stats, parent_stat_index);
103+
104+
if stat > parent_stat {
105+
heap[heap_index] = parent_stat_index;
106+
stats[usize::from(parent_stat_index)].2 = heap_index as _;
107+
heap_index = parent_heap_index;
108+
} else {
109+
break;
110+
}
111+
} else {
112+
break;
113+
}
114+
}
115+
116+
heap[heap_index] = stat_index as _;
117+
stats[stat_index].2 = heap_index as _;
118+
}
119+
120+
fn heap_build(heap: &mut Box<[u16]>, stats: &mut [(u32, u64, u16)]) {
121+
let n = stats.len();
122+
123+
*heap = (0..n as _).collect();
124+
125+
for i in (0..n / 2).rev() {
126+
let (rating, food_name, heap_index) = stats[i];
127+
128+
heap_sift_down(heap, stats, i, rating, food_name, usize::from(heap_index));
129+
}
130+
}
131+
132+
fn heap_update(heap: &mut [u16], stats: &mut [(u32, u64, u16)], stat_index: u16, rating: u32) {
133+
let stat_index = usize::from(stat_index);
134+
let stat = &mut stats[stat_index];
135+
136+
if rating != stat.0 {
137+
let (old_rating, food_name, heap_index) = *stat;
138+
let heap_index = usize::from(heap_index);
139+
140+
stat.0 = rating;
141+
142+
if rating < old_rating {
143+
heap_sift_down(heap, stats, stat_index, rating, food_name, heap_index);
144+
} else {
145+
heap_sift_up(heap, stats, stat_index, rating, food_name, heap_index);
146+
}
147+
}
148+
}
149+
150+
struct Ratings {
151+
heap: Box<[u16]>,
152+
stats: Vec<(u32, u64, u16)>,
153+
}
154+
155+
pub struct FoodRatings {
156+
food_to_cuisine: HashMap<u64, (u64, u16)>,
157+
cuisine_ratings: HashMap<u64, Ratings>,
158+
}
159+
160+
impl FoodRatings {
161+
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self {
162+
let mut food_to_cuisine = HashMap::new();
163+
let mut cuisine_ratings = HashMap::<_, Ratings>::new();
164+
165+
foods
166+
.into_iter()
167+
.zip(cuisines)
168+
.zip(ratings)
169+
.for_each(|((food, cuisine), rating)| {
170+
let food = encode_name(&convert::identity(food));
171+
let cuisine = encode_name(&convert::identity(cuisine));
172+
173+
let stat_index = match cuisine_ratings.entry(cuisine) {
174+
Entry::Occupied(occupied_entry) => {
175+
let stats = &mut occupied_entry.into_mut().stats;
176+
let stat_index = stats.len() as _;
177+
178+
stats.push((rating as _, food, stat_index));
179+
180+
stat_index
181+
}
182+
Entry::Vacant(vacant_entry) => {
183+
vacant_entry.insert(Ratings {
184+
heap: Box::new([]),
185+
stats: vec![(rating as _, food, 0)],
186+
});
187+
188+
0
189+
}
190+
};
191+
192+
food_to_cuisine.insert(food, (cuisine, stat_index));
193+
});
194+
195+
for ratings in cuisine_ratings.values_mut() {
196+
heap_build(&mut ratings.heap, &mut ratings.stats);
197+
}
198+
199+
Self {
200+
food_to_cuisine,
201+
cuisine_ratings,
202+
}
203+
}
204+
205+
fn change_rating(&mut self, food: String, new_rating: i32) {
206+
let food = encode_name(&convert::identity(food));
207+
let new_rating = new_rating as u32;
208+
let (cuisine, stat_index) = self.food_to_cuisine[&food];
209+
let ratings = self.cuisine_ratings.get_mut(&cuisine).unwrap();
210+
211+
heap_update(&mut ratings.heap, &mut ratings.stats, stat_index, new_rating);
212+
}
213+
214+
fn highest_rated(&self, cuisine: String) -> String {
215+
let encoded_cuisine = encode_name(&cuisine);
216+
let ratings = &self.cuisine_ratings[&encoded_cuisine];
217+
let stat_index = ratings.heap[0];
218+
let food_name = ratings.stats[usize::from(stat_index)].1;
219+
220+
decode_name(food_name, cuisine)
221+
}
222+
}
223+
224+
// ------------------------------------------------------ snip ------------------------------------------------------ //
225+
226+
impl super::FoodRatings for FoodRatings {
227+
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self {
228+
Self::new(foods, cuisines, ratings)
229+
}
230+
231+
fn change_rating(&mut self, food: String, new_rating: i32) {
232+
self.change_rating(food, new_rating);
233+
}
234+
235+
fn highest_rated(&self, cuisine: String) -> String {
236+
self.highest_rated(cuisine)
237+
}
238+
}
239+
240+
#[cfg(test)]
241+
mod tests {
242+
#[test]
243+
fn test_solution() {
244+
super::super::tests::run::<super::FoodRatings>();
245+
}
246+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
pub mod binary_heap;
2+
3+
pub trait FoodRatings {
4+
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self;
5+
fn change_rating(&mut self, food: String, new_rating: i32);
6+
fn highest_rated(&self, cuisine: String) -> String;
7+
}
8+
9+
#[cfg(test)]
10+
mod tests {
11+
use super::FoodRatings;
12+
13+
enum Operation {
14+
ChangeRating(&'static str, i32),
15+
HighestRated(&'static str, &'static str),
16+
}
17+
18+
pub fn run<F: FoodRatings>() {
19+
let test_cases = [
20+
(
21+
(
22+
&["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"] as &[_],
23+
&["korean", "japanese", "japanese", "greek", "japanese", "korean"] as &[_],
24+
&[9, 12, 8, 15, 14, 7] as &[_],
25+
),
26+
&[
27+
Operation::HighestRated("korean", "kimchi"),
28+
Operation::HighestRated("japanese", "ramen"),
29+
Operation::ChangeRating("sushi", 16),
30+
Operation::HighestRated("japanese", "sushi"),
31+
Operation::ChangeRating("ramen", 16),
32+
Operation::HighestRated("japanese", "ramen"),
33+
] as &[_],
34+
),
35+
(
36+
(
37+
&["emgqdbo", "jmvfxjohq", "qnvseohnoe", "yhptazyko", "ocqmvmwjq"],
38+
&["snaxol", "snaxol", "snaxol", "fajbervsj", "fajbervsj"],
39+
&[2, 6, 18, 6, 5],
40+
),
41+
&[
42+
Operation::ChangeRating("qnvseohnoe", 11),
43+
Operation::HighestRated("fajbervsj", "yhptazyko"),
44+
Operation::ChangeRating("emgqdbo", 3),
45+
Operation::ChangeRating("jmvfxjohq", 9),
46+
Operation::ChangeRating("emgqdbo", 14),
47+
Operation::HighestRated("fajbervsj", "yhptazyko"),
48+
Operation::HighestRated("snaxol", "emgqdbo"),
49+
],
50+
),
51+
(
52+
(
53+
&["xucxenyckh", "rmzka", "kiesprtv"],
54+
&["oqinnmfsaf", "tgeb", "onzfzqjw"],
55+
&[8, 4, 3],
56+
),
57+
&[
58+
Operation::ChangeRating("rmzka", 8),
59+
Operation::ChangeRating("rmzka", 8),
60+
Operation::ChangeRating("rmzka", 5),
61+
Operation::HighestRated("onzfzqjw", "kiesprtv"),
62+
Operation::HighestRated("oqinnmfsaf", "xucxenyckh"),
63+
Operation::HighestRated("onzfzqjw", "kiesprtv"),
64+
],
65+
),
66+
];
67+
68+
for ((foods, cuisines, ratings), operations) in test_cases {
69+
let mut food_ratings = F::new(
70+
foods.iter().copied().map(str::to_string).collect(),
71+
cuisines.iter().copied().map(str::to_string).collect(),
72+
ratings.to_vec(),
73+
);
74+
75+
for operation in operations {
76+
match *operation {
77+
Operation::ChangeRating(food, new_rating) => {
78+
food_ratings.change_rating(food.to_string(), new_rating);
79+
}
80+
Operation::HighestRated(cuisine, expected) => {
81+
assert_eq!(food_ratings.highest_rated(cuisine.to_string()), expected.to_string());
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)