diff --git a/src/lib.rs b/src/lib.rs index 3318007a..58a5dd9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1442,6 +1442,7 @@ pub mod problem_1943_describe_the_painting; pub mod problem_1944_number_of_visible_people_in_a_queue; pub mod problem_1945_sum_of_digits_of_string_after_convert; pub mod problem_1946_largest_number_after_mutating_substring; +pub mod problem_1948_delete_duplicate_folders_in_system; pub mod problem_1952_three_divisors; pub mod problem_1953_maximum_number_of_weeks_for_which_you_can_work; pub mod problem_1954_minimum_garden_perimeter_to_collect_enough_apples; diff --git a/src/problem_1948_delete_duplicate_folders_in_system/interning.rs b/src/problem_1948_delete_duplicate_folders_in_system/interning.rs new file mode 100644 index 00000000..109006ec --- /dev/null +++ b/src/problem_1948_delete_duplicate_folders_in_system/interning.rs @@ -0,0 +1,147 @@ +pub struct Solution; + +// ------------------------------------------------------ snip ------------------------------------------------------ // + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::{convert, str}; + +type Name = [u8; 10]; + +#[derive(Default)] +struct Node { + children: HashMap, + signature_id: u32, +} + +struct Context1 { + signature_to_id: HashMap, u32>, + id_to_duplication: Vec, +} + +struct Context2<'a> { + is_duplicated: Vec, + path: Vec<&'a str>, + result: Vec>, +} + +impl<'a> Context2<'a> { + fn enter_path(&mut self, name: &'a Name, f: impl FnOnce(&mut Self)) { + let length = name.iter().position(|&c| c == 0).unwrap_or(name.len()); + let name = str::from_utf8(&name[..length]).unwrap(); + + self.path.push(name); + + f(self); + + self.path.pop(); + } + + fn add_path_to_result(&mut self) { + self.result + .push(self.path.iter().copied().map(str::to_string).collect()); + } +} + +impl Solution { + fn find_duplicates(context: &mut Context1, node: &mut Node) -> u32 { + let signature_id = if node.children.is_empty() { + u32::MAX + } else { + let mut signature = node + .children + .iter_mut() + .map(|(&name, node)| (name, Self::find_duplicates(context, node))) + .collect::>(); + + signature.sort_unstable_by_key(|&(name, _)| name); + + match context.signature_to_id.entry(signature) { + Entry::Occupied(entry) => { + let id = *entry.get(); + + context.id_to_duplication[id as usize] = true; + + id + } + Entry::Vacant(entry) => { + let id = context.id_to_duplication.len() as _; + + entry.insert(id); + context.id_to_duplication.push(false); + + id + } + } + }; + + node.signature_id = signature_id; + + signature_id + } + + fn collect_results<'a>(context: &mut Context2<'a>, nodes: &'a HashMap) { + for (name, child) in nodes { + if let Some(&is_duplicated) = context.is_duplicated.get(child.signature_id as usize) { + if !is_duplicated { + context.enter_path(name, |context| { + context.add_path_to_result(); + + Self::collect_results(context, &child.children); + }); + } + } else { + context.enter_path(name, Context2::add_path_to_result); + } + } + } + + pub fn delete_duplicate_folder(paths: Vec>) -> Vec> { + let mut root = Node::default(); + + for path in &paths { + let mut node = &mut root; + + for component in path { + let mut new_component = [0_u8; 10]; + + new_component[..component.len()].copy_from_slice(component.as_bytes()); + + node = node.children.entry(new_component).or_insert_with(Node::default); + } + } + + let mut cx1 = Context1 { + signature_to_id: HashMap::new(), + id_to_duplication: Vec::new(), + }; + + Self::find_duplicates(&mut cx1, &mut root); + + let mut cx2 = Context2 { + is_duplicated: convert::identity(cx1).id_to_duplication, + path: Vec::new(), + result: Vec::new(), + }; + + Self::collect_results(&mut cx2, &root.children); + + cx2.result + } +} + +// ------------------------------------------------------ snip ------------------------------------------------------ // + +impl super::Solution for Solution { + fn delete_duplicate_folder(paths: Vec>) -> Vec> { + Self::delete_duplicate_folder(paths) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_solution() { + super::super::tests::run::(); + } +} diff --git a/src/problem_1948_delete_duplicate_folders_in_system/mod.rs b/src/problem_1948_delete_duplicate_folders_in_system/mod.rs new file mode 100644 index 00000000..eda5035e --- /dev/null +++ b/src/problem_1948_delete_duplicate_folders_in_system/mod.rs @@ -0,0 +1,49 @@ +pub mod interning; + +pub trait Solution { + fn delete_duplicate_folder(paths: Vec>) -> Vec>; +} + +#[cfg(test)] +mod tests { + use super::Solution; + use crate::test_utilities; + + pub fn run() { + let test_cases = [ + ( + &[&["a"] as &[_], &["c"], &["d"], &["a", "b"], &["c", "b"], &["d", "a"]] as &[&[_]], + &[&["d"] as &[_], &["d", "a"]] as &[&[_]], + ), + ( + &[ + &["a"], + &["c"], + &["a", "b"], + &["c", "b"], + &["a", "b", "x"], + &["a", "b", "x", "y"], + &["w"], + &["w", "y"], + ], + &[&["a"], &["a", "b"], &["c"], &["c", "b"]], + ), + ( + &[&["a", "b"], &["c", "d"], &["c"], &["a"]], + &[&["a"], &["a", "b"], &["c"], &["c", "d"]], + ), + ]; + + for (paths, expected) in test_cases { + assert_eq!( + test_utilities::unstable_sorted(S::delete_duplicate_folder( + paths + .iter() + .map(|account| account.iter().copied().map(str::to_string).collect()) + .collect() + )), + expected, + ); + } + } +}