diff --git a/2023/src/bin/day08.rs b/2023/src/bin/day08.rs index 884d481..8a0db2c 100644 --- a/2023/src/bin/day08.rs +++ b/2023/src/bin/day08.rs @@ -7,7 +7,7 @@ use aoc2023::{boilerplate, common::*}; const DAY: usize = 08; type Parsed<'a> = (Vec, HashMap<&'a str, (&'a str, &'a str)>); -#[derive(Debug, PartialEq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] enum Direction { Left, Right, @@ -27,30 +27,48 @@ fn parse_input(raw: &str) -> Parsed { (directions, map) } -fn part1((directions, map): &Parsed) -> usize { +fn part1(parsed: &Parsed) -> usize { + steps_until(parsed, "AAA", "ZZZ") +} + +fn steps_until((directions, map): &Parsed, start: &str, target: &str) -> usize { directions .iter() .cycle() - .scan("AAA", |pos, dir| { + .scan(start, |pos, dir| { let next = match dir { Direction::Left => map.get(pos)?.0, Direction::Right => map.get(pos)?.1, }; - if next == "ZZZ" { - None - } else { + (!next.ends_with(target)).then(|| { *pos = next; Some(next) - } + }) }) .count() + 1 } +// I’m honestly not sure why this works. It seems each path only has a single ghost node, and that +// node occurs right before looping, so we can just compute the least common multiple of their step counts. +// I assume this holds true for other inputs (it can’t have been random), +// but I don’t see it anywhere in the task and only found out by experimentation. fn part2(parsed: &Parsed) -> usize { - unimplemented!() + parsed.1.keys().filter(|start| start.ends_with("A")).map(|start| steps_until(parsed, start, "Z")).fold(1, |acc, n| lcm(acc, n)) } +#[cfg(test)] +const TEST_INPUT_P2: &str = "LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX)"; + boilerplate! { TEST_INPUT == "LLR @@ -59,9 +77,9 @@ BBB = (AAA, ZZZ) ZZZ = (ZZZ, ZZZ)", tests: { part1: { TEST_INPUT => 6 }, - part2: { TEST_INPUT => 0 }, + part2: { TEST_INPUT_P2 => 6 }, }, bench1 == 12083, - bench2 == 0, + bench2 == 13385272668829, bench_parse: |(v, m): &Parsed| { assert_eq!(m["AAA"], ("MJJ", "QBJ")); (v.len(), v[0]) } => (281, Direction::Left), } diff --git a/2023/src/common.rs b/2023/src/common.rs index b0a69de..6eb97c8 100644 --- a/2023/src/common.rs +++ b/2023/src/common.rs @@ -68,3 +68,17 @@ pub fn parse_num>(s: &str) -> I { let start = unsafe { digits.next().unwrap_unchecked() }; digits.fold(start, |acc, n| acc * I::from(10) + n) } + +fn gcd(mut x: usize, mut y: usize) -> usize { + let mut remainder; + while y != 0 { + remainder = x % y; + x = y; + y = remainder; + } + x +} + +pub fn lcm(x: usize, y: usize) -> usize { + x * y / gcd(x, y) +}