summaryrefslogtreecommitdiff
path: root/2023/src/bin/day_8.rs
diff options
context:
space:
mode:
Diffstat (limited to '2023/src/bin/day_8.rs')
-rw-r--r--2023/src/bin/day_8.rs213
1 files changed, 213 insertions, 0 deletions
diff --git a/2023/src/bin/day_8.rs b/2023/src/bin/day_8.rs
new file mode 100644
index 0000000..2da3cf5
--- /dev/null
+++ b/2023/src/bin/day_8.rs
@@ -0,0 +1,213 @@
+use nom::{
+ branch::alt,
+ bytes::complete::tag,
+ character::complete::{alpha1, char as nom_char, line_ending},
+ combinator::{map, value},
+ multi::{many1, separated_list1},
+ sequence::{pair, preceded, separated_pair, terminated},
+ IResult,
+};
+use std::{collections::BTreeMap, fs, ops::Range};
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let input = fs::read_to_string("inputs/day_8.txt")?;
+ let mut directions = Directions::parser(&input).unwrap().1;
+ dbg!(directions.steps_from_a_to_z());
+ dbg!(directions.ghost_steps_from_a_to_z());
+
+ Ok(())
+}
+
+#[derive(Debug)]
+struct Directions {
+ turns: Vec<Turn>,
+ packed_map: Vec<PackedFork>,
+ packed_ghost_starts: Range<u16>,
+ packed_ghost_destinations: Range<u16>,
+ distance_to_ghost_dest_cache: BTreeMap<(u16, usize), (u16, usize)>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Turn {
+ Left,
+ Right,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+struct Location(String);
+
+#[derive(Debug)]
+struct Fork {
+ left: Location,
+ right: Location,
+}
+
+#[derive(Debug)]
+struct PackedFork {
+ left: u16,
+ right: u16,
+}
+
+impl Directions {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ map(
+ separated_pair(
+ many1(Turn::parser),
+ pair(line_ending, line_ending),
+ separated_list1(
+ line_ending,
+ separated_pair(Location::parser, tag(" = "), Fork::parser),
+ ),
+ ),
+ |(turns, map)| {
+ let map: BTreeMap<Location, Fork> = map.into_iter().collect();
+ let mut locations: Vec<Location> = map.keys().cloned().collect();
+ locations.sort_by_key(|l| l.0.chars().rev().collect::<String>());
+
+ Directions {
+ turns,
+ packed_map: locations
+ .iter()
+ .map(|l| {
+ let unpacked_fork = map.get(l).unwrap();
+ PackedFork {
+ left: locations
+ .iter()
+ .position(|f| *f == unpacked_fork.left)
+ .unwrap() as u16,
+ right: locations
+ .iter()
+ .position(|f| *f == unpacked_fork.right)
+ .unwrap() as u16,
+ }
+ })
+ .collect(),
+ packed_ghost_starts: 0
+ ..locations.iter().position(|l| !l.ghost_start()).unwrap() as u16,
+ packed_ghost_destinations: locations.iter().position(|l| l.ghost_end()).unwrap()
+ as u16
+ ..locations.len() as u16,
+ distance_to_ghost_dest_cache: BTreeMap::new(),
+ }
+ },
+ )(input)
+ }
+
+ fn steps_from_a_to_z(&self) -> usize {
+ let mut step_count = 0;
+ let mut current_location: u16 = 0;
+
+ for dir in self.turns.iter().cycle() {
+ let current_fork: &PackedFork = &self.packed_map[current_location as usize];
+ current_location = match dir {
+ Turn::Left => current_fork.left,
+ Turn::Right => current_fork.right,
+ };
+ step_count += 1;
+
+ if current_location == self.packed_map.len() as u16 - 1 {
+ return step_count;
+ }
+ }
+ unreachable!()
+ }
+
+ fn ghost_steps_from_a_to_z(&mut self) -> usize {
+ let mut current_locations: Vec<(u16, usize)> = self
+ .packed_ghost_starts
+ .clone()
+ .map(|start| self.ghost_step_to_next_end(start, 0))
+ .collect();
+
+ let mut any_stepped = true;
+ while any_stepped {
+ any_stepped = false;
+
+ let current_max = current_locations
+ .iter()
+ .map(|(_, steps)| steps.clone())
+ .max()
+ .unwrap();
+
+ for current_location in &mut current_locations {
+ if current_location.1 < current_max {
+ any_stepped = true;
+ let (new_location, extra_steps) =
+ self.ghost_step_to_next_end(current_location.0, current_location.1);
+ current_location.0 = new_location;
+ current_location.1 += extra_steps;
+ }
+ }
+ }
+
+ current_locations[0].1
+ }
+
+ fn ghost_step_to_next_end(&mut self, start: u16, current_step_count: usize) -> (u16, usize) {
+ self.distance_to_ghost_dest_cache
+ .entry((start, current_step_count % self.turns.len()))
+ .or_insert_with(|| {
+ let mut step_count = 0;
+ let mut current_location: u16 = start;
+
+ for dir in self
+ .turns
+ .iter()
+ .skip(current_step_count % self.turns.len())
+ .cycle()
+ {
+ let current_fork: &PackedFork = &self.packed_map[current_location as usize];
+ current_location = match dir {
+ Turn::Left => current_fork.left,
+ Turn::Right => current_fork.right,
+ };
+ step_count += 1;
+
+ if self.packed_ghost_destinations.contains(&current_location) {
+ break;
+ }
+ }
+
+ (current_location, step_count)
+ })
+ .clone()
+ }
+}
+
+impl Turn {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ alt((
+ value(Turn::Left, nom_char('L')),
+ value(Turn::Right, nom_char('R')),
+ ))(input)
+ }
+}
+
+impl Location {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ map(alpha1, |l: &str| Location(l.to_string()))(input)
+ }
+
+ fn ghost_start(&self) -> bool {
+ self.0.ends_with("A")
+ }
+
+ fn ghost_end(&self) -> bool {
+ self.0.ends_with("Z")
+ }
+}
+
+impl Fork {
+ fn parser(input: &str) -> IResult<&str, Self> {
+ map(
+ preceded(
+ tag("("),
+ terminated(
+ separated_pair(Location::parser, tag(", "), Location::parser),
+ tag(")"),
+ ),
+ ),
+ |(left, right)| Fork { left, right },
+ )(input)
+ }
+}