2023-12-12 13:04:24 +01:00
#![ feature(test) ]
extern crate test ;
use aoc2023 ::{ boilerplate , common ::* } ;
2023-12-12 16:21:36 +01:00
use fnv ::FnvHashMap ;
2023-12-12 13:04:24 +01:00
use itertools ::Itertools ;
2023-12-12 16:21:36 +01:00
use std ::mem ::transmute ;
2023-12-12 13:04:24 +01:00
const DAY : usize = 12 ;
2023-12-12 16:21:36 +01:00
type I = usize ;
2023-12-12 13:04:24 +01:00
type Parsed < ' a > = Vec < ( & ' a [ Spring ] , Vec < I > ) > ;
#[ repr(u8) ]
#[ derive(Debug, PartialEq, Eq, Copy, Clone) ]
#[ allow(dead_code) ]
enum Spring {
Operational = b '.' ,
Broken = b '#' ,
Unknown = b '?' ,
}
fn parse_input ( raw : & str ) -> Parsed {
raw . lines ( )
. map ( | l | l . split_once ( ' ' ) . unwrap ( ) )
. map ( | ( springs , counts ) | ( unsafe { transmute ( springs ) } , parse_nums_separator ( counts , ',' ) ) )
. collect ( )
}
2023-12-12 16:21:36 +01:00
fn part1 ( lines : & Parsed ) -> usize {
lines
2023-12-12 13:04:24 +01:00
. iter ( )
2023-12-12 16:21:36 +01:00
. map ( | ( springs , expected ) | {
let mut cache = FnvHashMap ::default ( ) ;
2023-12-13 10:28:05 +01:00
valid_combinations ( springs , expected , CallState ::default ( ) , & mut cache )
2023-12-12 16:21:36 +01:00
} )
. sum ( )
2023-12-12 13:04:24 +01:00
}
2023-12-12 16:39:07 +01:00
#[ derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy) ]
struct CallState {
index : usize ,
broken_count : usize ,
current_constraint : usize ,
broken_streak : usize ,
2023-12-12 16:21:36 +01:00
just_completed_streak : bool ,
2023-12-12 16:39:07 +01:00
}
impl CallState {
fn new ( index : usize , broken_count : usize , current_constraint : usize , broken_streak : usize , just_completed_streak : bool ) -> Self {
Self { index , broken_count , current_constraint , broken_streak , just_completed_streak }
2023-12-12 16:21:36 +01:00
}
2023-12-12 16:39:07 +01:00
fn operational ( self ) -> Self {
CallState ::new ( self . index + 1 , self . broken_count , self . current_constraint , 0 , false )
2023-12-12 13:04:24 +01:00
}
2023-12-12 16:39:07 +01:00
}
2023-12-12 16:21:36 +01:00
2023-12-12 16:39:07 +01:00
fn add_broken ( springs : & [ Spring ] , constraints : & [ usize ] , state : CallState , cache : & mut FnvHashMap < CallState , usize > ) -> Option < usize > {
( state . current_constraint < constraints . len ( )
& & state . broken_count < constraints . iter ( ) . take ( state . current_constraint + 1 ) . sum ( )
& & ! state . just_completed_streak )
. then ( | | {
let just_completed_streak = state . broken_streak + 1 = = constraints [ state . current_constraint ] ;
valid_combinations (
springs ,
constraints ,
CallState ::new (
state . index + 1 ,
state . broken_count + 1 ,
state . current_constraint + just_completed_streak as usize ,
state . broken_streak + 1 ,
2023-12-12 16:21:36 +01:00
just_completed_streak ,
2023-12-12 16:39:07 +01:00
) ,
cache ,
)
} )
}
fn valid_combinations ( springs : & [ Spring ] , constraints : & [ usize ] , state : CallState , cache : & mut FnvHashMap < CallState , usize > ) -> usize {
if let Some ( & cached ) = cache . get ( & state ) {
return cached ;
}
if state . index = = springs . len ( ) {
return ( state . broken_count = = constraints . iter ( ) . sum ::< usize > ( ) & & state . current_constraint = = constraints . len ( ) ) as _ ;
}
let valid = match springs [ state . index ] {
Spring ::Operational = > valid_combinations ( springs , constraints , state . operational ( ) , cache ) ,
Spring ::Broken = > add_broken ( springs , constraints , state , cache ) . unwrap_or ( 0 ) ,
2023-12-12 16:21:36 +01:00
Spring ::Unknown = > {
2023-12-12 16:39:07 +01:00
let operational = valid_combinations ( springs , constraints , state . operational ( ) , cache ) ;
let broken = add_broken ( springs , constraints , state , cache ) . unwrap_or ( 0 ) ;
2023-12-12 16:21:36 +01:00
operational + broken
}
} ;
2023-12-12 16:39:07 +01:00
cache . insert ( state , valid ) ;
2023-12-12 16:21:36 +01:00
valid
2023-12-12 13:04:24 +01:00
}
2023-12-12 16:21:36 +01:00
fn part2 ( lines : & Parsed ) -> usize {
2023-12-12 13:04:24 +01:00
lines
. iter ( )
. map ( | ( springs , expected ) | {
2023-12-12 16:39:07 +01:00
// this seems cursed. is there a join() for iterators?
2023-12-12 16:21:36 +01:00
let springs = springs . to_vec ( ) ;
let new_spring_length = springs . len ( ) * 5 + 4 ;
(
springs . into_iter ( ) . chain ( std ::iter ::once ( Spring ::Unknown ) ) . cycle ( ) . take ( new_spring_length ) . collect_vec ( ) ,
expected . iter ( ) . cloned ( ) . cycle ( ) . take ( expected . len ( ) * 5 ) . collect_vec ( ) ,
)
} )
. map ( | ( springs , expected ) | {
let mut cache = FnvHashMap ::default ( ) ;
2023-12-12 16:39:07 +01:00
valid_combinations ( & springs , & expected , CallState ::default ( ) , & mut cache )
2023-12-12 13:04:24 +01:00
} )
. sum ( )
}
boilerplate! {
TEST_INPUT = = " \
? ? ? . ### 1 , 1 , 3
. ? ? .. ? ? .. . ? ##. 1 , 1 , 3
? #? #? #? #? #? #? #? 1 , 3 , 1 , 6
? ? ? ? . #.. . #.. . 4 , 1 , 1
? ? ? ? . ######.. #####. 1 , 6 , 5
2023-12-13 09:30:03 +01:00
? ###? ? ? ? ? ? ? ? 3 , 2 , 1 "
for tests : {
2023-12-12 13:04:24 +01:00
part1 : { TEST_INPUT = > 21 } ,
2023-12-12 16:21:36 +01:00
part2 : { TEST_INPUT = > 525152 } ,
2023-12-12 13:04:24 +01:00
} ,
bench1 = = 7260 ,
2023-12-12 16:21:36 +01:00
bench2 = = 1909291258644 ,
2023-12-12 13:04:24 +01:00
bench_parse : Vec ::len = > 1000 ,
}