diff --git a/aoc2020/src/day23.rs b/aoc2020/src/day23.rs index 93d6ceb..8c137f2 100644 --- a/aoc2020/src/day23.rs +++ b/aoc2020/src/day23.rs @@ -4,10 +4,14 @@ use anyhow::{Context, Result}; const INPUT: &str = include_str!("../input/day23.txt"); +const CUP_NUMBER: usize = 1_000_000; +const TURNS_NUMBER: usize = 10_000_000; + pub fn run() -> Result { let mut res = String::with_capacity(128); writeln!(res, "part 1: {}", part1(INPUT)?)?; + writeln!(res, "part 2: {}", part2(INPUT)?)?; Ok(res) } @@ -22,40 +26,28 @@ fn part1(input: &str) -> Result { Ok(format!("{}", cup_circle)) } -struct CupCircle(VecDeque); +fn part2(input: &str) -> Result { + let mut cup_circle: FastCupCircle = input.parse()?; + + for _ in 0..TURNS_NUMBER { + cup_circle.step(); + } + + let first = cup_circle.get_next_cup(1); + let second = cup_circle.get_next_cup(first); + + Ok(first * second) +} /// CupCircle provides an abstraction over a VecDeque to emulate the steps of the game -/// +struct CupCircle(VecDeque); + /// The "current" cup should always be the first one in the VecDeque at the end of a turn, for /// convenience /// /// The Circle should never become empty if you only use its public interface, so all calls to -/// unwrap() in its implementation should never panic. +/// `unwrap()` in its implementation should never panic. impl CupCircle { - pub fn front(&self) -> u64 { - *self.0.front().unwrap() - } - - fn pop_front(&mut self) -> u64 { - self.0.pop_front().unwrap() - } - - fn push_back(&mut self, value: u64) { - self.0.push_back(value); - } - - pub fn max(&self) -> u64 { - *self.0.iter().max().unwrap() - } - - fn remove_next_3(&mut self) -> [u64; 3] { - let first = self.pop_front(); - let second = self.pop_front(); - let third = self.pop_front(); - - [first, second, third] - } - /// Shifts the cup circle, putting the first cup at the end of the deque /// /// This doesn't change anything to the cup circle layout, except that the new first cup is @@ -110,6 +102,30 @@ impl CupCircle { } self.shift(); } + + fn remove_next_3(&mut self) -> [u64; 3] { + let first = self.pop_front(); + let second = self.pop_front(); + let third = self.pop_front(); + + [first, second, third] + } + + pub fn front(&self) -> u64 { + *self.0.front().unwrap() + } + + fn pop_front(&mut self) -> u64 { + self.0.pop_front().unwrap() + } + + fn push_back(&mut self, value: u64) { + self.0.push_back(value); + } + + pub fn max(&self) -> u64 { + *self.0.iter().max().unwrap() + } } impl std::str::FromStr for CupCircle { @@ -138,6 +154,108 @@ impl std::fmt::Display for CupCircle { } } +/// CupCircle provides a fast abstraction to emulate the steps of the game. +/// +/// It is considerably faster than the above naive implementation, but its representation isn't as +/// intuitive. It uses a [`std::vec::Vec`] of indices, where `vec[cup]` returns the next cup in the +/// circle, for a given cup. +struct FastCupCircle { + cups: Vec, + current: usize, +} + +impl FastCupCircle { + fn get_next_cup(&self, cup: usize) -> usize { + self.cups[cup - 1] + } + + fn set_next_cup(&mut self, cup: usize, next: usize) { + self.cups[cup - 1] = next; + } + + fn remove_next_3(&mut self, cup: usize) -> [usize; 3] { + let first = self.get_next_cup(cup); + let second = self.get_next_cup(first); + let third = self.get_next_cup(second); + + // shortcut the links to remove them from the loop temporarily + self.set_next_cup(cup, self.get_next_cup(third)); + + [first, second, third] + } + + fn step(&mut self) { + // The crab picks up the three cups that are immediately clockwise of the current cup. They + // are removed from the circle; cup spacing is adjusted as necessary to maintain the circle. + let removed_cups = self.remove_next_3(self.current); + + // The crab selects a destination cup: the cup with a label equal to the current cup's label + // minus one. If this would select one of the cups that was just picked up, the crab will + // keep subtracting one until it finds a cup that wasn't just picked up. If at any point in + // this process the value goes below the lowest value on any cup's label, it wraps around to + // the highest value on any cup's label instead. + // + // TODO: use std::cmp::Ord::clamp when stabilized (Rust 1.50) + let mut destination = if self.current > 1 { + self.current - 1 + } else { + self.cups.len() + }; + while removed_cups.contains(&destination) { + destination = if destination > 1 { + destination - 1 + } else { + self.cups.len() + }; + } + + // The crab places the cups it just picked up so that they are immediately clockwise of the + // destination cup. They keep the same order as when they were picked up. + // + // The links from first to second and from second to third haven't changed, no need to + // update them + let [first, _, third] = removed_cups; + self.set_next_cup(third, self.get_next_cup(destination)); + self.set_next_cup(destination, first); + + // The crab selects a new current cup: the cup which is immediately clockwise of the current + // cup. + self.current = self.get_next_cup(self.current); + } +} + +impl std::str::FromStr for FastCupCircle { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let input_cups: Vec = s + .trim_end() + .chars() + .map(|c| Ok(c.to_digit(10).context("character was not a digit")? as usize)) + .collect::>()?; + + let max = *input_cups.iter().max().context("input contained 0 cups")?; + + let mut cups = Vec::new(); + cups.resize_with(CUP_NUMBER, Default::default); + + let cup_iter = input_cups.clone().into_iter().chain((max + 1)..=CUP_NUMBER); + let next_cup_iter = input_cups + .into_iter() + .chain((max + 1)..=CUP_NUMBER) + .cycle() + .skip(1); + + for (cup, next) in cup_iter.zip(next_cup_iter) { + cups[cup - 1] = next; + } + + let current = cups[CUP_NUMBER - 1]; + + Ok(Self { cups, current }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -153,4 +271,16 @@ mod tests { fn part1_real() { assert_eq!(part1(INPUT).unwrap(), "72496583"); } + + #[test] + #[ignore] + fn part2_provided() { + assert_eq!(part2(PROVIDED).unwrap(), 149245887792); + } + + #[test] + #[ignore] + fn part2_real() { + assert_eq!(part2(INPUT).unwrap(), 41785843847); + } }