switch to nom
This commit is contained in:
parent
a5f19ecae1
commit
b00835c25e
7 changed files with 288 additions and 181 deletions
|
|
@ -6,9 +6,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
const_format = "0.2.31"
|
||||
itertools = "0.11"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1.18.0"
|
||||
regex = "1.7"
|
||||
thiserror = "1.0"
|
||||
nom = "7"
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ pub mod direction;
|
|||
pub mod file;
|
||||
pub mod helper;
|
||||
pub mod math;
|
||||
pub mod parser;
|
||||
pub mod pos;
|
||||
pub mod turn;
|
||||
|
|
|
|||
70
src/common/parser.rs
Normal file
70
src/common/parser.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{line_ending, space0, space1},
|
||||
combinator::{eof, opt, value},
|
||||
error::ParseError,
|
||||
multi::many0,
|
||||
sequence::{delimited, preceded, terminated, tuple},
|
||||
Err, IResult, Parser,
|
||||
};
|
||||
|
||||
pub fn extract_result<I, F, O, E: ParseError<I>>(
|
||||
mut parser: F,
|
||||
) -> impl FnMut(I) -> Result<O, Err<E>>
|
||||
where
|
||||
F: FnMut(I) -> IResult<I, O, E>,
|
||||
{
|
||||
move |input: I| parser(input).map(|(_, value)| value)
|
||||
}
|
||||
|
||||
pub fn ignore<I, F, O, E: ParseError<I>>(mut parser: F) -> impl FnMut(I) -> Result<I, Err<E>>
|
||||
where
|
||||
F: FnMut(I) -> IResult<I, O, E>,
|
||||
{
|
||||
move |input: I| parser(input).map(|(i, _)| i)
|
||||
}
|
||||
|
||||
pub fn eol_terminated<'a, F, O, E: ParseError<&'a str>>(
|
||||
line: F,
|
||||
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
|
||||
where
|
||||
F: Parser<&'a str, O, E>,
|
||||
{
|
||||
terminated(line, alt((line_ending, eof)))
|
||||
}
|
||||
|
||||
pub fn true_false<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> {
|
||||
alt((value(true, tag("true")), value(false, tag("false"))))(input)
|
||||
}
|
||||
|
||||
pub fn empty_lines<'a, E: ParseError<&'a str>>(input: &'a str) -> Result<&'a str, Err<E>> {
|
||||
ignore(tuple((many0(line_ending), opt(eof))))(input)
|
||||
}
|
||||
|
||||
pub fn trim_left1<'a, F, O, E: ParseError<&'a str>>(
|
||||
inner: F,
|
||||
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
|
||||
where
|
||||
F: Parser<&'a str, O, E>,
|
||||
{
|
||||
preceded(space1, inner)
|
||||
}
|
||||
|
||||
pub fn trim0<'a, F, O, E: ParseError<&'a str>>(
|
||||
inner: F,
|
||||
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
|
||||
where
|
||||
F: Parser<&'a str, O, E>,
|
||||
{
|
||||
delimited(space0, inner, space0)
|
||||
}
|
||||
|
||||
pub fn trim1<'a, F, O, E: ParseError<&'a str>>(
|
||||
inner: F,
|
||||
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
|
||||
where
|
||||
F: Parser<&'a str, O, E>,
|
||||
{
|
||||
delimited(space1, inner, space1)
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
|
|
@ -14,13 +16,13 @@ impl DayTrait for Day {
|
|||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let forest = Forest::try_from(lines)?;
|
||||
let forest: Forest = lines.parse()?;
|
||||
let result = forest.count_visible();
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let forest = Forest::try_from(lines)?;
|
||||
let forest: Forest = lines.parse()?;
|
||||
let result = forest.best_score();
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
|
@ -181,10 +183,10 @@ impl Forest {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Forest {
|
||||
type Error = ForestError;
|
||||
impl FromStr for Forest {
|
||||
type Err = ForestError;
|
||||
|
||||
fn try_from(lines: &str) -> Result<Self, Self::Error> {
|
||||
fn from_str(lines: &str) -> Result<Self, Self::Err> {
|
||||
split_lines(lines)
|
||||
.map(|line| {
|
||||
line.chars()
|
||||
|
|
@ -206,7 +208,7 @@ mod test {
|
|||
fn test_parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let forest = Forest::try_from(lines.as_str())?;
|
||||
let forest: Forest = lines.parse()?;
|
||||
assert_eq!(forest.width, 5);
|
||||
assert_eq!(forest.height, 5);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
use crate::common::file::split_lines;
|
||||
use crate::common::parser::{
|
||||
empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false,
|
||||
};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::{iter::zip, num::ParseIntError};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{char, i64, u32},
|
||||
combinator::{map, value, verify},
|
||||
error::Error,
|
||||
multi::{many1, separated_list1},
|
||||
sequence::{preceded, tuple},
|
||||
Err, IResult, Parser,
|
||||
};
|
||||
use std::{iter::zip, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 11;
|
||||
|
|
@ -16,50 +26,50 @@ impl DayTrait for Day {
|
|||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let troop = Troop::try_from(lines)?;
|
||||
let troop: Troop = lines.parse()?;
|
||||
Ok(ResultType::Integer(troop.play(20)))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let troop = Troop::try_from(lines)?;
|
||||
let troop: Troop = lines.parse()?;
|
||||
Ok(ResultType::Integer(troop.play_again(10_000)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum MonkeyError {
|
||||
#[error("Not an Integer")]
|
||||
NotAnInteger(#[from] ParseIntError),
|
||||
|
||||
#[error("Can't parse line: {0}")]
|
||||
UnknownLine(String),
|
||||
|
||||
#[error("Did not expect end of Input")]
|
||||
PrematureEndOfInput,
|
||||
#[error("Error while parsing: {0}")]
|
||||
ParsingError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
impl From<Err<Error<&str>>> for MonkeyError {
|
||||
fn from(value: Err<Error<&str>>) -> Self {
|
||||
MonkeyError::ParsingError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
enum Operation {
|
||||
Plus(i64),
|
||||
Times(i64),
|
||||
Squared,
|
||||
Times(i64),
|
||||
Plus(i64),
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn parse(line: &str) -> Operation {
|
||||
match line.split_whitespace().collect::<Vec<_>>()[..] {
|
||||
["*", "old"] => Operation::Squared,
|
||||
["*", value] => Operation::Times(value.parse().unwrap()),
|
||||
["+", value] => Operation::Plus(value.parse().unwrap()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
fn parse(input: &str) -> IResult<&str, Self> {
|
||||
let (input, _) = tag("new = old ")(input)?;
|
||||
alt((
|
||||
value(Operation::Squared, tag("* old")),
|
||||
preceded(char('*'), trim0(i64.map(|a| Operation::Times(a)))),
|
||||
preceded(char('+'), trim0(i64.map(|a| Operation::Plus(a)))),
|
||||
))(input)
|
||||
}
|
||||
|
||||
pub fn calc(&self, old: i64) -> i64 {
|
||||
match self {
|
||||
Operation::Plus(value) => old + *value,
|
||||
Operation::Times(value) => old * value,
|
||||
Operation::Squared => old.pow(2),
|
||||
Operation::Times(value) => old * value,
|
||||
Operation::Plus(value) => old + value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,90 +84,96 @@ struct Monkey {
|
|||
bad_monkey: usize,
|
||||
}
|
||||
|
||||
static MONKEY: Lazy<Regex> = Lazy::new(|| Regex::new(r"Monkey (\d+)").unwrap());
|
||||
static STARTING: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"Starting items: (\d+(?:, \d+)*)").unwrap());
|
||||
static OP: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"Operation: new = old ([+*] \d+|\* old)").unwrap());
|
||||
static TEST: Lazy<Regex> = Lazy::new(|| Regex::new(r"Test: divisible by (\d+)").unwrap());
|
||||
static NEXT: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"If (?:true|false): throw to monkey (\d+)").unwrap());
|
||||
|
||||
impl Monkey {
|
||||
fn parse_line<'a>(re: &Regex, line: &'a str) -> Result<&'a str, MonkeyError> {
|
||||
let caps = re
|
||||
.captures(line)
|
||||
.ok_or(MonkeyError::UnknownLine(line.to_owned()))?;
|
||||
Ok(caps.get(1).unwrap().as_str())
|
||||
fn number_parse(input: &str) -> IResult<&str, usize> {
|
||||
let input = ignore(tag("Monkey "))(input)?;
|
||||
let (input, number) = trim0(u32.map(|val| val as usize))(input)?;
|
||||
let input = ignore(char(':'))(input)?;
|
||||
|
||||
Ok((input, number))
|
||||
}
|
||||
|
||||
pub fn parse_one(
|
||||
iterator: &mut dyn Iterator<Item = &str>,
|
||||
) -> Result<Option<Monkey>, MonkeyError> {
|
||||
if let Some(line) = iterator.next() {
|
||||
let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?;
|
||||
fn starting_items_parse(input: &str) -> IResult<&str, Vec<i64>> {
|
||||
let input = ignore(tag("Starting items:"))(input)?;
|
||||
separated_list1(char(','), trim0(i64))(input)
|
||||
}
|
||||
|
||||
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
|
||||
let start = Monkey::parse_line(&STARTING, line)?;
|
||||
let items = start
|
||||
.split(", ")
|
||||
.map(|item| item.parse::<i64>())
|
||||
.collect::<Result<Vec<i64>, _>>()?;
|
||||
fn operation_parse(input: &str) -> IResult<&str, Operation> {
|
||||
let input = ignore(tag("Operation:"))(input)?;
|
||||
trim0(Operation::parse)(input)
|
||||
}
|
||||
|
||||
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
|
||||
let operation = Operation::parse(Monkey::parse_line(&OP, line)?);
|
||||
fn divisor_parse(input: &str) -> IResult<&str, i64> {
|
||||
let input = ignore(tag("Test: divisible by"))(input)?;
|
||||
trim0(i64)(input)
|
||||
}
|
||||
|
||||
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
|
||||
let divisor: i64 = Monkey::parse_line(&TEST, line)?.parse()?;
|
||||
fn target_parse(want_good: bool) -> impl FnMut(&str) -> IResult<&str, usize> {
|
||||
move |input: &str| {
|
||||
let input = ignore(tuple((
|
||||
tag("If"),
|
||||
trim_left1(verify(true_false, |is_good| *is_good == want_good)),
|
||||
tag(": throw to monkey"),
|
||||
)))(input)?;
|
||||
trim0(u32.map(|val| val as usize))(input)
|
||||
}
|
||||
}
|
||||
|
||||
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
|
||||
let good_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?;
|
||||
fn parse(input: &str) -> IResult<&str, Monkey> {
|
||||
let input = empty_lines(input)?;
|
||||
let (input, number) = eol_terminated(trim0(Monkey::number_parse))(input)?;
|
||||
let (input, items) = eol_terminated(trim0(Monkey::starting_items_parse))(input)?;
|
||||
let (input, operation) = eol_terminated(trim0(Monkey::operation_parse))(input)?;
|
||||
let (input, divisor) = eol_terminated(trim0(Monkey::divisor_parse))(input)?;
|
||||
let (input, good_monkey) = eol_terminated(trim0(Monkey::target_parse(true)))(input)?;
|
||||
let (input, bad_monkey) = eol_terminated(trim0(Monkey::target_parse(false)))(input)?;
|
||||
|
||||
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
|
||||
let bad_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?;
|
||||
|
||||
iterator.next();
|
||||
|
||||
Ok(Some(Monkey {
|
||||
Ok((
|
||||
input,
|
||||
Monkey {
|
||||
number,
|
||||
items,
|
||||
operation,
|
||||
divisor,
|
||||
good_monkey,
|
||||
bad_monkey,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn process(&self, value: i64) -> i64 {
|
||||
self.operation.calc(value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check(&self, value: i64) -> bool {
|
||||
value % self.divisor == 0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_one(lines: &str) -> Result<Monkey, MonkeyError> {
|
||||
Ok(extract_result(Monkey::parse)(lines)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct Troop {
|
||||
monkeys: Vec<Monkey>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Troop {
|
||||
type Error = MonkeyError;
|
||||
impl FromStr for Troop {
|
||||
type Err = MonkeyError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let mut iter = split_lines(value);
|
||||
let mut monkeys = Vec::new();
|
||||
while let Some(monkey) = Monkey::parse_one(&mut iter)? {
|
||||
monkeys.push(monkey);
|
||||
}
|
||||
Ok(Troop { monkeys })
|
||||
fn from_str(lines: &str) -> Result<Self, Self::Err> {
|
||||
Ok(extract_result(Troop::parse)(lines)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Troop {
|
||||
fn parse(input: &str) -> IResult<&str, Troop> {
|
||||
map(many1(Monkey::parse), |monkeys| Troop { monkeys })(input)
|
||||
}
|
||||
|
||||
fn do_play<F>(&self, rounds: usize, alter: F) -> i64
|
||||
where
|
||||
F: Fn(i64) -> i64,
|
||||
|
|
@ -204,7 +220,6 @@ mod test {
|
|||
fn test_parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let mut lines = split_lines(&lines);
|
||||
let expected = Monkey {
|
||||
number: 0,
|
||||
items: vec![79, 98],
|
||||
|
|
@ -213,7 +228,7 @@ mod test {
|
|||
good_monkey: 2,
|
||||
bad_monkey: 3,
|
||||
};
|
||||
let result = Monkey::parse_one(&mut lines)?.unwrap();
|
||||
let result = Monkey::parse_one(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -224,7 +239,7 @@ mod test {
|
|||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = 4;
|
||||
let result = Troop::try_from(lines.as_str())?;
|
||||
let result: Troop = lines.parse()?;
|
||||
assert_eq!(result.monkeys.len(), expected);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,37 +1,44 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::{file::split_lines, pos::Pos};
|
||||
use crate::common::{
|
||||
parser::{eol_terminated, extract_result, ignore, trim0},
|
||||
pos::Pos,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use nom::{
|
||||
bytes::complete::tag,
|
||||
character::complete::{char, i64},
|
||||
error::Error,
|
||||
multi::many0,
|
||||
sequence::{separated_pair, tuple},
|
||||
Err, IResult,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 15;
|
||||
|
||||
pub struct Day;
|
||||
|
||||
fn parse_row(input: &str) -> IResult<&str, i64> {
|
||||
eol_terminated(i64)(input)
|
||||
}
|
||||
|
||||
impl DayTrait for Day {
|
||||
fn get_day_number(&self) -> usize {
|
||||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let lines = split_lines(lines).collect_vec();
|
||||
if lines.len() < 2 {
|
||||
Err(SensorError::ToFewLines)?;
|
||||
}
|
||||
let row = lines[0].parse()?;
|
||||
let (sensors, beacons) = Day::parse_all(&lines[1..])?;
|
||||
let (lines, row) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
|
||||
let (sensors, beacons) = Day::parse_all(lines)?;
|
||||
|
||||
let result = Day::count_coverage_at(&sensors, &beacons, row);
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let lines = split_lines(lines).collect_vec();
|
||||
if lines.len() < 2 {
|
||||
Err(SensorError::ToFewLines)?;
|
||||
}
|
||||
let (sensors, _) = Day::parse_all(&lines[1..])?;
|
||||
let (lines, _) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
|
||||
let (sensors, _) = Day::parse_all(lines)?;
|
||||
let mid_lines = Day::dividing_lines(&sensors);
|
||||
let points = mid_lines
|
||||
.iter()
|
||||
|
|
@ -51,11 +58,12 @@ impl DayTrait for Day {
|
|||
}
|
||||
|
||||
impl Day {
|
||||
fn parse_all(lines: &[&str]) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
|
||||
fn parse_all(lines: &str) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
|
||||
let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?;
|
||||
|
||||
let mut sensors = HashSet::new();
|
||||
let mut beacons = HashSet::new();
|
||||
for line in lines {
|
||||
let (sensor, beacon) = Sensor::parse(line)?;
|
||||
for (sensor, beacon) in data {
|
||||
sensors.insert(sensor);
|
||||
beacons.insert(beacon);
|
||||
}
|
||||
|
|
@ -91,17 +99,20 @@ impl Day {
|
|||
|
||||
#[derive(Debug, Error)]
|
||||
enum SensorError {
|
||||
#[error("Not an Integer")]
|
||||
NotAnInt(#[from] ParseIntError),
|
||||
|
||||
#[error("Unknown line: {0}")]
|
||||
UnknownLine(String),
|
||||
#[error("Error Parsing Input: {0}")]
|
||||
ParsingError(String),
|
||||
|
||||
#[error("Did not find exactly one point for the sensor: {0}")]
|
||||
NotExactlyOneResult(usize),
|
||||
|
||||
#[error("Too few lines in input.txt")]
|
||||
ToFewLines,
|
||||
#[error("Row line not found at start of Input")]
|
||||
RowLineNotFound,
|
||||
}
|
||||
|
||||
impl From<Err<Error<&str>>> for SensorError {
|
||||
fn from(error: Err<Error<&str>>) -> Self {
|
||||
SensorError::ParsingError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -220,28 +231,30 @@ struct Sensor {
|
|||
radius: i64,
|
||||
}
|
||||
|
||||
static SENSOR: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap());
|
||||
|
||||
impl Sensor {
|
||||
pub fn parse(line: &str) -> Result<(Sensor, Pos<i64>), SensorError> {
|
||||
let caps = SENSOR
|
||||
.captures(line)
|
||||
.ok_or(SensorError::UnknownLine(line.to_owned()))?;
|
||||
let sensor_x = caps.get(1).unwrap().as_str().parse()?;
|
||||
let sensor_y = caps.get(2).unwrap().as_str().parse()?;
|
||||
let beacon_x = caps.get(3).unwrap().as_str().parse()?;
|
||||
let beacon_y = caps.get(4).unwrap().as_str().parse()?;
|
||||
let sensor = Pos::new(sensor_x, sensor_y);
|
||||
let beacon = Pos::new(beacon_x, beacon_y);
|
||||
let radius = sensor.taxicab(&beacon);
|
||||
Ok((
|
||||
Sensor {
|
||||
pos: sensor,
|
||||
radius,
|
||||
},
|
||||
beacon,
|
||||
))
|
||||
fn component<'a>(name: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, i64> {
|
||||
move |input: &str| {
|
||||
let input = ignore(tuple((tag(name), char('='))))(input)?;
|
||||
trim0(i64)(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pos(input: &str) -> IResult<&str, Pos<i64>> {
|
||||
let (input, (x, y)) = separated_pair(
|
||||
Sensor::component("x"),
|
||||
trim0(char(',')),
|
||||
Sensor::component("y"),
|
||||
)(input)?;
|
||||
Ok((input, Pos::new(x, y)))
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos<i64>)> {
|
||||
let input = ignore(tag("Sensor at"))(input)?;
|
||||
let (input, pos) = trim0(Sensor::parse_pos)(input)?;
|
||||
let input = ignore(tag(": closest beacon is at"))(input)?;
|
||||
let (input, beacon) = trim0(Sensor::parse_pos)(input)?;
|
||||
let radius = pos.taxicab(&beacon);
|
||||
Ok((input, (Sensor { pos, radius }, beacon)))
|
||||
}
|
||||
|
||||
pub fn range_at(&self, y: i64) -> Option<Range> {
|
||||
|
|
@ -279,7 +292,7 @@ mod test {
|
|||
},
|
||||
Pos::new(-2, 15),
|
||||
);
|
||||
let result = Sensor::parse(input)?;
|
||||
let result = extract_result(Sensor::parse)(input)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1};
|
||||
use itertools::Itertools;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{alpha1, i64},
|
||||
error::Error,
|
||||
multi::{many1, separated_list1},
|
||||
Err, IResult,
|
||||
};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::iter::Sum;
|
||||
use std::ops::{Add, Deref, Mul, Sub};
|
||||
use std::{collections::BinaryHeap, num::ParseIntError};
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use const_format::concatcp;
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 16;
|
||||
|
|
@ -129,11 +132,8 @@ impl Deref for Index {
|
|||
|
||||
#[derive(Debug, Error)]
|
||||
enum ValveError {
|
||||
#[error("Not an Integer")]
|
||||
NotAnInt(#[from] ParseIntError),
|
||||
|
||||
#[error("Not a valid description: {0}")]
|
||||
CouldNotParse(String),
|
||||
ParsingError(String),
|
||||
|
||||
#[error("Not a valid valve: {0}")]
|
||||
ValveNotFound(String),
|
||||
|
|
@ -142,17 +142,11 @@ enum ValveError {
|
|||
NoOptimumFound,
|
||||
}
|
||||
|
||||
const ID: &str = "[[:alpha:]]+";
|
||||
const COMMON: &str = concatcp!("^Valve (?<id>", ID, r") has flow rate=(?<rate>\d+); ");
|
||||
const PLURAL_STR: &str = concatcp!(
|
||||
COMMON,
|
||||
"tunnels lead to valves (?<exits>",
|
||||
ID,
|
||||
"(?:, ",
|
||||
ID,
|
||||
")+)$"
|
||||
);
|
||||
const SINGULAR_STR: &str = concatcp!(COMMON, "tunnel leads to valve (?<exits>", ID, ")$");
|
||||
impl From<Err<Error<&str>>> for ValveError {
|
||||
fn from(error: Err<Error<&str>>) -> Self {
|
||||
ValveError::ParsingError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct RawValve<'a> {
|
||||
|
|
@ -170,29 +164,45 @@ impl<'a> RawValve<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn from_regex(regex: &Regex, line: &'a str) -> Option<Result<RawValve<'a>, ValveError>> {
|
||||
regex.captures(line).map(|caps| {
|
||||
match (caps.name("id"), caps.name("rate"), caps.name("exits")) {
|
||||
(Some(id), Some(rate), Some(exits)) => {
|
||||
let rate = rate.as_str().parse()?;
|
||||
let neighbors = exits.as_str().split(",").map(|s| s.trim_start()).collect();
|
||||
Ok(RawValve::create(id.as_str(), Flow(rate), neighbors))
|
||||
}
|
||||
_ => Err(ValveError::CouldNotParse(line.to_string())),
|
||||
}
|
||||
})
|
||||
fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> {
|
||||
let input = ignore(tag("Valve"))(input)?;
|
||||
let (input, valve) = trim1(alpha1)(input)?;
|
||||
let input = ignore(tag("has flow rate="))(input)?;
|
||||
let (input, flow) = trim0(i64)(input)?;
|
||||
let input = ignore(tag(";"))(input)?;
|
||||
Ok((input, (valve, Flow(flow))))
|
||||
}
|
||||
|
||||
fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> {
|
||||
let input = ignore(tag("tunnel leads to valve "))(input)?;
|
||||
let (input, valve) = alpha1(input)?;
|
||||
|
||||
Ok((input, vec![valve]))
|
||||
}
|
||||
|
||||
fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> {
|
||||
let input = ignore(tag("tunnels lead to valves "))(input)?;
|
||||
let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?;
|
||||
|
||||
Ok((input, valve))
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> IResult<&str, RawValve> {
|
||||
let (input, (id, flow)) = RawValve::parse_start(input)?;
|
||||
let (input, neighbors) = trim_left1(eol_terminated(alt((
|
||||
RawValve::parse_plural,
|
||||
RawValve::parse_singular,
|
||||
))))(input)?;
|
||||
|
||||
Ok((input, RawValve::create(id, flow, neighbors)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for RawValve<'a> {
|
||||
type Error = ValveError;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<RawValve<'a>, Self::Error> {
|
||||
static PLURAL: Lazy<Regex> = Lazy::new(|| Regex::new(PLURAL_STR).unwrap());
|
||||
static SINGULAR: Lazy<Regex> = Lazy::new(|| Regex::new(SINGULAR_STR).unwrap());
|
||||
RawValve::from_regex(&PLURAL, value)
|
||||
.or_else(|| RawValve::from_regex(&SINGULAR, value))
|
||||
.unwrap_or_else(|| Err(ValveError::CouldNotParse(value.to_string())))
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
Ok(extract_result(RawValve::parse)(value)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,9 +277,7 @@ struct ValveSystem {
|
|||
|
||||
impl ValveSystem {
|
||||
fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> {
|
||||
let mut raw = split_lines(lines)
|
||||
.map(|line| RawValve::try_from(line))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut raw = extract_result(many1(RawValve::parse))(lines)?;
|
||||
raw.sort_unstable_by_key(|valve| valve.id);
|
||||
|
||||
let start_idx = raw
|
||||
|
|
@ -931,7 +939,7 @@ mod test {
|
|||
fn parse_plural() -> Result<()> {
|
||||
let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA";
|
||||
let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]);
|
||||
let result = RawValve::try_from(line)?;
|
||||
let result = line.try_into()?;
|
||||
assert_eq!(expected, result);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -941,7 +949,7 @@ mod test {
|
|||
fn parse_singular() -> Result<()> {
|
||||
let line = "Valve HH has flow rate=22; tunnel leads to valve GG";
|
||||
let expected = RawValve::create("HH", Flow(22), vec!["GG"]);
|
||||
let result = RawValve::try_from(line)?;
|
||||
let result = line.try_into()?;
|
||||
assert_eq!(expected, result);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue