diff --git a/README.md b/README.md index d4a984e..7c8d7ce 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 11](./src/bin/11.rs) | `30.5s` | `-` | | [Day 12](./src/bin/12.rs) | `3.1ms` | `91.5ms` | | [Day 13](./src/bin/13.rs) | `730.1µs` | `144.7µs` | +| [Day 14](./src/bin/14.rs) | `229.0ms` | `26.4s` | -**Total: 47929.23ms** +**Total: 74558.23ms** --- diff --git a/data/examples/14.txt b/data/examples/14.txt new file mode 100644 index 0000000..8baef1b --- /dev/null +++ b/data/examples/14.txt @@ -0,0 +1 @@ +abc diff --git a/src/bin/11.rs b/src/bin/11.rs index 0bf5010..3570a61 100644 --- a/src/bin/11.rs +++ b/src/bin/11.rs @@ -191,7 +191,7 @@ fn parse_line(input: &str) -> Floor { let mut words = input.split_whitespace(); let mut items: Vec = Vec::new(); loop { - words.by_ref().skip_while(|&w| w != "a").next(); + words.by_ref().find(|&w| w == "a"); // skip_while(|&w| w != "a").next(); if let Some(matter) = words.next() { if let Some(it_type) = words.next() { // let it_type = it_type.strip_suffix(".").unwrap_or(it_type); diff --git a/src/bin/14.rs b/src/bin/14.rs new file mode 100644 index 0000000..6f0ada0 --- /dev/null +++ b/src/bin/14.rs @@ -0,0 +1,205 @@ +advent_of_code::solution!(14); + +#[derive(Debug, Clone)] +struct Key { + index: u32, + threes: Vec, + fives: Vec, + // digest: String, +} + +#[derive(Clone)] +struct State { + valid_keys: Vec, + pending_keys: [Vec; 16], +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{{ valid keys: {:?}, pending => ", self.valid_keys)?; + for p in 0..16 { + write!(f, "[{}] -> {}, ", p, self.pending_keys[p].len())?; + } + writeln!(f, "}}")?; + Ok(()) + } +} + +fn get_char_idx(c: char) -> usize { + match c { + '0'..='9' => c as usize - '0' as usize, + 'a'..='f' => c as usize - 'a' as usize + 10, + _ => { + println!("char: {}", c); + panic!("invalid char !"); + } + } +} + +const ARRAY_REPEAT: Vec = Vec::new(); +impl State { + fn init() -> Self { + Self { + valid_keys: Vec::new(), + pending_keys: [ARRAY_REPEAT; 16], + } + } + + fn update_state(&mut self, new_key: &Key) -> Self { + // println!("Update state with key {:?}", new_key); + for idx in &new_key.fives { + for pkey in &self.pending_keys[*idx] { + if pkey.index + 1000 >= new_key.index && !self.valid_keys.contains(&pkey.index) { + self.valid_keys.push(pkey.index); + // println!("{:4} New key validated: {:?}", self.valid_keys.len(), pkey); + } + } + self.pending_keys[*idx] = vec![]; + } + for idx in &new_key.threes { + self.pending_keys[*idx].push(new_key.clone()); + } + self.clone() + } +} + +impl Key { + fn init(index: u32) -> Self { + Self { + index, + // digest, + threes: Vec::new(), + fives: Vec::new(), + } + } + + fn add_3streak(&mut self, c: char) { + let idx = get_char_idx(c); + if self.threes.is_empty() { + // if !self.threes.contains(&idx) { + self.threes.push(idx); + } + } + fn add_5streak(&mut self, c: char) { + let idx = get_char_idx(c); + if !self.fives.contains(&idx) { + self.fives.push(idx); + } + } + + fn has_streak(self) -> Option { + if !self.threes.is_empty() { + Some(self) + } else { + None + } + } +} + +fn build_key_p1(salt: &str, index: u32) -> Option { + let digest = format!("{:x}", md5::compute(format!("{}{}", salt, index))); + let mut res = Key::init(index); // , digest.clone()); + let mut cs = digest.chars().peekable(); + let mut cur_streak = 1; + while let Some(c) = cs.next() { + if Some(c) == cs.peek().copied() { + cur_streak += 1; + if cur_streak == 3 { + res.add_3streak(c); + } else if cur_streak == 5 { + res.add_5streak(c); + } + } else { + cur_streak = 1; + } + } + res.has_streak() +} + +fn md5_loop(data: String, times: usize) -> String { + let digest = format!("{:x}", md5::compute(data)); + if times > 0 { + md5_loop(digest, times - 1) + } else { + digest + } +} + +fn build_key_p2(salt: &str, index: u32) -> Option { + let digest = md5_loop(format!("{}{}", salt, index), 2016); + let mut res = Key::init(index); // , digest.clone()); + let mut cs = digest.chars().peekable(); + let mut cur_streak = 1; + while let Some(c) = cs.next() { + if Some(c) == cs.peek().copied() { + cur_streak += 1; + if cur_streak == 3 { + res.add_3streak(c); + } else if cur_streak == 5 { + res.add_5streak(c); + } + } else { + cur_streak = 1; + } + } + res.has_streak() +} + +fn solve Option>(input: &str, key_fn: F) -> Option { + let input = input.strip_suffix("\n").unwrap_or(input); + let states = (0..) + .flat_map(|i| key_fn(input, i)) + .scan((State::init(), false), |(state, done), key| { + if *done { + None + } else { + let new_state = state.update_state(&key); + let valid_keys = new_state.valid_keys.len(); + *done = valid_keys >= 80; + Some((new_state, valid_keys >= 80)) + } + }) + .collect::>(); + + if let Some((last, _)) = states.last() { + if last.valid_keys.len() < 64 { + panic!("?!?! stopped before 64??"); + } + let mut keys = last.valid_keys.clone(); + keys.sort(); + keys.get(63).copied() + } else { + None + } +} + +pub fn part_one(input: &str) -> Option { + solve(input, build_key_p1) +} + +pub fn part_two(input: &str) -> Option { + solve(input, build_key_p2) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_md5_loop() { + let res = md5_loop("abc0".into(), 2016); + assert_eq!(res, "a107ff634856bb300138cac6568c0f24".to_string()); + } + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(22728)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(22551)); + } +}