add 2023/08/2

This commit is contained in:
kageru 2023-12-08 07:05:56 +01:00
parent 13daf93f49
commit bf6619d638
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
2 changed files with 42 additions and 10 deletions

View File

@ -7,7 +7,7 @@ use aoc2023::{boilerplate, common::*};
const DAY: usize = 08; const DAY: usize = 08;
type Parsed<'a> = (Vec<Direction>, HashMap<&'a str, (&'a str, &'a str)>); type Parsed<'a> = (Vec<Direction>, HashMap<&'a str, (&'a str, &'a str)>);
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
enum Direction { enum Direction {
Left, Left,
Right, Right,
@ -27,30 +27,48 @@ fn parse_input(raw: &str) -> Parsed {
(directions, map) (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 directions
.iter() .iter()
.cycle() .cycle()
.scan("AAA", |pos, dir| { .scan(start, |pos, dir| {
let next = match dir { let next = match dir {
Direction::Left => map.get(pos)?.0, Direction::Left => map.get(pos)?.0,
Direction::Right => map.get(pos)?.1, Direction::Right => map.get(pos)?.1,
}; };
if next == "ZZZ" { (!next.ends_with(target)).then(|| {
None
} else {
*pos = next; *pos = next;
Some(next) Some(next)
} })
}) })
.count() .count()
+ 1 + 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 { 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! { boilerplate! {
TEST_INPUT == "LLR TEST_INPUT == "LLR
@ -59,9 +77,9 @@ BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)", ZZZ = (ZZZ, ZZZ)",
tests: { tests: {
part1: { TEST_INPUT => 6 }, part1: { TEST_INPUT => 6 },
part2: { TEST_INPUT => 0 }, part2: { TEST_INPUT_P2 => 6 },
}, },
bench1 == 12083, bench1 == 12083,
bench2 == 0, bench2 == 13385272668829,
bench_parse: |(v, m): &Parsed| { assert_eq!(m["AAA"], ("MJJ", "QBJ")); (v.len(), v[0]) } => (281, Direction::Left), bench_parse: |(v, m): &Parsed| { assert_eq!(m["AAA"], ("MJJ", "QBJ")); (v.len(), v[0]) } => (281, Direction::Left),
} }

View File

@ -68,3 +68,17 @@ pub fn parse_num<I: ParseableNumber<I>>(s: &str) -> I {
let start = unsafe { digits.next().unwrap_unchecked() }; let start = unsafe { digits.next().unwrap_unchecked() };
digits.fold(start, |acc, n| acc * I::from(10) + n) 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)
}