2020-12-19 12:32:28 +01:00
#![ feature(test, str_split_once, bool_to_option) ]
extern crate test ;
use aoc2020 ::common ::* ;
use itertools ::Itertools ;
#[ derive(Debug, PartialEq, Clone, Copy) ]
enum Rule {
Char ( u8 ) ,
And ( ( usize , usize ) ) ,
AndOr ( ( usize , usize ) , ( usize , usize ) ) ,
Or ( ( usize , usize ) ) ,
Useless ( usize ) , // just delegates to another rule and makes parsing annoying
// This is why you don’t change the requirements once the implementation is done. REEEEEE
Special8 ( usize , ( usize , usize ) ) ,
Special11 ( ( usize , usize ) , ( usize , usize , usize ) ) ,
// If Rule was a recursive struct, this could have been defined as And(first, And(second, third)), but alas
Triple ( ( usize , usize , usize ) ) ,
}
type Parsed < ' a > = ( RuleSet , & ' a str ) ;
#[ derive(Debug, Clone) ]
struct RuleSet ( Vec < Rule > ) ;
impl RuleSet {
fn matches ( & self , s : & str , rule : Rule ) -> Option < usize > {
2020-12-19 12:50:48 +01:00
if s . len ( ) = = 0 {
return None ;
}
2020-12-19 12:32:28 +01:00
let res = match rule {
Rule ::Char ( c ) = > ( s . as_bytes ( ) . first ( ) = = Some ( & c ) ) . then_some ( 1 ) ,
Rule ::And ( ( r1 , r2 ) ) = > self
. matches ( s , self . 0 [ r1 ] )
. and_then ( | i | self . matches ( & s [ i .. ] , self . 0 [ r2 ] ) . map ( | j | i + j ) ) ,
Rule ::AndOr ( ( r1 , r2 ) , ( r3 , r4 ) ) = > self
. matches ( s , Rule ::And ( ( r1 , r2 ) ) )
. or_else ( | | self . matches ( s , Rule ::And ( ( r3 , r4 ) ) ) ) ,
2020-12-19 12:50:48 +01:00
Rule ::Or ( ( r1 , r2 ) ) = > self . matches ( s , Rule ::Useless ( r1 ) ) . or_else ( | | self . matches ( s , self . 0 [ r2 ] ) ) ,
2020-12-19 12:32:28 +01:00
Rule ::Useless ( r ) = > self . matches ( s , self . 0 [ r ] ) ,
// part 2 shit below:
2020-12-19 12:50:48 +01:00
Rule ::Special8 ( first , second ) = > self . matches ( s , self . 0 [ first ] ) . or_else ( | | self . matches ( s , Rule ::And ( second ) ) ) ,
2020-12-19 12:32:28 +01:00
Rule ::Special11 ( first , second ) = > self . matches ( s , Rule ::And ( first ) ) . or_else ( | | self . matches ( s , Rule ::Triple ( second ) ) ) ,
Rule ::Triple ( ( r1 , r2 , r3 ) ) = > self
. matches ( s , self . 0 [ r1 ] )
. and_then ( | i | self . matches ( & s [ i .. ] , self . 0 [ r2 ] ) . map ( | j | i + j ) )
. and_then ( | i | self . matches ( & s [ i .. ] , self . 0 [ r3 ] ) . map ( | j | i + j ) ) ,
} ;
res
}
}
fn read_input ( ) -> String {
read_file ( 19 )
}
fn parse_input < ' a > ( raw : & ' a str ) -> Parsed < ' a > {
let ( rules , inputs ) = raw . split_once ( " \n \n " ) . unwrap ( ) ;
let rules = RuleSet (
rules
. lines ( )
. filter_map ( | l | l . split_once ( " : " ) )
// why isn’t this already sorted like it was in the example?
. sorted_by_key ( | t | t . 0. parse ::< i32 > ( ) . unwrap ( ) )
. map ( | ( _ , r ) | match r {
or if or . contains ( '|' ) = > {
let mut split = or . split ( ' ' ) . filter ( | s | s ! = & " | " ) . map ( | s | s . parse ( ) . unwrap ( ) ) ;
split
. clone ( )
. next_tuple ( )
. map ( | ( a , b , c , d ) | Rule ::AndOr ( ( a , b ) , ( c , d ) ) )
. or_else ( | | split . next_tuple ( ) . map ( | ( a , b ) | Rule ::Or ( ( a , b ) ) ) )
. unwrap ( )
}
chr if chr . contains ( '"' ) = > Rule ::Char ( chr . bytes ( ) . skip_while ( | & b | b ! = b '"' ) . skip ( 1 ) . next ( ) . unwrap ( ) ) ,
and if and . contains ( ' ' ) = > Rule ::And ( and . split ( ' ' ) . map ( | s | s . parse ( ) . unwrap ( ) ) . next_tuple ( ) . unwrap ( ) ) ,
useless = > Rule ::Useless ( useless . parse ( ) . unwrap ( ) ) ,
} )
. collect ( ) ,
) ;
( rules , inputs )
}
fn part1 ( ( rules , input ) : & Parsed ) -> usize {
input . lines ( ) . filter ( | l | rules . matches ( l , rules . 0 [ 0 ] ) = = Some ( l . len ( ) ) ) . count ( )
}
fn part2 ( parsed : & Parsed ) -> usize {
let mut rules = parsed . 0. clone ( ) ;
rules . 0 [ 8 ] = Rule ::Special8 ( 42 , ( 42 , 8 ) ) ;
rules . 0 [ 11 ] = Rule ::Special11 ( ( 42 , 31 ) , ( 42 , 11 , 31 ) ) ;
dbg! ( parsed . 1. lines ( ) . map ( | l | ( l , rules . matches ( l , rules . 0 [ 0 ] ) ) ) . collect_vec ( ) ) ;
parsed . 1. lines ( ) . filter ( | l | rules . matches ( l , rules . 0 [ 0 ] ) = = Some ( l . len ( ) ) ) . count ( )
}
fn main ( ) {
let raw = read_input ( ) ;
let input = parse_input ( & raw ) ;
println! ( " Part 1: {} " , part1 ( & input ) ) ;
// 265 too low, 395 too high. apparently 379? no idea how that’s supposed to work
println! ( " Part 2: {} " , part2 ( & input ) ) ;
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use aoc2020 ::* ;
use paste ::paste ;
use test ::black_box ;
const TEST_INPUT : & str = r #" 0: 8 11
1 : " a "
2 : 1 24 | 14 4
3 : 5 14 | 16 1
4 : 1 1
5 : 1 14 | 15 1
6 : 14 14 | 1 14
7 : 14 5 | 1 21
8 : 42
9 : 14 27 | 1 26
10 : 23 14 | 28 1
11 : 42 31
12 : 24 14 | 19 1
13 : 14 3 | 1 12
14 : " b "
15 : 1 | 14
16 : 15 1 | 14 14
17 : 14 2 | 1 7
18 : 15 15
19 : 14 1 | 14 14
20 : 14 14 | 1 15
21 : 14 1 | 1 14
22 : 14 14
23 : 25 1 | 22 14
24 : 14 1
25 : 1 1 | 1 14
26 : 14 22 | 1 20
27 : 1 6 | 14 18
28 : 16 1
29 : 29
30 : 30
31 : 14 17 | 1 13
32 : 32
33 : 33
34 : 34
35 : 35
36 : 36
37 : 37
38 : 38
39 : 39
40 : 40
41 : 41
42 : 9 14 | 10 1
abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa
bbabbbbaabaabba
babbbbaabbbbbabbbbbbaabaaabaaa
aaabbbbbbaaaabaababaabababbabaaabbababababaaa
bbbbbbbaaaabbbbaaabbabaaa
bbbababbbbaaaaaaaabbababaaababaabab
ababaaaaaabaaab
ababaaaaabbbaba
baabbaaaabbaaaababbaababb
abbbbabbbbaaaababbbbbbaaaababb
aaaaabbaabaaaaababaa
aaaabbaaaabbaaa
aaaabbaabbaaaaaaabbbabbbaaabbaabaaa
babaaabbbaaabaababbaabababaaab
aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba " #;
2020-12-19 12:50:48 +01:00
2020-12-19 12:32:28 +01:00
test! ( part1 ( ) = = 3 ) ;
test! ( part2 ( ) = = 12 ) ;
bench! ( part1 ( ) = = 235 ) ;
// bench!(part2() == 0);
// bench_input!(0.len == 0);
}