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