2023-12-08 06:30:11 +01:00
#![ feature(test) ]
extern crate test ;
2023-12-08 10:40:04 +01:00
use std ::hint ::unreachable_unchecked ;
2023-12-08 06:30:11 +01:00
use aoc2023 ::{ boilerplate , common ::* } ;
2023-12-08 10:06:19 +01:00
const DAY : usize = 8 ;
2023-12-08 10:40:04 +01:00
type Parsed < ' a > = ( Vec < Direction > , Vec < u16 > , [ [ u16 ; 2 ] ; LUT_SIZE ] ) ;
2023-12-08 06:30:11 +01:00
2023-12-08 07:05:56 +01:00
#[ derive(Debug, PartialEq, Eq, Copy, Clone, Hash) ]
2023-12-08 06:30:11 +01:00
enum Direction {
2023-12-08 10:06:19 +01:00
Left = 0 ,
Right = 1 ,
2023-12-08 06:30:11 +01:00
}
2023-12-08 10:40:04 +01:00
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 ) )
}
2023-12-08 06:30:11 +01:00
fn parse_input ( raw : & str ) -> Parsed {
let ( directions , map ) = raw . split_once ( " \n \n " ) . unwrap ( ) ;
let directions = directions
. bytes ( )
. map ( | i | match i {
b 'L' = > Direction ::Left ,
b 'R' = > Direction ::Right ,
2023-12-08 10:40:04 +01:00
_ = > unsafe { unreachable_unchecked ( ) } ,
2023-12-08 06:30:11 +01:00
} )
. collect ( ) ;
2023-12-08 10:40:04 +01:00
let mut lut = [ [ 0 u16 , 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 )
}
#[ inline ]
fn ends_with ( packed : u16 , suffix : u8 ) -> bool {
packed & 0b11111 = = suffix as u16 & 0b11111
2023-12-08 06:30:11 +01:00
}
2023-12-08 10:40:04 +01:00
fn steps_until ( ( directions , _ , map ) : & Parsed , start : u16 ) -> usize {
2023-12-08 06:30:11 +01:00
directions
. iter ( )
. cycle ( )
2023-12-08 07:05:56 +01:00
. scan ( start , | pos , dir | {
2023-12-08 10:40:04 +01:00
let next = map [ * pos as usize ] [ * dir as usize ] ;
( ! ends_with ( next , b 'Z' ) ) . then ( | | * pos = next )
2023-12-08 06:30:11 +01:00
} )
. count ( )
+ 1
}
2023-12-08 08:54:41 +01:00
fn part1 ( parsed : & Parsed ) -> usize {
2023-12-08 10:40:04 +01:00
steps_until ( parsed , pack ( & [ b 'A' ; 3 ] ) )
2023-12-08 08:54:41 +01:00
}
2023-12-08 07:05:56 +01:00
// 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.
2023-12-08 06:30:11 +01:00
fn part2 ( parsed : & Parsed ) -> usize {
2023-12-08 10:40:04 +01:00
parsed . 1. iter ( ) . filter ( | & & n | ends_with ( n , b 'A' ) ) . fold ( 1 , | acc , n | lcm ( acc , steps_until ( parsed , * n ) ) )
2023-12-08 06:30:11 +01:00
}
2023-12-08 07:05:56 +01:00
#[ cfg(test) ]
const TEST_INPUT_P2 : & str = " LR
11 A = ( 11 B , XXX )
11 B = ( XXX , 11 Z )
11 Z = ( 11 B , XXX )
22 A = ( 22 B , XXX )
22 B = ( 22 C , 22 C )
22 C = ( 22 Z , 22 Z )
22 Z = ( 22 B , 22 B )
XXX = ( XXX , XXX ) " ;
2023-12-08 06:30:11 +01:00
boilerplate! {
TEST_INPUT = = " LLR
AAA = ( BBB , BBB )
BBB = ( AAA , ZZZ )
ZZZ = ( ZZZ , ZZZ ) " ,
tests : {
part1 : { TEST_INPUT = > 6 } ,
2023-12-08 07:05:56 +01:00
part2 : { TEST_INPUT_P2 = > 6 } ,
2023-12-08 06:30:11 +01:00
} ,
bench1 = = 12083 ,
2023-12-08 07:05:56 +01:00
bench2 = = 13385272668829 ,
2023-12-08 10:40:04 +01:00
bench_parse : | ( v , v2 , m ) : & Parsed | { assert_eq! ( m [ 0 ] , [ 0 , 0 ] ) ; ( v . len ( ) , v2 . len ( ) , v [ 0 ] ) } = > ( 281 , 6 , Direction ::Left ) ,
2023-12-08 06:30:11 +01:00
}