263 lines
7.4 KiB
Rust
263 lines
7.4 KiB
Rust
use std::collections::HashMap;
|
|
use std::error::Error;
|
|
use std::fs::File;
|
|
use std::io::{self, BufRead};
|
|
use std::vec::Vec;
|
|
|
|
#[derive(PartialEq, Clone, Debug)]
|
|
enum Rule {
|
|
Terminal(char),
|
|
NoneTerminal(Vec<Vec<u32>>),
|
|
}
|
|
|
|
fn read_rule(rule: &str) -> (u32, Rule) {
|
|
let mut parts = rule.split(':');
|
|
|
|
let num: u32 = parts
|
|
.nth(0)
|
|
.expect("Missing rule Number")
|
|
.trim()
|
|
.parse()
|
|
.expect("Unable to parse rule numer");
|
|
|
|
let rest = parts.nth(0).expect("Missing actual rule").trim();
|
|
|
|
if rest.starts_with('"') {
|
|
(
|
|
num,
|
|
Rule::Terminal(rest.chars().nth(1).expect("Rule char not found")),
|
|
)
|
|
} else {
|
|
let options: Vec<Vec<u32>> = rest
|
|
.split('|')
|
|
.map(|o| {
|
|
o.trim()
|
|
.split(' ')
|
|
.map(|r| r.parse().expect("Unable to parse sub rule numer"))
|
|
.collect()
|
|
})
|
|
.collect();
|
|
(num, Rule::NoneTerminal(options))
|
|
}
|
|
}
|
|
|
|
fn match_rule<'a>(input: &'a str, rule_num: u32, rules: &HashMap<u32, Rule>) -> (bool, &'a str) {
|
|
let rule = &rules[&rule_num];
|
|
|
|
match rule {
|
|
Rule::Terminal(a) => {
|
|
if let Some(b) = input.chars().nth(0) {
|
|
if *a == b {
|
|
return (true, &input[1..]);
|
|
} else {
|
|
return (false, "");
|
|
}
|
|
} else {
|
|
return (false, "");
|
|
}
|
|
}
|
|
Rule::NoneTerminal(opts) => {
|
|
for seq in opts {
|
|
let mut rest = input;
|
|
let mut valid = true;
|
|
for r in seq {
|
|
let (ok, tail) = match_rule(rest, *r, &rules);
|
|
|
|
if !ok {
|
|
valid = false;
|
|
break;
|
|
}
|
|
rest = tail;
|
|
|
|
// Weird HACK
|
|
if valid && rest == "" {
|
|
if *r == 11 {
|
|
valid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if valid {
|
|
return (true, rest);
|
|
}
|
|
}
|
|
return (false, "");
|
|
}
|
|
};
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let file = File::open("inputs/day19.txt")?;
|
|
let mut lines = io::BufReader::new(file).lines().map(|l| l.unwrap());
|
|
|
|
let mut rules: HashMap<u32, Rule> = HashMap::new();
|
|
|
|
while let Some(line) = lines.next() {
|
|
if line == "" {
|
|
break;
|
|
}
|
|
let (num, rule) = read_rule(&line);
|
|
rules.insert(num, rule);
|
|
}
|
|
|
|
let mut input_lines: Vec<String> = Vec::new();
|
|
while let Some(line) = lines.next() {
|
|
if line == "" {
|
|
break;
|
|
}
|
|
input_lines.push(line);
|
|
}
|
|
|
|
let count1 = input_lines
|
|
.iter()
|
|
.map(|l| {
|
|
if (true, "") == match_rule(l, 0, &rules) {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
})
|
|
.fold(0, |x, y| x + y);
|
|
|
|
println!("Answer1: {}", count1);
|
|
|
|
rules.insert(8, Rule::NoneTerminal(vec![vec![42], vec![42, 8]]));
|
|
rules.insert(11, Rule::NoneTerminal(vec![vec![42, 31], vec![42, 11, 31]]));
|
|
|
|
let count2 = input_lines
|
|
.iter()
|
|
.map(|l| {
|
|
if (true, "") == match_rule(l, 0, &rules) {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
})
|
|
.fold(0, |x, y| x + y);
|
|
|
|
println!("Answer2: {}", count2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::*;
|
|
|
|
#[test]
|
|
fn test_read() {
|
|
assert_eq!(read_rule("5 : \"a\""), (5, Rule::Terminal('a')));
|
|
assert_eq!(
|
|
read_rule("3 : 2 4 | 1 2 3 | 5 7"),
|
|
(
|
|
3,
|
|
Rule::NoneTerminal(vec![vec![2, 4], vec![1, 2, 3], vec![5, 7]])
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_example() {
|
|
let rule_str = r#"0: 4 1 5
|
|
1: 2 3 | 3 2
|
|
2: 4 4 | 5 5
|
|
3: 4 5 | 5 4
|
|
4: "a"
|
|
5: "b""#;
|
|
let rules: HashMap<u32, Rule> = rule_str.lines().map(|l| read_rule(l)).collect();
|
|
|
|
let inputs = r#"
|
|
ababbb
|
|
bababa
|
|
abbbab
|
|
aaabbb
|
|
aaaabbb
|
|
"#;
|
|
|
|
let in_lines = inputs.lines().map(|l| l.trim());
|
|
let mut count = 0;
|
|
for line in in_lines {
|
|
let (matched, rest) = match_rule(line, 0, &rules);
|
|
if matched && rest == "" {
|
|
println!("Matched: {}", line);
|
|
count += 1;
|
|
} else {
|
|
println!("Not matched: {} {:?} {:?}", matched, line, rest);
|
|
}
|
|
}
|
|
|
|
assert_eq!(count, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_loops() {
|
|
let rule_str = r#"42: 9 14 | 10 1
|
|
9: 14 27 | 1 26
|
|
10: 23 14 | 28 1
|
|
1: "a"
|
|
11: 42 31
|
|
5: 1 14 | 15 1
|
|
19: 14 1 | 14 14
|
|
12: 24 14 | 19 1
|
|
16: 15 1 | 14 14
|
|
31: 14 17 | 1 13
|
|
6: 14 14 | 1 14
|
|
2: 1 24 | 14 4
|
|
0: 8 11
|
|
13: 14 3 | 1 12
|
|
15: 1 | 14
|
|
17: 14 2 | 1 7
|
|
23: 25 1 | 22 14
|
|
28: 16 1
|
|
4: 1 1
|
|
20: 14 14 | 1 15
|
|
3: 5 14 | 16 1
|
|
27: 1 6 | 14 18
|
|
14: "b"
|
|
21: 14 1 | 1 14
|
|
25: 1 1 | 1 14
|
|
22: 14 14
|
|
8: 42
|
|
26: 14 22 | 1 20
|
|
18: 15 15
|
|
7: 14 5 | 1 21
|
|
24: 14 1"#;
|
|
let mut rules: HashMap<u32, Rule> = rule_str.lines().map(|l| read_rule(l)).collect();
|
|
rules.insert(8, Rule::NoneTerminal(vec![vec![42], vec![42, 8]]));
|
|
rules.insert(11, Rule::NoneTerminal(vec![vec![42, 31], vec![42, 11, 31]]));
|
|
|
|
let inputs = r#"
|
|
abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa
|
|
bbabbbbaabaabba
|
|
babbbbaabbbbbabbbbbbaabaaabaaa
|
|
aaabbbbbbaaaabaababaabababbabaaabbababababaaa
|
|
bbbbbbbaaaabbbbaaabbabaaa
|
|
bbbababbbbaaaaaaaabbababaaababaabab
|
|
ababaaaaaabaaab
|
|
ababaaaaabbbaba
|
|
baabbaaaabbaaaababbaababb
|
|
abbbbabbbbaaaababbbbbbaaaababb
|
|
aaaaabbaabaaaaababaa
|
|
aaaabbaaaabbaaa
|
|
aaaabbaabbaaaaaaabbbabbbaaabbaabaaa
|
|
babaaabbbaaabaababbaabababaaab
|
|
aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba
|
|
"#;
|
|
|
|
let in_lines = inputs.lines().map(|l| l.trim());
|
|
let mut count = 0;
|
|
for line in in_lines {
|
|
let (matched, rest) = match_rule(line, 0, &rules);
|
|
if matched && rest == "" {
|
|
println!("Matched: {}", line);
|
|
count += 1;
|
|
} else {
|
|
println!("Not matched: {} {:?} {:?}", matched, line, rest);
|
|
}
|
|
}
|
|
|
|
assert_eq!(count, 12);
|
|
}
|
|
}
|