Day 7. Now I remember why I'm skeptical of parser combinators.

This commit is contained in:
2020-12-07 19:49:35 -08:00
parent 8c79964316
commit b06c528c4c
5 changed files with 867 additions and 4 deletions

220
src/bin/baggage.rs Normal file
View File

@@ -0,0 +1,220 @@
use advent2020::errors::{BaggageRuleParseError, TopLevelError};
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{alphanumeric1, char, digit1, multispace0, multispace1};
use nom::multi::{fold_many1, separated_list1};
use nom::sequence::preceded;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::str::FromStr;
struct RuleSet {
contain_rules: HashMap<String, Vec<Rule>>,
}
#[derive(Clone)]
struct Rule {
count: usize,
bag: String,
}
impl RuleSet {
fn empty() -> RuleSet {
RuleSet {
contain_rules: HashMap::new(),
}
}
fn merge(&mut self, other: &mut RuleSet) {
self.contain_rules.extend(other.contain_rules.drain());
}
fn pretty_print(&self) {
for (key, value) in self.contain_rules.iter() {
if value.is_empty() {
println!("{} --> <empty>", key);
} else {
let blank = " ".repeat(key.len());
let mut first = true;
for rule in value.iter() {
println!(
"{} --> {} {}",
if first { key } else { &blank },
rule.count,
rule.bag
);
first = false;
}
}
}
}
fn can_reach(&self, start: &str, end: &str) -> bool {
let mut stack = vec![start];
let mut visited = HashSet::new();
while let Some(next) = stack.pop() {
if next == end {
return true;
}
if visited.contains(next) {
continue;
}
visited.insert(next);
match self.contain_rules.get(next) {
None => println!("WARNING: Can't find color {}", next),
Some(rules) => {
for rule in rules.iter() {
stack.push(&rule.bag);
}
}
}
}
false
}
fn bags_required(&self, color: &str) -> usize {
match self.contain_rules.get(color) {
None => {
println!("WARNING: Can't find color {}", color);
0
}
Some(rules) => {
rules
.iter()
.map(|x| self.bags_required(&x.bag) * x.count)
.sum::<usize>()
+ 1
}
}
}
}
impl FromStr for RuleSet {
type Err = BaggageRuleParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, parse_result) = parse_rules(s)?;
Ok(RuleSet {
contain_rules: parse_result,
})
}
}
fn parse_rules(input0: &str) -> nom::IResult<&str, HashMap<String, Vec<Rule>>> {
fold_many1(parse_rule, HashMap::new(), |mut acc, (key, value)| {
acc.insert(key, value);
acc
})(input0)
}
fn parse_rule(input0: &str) -> nom::IResult<&str, (String, Vec<Rule>)> {
let (input1, _) = multispace0(input0)?;
let (input2, key_color) = parse_color(input1)?;
let (input3, _) = multispace1(input2)?;
let (input4, _) = tag("bags")(input3)?;
let (input5, _) = multispace1(input4)?;
let (input6, _) = tag("contain")(input5)?;
let (input7, _) = multispace1(input6)?;
let (input8, rules) = parse_bag_set(input7)?;
let (input9, _) = multispace0(input8)?;
let (input10, _) = tag(".")(input9)?;
Ok((input10, (key_color, rules)))
}
fn parse_color(input0: &str) -> nom::IResult<&str, String> {
let (input1, _) = multispace0(input0)?;
let (input2, word1) = alphanumeric1(input1)?;
let (input3, _) = multispace1(input2)?;
let (input4, word2) = alphanumeric1(input3)?;
Ok((input4, format!("{} {}", word1, word2)))
}
fn parse_bag_set(input0: &str) -> nom::IResult<&str, Vec<Rule>> {
let (input1, _) = multispace0(input0)?;
let (input2, list) = alt((parse_no_rules, parse_rule_list))(input1)?;
Ok((input2, list))
}
fn parse_no_rules(input0: &str) -> nom::IResult<&str, Vec<Rule>> {
let (input1, _) = multispace0(input0)?;
let (input2, _) = tag("no")(input1)?;
let (input3, _) = multispace1(input2)?;
let (input4, _) = tag("other")(input3)?;
let (input5, _) = multispace1(input4)?;
let (input6, _) = tag("bags")(input5)?;
Ok((input6, Vec::new()))
}
fn parse_rule_list(input0: &str) -> nom::IResult<&str, Vec<Rule>> {
let (input1, _) = multispace0(input0)?;
let (input2, list) =
separated_list1(preceded(char(','), multispace1), parse_rule_item)(input1)?;
Ok((input2, list))
}
fn parse_rule_item(input0: &str) -> nom::IResult<&str, Rule> {
let (input1, _) = multispace0(input0)?;
let (input2, number_string) = digit1(input1)?;
let (input3, _) = multispace1(input2)?;
let (input4, bag) = parse_color(input3)?;
let (input5, _) = multispace1(input4)?;
let count = usize::from_str(number_string).map_err(|_| {
nom::Err::Error(nom::error::Error {
input: input2,
code: nom::error::ErrorKind::Digit,
})
})?;
let (input6, _) = if count == 1 {
tag("bag")(input5)?
} else {
tag("bags")(input5)?
};
Ok((input6, Rule { count, bag }))
}
fn real_main() -> Result<(), TopLevelError> {
let mut rules = RuleSet::empty();
for argument in env::args().skip(1) {
let contents = fs::read_to_string(argument)?;
let mut this_one = RuleSet::from_str(&contents)?;
rules.merge(&mut this_one);
}
rules.pretty_print();
let mut count = 0;
for color in rules.contain_rules.keys() {
if color != "shiny gold" && rules.can_reach(color, "shiny gold") {
println!("I can get to shiny gold from {}", color);
count += 1;
}
}
println!("Can reach shiny gold from {} colors.", count);
println!(
"A single shiny gold bag contains {} colors.",
rules.bags_required("shiny gold") - 1
);
Ok(())
}
fn main() {
if let Err(e) = real_main() {
eprintln!("ERROR: {}", e);
}
}

View File

@@ -19,6 +19,7 @@ pub enum TopLevelError {
UnknownError,
PassportParseError(PassportParseError),
SeatParseError(SeatParseError),
BaggageParseError(BaggageRuleParseError),
}
impl fmt::Display for TopLevelError {
@@ -29,6 +30,7 @@ impl fmt::Display for TopLevelError {
TopLevelError::NoSolutionFound => write!(f, "No solution found."),
TopLevelError::PassportParseError(p) => write!(f, "Error parsing passport: {}", p),
TopLevelError::SeatParseError(s) => write!(f, "Error parsing seat: {}", s),
TopLevelError::BaggageParseError(e) => write!(f, "Error parsing baggage rule: {}", e),
TopLevelError::UnknownError => {
write!(f, "Unknown error occurred; this shouldn't be possible.")
}
@@ -110,13 +112,13 @@ impl fmt::Display for SeatParseError {
}
SeatParseError::BadSeatRowSectionSize(x) => write!(
f,
"Bad identifiers for rows; expected {} characters, got {}",
7, x
"Bad identifiers for rows; expected 7 characters, got {}",
x
),
SeatParseError::BadSeatColumnSectionSize(x) => write!(
f,
"Bad identifiers for columns; expected {} characters, got {}",
3, x
"Bad identifiers for columns; expected 3 characters, got {}",
x
),
SeatParseError::UnexpectedRowCharacter(c) => {
write!(f, "Unexpected character when parsing rows: {:?}", c)
@@ -135,3 +137,34 @@ impl fmt::Display for SeatParseError {
}
convert_error!(SeatParseError, TopLevelError, SeatParseError);
pub enum BaggageRuleParseError {
NomError(String),
NumericConversionError(ParseIntError),
}
impl fmt::Display for BaggageRuleParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BaggageRuleParseError::NomError(e) => write!(f, "General parsing error: {}", e),
BaggageRuleParseError::NumericConversionError(e) => {
write!(f, "Error converting number to value: {}", e)
}
}
}
}
convert_error!(BaggageRuleParseError, TopLevelError, BaggageParseError);
convert_error!(ParseIntError, BaggageRuleParseError, NumericConversionError);
impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for BaggageRuleParseError {
fn from(x: nom::Err<nom::error::Error<&'a str>>) -> BaggageRuleParseError {
match x {
nom::Err::Incomplete(x) => {
BaggageRuleParseError::NomError(format!("Incomplete data stream (need: {:?})", x))
}
nom::Err::Error(e) => BaggageRuleParseError::NomError(e.to_string()),
nom::Err::Failure(e) => BaggageRuleParseError::NomError(e.to_string()),
}
}
}