2023-12-05 12:26:05 +01:00
#![ feature(test, iter_array_chunks) ]
2023-12-05 10:52:38 +01:00
extern crate test ;
use std ::ops ::Range ;
use aoc2023 ::{ boilerplate , common ::* } ;
use itertools ::Itertools ;
const DAY : usize = 5 ;
type I = i64 ;
2023-12-05 12:26:05 +01:00
type Mapping = Vec < ( Range < I > , Range < I > , I ) > ;
2023-12-05 10:52:38 +01:00
type Parsed = ( Vec < I > , Vec < Mapping > ) ;
fn parse_input ( raw : & str ) -> Parsed {
let mut groups = raw . split ( " \n \n " ) ;
let seeds = groups . next ( ) . unwrap ( ) . trim_start_matches ( " seeds: " ) . split ( ' ' ) . map ( parse_num ) . collect ( ) ;
let ranges = groups
. map ( | g | {
g . lines ( )
. skip ( 1 )
. map ( | l | l . split ( ' ' ) . map ( parse_num ) . collect_tuple ( ) . unwrap ( ) )
2023-12-05 12:26:05 +01:00
// We swap the order of dst and src here because it just makes much more sense to me this way.
. map ( | ( dst , src , len ) | ( src .. src + len , dst .. dst + len , dst - src ) )
2023-12-05 10:52:38 +01:00
. collect ( )
} )
. collect ( ) ;
( seeds , ranges )
}
2023-12-07 12:49:37 +01:00
fn resolve ( start : I , mappings : & [ Mapping ] ) -> I {
2023-12-05 12:26:05 +01:00
mappings . iter ( ) . fold ( start , | i , map | map . iter ( ) . find_map ( | ( range , _ , offset ) | range . contains ( & i ) . then_some ( i + offset ) ) . unwrap_or ( i ) )
2023-12-05 10:52:38 +01:00
}
fn part1 ( ( seeds , mappings ) : & Parsed ) -> I {
seeds . iter ( ) . map ( | & s | resolve ( s , mappings ) ) . min ( ) . unwrap ( )
}
2023-12-07 12:49:37 +01:00
fn resolve_backwards ( start : I , mappings : & [ Mapping ] ) -> I {
2023-12-05 12:26:05 +01:00
mappings . iter ( ) . fold ( start , | i , map | map . iter ( ) . find_map ( | ( _ , range , offset ) | range . contains ( & i ) . then_some ( i - offset ) ) . unwrap_or ( i ) )
}
fn part2 ( ( seeds , mappings ) : & Parsed ) -> I {
let seed_ranges = seeds . iter ( ) . array_chunks ( ) . map ( | [ & a , & b ] | a .. a + b ) . collect_vec ( ) ;
let mut mappings = mappings . clone ( ) ;
2023-12-05 12:46:57 +01:00
mappings . reverse ( ) ;
2023-12-05 12:26:05 +01:00
2023-12-05 12:46:57 +01:00
let mut destinations = mappings . remove ( 0 ) ;
2023-12-05 12:26:05 +01:00
destinations . sort_by_key ( | ( _ , range , _ ) | range . start ) ;
let mut ranges = Vec ::< ( Range < I > , I ) > ::new ( ) ;
for ( _ , range , offset ) in destinations {
2023-12-05 12:46:57 +01:00
ranges . push ( ( ranges . last ( ) . map ( | ( r , _ ) | r . end ) . unwrap_or ( 0 ) .. range . start , 0 ) ) ;
2023-12-05 12:26:05 +01:00
ranges . push ( ( range , offset ) ) ;
}
2023-12-05 12:46:57 +01:00
// By calculating the smallest range in the input data, we can perform something similar to binary search.
// We start at location 0 and move in steps of this size,
// and when we first encounter a location that corresponds to a starting seed we start binary searching,
// i.e. we half the step size and move back by that until we find the lowest location that has a starting seed.
let mut step = mappings . iter ( ) . flatten ( ) . map ( | ( r , _ , _ ) | r . try_len ( ) . unwrap ( ) ) . min ( ) . unwrap ( ) as I ;
ranges
. into_iter ( )
. find_map ( | ( range , offset ) | {
let mut best = I ::MAX ;
let mut location = range . start + step ;
while location < range . end & & step ! = 0 {
if has_starting_seed ( location , offset , & seed_ranges , & mappings ) {
best = best . min ( location ) ;
step = ( step > > 1 ) + 1 ;
location - = step ;
} else {
step > > = ( best ! = I ::MAX ) as usize ;
location + = step ;
}
}
( best ! = I ::MAX ) . then_some ( best )
} )
. unwrap ( )
2023-12-05 12:26:05 +01:00
}
2023-12-07 12:49:37 +01:00
fn has_starting_seed ( start : I , offset : i64 , seed_ranges : & [ Range < I > ] , mappings : & [ Mapping ] ) -> bool {
let seed = resolve_backwards ( start - offset , mappings ) ;
2023-12-05 12:46:57 +01:00
// If seed == s, the entire resolution didn’t hit a single mapping, so we don’t need to check seeds.
seed ! = start & & seed_ranges . iter ( ) . any ( | r | r . contains ( & seed ) )
2023-12-05 10:52:38 +01:00
}
boilerplate! {
TEST_INPUT = = " seeds: 79 14 55 13
seed - to - soil map :
50 98 2
52 50 48
soil - to - fertilizer map :
0 15 37
37 52 2
39 0 15
fertilizer - to - water map :
49 53 8
0 11 42
42 0 7
57 7 4
water - to - light map :
88 18 7
18 25 70
light - to - temperature map :
45 77 23
81 45 19
68 64 13
temperature - to - humidity map :
0 69 1
1 0 69
humidity - to - location map :
60 56 37
56 93 4 " ,
tests : {
part1 : { TEST_INPUT = > 35 } ,
2023-12-05 12:26:05 +01:00
part2 : { TEST_INPUT = > 46 } ,
2023-12-05 10:52:38 +01:00
} ,
bench1 = = 462648396 ,
2023-12-05 12:26:05 +01:00
bench2 = = 2520479 ,
2023-12-05 10:52:38 +01:00
bench_parse : | ( v1 , v2 ) : & Parsed | ( v1 . len ( ) , v2 . len ( ) ) = > ( 20 , 7 ) ,
}