2023-12-14 09:18:51 +01:00
#![ feature(test, try_blocks) ]
extern crate test ;
2023-12-14 10:43:20 +01:00
use aoc2023 ::{ boilerplate , common ::* } ;
2023-12-14 10:50:44 +01:00
use fnv ::FnvHashMap ;
use std ::mem ::transmute ;
2023-12-14 09:18:51 +01:00
const DAY : usize = 14 ;
type Parsed = Vec < Vec < Tile > > ;
#[ allow(dead_code) ]
2023-12-14 10:43:20 +01:00
#[ derive(Debug, Copy, Clone, PartialEq, Eq, Hash) ]
2023-12-14 09:18:51 +01:00
#[ repr(u8) ]
enum Tile {
Round = b 'O' ,
Cube = b '#' ,
Empty = b '.' ,
}
fn parse_input ( raw : & str ) -> Parsed {
raw . lines ( ) . map ( | l | unsafe { transmute ::< & str , & [ Tile ] > ( l ) } . to_vec ( ) ) . collect ( )
}
2023-12-14 10:43:20 +01:00
fn tilt_north ( grid : & mut Parsed ) {
2023-12-14 09:18:51 +01:00
for y in 0 .. grid . len ( ) {
2023-12-14 10:43:20 +01:00
for x in 0 .. grid [ y ] . len ( ) {
if grid [ y ] [ x ] = = Tile ::Round {
let mut i = 0 ;
while let Some ( Tile ::Empty ) = try { grid . get ( y . checked_sub ( i + 1 ) ? ) ? [ x ] } {
i + = 1 ;
}
grid [ y ] [ x ] = Tile ::Empty ;
grid [ y - i ] [ x ] = Tile ::Round ;
}
}
}
}
fn tilt_west ( grid : & mut Parsed ) {
2023-12-14 10:50:44 +01:00
for line in grid . iter_mut ( ) {
for x in 0 .. line . len ( ) {
if line [ x ] = = Tile ::Round {
2023-12-14 10:43:20 +01:00
let mut i = 0 ;
2023-12-14 10:50:44 +01:00
while let Some ( Tile ::Empty ) = try { line [ x . checked_sub ( i + 1 ) ? ] } {
2023-12-14 10:43:20 +01:00
i + = 1 ;
}
2023-12-14 10:50:44 +01:00
line [ x ] = Tile ::Empty ;
line [ x - i ] = Tile ::Round ;
2023-12-14 10:43:20 +01:00
}
}
}
}
fn tilt_south ( grid : & mut Parsed ) {
for y in ( 0 .. grid . len ( ) ) . rev ( ) {
2023-12-14 09:18:51 +01:00
for x in 0 .. ( grid [ y ] . len ( ) ) {
if grid [ y ] [ x ] = = Tile ::Round {
2023-12-14 10:43:20 +01:00
let mut i = 0 ;
while let Some ( Tile ::Empty ) = try { grid . get ( y + i + 1 ) ? [ x ] } {
i + = 1 ;
}
2023-12-14 09:18:51 +01:00
grid [ y ] [ x ] = Tile ::Empty ;
2023-12-14 10:43:20 +01:00
grid [ y + i ] [ x ] = Tile ::Round ;
}
}
}
}
fn tilt_east ( grid : & mut Parsed ) {
2023-12-14 10:50:44 +01:00
for line in grid . iter_mut ( ) {
for x in ( 0 .. line . len ( ) ) . rev ( ) {
if line [ x ] = = Tile ::Round {
2023-12-14 10:43:20 +01:00
let mut i = 0 ;
2023-12-14 10:50:44 +01:00
while let Some ( Tile ::Empty ) = try { line . get ( x + i + 1 ) ? } {
2023-12-14 10:43:20 +01:00
i + = 1 ;
2023-12-14 09:18:51 +01:00
}
2023-12-14 10:50:44 +01:00
line [ x ] = Tile ::Empty ;
line [ x + i ] = Tile ::Round ;
2023-12-14 09:18:51 +01:00
}
}
}
}
fn weight ( grid : & Parsed ) -> usize {
grid . iter ( ) . rev ( ) . zip ( 1 .. ) . map ( | ( row , weight ) | row . iter ( ) . filter ( | & & t | t = = Tile ::Round ) . count ( ) * weight ) . sum ( )
}
fn part1 ( parsed : & Parsed ) -> usize {
let mut grid = parsed . clone ( ) ;
2023-12-14 10:43:20 +01:00
tilt_north ( & mut grid ) ;
2023-12-14 09:18:51 +01:00
weight ( & grid )
}
fn part2 ( parsed : & Parsed ) -> usize {
2023-12-14 10:43:20 +01:00
let mut grid = parsed . clone ( ) ;
2023-12-14 10:50:44 +01:00
let mut grids = FnvHashMap ::default ( ) ;
2023-12-14 10:43:20 +01:00
let mut n = 0 ;
let cycle = loop {
2023-12-14 10:50:44 +01:00
// I tried not cloning the grid and instead inserting only a grid hash and the weight here,
// but that’s only ~15% faster and ugly, so we’ll just do it naively here.
if let Some ( old_n ) = grids . insert ( grid . clone ( ) , n ) {
2023-12-14 10:43:20 +01:00
break n - old_n ;
}
n + = 1 ;
tilt_north ( & mut grid ) ;
tilt_west ( & mut grid ) ;
tilt_south ( & mut grid ) ;
tilt_east ( & mut grid ) ;
} ;
const REPETITIONS : usize = 1000000000 ;
let steps = ( REPETITIONS - n ) % cycle ;
2023-12-14 10:50:44 +01:00
let cycle_start = n - cycle ;
grids . into_iter ( ) . find_map ( | ( g , n2 ) | ( n2 = = cycle_start + steps ) . then ( | | weight ( & g ) ) ) . unwrap ( )
2023-12-14 09:18:51 +01:00
}
boilerplate! {
TEST_INPUT = = " \
O .. .. #.. ..
O . OO #.. .. #
.. .. . ##.. .
OO . #O .. .. O
. O .. .. . O #.
O . #.. O . #. #
.. O .. #O .. O
.. .. .. . O ..
#.. .. ###..
#OO .. #.. .. "
for tests : {
part1 : { TEST_INPUT = > 136 } ,
part2 : { TEST_INPUT = > 64 } ,
} ,
bench1 = = 110407 ,
2023-12-14 10:43:20 +01:00
bench2 = = 87273 ,
2023-12-14 09:18:51 +01:00
bench_parse : Vec ::len = > 100 ,
}