Skip to content

Commit

Permalink
Add problem 2353: Design a Food Rating System
Browse files Browse the repository at this point in the history
  • Loading branch information
EFanZh committed Dec 6, 2024
1 parent 3fe4e71 commit c9d0182
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,7 @@ pub mod problem_2348_number_of_zero_filled_subarrays;
pub mod problem_2349_design_a_number_container_system;
pub mod problem_2351_first_letter_to_appear_twice;
pub mod problem_2352_equal_row_and_column_pairs;
pub mod problem_2353_design_a_food_rating_system;

#[cfg(test)]
mod test_utilities;
246 changes: 246 additions & 0 deletions src/problem_2353_design_a_food_rating_system/binary_heap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// ------------------------------------------------------ snip ------------------------------------------------------ //

use std::cmp::Reverse;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::convert;

fn encode_name(name: &str) -> u64 {
let mut result = 0;
let mut offset = 64;

for c in name.bytes() {
offset -= 5;
result |= u64::from(c & 0b_11111) << offset;
}

result
}

fn decode_name(mut name: u64, buffer: String) -> String {
let mut result = buffer.into_bytes();

result.clear();

loop {
result.push((name >> 59) as u8 | 0b_0110_0000);

name <<= 5;

if name == 0 {
break;
}
}

String::from_utf8(result).unwrap()
}

fn get_stat(stats: &[(u32, u64, u16)], index: u16) -> (u32, Reverse<u64>) {
let (rating, food_name, _) = stats[usize::from(index)];

(rating, Reverse(food_name))
}

fn heap_sift_down(
heap: &mut [u16],
stats: &mut [(u32, u64, u16)],
stat_index: usize,
rating: u32,
food_name: u64,
mut heap_index: usize,
) {
let stat = (rating, Reverse(food_name));

loop {
let left_child_heap_index = heap_index * 2 + 1;

if let Some(&left_child_stat_index) = heap.get(left_child_heap_index) {
let right_child_heap_index = left_child_heap_index + 1;

let child_heap_index = if let Some(&right_child_stat_index) = heap.get(right_child_heap_index) {
if get_stat(stats, right_child_stat_index) > get_stat(stats, left_child_stat_index) {
right_child_heap_index
} else {
left_child_heap_index
}
} else {
left_child_heap_index
};

let child_stat_index = heap[child_heap_index];

if get_stat(stats, child_stat_index) > stat {
heap[heap_index] = child_stat_index;
stats[usize::from(child_stat_index)].2 = heap_index as _;
heap_index = child_heap_index;
} else {
break;
}
} else {
break;
}
}

heap[heap_index] = stat_index as _;
stats[stat_index].2 = heap_index as _;
}

fn heap_sift_up(
heap: &mut [u16],
stats: &mut [(u32, u64, u16)],
stat_index: usize,
rating: u32,
food_name: u64,
mut heap_index: usize,
) {
let stat = (rating, Reverse(food_name));

loop {
let parent_heap_index = heap_index.wrapping_sub(1) / 2;

if let Some(&parent_stat_index) = heap.get(parent_heap_index) {
let parent_stat = get_stat(stats, parent_stat_index);

if stat > parent_stat {
heap[heap_index] = parent_stat_index;
stats[usize::from(parent_stat_index)].2 = heap_index as _;
heap_index = parent_heap_index;
} else {
break;
}
} else {
break;
}
}

heap[heap_index] = stat_index as _;
stats[stat_index].2 = heap_index as _;
}

fn heap_build(heap: &mut Box<[u16]>, stats: &mut [(u32, u64, u16)]) {
let n = stats.len();

*heap = (0..n as _).collect();

for i in (0..n / 2).rev() {
let (rating, food_name, heap_index) = stats[i];

heap_sift_down(heap, stats, i, rating, food_name, usize::from(heap_index));
}
}

fn heap_update(heap: &mut [u16], stats: &mut [(u32, u64, u16)], stat_index: u16, rating: u32) {
let stat_index = usize::from(stat_index);
let stat = &mut stats[stat_index];

if rating != stat.0 {
let (old_rating, food_name, heap_index) = *stat;
let heap_index = usize::from(heap_index);

stat.0 = rating;

if rating < old_rating {
heap_sift_down(heap, stats, stat_index, rating, food_name, heap_index);
} else {
heap_sift_up(heap, stats, stat_index, rating, food_name, heap_index);
}
}
}

struct Ratings {
heap: Box<[u16]>,
stats: Vec<(u32, u64, u16)>,
}

pub struct FoodRatings {
food_to_cuisine: HashMap<u64, (u64, u16)>,
cuisine_ratings: HashMap<u64, Ratings>,
}

impl FoodRatings {
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self {
let mut food_to_cuisine = HashMap::new();
let mut cuisine_ratings = HashMap::<_, Ratings>::new();

foods
.into_iter()
.zip(cuisines)
.zip(ratings)
.for_each(|((food, cuisine), rating)| {
let food = encode_name(&convert::identity(food));
let cuisine = encode_name(&convert::identity(cuisine));

let stat_index = match cuisine_ratings.entry(cuisine) {
Entry::Occupied(occupied_entry) => {
let stats = &mut occupied_entry.into_mut().stats;
let stat_index = stats.len() as _;

stats.push((rating as _, food, stat_index));

stat_index
}
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(Ratings {
heap: Box::new([]),
stats: vec![(rating as _, food, 0)],
});

0
}
};

food_to_cuisine.insert(food, (cuisine, stat_index));
});

for ratings in cuisine_ratings.values_mut() {
heap_build(&mut ratings.heap, &mut ratings.stats);
}

Self {
food_to_cuisine,
cuisine_ratings,
}
}

fn change_rating(&mut self, food: String, new_rating: i32) {
let food = encode_name(&convert::identity(food));
let new_rating = new_rating as u32;
let (cuisine, stat_index) = self.food_to_cuisine[&food];
let ratings = self.cuisine_ratings.get_mut(&cuisine).unwrap();

heap_update(&mut ratings.heap, &mut ratings.stats, stat_index, new_rating);
}

fn highest_rated(&self, cuisine: String) -> String {
let encoded_cuisine = encode_name(&cuisine);
let ratings = &self.cuisine_ratings[&encoded_cuisine];
let stat_index = ratings.heap[0];
let food_name = ratings.stats[usize::from(stat_index)].1;

decode_name(food_name, cuisine)
}
}

// ------------------------------------------------------ snip ------------------------------------------------------ //

impl super::FoodRatings for FoodRatings {
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self {
Self::new(foods, cuisines, ratings)
}

fn change_rating(&mut self, food: String, new_rating: i32) {
self.change_rating(food, new_rating);
}

fn highest_rated(&self, cuisine: String) -> String {
self.highest_rated(cuisine)
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_solution() {
super::super::tests::run::<super::FoodRatings>();
}
}
87 changes: 87 additions & 0 deletions src/problem_2353_design_a_food_rating_system/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
pub mod binary_heap;

pub trait FoodRatings {
fn new(foods: Vec<String>, cuisines: Vec<String>, ratings: Vec<i32>) -> Self;
fn change_rating(&mut self, food: String, new_rating: i32);
fn highest_rated(&self, cuisine: String) -> String;
}

#[cfg(test)]
mod tests {
use super::FoodRatings;

enum Operation {
ChangeRating(&'static str, i32),
HighestRated(&'static str, &'static str),
}

pub fn run<F: FoodRatings>() {
let test_cases = [
(
(
&["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"] as &[_],
&["korean", "japanese", "japanese", "greek", "japanese", "korean"] as &[_],
&[9, 12, 8, 15, 14, 7] as &[_],
),
&[
Operation::HighestRated("korean", "kimchi"),
Operation::HighestRated("japanese", "ramen"),
Operation::ChangeRating("sushi", 16),
Operation::HighestRated("japanese", "sushi"),
Operation::ChangeRating("ramen", 16),
Operation::HighestRated("japanese", "ramen"),
] as &[_],
),
(
(
&["emgqdbo", "jmvfxjohq", "qnvseohnoe", "yhptazyko", "ocqmvmwjq"],
&["snaxol", "snaxol", "snaxol", "fajbervsj", "fajbervsj"],
&[2, 6, 18, 6, 5],
),
&[
Operation::ChangeRating("qnvseohnoe", 11),
Operation::HighestRated("fajbervsj", "yhptazyko"),
Operation::ChangeRating("emgqdbo", 3),
Operation::ChangeRating("jmvfxjohq", 9),
Operation::ChangeRating("emgqdbo", 14),
Operation::HighestRated("fajbervsj", "yhptazyko"),
Operation::HighestRated("snaxol", "emgqdbo"),
],
),
(
(
&["xucxenyckh", "rmzka", "kiesprtv"],
&["oqinnmfsaf", "tgeb", "onzfzqjw"],
&[8, 4, 3],
),
&[
Operation::ChangeRating("rmzka", 8),
Operation::ChangeRating("rmzka", 8),
Operation::ChangeRating("rmzka", 5),
Operation::HighestRated("onzfzqjw", "kiesprtv"),
Operation::HighestRated("oqinnmfsaf", "xucxenyckh"),
Operation::HighestRated("onzfzqjw", "kiesprtv"),
],
),
];

for ((foods, cuisines, ratings), operations) in test_cases {
let mut food_ratings = F::new(
foods.iter().copied().map(str::to_string).collect(),
cuisines.iter().copied().map(str::to_string).collect(),
ratings.to_vec(),
);

for operation in operations {
match *operation {
Operation::ChangeRating(food, new_rating) => {
food_ratings.change_rating(food.to_string(), new_rating);
}
Operation::HighestRated(cuisine, expected) => {
assert_eq!(food_ratings.highest_rated(cuisine.to_string()), expected.to_string());
}
}
}
}
}
}

0 comments on commit c9d0182

Please sign in to comment.