day 8 benchmark chasing
This commit is contained in:
parent
68592f33ac
commit
55e6875732
|
@ -1,11 +1,12 @@
|
|||
#![feature(test)]
|
||||
extern crate test;
|
||||
|
||||
use std::hint::unreachable_unchecked;
|
||||
|
||||
use aoc2023::{boilerplate, common::*};
|
||||
use fnv::FnvHashMap as HashMap;
|
||||
|
||||
const DAY: usize = 8;
|
||||
type Parsed<'a> = (Vec<Direction>, HashMap<&'a str, [&'a str; 2]>);
|
||||
type Parsed<'a> = (Vec<Direction>, Vec<u16>, [[u16; 2]; LUT_SIZE]);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
|
||||
enum Direction {
|
||||
|
@ -13,6 +14,12 @@ enum Direction {
|
|||
Right = 1,
|
||||
}
|
||||
|
||||
const LUT_SIZE: usize = 27483; // pack("ZZZ") + 1
|
||||
|
||||
fn pack(s: &[u8; 3]) -> u16 {
|
||||
s.iter().cloned().fold(0, |acc, n| (acc << 5) + (n as u16 & 0b11111))
|
||||
}
|
||||
|
||||
fn parse_input(raw: &str) -> Parsed {
|
||||
let (directions, map) = raw.split_once("\n\n").unwrap();
|
||||
let directions = directions
|
||||
|
@ -20,27 +27,44 @@ fn parse_input(raw: &str) -> Parsed {
|
|||
.map(|i| match i {
|
||||
b'L' => Direction::Left,
|
||||
b'R' => Direction::Right,
|
||||
_ => unreachable!(),
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
})
|
||||
.collect();
|
||||
let map = map.lines().map(|l| (&l[0..=2], [&l[7..=9], &l[12..=14]])).collect();
|
||||
(directions, map)
|
||||
let mut lut = [[0u16, 0]; LUT_SIZE];
|
||||
let mut indices = Vec::new();
|
||||
for x in map.lines().map(|l| l.as_bytes()) {
|
||||
unsafe {
|
||||
let idx = pack(&*x[0..=2].as_ptr().cast());
|
||||
let left = pack(&*x[7..=9].as_ptr().cast());
|
||||
let right = pack(&*x[12..=14].as_ptr().cast());
|
||||
lut[idx as usize] = [left, right];
|
||||
if x[2] == b'A' {
|
||||
indices.push(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
(directions, indices, lut)
|
||||
}
|
||||
|
||||
fn steps_until((directions, map): &Parsed, start: &str, target: &str) -> usize {
|
||||
#[inline]
|
||||
fn ends_with(packed: u16, suffix: u8) -> bool {
|
||||
packed & 0b11111 == suffix as u16 & 0b11111
|
||||
}
|
||||
|
||||
fn steps_until((directions, _, map): &Parsed, start: u16) -> usize {
|
||||
directions
|
||||
.iter()
|
||||
.cycle()
|
||||
.scan(start, |pos, dir| {
|
||||
let next = map.get(pos)?[*dir as usize];
|
||||
(!next.ends_with(target)).then(|| *pos = next)
|
||||
let next = map[*pos as usize][*dir as usize];
|
||||
(!ends_with(next, b'Z')).then(|| *pos = next)
|
||||
})
|
||||
.count()
|
||||
+ 1
|
||||
}
|
||||
|
||||
fn part1(parsed: &Parsed) -> usize {
|
||||
steps_until(parsed, "AAA", "ZZZ")
|
||||
steps_until(parsed, pack(&[b'A'; 3]))
|
||||
}
|
||||
|
||||
// I’m honestly not sure why this works. It seems each path only has a single ghost node, and that
|
||||
|
@ -48,7 +72,7 @@ fn part1(parsed: &Parsed) -> usize {
|
|||
// 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 {
|
||||
parsed.1.keys().filter(|start| start.ends_with("A")).fold(1, |acc, n| lcm(acc, steps_until(parsed, n, "Z")))
|
||||
parsed.1.iter().filter(|&&n| ends_with(n, b'A')).fold(1, |acc, n| lcm(acc, steps_until(parsed, *n)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -75,5 +99,5 @@ ZZZ = (ZZZ, ZZZ)",
|
|||
},
|
||||
bench1 == 12083,
|
||||
bench2 == 13385272668829,
|
||||
bench_parse: |(v, m): &Parsed| { assert_eq!(m["AAA"], ["MJJ", "QBJ"]); (v.len(), v[0]) } => (281, Direction::Left),
|
||||
bench_parse: |(v, v2, m): &Parsed| { assert_eq!(m[0], [0, 0]); (v.len(), v2.len(), v[0]) } => (281, 6, Direction::Left),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user