Compare commits
3 commits
7f5b6e03f9
...
062ede1df1
| Author | SHA1 | Date | |
|---|---|---|---|
| 062ede1df1 | |||
| b00835c25e | |||
| a5f19ecae1 |
29 changed files with 1261 additions and 422 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"
|
||||
|
|
|
|||
11
data/day19/example01.txt
Normal file
11
data/day19/example01.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
Blueprint 1:
|
||||
Each ore robot costs 4 ore.
|
||||
Each clay robot costs 2 ore.
|
||||
Each obsidian robot costs 3 ore and 14 clay.
|
||||
Each geode robot costs 2 ore and 7 obsidian.
|
||||
|
||||
Blueprint 2:
|
||||
Each ore robot costs 2 ore.
|
||||
Each clay robot costs 3 ore.
|
||||
Each obsidian robot costs 3 ore and 8 clay.
|
||||
Each geode robot costs 3 ore and 12 obsidian.
|
||||
30
data/day19/input.txt
Normal file
30
data/day19/input.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 4 ore and 11 obsidian.
|
||||
Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||
Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 10 obsidian.
|
||||
Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian.
|
||||
Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 4 ore and 20 obsidian.
|
||||
Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 2 ore and 18 obsidian.
|
||||
Blueprint 7: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 20 obsidian.
|
||||
Blueprint 8: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 4 ore and 17 obsidian.
|
||||
Blueprint 9: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 4 ore and 7 obsidian.
|
||||
Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian.
|
||||
Blueprint 11: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian.
|
||||
Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 18 obsidian.
|
||||
Blueprint 13: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 2 ore and 20 obsidian.
|
||||
Blueprint 14: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 14 obsidian.
|
||||
Blueprint 15: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 16 obsidian.
|
||||
Blueprint 16: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 3 ore and 19 obsidian.
|
||||
Blueprint 17: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 19 obsidian.
|
||||
Blueprint 18: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian.
|
||||
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 3 ore and 10 obsidian.
|
||||
Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 12 obsidian.
|
||||
Blueprint 21: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 17 obsidian.
|
||||
Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||
Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 3 ore and 9 obsidian.
|
||||
Blueprint 24: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 2 ore and 10 obsidian.
|
||||
Blueprint 25: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 8 obsidian.
|
||||
Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 17 obsidian.
|
||||
Blueprint 27: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 8 obsidian.
|
||||
Blueprint 28: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||
Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 13 obsidian.
|
||||
Blueprint 30: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 4 ore and 11 obsidian.
|
||||
|
|
@ -5,13 +5,16 @@ fn format_path(day_num: usize, file: &str) -> String {
|
|||
format!("data/day{day_num:02}/{file}")
|
||||
}
|
||||
|
||||
pub fn read_lines(day_num: usize, file: &str) -> io::Result<Vec<String>> {
|
||||
Ok(fs::read_to_string(format_path(day_num, file))?
|
||||
pub fn read_string(day_num: usize, file: &str) -> io::Result<String> {
|
||||
Ok(fs::read_to_string(format_path(day_num, file))?)
|
||||
}
|
||||
|
||||
pub fn split_lines<'a>(lines: &'a str) -> impl Iterator<Item = &'a str> + 'a {
|
||||
lines
|
||||
.split('\n')
|
||||
.with_position()
|
||||
.filter_map(|(pos, line)| match pos {
|
||||
itertools::Position::Last if line.is_empty() => None,
|
||||
_ => Some(line.to_owned()),
|
||||
_ => Some(line),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ use std::num::ParseIntError;
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
|
||||
pub struct Day;
|
||||
|
|
@ -13,13 +15,15 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let lines = split_lines(lines);
|
||||
let vector = Day::parse(lines)?;
|
||||
let max = vector.iter().max().ok_or(CalorieError::Empty)?;
|
||||
Ok(ResultType::Integer(*max))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let lines = split_lines(lines);
|
||||
let vector = Day::parse(lines)?;
|
||||
let sum = vector.iter().sorted_by(|a, b| Ord::cmp(b, a)).take(3).sum();
|
||||
Ok(ResultType::Integer(sum))
|
||||
|
|
@ -27,9 +31,8 @@ impl DayTrait for Day {
|
|||
}
|
||||
|
||||
impl Day {
|
||||
fn parse(lines: &[String]) -> Result<Vec<i64>, CalorieError> {
|
||||
fn parse<'a>(lines: impl Iterator<Item = &'a str>) -> Result<Vec<i64>, CalorieError> {
|
||||
Ok(lines
|
||||
.iter()
|
||||
.batching(|it| {
|
||||
let result = it
|
||||
.take_while(|line| !line.is_empty())
|
||||
|
|
@ -57,13 +60,13 @@ pub enum CalorieError {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(24_000);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -74,7 +77,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(45_000);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -84,9 +87,10 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
let lines = read_lines(DAY_NUMBER, "example01.txt")?;
|
||||
let lines = read_string(DAY_NUMBER, "example01.txt")?;
|
||||
let lines = split_lines(&lines);
|
||||
let expected = vec![6000, 4000, 11000, 24000, 10000];
|
||||
let result = Day::parse(&lines)?;
|
||||
let result = Day::parse(lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -12,9 +14,8 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.map(|line| Rps::parse_line(line))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
|
|
@ -24,9 +25,8 @@ impl DayTrait for Day {
|
|||
Ok(ResultType::Integer(sum))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.map(|line| Strategy::parse_line(line))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
|
|
@ -155,7 +155,7 @@ impl TryFrom<&str> for Strategy {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -182,7 +182,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(15);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -214,7 +214,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(12);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
|
@ -11,9 +13,8 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.map(|line| find_double(line))
|
||||
.map_ok(priority)
|
||||
.flatten_ok()
|
||||
|
|
@ -21,12 +22,11 @@ impl DayTrait for Day {
|
|||
Ok(ResultType::Integer(sum))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.chunks(3)
|
||||
.into_iter()
|
||||
.map(|chunk| find_badge(&chunk.collect::<Vec<_>>()))
|
||||
.map(|chunk| find_badge(&chunk.collect_vec()))
|
||||
.map_ok(priority)
|
||||
.flatten_ok()
|
||||
.fold_ok(0, |a, b| a + b)?;
|
||||
|
|
@ -71,7 +71,7 @@ fn find_double(content: &str) -> Result<char, RucksackError> {
|
|||
Err(RucksackError::NoDoubleFound)?
|
||||
}
|
||||
|
||||
fn find_badge(elves: &[&String]) -> Result<char, RucksackError> {
|
||||
fn find_badge(elves: &[&str]) -> Result<char, RucksackError> {
|
||||
if elves.len() < 2 {
|
||||
return Err(RucksackError::NeedAtLeastTwo);
|
||||
};
|
||||
|
|
@ -92,7 +92,7 @@ fn find_badge(elves: &[&String]) -> Result<char, RucksackError> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -108,7 +108,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(157);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -119,7 +119,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(70);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
use std::num::ParseIntError;
|
||||
|
|
@ -12,18 +14,16 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.map(|line| parse(line))
|
||||
.filter_ok(fully_contained)
|
||||
.fold_ok(0i64, |a, _| a + 1)?;
|
||||
Ok(ResultType::Integer(sum))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let sum = lines
|
||||
.iter()
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let sum = split_lines(lines)
|
||||
.map(|line| parse(line))
|
||||
.filter_ok(overlaps)
|
||||
.fold_ok(0i64, |a, _| a + 1)?;
|
||||
|
|
@ -77,7 +77,7 @@ fn overlaps((first, second): &(Range, Range)) -> bool {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -93,7 +93,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(2);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -104,7 +104,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(4);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
use std::num::ParseIntError;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -11,7 +14,7 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let (mut stacks, commands) = parse(lines)?;
|
||||
for command in commands {
|
||||
command.with_turn(&mut stacks)?;
|
||||
|
|
@ -19,7 +22,7 @@ impl DayTrait for Day {
|
|||
Ok(ResultType::String(get_word(&stacks)))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let (mut stacks, commands) = parse(lines)?;
|
||||
for command in commands {
|
||||
command.without_turn(&mut stacks)?;
|
||||
|
|
@ -57,11 +60,13 @@ fn parse_crate(line: &str) -> Vec<Option<char>> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn parse_all_crates(line_it: &mut dyn Iterator<Item = &String>) -> Result<Vec<String>, CrateError> {
|
||||
let rows: Vec<_> = line_it
|
||||
fn parse_all_crates<'a>(
|
||||
line_it: &mut dyn Iterator<Item = &'a str>,
|
||||
) -> Result<Vec<String>, CrateError> {
|
||||
let rows = line_it
|
||||
.take_while(|line| !line.is_empty())
|
||||
.map(|row| parse_crate(row))
|
||||
.collect();
|
||||
.collect_vec();
|
||||
let Some(stacks_count) = rows.iter().map(|row| row.len()).max() else {
|
||||
return Err(CrateError::NoCargoStacksGiven);
|
||||
};
|
||||
|
|
@ -79,11 +84,11 @@ fn parse_all_crates(line_it: &mut dyn Iterator<Item = &String>) -> Result<Vec<St
|
|||
Ok(stacks)
|
||||
}
|
||||
|
||||
fn parse(line: &[String]) -> Result<(Vec<String>, Vec<Move>), CrateError> {
|
||||
let mut iter = line.iter();
|
||||
fn parse<'a>(lines: &'a str) -> Result<(Vec<String>, Vec<Move>), CrateError> {
|
||||
let mut iter = split_lines(lines);
|
||||
let stacks = parse_all_crates(&mut iter)?;
|
||||
let moves: Vec<_> = iter
|
||||
.map(|command| Move::try_from(command.as_str()))
|
||||
.map(|command| Move::try_from(command))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok((stacks, moves))
|
||||
}
|
||||
|
|
@ -167,7 +172,7 @@ impl TryFrom<&str> for Move {
|
|||
mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -191,14 +196,15 @@ mod test {
|
|||
#[test]
|
||||
fn test_parse_all_crates() -> Result<()> {
|
||||
let lines = vec![
|
||||
" [D] ".to_owned(),
|
||||
"[N] [C] ".to_owned(),
|
||||
"[Z] [M] [P]".to_owned(),
|
||||
" 1 2 3 ".to_owned(),
|
||||
"".to_owned(),
|
||||
"move 1 from to 2 1".to_owned(),
|
||||
];
|
||||
let mut input = lines.iter();
|
||||
" [D] ",
|
||||
"[N] [C] ",
|
||||
"[Z] [M] [P]",
|
||||
" 1 2 3 ",
|
||||
"",
|
||||
"move 1 from to 2 1",
|
||||
]
|
||||
.join("\n");
|
||||
let mut input = split_lines(&lines);
|
||||
let expected = vec!["NZ1".to_owned(), "DCM2".to_owned(), "P3".to_owned()];
|
||||
let result = parse_all_crates(&mut input)?;
|
||||
|
||||
|
|
@ -221,7 +227,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::String("CMZ".to_owned());
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -243,7 +249,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::String("MCD".to_owned());
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let count = find_marker(&lines[0], 4)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let count = find_marker(lines.trim_end(), 4)?;
|
||||
Ok(ResultType::Integer(count as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let count = find_marker(&lines[0], 14)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let count = find_marker(lines.trim_end(), 14)?;
|
||||
Ok(ResultType::Integer(count as i64))
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ fn find_marker(word: &str, marker_length: usize) -> Result<usize, MarkerError> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -98,7 +98,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(10);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -109,7 +109,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(29);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use std::{
|
|||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -16,7 +18,8 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let lines = split_lines(lines);
|
||||
let root = Directory::parse(lines)?;
|
||||
let result = root
|
||||
.iter()
|
||||
|
|
@ -32,9 +35,10 @@ impl DayTrait for Day {
|
|||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
const AVAIL: i64 = 70_000_000;
|
||||
const REQUIRED: i64 = 30_000_000;
|
||||
let lines = split_lines(lines);
|
||||
let root = Directory::parse(lines)?;
|
||||
let to_free = REQUIRED - (AVAIL - root.size());
|
||||
let result = root
|
||||
|
|
@ -167,7 +171,7 @@ impl Directory {
|
|||
sub_node.map(|node| Directory::create(node.clone()))
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> Result<Directory, DirectoryError> {
|
||||
pub fn parse<'a>(lines: impl Iterator<Item = &'a str>) -> Result<Directory, DirectoryError> {
|
||||
let root = Directory::root();
|
||||
let mut current = root.clone();
|
||||
for line in lines {
|
||||
|
|
@ -269,7 +273,7 @@ impl Iterator for DirectoryIterator {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -298,8 +302,9 @@ mod test {
|
|||
#[test]
|
||||
fn test_total_size() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let dir = Directory::parse(&lines)?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let lines = split_lines(&lines);
|
||||
let dir = Directory::parse(lines)?;
|
||||
assert_eq!(dir.size(), 48381165);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -308,9 +313,10 @@ mod test {
|
|||
#[test]
|
||||
fn test_iterator() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let lines = split_lines(&lines);
|
||||
let expected = vec!["/", "a", "e", "d"];
|
||||
let dir = Directory::parse(&lines)?;
|
||||
let dir = Directory::parse(lines)?;
|
||||
let result: Vec<_> = dir.iter().map(|dir| dir.name()).collect();
|
||||
assert_eq!(result, expected);
|
||||
|
||||
|
|
@ -320,7 +326,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(95437);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -331,7 +337,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(24933642);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::{iproduct, FoldWhile, Itertools};
|
||||
use thiserror::Error;
|
||||
|
|
@ -11,14 +15,14 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let forest = Forest::parse(lines)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let forest: Forest = lines.parse()?;
|
||||
let result = forest.count_visible();
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let forest = Forest::parse(lines)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let forest: Forest = lines.parse()?;
|
||||
let result = forest.best_score();
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
|
@ -67,18 +71,6 @@ impl Forest {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> Result<Forest, ForestError> {
|
||||
lines
|
||||
.iter()
|
||||
.map(|line| {
|
||||
line.chars()
|
||||
.map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.and_then(Forest::create)
|
||||
}
|
||||
|
||||
pub fn count_visible(&self) -> i64 {
|
||||
let mut vis_count = 2 * (self.width + self.height - 2) as i64;
|
||||
let mut visible = vec![vec![false; self.width - 2]; self.height - 2];
|
||||
|
|
@ -191,17 +183,32 @@ impl Forest {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for Forest {
|
||||
type Err = ForestError;
|
||||
|
||||
fn from_str(lines: &str) -> Result<Self, Self::Err> {
|
||||
split_lines(lines)
|
||||
.map(|line| {
|
||||
line.chars()
|
||||
.map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.and_then(Forest::create)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let forest = Forest::parse(&lines)?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let forest: Forest = lines.parse()?;
|
||||
assert_eq!(forest.width, 5);
|
||||
assert_eq!(forest.height, 5);
|
||||
|
||||
|
|
@ -211,7 +218,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(21);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -222,7 +229,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(8);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::{direction::Direction, pos::Pos};
|
||||
use crate::common::{direction::Direction, file::split_lines, pos::Pos};
|
||||
use itertools::Itertools;
|
||||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -12,14 +13,14 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let commands = parse_commands(lines)?;
|
||||
let mut rope = Rope::create(2);
|
||||
let result = rope.run(&commands);
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let commands = parse_commands(lines)?;
|
||||
let mut rope = Rope::create(10);
|
||||
let result = rope.run(&commands);
|
||||
|
|
@ -39,12 +40,12 @@ enum RopeError {
|
|||
StepParseError(#[from] ParseIntError),
|
||||
}
|
||||
|
||||
fn parse_commands(lines: &[String]) -> Result<Vec<(Direction, usize)>, RopeError> {
|
||||
lines.iter().map(parse_line).collect::<Result<_, _>>()
|
||||
fn parse_commands(lines: &str) -> Result<Vec<(Direction, usize)>, RopeError> {
|
||||
split_lines(lines).map(parse_line).try_collect()
|
||||
}
|
||||
|
||||
fn parse_line(line: &String) -> Result<(Direction, usize), RopeError> {
|
||||
match line.split_whitespace().collect::<Vec<_>>()[..] {
|
||||
fn parse_line(line: &str) -> Result<(Direction, usize), RopeError> {
|
||||
match line.split_whitespace().collect_vec()[..] {
|
||||
[direction, steps] => {
|
||||
let direction = match direction {
|
||||
"U" => Direction::North,
|
||||
|
|
@ -125,13 +126,13 @@ impl Rope {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(13);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -142,7 +143,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example02.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example02.txt")?;
|
||||
let expected = ResultType::Integer(36);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use std::{num::ParseIntError, slice::Iter};
|
||||
use thiserror::Error;
|
||||
|
|
@ -11,18 +13,16 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let instructions = lines
|
||||
.iter()
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let instructions = split_lines(lines)
|
||||
.map(|line| Instruction::parse(line))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let result = CpuCycles::signal_strength(&instructions, &[20, 60, 100, 140, 180, 220]);
|
||||
Ok(ResultType::Integer(result as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let instructions = lines
|
||||
.iter()
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let instructions = split_lines(lines)
|
||||
.map(|line| Instruction::parse(line))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let result = CpuCycles::draw(&instructions);
|
||||
|
|
@ -128,8 +128,9 @@ impl<'a> Iterator for CpuCycles<'a> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
#[test]
|
||||
fn test_simple() -> Result<()> {
|
||||
|
|
@ -144,7 +145,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(13140);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -155,8 +156,12 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Lines(read_lines(day.get_day_number(), "expected01.txt")?);
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected_lines = read_string(day.get_day_number(), "expected01.txt")?;
|
||||
let expected_lines = split_lines(&expected_lines)
|
||||
.map(|s| s.to_owned())
|
||||
.collect_vec();
|
||||
let expected = ResultType::Lines(expected_lines);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
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;
|
||||
|
|
@ -13,51 +25,51 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let troop = Troop::parse(lines)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let troop: Troop = lines.parse()?;
|
||||
Ok(ResultType::Integer(troop.play(20)))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let troop = Troop::parse(lines)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,84 +84,94 @@ 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 = &String>,
|
||||
) -> 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 Troop {
|
||||
pub fn parse(lines: &[String]) -> Result<Troop, MonkeyError> {
|
||||
let mut iter = lines.iter();
|
||||
let mut monkeys = Vec::new();
|
||||
while let Some(monkey) = Monkey::parse_one(&mut iter)? {
|
||||
monkeys.push(monkey);
|
||||
impl FromStr for Troop {
|
||||
type Err = MonkeyError;
|
||||
|
||||
fn from_str(lines: &str) -> Result<Self, Self::Err> {
|
||||
Ok(extract_result(Troop::parse)(lines)?)
|
||||
}
|
||||
Ok(Troop { monkeys })
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -191,13 +213,13 @@ impl Troop {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = Monkey {
|
||||
number: 0,
|
||||
items: vec![79, 98],
|
||||
|
|
@ -206,7 +228,7 @@ mod test {
|
|||
good_monkey: 2,
|
||||
bad_monkey: 3,
|
||||
};
|
||||
let result = Monkey::parse_one(&mut lines.iter())?.unwrap();
|
||||
let result = Monkey::parse_one(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -215,9 +237,9 @@ mod test {
|
|||
#[test]
|
||||
fn test_parse_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = 4;
|
||||
let result = Troop::parse(&lines)?;
|
||||
let result: Troop = lines.parse()?;
|
||||
assert_eq!(result.monkeys.len(), expected);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -226,7 +248,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(10605);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -237,7 +259,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(2713310158);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::{BinaryHeap, HashSet};
|
||||
|
||||
use crate::common::pos::Pos;
|
||||
use crate::common::{file::split_lines, pos::Pos};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use thiserror::Error;
|
||||
|
|
@ -14,13 +14,13 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let valley = Valley::parse(lines)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let valley = Valley::try_from(lines)?;
|
||||
Ok(ResultType::Integer(valley.walk()? as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let valley = Valley::parse(lines)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let valley = Valley::try_from(lines)?;
|
||||
Ok(ResultType::Integer(valley.walk_short()? as i64))
|
||||
}
|
||||
}
|
||||
|
|
@ -158,13 +158,16 @@ struct Valley {
|
|||
width: usize,
|
||||
}
|
||||
|
||||
impl Valley {
|
||||
pub fn parse(lines: &[String]) -> Result<Valley, ValleyError> {
|
||||
impl TryFrom<&str> for Valley {
|
||||
type Error = ValleyError;
|
||||
|
||||
fn try_from(lines: &str) -> Result<Self, Self::Error> {
|
||||
let lines = split_lines(lines);
|
||||
let mut map = Vec::new();
|
||||
let mut start = None;
|
||||
let mut exit = None;
|
||||
let mut valley_width = None;
|
||||
for (y, row) in lines.iter().enumerate() {
|
||||
for (y, row) in lines.enumerate() {
|
||||
let mut height_row = Vec::new();
|
||||
for (x, height_char) in row.chars().enumerate() {
|
||||
match height_char {
|
||||
|
|
@ -205,7 +208,9 @@ impl Valley {
|
|||
width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Valley {
|
||||
fn get_height(&self, pos: Pos<usize>) -> char {
|
||||
self.map[pos.y()][pos.x()]
|
||||
}
|
||||
|
|
@ -255,14 +260,14 @@ impl Valley {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let valley = Valley::parse(&lines)?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let valley = Valley::try_from(lines.as_str())?;
|
||||
assert_eq!(valley.width, 8);
|
||||
assert_eq!(valley.start, Pos::new(0, 0));
|
||||
assert_eq!(valley.exit, Pos::new(5, 2));
|
||||
|
|
@ -273,7 +278,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(31);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -284,7 +289,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(29);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
|
@ -13,7 +15,7 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let packets = Packets::parse_all(lines)?;
|
||||
let result: i64 = packets
|
||||
.iter()
|
||||
|
|
@ -30,7 +32,7 @@ impl DayTrait for Day {
|
|||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let small = Packets::parse("[[2]]")?;
|
||||
let large = Packets::parse("[[6]]")?;
|
||||
let mut pos_small = 1;
|
||||
|
|
@ -144,9 +146,8 @@ impl Packets {
|
|||
Err(PacketError::PrematureEndOfInput)
|
||||
}
|
||||
|
||||
pub fn parse_all(lines: &[String]) -> Result<Vec<Packets>, PacketError> {
|
||||
lines
|
||||
.iter()
|
||||
pub fn parse_all(lines: &str) -> Result<Vec<Packets>, PacketError> {
|
||||
split_lines(lines)
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(|line| Packets::parse(line))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
|
|
@ -156,7 +157,7 @@ impl Packets {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -174,7 +175,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_parse_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let result = Packets::parse_all(&lines)?;
|
||||
assert_eq!(result.len(), 16);
|
||||
Ok(())
|
||||
|
|
@ -183,7 +184,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_compare_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = vec![true, true, false, true, false, true, false, false];
|
||||
let result = Packets::parse_all(&lines)?;
|
||||
let compare = result
|
||||
|
|
@ -199,7 +200,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(13);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -210,7 +211,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(140);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use itertools::Itertools;
|
|||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::common::pos::Pos;
|
||||
use crate::common::{file::split_lines, pos::Pos};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
|
||||
|
|
@ -15,13 +15,13 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::parse(lines)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::try_from(lines)?;
|
||||
Ok(ResultType::Integer(cave.drop_bottomless() as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::parse(lines)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let cave = Cave::try_from(lines)?;
|
||||
Ok(ResultType::Integer(cave.drop_floor()))
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,46 @@ struct Cave {
|
|||
height: i32,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Cave {
|
||||
type Error = CaveError;
|
||||
|
||||
fn try_from(lines: &str) -> Result<Self, Self::Error> {
|
||||
let lines = split_lines(lines);
|
||||
let mut cave: HashSet<Pos<i32>> = HashSet::new();
|
||||
for line in lines {
|
||||
cave.extend(Cave::parse_one(line)?.iter());
|
||||
}
|
||||
if cave.is_empty() {
|
||||
Err(CaveError::EmptyCave)
|
||||
} else {
|
||||
let mut max_x = i32::MIN;
|
||||
let mut min_x = i32::MAX;
|
||||
let mut max_y = i32::MIN;
|
||||
for block in &cave {
|
||||
if block.x() < min_x {
|
||||
min_x = block.x();
|
||||
}
|
||||
if block.x() > max_x {
|
||||
max_x = block.x();
|
||||
}
|
||||
if block.y() > max_y {
|
||||
max_y = block.y()
|
||||
}
|
||||
}
|
||||
let mut map = vec![vec![false; (max_x - min_x + 1) as usize]; (max_y + 1) as usize];
|
||||
for block in cave {
|
||||
map[block.y() as usize][(block.x() - min_x) as usize] = true;
|
||||
}
|
||||
Ok(Cave {
|
||||
map,
|
||||
min_x,
|
||||
max_x,
|
||||
height: max_y + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cave {
|
||||
fn get(&self, map: &[Vec<bool>], pos: Pos<i32>) -> bool {
|
||||
if pos.y() >= self.height || pos.x() < self.min_x || pos.x() > self.max_x {
|
||||
|
|
@ -118,41 +158,6 @@ impl Cave {
|
|||
Ok(blocks)
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> Result<Cave, CaveError> {
|
||||
let mut cave: HashSet<Pos<i32>> = HashSet::new();
|
||||
for line in lines {
|
||||
cave.extend(Cave::parse_one(line)?.iter());
|
||||
}
|
||||
if cave.is_empty() {
|
||||
Err(CaveError::EmptyCave)
|
||||
} else {
|
||||
let mut max_x = i32::MIN;
|
||||
let mut min_x = i32::MAX;
|
||||
let mut max_y = i32::MIN;
|
||||
for block in &cave {
|
||||
if block.x() < min_x {
|
||||
min_x = block.x();
|
||||
}
|
||||
if block.x() > max_x {
|
||||
max_x = block.x();
|
||||
}
|
||||
if block.y() > max_y {
|
||||
max_y = block.y()
|
||||
}
|
||||
}
|
||||
let mut map = vec![vec![false; (max_x - min_x + 1) as usize]; (max_y + 1) as usize];
|
||||
for block in cave {
|
||||
map[block.y() as usize][(block.x() - min_x) as usize] = true;
|
||||
}
|
||||
Ok(Cave {
|
||||
map,
|
||||
min_x,
|
||||
max_x,
|
||||
height: max_y + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_bottomless(&self) -> u32 {
|
||||
let mut drops = 0;
|
||||
let mut filled_map = self.map.to_vec();
|
||||
|
|
@ -243,7 +248,7 @@ impl Cave {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{common::file::read_lines, hashset};
|
||||
use crate::{common::file::read_string, hashset};
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
|
|
@ -259,8 +264,8 @@ mod test {
|
|||
#[test]
|
||||
fn test_parse_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let result = Cave::parse(&lines)?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let result = Cave::try_from(lines.as_str())?;
|
||||
assert_eq!(result.min_x, 494);
|
||||
assert_eq!(result.max_x, 503);
|
||||
assert_eq!(result.height, 10);
|
||||
|
|
@ -271,7 +276,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(24);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -282,7 +287,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(93);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,44 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::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: &[String]) -> anyhow::Result<ResultType> {
|
||||
let row = lines[0].parse()?;
|
||||
let (sensors, beacons) = Day::parse_all(&lines[1..])?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
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: &[String]) -> anyhow::Result<ResultType> {
|
||||
let (sensors, _) = Day::parse_all(&lines[1..])?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
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()
|
||||
|
|
@ -43,11 +58,12 @@ impl DayTrait for Day {
|
|||
}
|
||||
|
||||
impl Day {
|
||||
fn parse_all(lines: &[String]) -> 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);
|
||||
}
|
||||
|
|
@ -83,14 +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("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)]
|
||||
|
|
@ -209,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> {
|
||||
|
|
@ -256,7 +280,7 @@ impl Sensor {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
|
|
@ -268,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(())
|
||||
|
|
@ -295,7 +319,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(26);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -306,7 +330,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(56000011);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,15 +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 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;
|
||||
|
|
@ -21,13 +26,13 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let system = ValveSystem::build(lines, "AA")?;
|
||||
let result = system.maximum_flow(system.single_actor(Time(30)))?;
|
||||
Ok(ResultType::Integer(*result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let system = ValveSystem::build(lines, "AA")?;
|
||||
let result = system.maximum_flow(system.double_actor(Time(26)))?;
|
||||
Ok(ResultType::Integer(*result))
|
||||
|
|
@ -127,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),
|
||||
|
|
@ -140,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> {
|
||||
|
|
@ -168,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))
|
||||
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))))
|
||||
}
|
||||
_ => 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> {
|
||||
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)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,11 +276,8 @@ struct ValveSystem {
|
|||
}
|
||||
|
||||
impl ValveSystem {
|
||||
fn build(lines: &[String], start: &str) -> Result<ValveSystem, ValveError> {
|
||||
let mut raw = lines
|
||||
.iter()
|
||||
.map(|line| RawValve::try_from(line.as_str()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> {
|
||||
let mut raw = extract_result(many1(RawValve::parse))(lines)?;
|
||||
raw.sort_unstable_by_key(|valve| valve.id);
|
||||
|
||||
let start_idx = raw
|
||||
|
|
@ -912,13 +921,13 @@ impl Hash for HashableState {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(1651);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -930,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(())
|
||||
|
|
@ -940,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(())
|
||||
|
|
@ -949,7 +958,7 @@ mod test {
|
|||
#[test]
|
||||
fn parse_system() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
|
||||
assert_eq!(10, system.len());
|
||||
|
|
@ -966,7 +975,7 @@ mod test {
|
|||
#[test]
|
||||
fn single_state_init() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.single_actor(Time(5));
|
||||
|
||||
|
|
@ -980,7 +989,7 @@ mod test {
|
|||
#[test]
|
||||
fn next_single_actor_states() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.single_actor(Time(5));
|
||||
let state = ValveState::init(&system, actor);
|
||||
|
|
@ -1002,7 +1011,7 @@ mod test {
|
|||
#[test]
|
||||
fn next_single_states() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.single_actor(Time(6));
|
||||
let state = ValveState::init(&system, actor);
|
||||
|
|
@ -1023,7 +1032,7 @@ mod test {
|
|||
#[test]
|
||||
fn double_state_init() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.double_actor(Time(5));
|
||||
let state = ValveState::init(&system, actor);
|
||||
|
|
@ -1037,7 +1046,7 @@ mod test {
|
|||
#[test]
|
||||
fn next_double_actor_states() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.double_actor(Time(5));
|
||||
let state = ValveState::init(&system, actor);
|
||||
|
|
@ -1068,7 +1077,7 @@ mod test {
|
|||
#[test]
|
||||
fn next_double_states() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = system.double_actor(Time(5));
|
||||
let state = ValveState::init(&system, actor);
|
||||
|
|
@ -1090,7 +1099,7 @@ mod test {
|
|||
#[test]
|
||||
fn special_double_stats() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = DoubleActor(DoubleWork::Simultaneous(Index(1), Index(7), Time(19)));
|
||||
let state = ValveState {
|
||||
|
|
@ -1132,7 +1141,7 @@ mod test {
|
|||
#[test]
|
||||
fn double_long_run() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let system = ValveSystem::build(&lines, "AA")?;
|
||||
let actor = DoubleActor(DoubleWork::Sequential(Index(3), Time(5), Index(7), Time(2)));
|
||||
let state = ValveState {
|
||||
|
|
@ -1158,7 +1167,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(1707);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{cell::Cell, marker::PhantomData, ops::Index};
|
||||
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::{read_string, split_lines};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
|
|
@ -16,16 +16,28 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let pushes = Dispenser::new(
|
||||
lines
|
||||
.trim_end()
|
||||
.chars()
|
||||
.map(|c| Push::parse(c))
|
||||
.try_collect()?,
|
||||
)?;
|
||||
|
||||
let result = Day::run(pushes, 2022)?;
|
||||
|
||||
Ok(ResultType::Integer(result as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let pushes = Dispenser::new(
|
||||
lines
|
||||
.trim_end()
|
||||
.chars()
|
||||
.map(|c| Push::parse(c))
|
||||
.try_collect()?,
|
||||
)?;
|
||||
|
||||
let result = Day::run(pushes, 1000000000000)?;
|
||||
|
||||
|
|
@ -35,8 +47,8 @@ impl DayTrait for Day {
|
|||
|
||||
impl Day {
|
||||
fn run(push_cycle: Dispenser<Push>, max_cycles: i64) -> Result<i64, RockError> {
|
||||
let raw = read_lines(DAY_NUMBER, "blocks.txt")?;
|
||||
let rock_cycle = Dispenser::new(Rock::parse(&raw)?);
|
||||
let raw = read_string(DAY_NUMBER, "blocks.txt")?;
|
||||
let rock_cycle = Dispenser::new(Rock::parse(&raw)?)?;
|
||||
let mut cycle = 0;
|
||||
let mut stack = Stack::new();
|
||||
|
||||
|
|
@ -114,6 +126,9 @@ enum RockError {
|
|||
|
||||
#[error("Unknown direction: {0}")]
|
||||
UnknownDirection(char),
|
||||
|
||||
#[error("A Dispenser must never be empty")]
|
||||
EmptyDispenserNotAllowed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
|
@ -158,9 +173,8 @@ impl Rock {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse(lines: &[String]) -> Result<Vec<Rock>, RockError> {
|
||||
lines
|
||||
.iter()
|
||||
pub fn parse(lines: &str) -> Result<Vec<Rock>, RockError> {
|
||||
split_lines(lines)
|
||||
.batching(|it| {
|
||||
let blocks = it
|
||||
.skip_while(|line| line.is_empty())
|
||||
|
|
@ -330,11 +344,15 @@ where
|
|||
}
|
||||
|
||||
impl<'a, T> Dispenser<'a, T> {
|
||||
pub fn new(data: Vec<T>) -> Self {
|
||||
Dispenser {
|
||||
pub fn new(data: Vec<T>) -> Result<Self, RockError> {
|
||||
if data.is_empty() {
|
||||
Err(RockError::EmptyDispenserNotAllowed)
|
||||
} else {
|
||||
Ok(Dispenser {
|
||||
data,
|
||||
current: Cell::new(0),
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,13 +369,13 @@ impl<'a, T> Dispenser<'a, T> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(3068);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -368,7 +386,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(1514285714288);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -379,7 +397,7 @@ mod test {
|
|||
#[test]
|
||||
fn read_blocks() -> Result<()> {
|
||||
let day = Day {};
|
||||
let raw = read_lines(day.get_day_number(), "blocks.txt")?;
|
||||
let raw = read_string(day.get_day_number(), "blocks.txt")?;
|
||||
let rocks = Rock::parse(&raw)?;
|
||||
let expected = Rock::new(vec![vec![true]; 4])?;
|
||||
assert_eq!(rocks[3], expected);
|
||||
|
|
@ -396,10 +414,16 @@ mod test {
|
|||
#[test]
|
||||
fn drop_one() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
|
||||
let raw = read_lines(day.get_day_number(), "blocks.txt")?;
|
||||
let rocks = Dispenser::new(Rock::parse(&raw)?);
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let pushes = Dispenser::new(
|
||||
lines
|
||||
.trim_end()
|
||||
.chars()
|
||||
.map(|c| Push::parse(c))
|
||||
.try_collect()?,
|
||||
)?;
|
||||
let raw = read_string(day.get_day_number(), "blocks.txt")?;
|
||||
let rocks = Dispenser::new(Rock::parse(&raw)?)?;
|
||||
|
||||
let mut stack = Stack::new();
|
||||
stack.one_rock(rocks.next(), &pushes);
|
||||
|
|
@ -412,8 +436,14 @@ mod test {
|
|||
#[test]
|
||||
fn drop_some() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?);
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let pushes = Dispenser::new(
|
||||
lines
|
||||
.trim_end()
|
||||
.chars()
|
||||
.map(|c| Push::parse(c))
|
||||
.try_collect()?,
|
||||
)?;
|
||||
|
||||
let result = Day::run(pushes, 10)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use itertools::Itertools;
|
|||
use std::{collections::HashSet, num::ParseIntError};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::common::file::split_lines;
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
|
||||
const DAY_NUMBER: usize = 18;
|
||||
|
|
@ -13,13 +15,13 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::parse(lines)?;
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::try_from(lines)?;
|
||||
Ok(ResultType::Integer(blob.count_sides()))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::parse(lines)?;
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let blob = Blob::try_from(lines)?;
|
||||
Ok(ResultType::Integer(blob.count_outside()))
|
||||
}
|
||||
}
|
||||
|
|
@ -131,15 +133,18 @@ struct Blob {
|
|||
droplets: HashSet<Droplet>,
|
||||
}
|
||||
|
||||
impl Blob {
|
||||
fn parse(lines: &[String]) -> Result<Self, DropletError> {
|
||||
let droplets = lines
|
||||
.iter()
|
||||
impl TryFrom<&str> for Blob {
|
||||
type Error = DropletError;
|
||||
|
||||
fn try_from(lines: &str) -> Result<Self, Self::Error> {
|
||||
let droplets = split_lines(lines)
|
||||
.map(|line| Droplet::parse(line))
|
||||
.try_collect()?;
|
||||
Ok(Blob { droplets })
|
||||
}
|
||||
}
|
||||
|
||||
impl Blob {
|
||||
fn extent(&self) -> Option<Ranges> {
|
||||
if self.droplets.is_empty() {
|
||||
return None;
|
||||
|
|
@ -198,13 +203,13 @@ impl Blob {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(64);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -215,7 +220,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(58);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
587
src/days/day19/mod.rs
Normal file
587
src/days/day19/mod.rs
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::parser::{extract_result, ignore, trim0, trim1, trim_left1};
|
||||
use itertools::Itertools;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{char, i64, multispace0, u32},
|
||||
combinator::{value, verify},
|
||||
error::Error,
|
||||
multi::{count, many0, separated_list1},
|
||||
sequence::{preceded, tuple},
|
||||
Err, IResult, Parser,
|
||||
};
|
||||
use std::{
|
||||
collections::{BinaryHeap, HashMap},
|
||||
mem,
|
||||
ops::{Add, IndexMut, Sub},
|
||||
sync::mpsc,
|
||||
thread,
|
||||
};
|
||||
use std::{ops::Index, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 19;
|
||||
|
||||
const NUMBER_MATERIALS: usize = 4;
|
||||
const USE_THREADED: bool = true;
|
||||
|
||||
pub struct Day;
|
||||
|
||||
impl DayTrait for Day {
|
||||
fn get_day_number(&self) -> usize {
|
||||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let cabinet: Cabinet = lines.parse()?;
|
||||
let result = if USE_THREADED {
|
||||
cabinet.threaded_quality_level(24)?
|
||||
} else {
|
||||
cabinet.quality_level(24)?
|
||||
};
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let cabinet: Cabinet = lines.parse()?;
|
||||
let result = if USE_THREADED {
|
||||
cabinet.threaded_reduced_quality(3, 32)?
|
||||
} else {
|
||||
cabinet.reduced_quality(3, 32)?
|
||||
};
|
||||
Ok(ResultType::Integer(result))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum RobotError {
|
||||
#[error("Not a valid description: {0}")]
|
||||
ParsingError(String),
|
||||
|
||||
#[error("No optimum was found")]
|
||||
NoOptimumFound,
|
||||
}
|
||||
|
||||
impl From<Err<Error<&str>>> for RobotError {
|
||||
fn from(error: Err<Error<&str>>) -> Self {
|
||||
RobotError::ParsingError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
enum Material {
|
||||
Geode = 0,
|
||||
Obsidian = 1,
|
||||
Clay = 2,
|
||||
Ore = 3,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn prev(&self) -> Option<Material> {
|
||||
match self {
|
||||
Material::Geode => None,
|
||||
Material::Obsidian => Some(Material::Geode),
|
||||
Material::Clay => Some(Material::Obsidian),
|
||||
Material::Ore => Some(Material::Clay),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Option<Material> {
|
||||
match self {
|
||||
Material::Geode => Some(Material::Obsidian),
|
||||
Material::Obsidian => Some(Material::Clay),
|
||||
Material::Clay => Some(Material::Ore),
|
||||
Material::Ore => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> IResult<&str, Material> {
|
||||
alt((
|
||||
value(Material::Ore, tag("ore")),
|
||||
value(Material::Clay, tag("clay")),
|
||||
value(Material::Obsidian, tag("obsidian")),
|
||||
value(Material::Geode, tag("geode")),
|
||||
))(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Ingredients([i64; NUMBER_MATERIALS]);
|
||||
|
||||
impl Ingredients {
|
||||
pub fn new(items: Vec<(i64, Material)>) -> Self {
|
||||
let mut ingredients = [0; NUMBER_MATERIALS];
|
||||
for (amount, material) in items {
|
||||
ingredients[material as usize] = amount;
|
||||
}
|
||||
Ingredients(ingredients)
|
||||
}
|
||||
|
||||
pub fn is_non_negative(&self) -> bool {
|
||||
self.0.iter().all(|item| *item >= 0)
|
||||
}
|
||||
|
||||
fn inc(&self, mat: Material) -> Ingredients {
|
||||
let mut next = self.0.clone();
|
||||
next[mat as usize] += 1;
|
||||
Ingredients(next)
|
||||
}
|
||||
|
||||
fn pos_max(&self, other: &Ingredients) -> Self {
|
||||
let next = self
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.map(|(a, b)| *a.max(b))
|
||||
.collect_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
Ingredients(next)
|
||||
}
|
||||
|
||||
fn none_smaller(&self, other: &Ingredients) -> bool {
|
||||
self.0.iter().zip(other.0.iter()).all(|(a, b)| a >= b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Material> for Ingredients {
|
||||
type Output = i64;
|
||||
|
||||
fn index(&self, index: Material) -> &Self::Output {
|
||||
&self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<Material> for Ingredients {
|
||||
fn index_mut(&mut self, index: Material) -> &mut Self::Output {
|
||||
&mut self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Ingredients> for Ingredients {
|
||||
type Output = Ingredients;
|
||||
|
||||
fn add(self, rhs: &Ingredients) -> Self::Output {
|
||||
let mut result = self.clone();
|
||||
for pos in 0..NUMBER_MATERIALS {
|
||||
result.0[pos] += rhs.0[pos];
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Ingredients> for &Ingredients {
|
||||
type Output = Ingredients;
|
||||
|
||||
fn add(self, rhs: &Ingredients) -> Self::Output {
|
||||
let mut result = self.clone();
|
||||
for pos in 0..NUMBER_MATERIALS {
|
||||
result.0[pos] += rhs.0[pos];
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&Ingredients> for Ingredients {
|
||||
type Output = Ingredients;
|
||||
|
||||
fn sub(self, rhs: &Self) -> Self::Output {
|
||||
let mut result = self.clone();
|
||||
for pos in 0..NUMBER_MATERIALS {
|
||||
result.0[pos] -= rhs.0[pos];
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&Ingredients> for &Ingredients {
|
||||
type Output = Ingredients;
|
||||
|
||||
fn sub(self, rhs: &Ingredients) -> Self::Output {
|
||||
let mut result = self.clone();
|
||||
for pos in 0..NUMBER_MATERIALS {
|
||||
result.0[pos] -= rhs.0[pos];
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Blueprint {
|
||||
id: usize,
|
||||
material: [Ingredients; NUMBER_MATERIALS],
|
||||
max_robots: Ingredients,
|
||||
}
|
||||
|
||||
impl Blueprint {
|
||||
pub fn new(id: usize, material: [Ingredients; NUMBER_MATERIALS]) -> Self {
|
||||
let mut max_robots = material[Material::Geode as usize]
|
||||
.pos_max(&material[Material::Obsidian as usize])
|
||||
.pos_max(&material[Material::Clay as usize]);
|
||||
max_robots[Material::Geode] = i32::MAX as i64;
|
||||
|
||||
Blueprint {
|
||||
id,
|
||||
material,
|
||||
max_robots,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ingredients_for(&self, mat: Material) -> &Ingredients {
|
||||
&self.material[mat as usize]
|
||||
}
|
||||
|
||||
fn parse_line(input: &str) -> IResult<&str, (Material, Ingredients)> {
|
||||
let input = ignore(tag("Each"))(input)?;
|
||||
let (input, robot) = trim1(Material::parse)(input)?;
|
||||
let input = ignore(tag("robot costs"))(input)?;
|
||||
let (input, ingredients) =
|
||||
separated_list1(tag("and"), tuple((trim0(i64), trim0(Material::parse))))(input)?;
|
||||
let input = ignore(char('.'))(input)?;
|
||||
Ok((input, (robot, Ingredients::new(ingredients))))
|
||||
}
|
||||
|
||||
fn parse(start: &str) -> IResult<&str, Self> {
|
||||
let input = ignore(tag("Blueprint"))(start)?;
|
||||
let (input, id) = trim_left1(u32.map(|v| v as usize))(input)?;
|
||||
let input = ignore(char(':'))(input)?;
|
||||
let (input, robots) = verify(
|
||||
count(
|
||||
preceded(multispace0, Blueprint::parse_line),
|
||||
NUMBER_MATERIALS,
|
||||
),
|
||||
|robots: &[(Material, Ingredients)]| {
|
||||
robots.iter().map(|(material, _)| material).all_unique()
|
||||
},
|
||||
)(input)?;
|
||||
|
||||
let material: [Ingredients; NUMBER_MATERIALS] = robots
|
||||
.into_iter()
|
||||
.sorted_by_key(|(material, _)| *material)
|
||||
.map(|(_, ingreditents)| ingreditents)
|
||||
.collect_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
Ok((input, Blueprint::new(id, material)))
|
||||
}
|
||||
|
||||
pub fn simulate(&self, max_time: usize) -> Result<(usize, i64), RobotError> {
|
||||
let simulation = Simulation::new(max_time);
|
||||
let mut queue = BinaryHeap::new();
|
||||
let mut seen: HashMap<(Ingredients, usize), Ingredients> = HashMap::new();
|
||||
queue.push(simulation);
|
||||
|
||||
while let Some(current) = queue.pop() {
|
||||
if current.time == 0 {
|
||||
return Ok((self.id, current.material(Material::Geode)));
|
||||
}
|
||||
let key = (current.all_robots().clone(), current.time);
|
||||
if let Some(last) = seen.get(&key) {
|
||||
if last.none_smaller(current.all_material()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
seen.insert(key, current.all_material().clone());
|
||||
|
||||
queue.extend(current.next_round(self));
|
||||
}
|
||||
|
||||
Err(RobotError::NoOptimumFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Blueprint {
|
||||
type Err = RobotError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
Ok(extract_result(Blueprint::parse)(input)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct Cabinet(Vec<Blueprint>);
|
||||
|
||||
impl FromStr for Cabinet {
|
||||
type Err = RobotError;
|
||||
|
||||
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
||||
let blueprints = extract_result(many0(preceded(multispace0, Blueprint::parse)))(line)?;
|
||||
Ok(Cabinet(blueprints))
|
||||
}
|
||||
}
|
||||
|
||||
impl Cabinet {
|
||||
pub fn quality_level(&self, max_time: usize) -> Result<i64, RobotError> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|blueprint| blueprint.simulate(max_time))
|
||||
.map_ok(|(id, geodes)| id as i64 * geodes)
|
||||
.fold_ok(0, |a, b| a + b)
|
||||
}
|
||||
|
||||
pub fn reduced_quality(&self, count: usize, max_time: usize) -> Result<i64, RobotError> {
|
||||
self.0
|
||||
.iter()
|
||||
.take(count)
|
||||
.map(|blueprint| blueprint.simulate(max_time))
|
||||
.map_ok(|(_, geodes)| geodes)
|
||||
.fold_ok(1, |a, b| a * b)
|
||||
}
|
||||
|
||||
pub fn threaded_quality_level(self, max_time: usize) -> Result<i64, RobotError> {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
for blueprint in self.0 {
|
||||
let sender = sender.clone();
|
||||
thread::spawn(move || {
|
||||
let result = blueprint.simulate(max_time);
|
||||
let _ = sender.send(result);
|
||||
});
|
||||
}
|
||||
mem::drop(sender);
|
||||
|
||||
receiver
|
||||
.into_iter()
|
||||
.map_ok(|(id, geodes)| id as i64 * geodes)
|
||||
.fold_ok(0, |a, b| a + b)
|
||||
}
|
||||
|
||||
pub fn threaded_reduced_quality(
|
||||
self,
|
||||
count: usize,
|
||||
max_time: usize,
|
||||
) -> Result<i64, RobotError> {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let mut done = 0;
|
||||
for blueprint in self.0 {
|
||||
let sender = sender.clone();
|
||||
thread::spawn(move || {
|
||||
let result = blueprint.simulate(max_time);
|
||||
let _ = sender.send(result);
|
||||
});
|
||||
|
||||
done = done + 1;
|
||||
if done >= count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mem::drop(sender);
|
||||
|
||||
receiver
|
||||
.into_iter()
|
||||
.map_ok(|(_, geodes)| geodes)
|
||||
.fold_ok(1, |a, b| a * b)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Simulation {
|
||||
time: usize,
|
||||
robots: Ingredients,
|
||||
material: Ingredients,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new(time: usize) -> Self {
|
||||
let _robots = Ingredients::new(vec![(1, Material::Ore)]);
|
||||
let _material = Ingredients::new(vec![]);
|
||||
Self {
|
||||
time,
|
||||
robots: _robots,
|
||||
material: _material,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn all_robots(&self) -> &Ingredients {
|
||||
&self.robots
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn robots_for(&self, mat: Material) -> i64 {
|
||||
self.robots[mat]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn all_material(&self) -> &Ingredients {
|
||||
&self.material
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn material(&self, mat: Material) -> i64 {
|
||||
self.material[mat]
|
||||
}
|
||||
|
||||
pub fn no_production(mut self) -> Self {
|
||||
self.time = self.time - 1;
|
||||
self.material = self.material + &self.robots;
|
||||
self
|
||||
}
|
||||
|
||||
fn check_create(&self, blueprint: &Blueprint, mat: Material) -> Option<Simulation> {
|
||||
let rest = self.all_material() - blueprint.ingredients_for(mat);
|
||||
if rest.is_non_negative() {
|
||||
let robots = self.all_robots().inc(mat);
|
||||
let material = rest + self.all_robots();
|
||||
Some(Simulation {
|
||||
time: self.time - 1,
|
||||
robots,
|
||||
material,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_round(self, blueprint: &Blueprint) -> Vec<Simulation> {
|
||||
let mut result = vec![];
|
||||
if let Some(next) = self.check_create(blueprint, Material::Geode) {
|
||||
result.push(next);
|
||||
}
|
||||
|
||||
let mut current = Some(Material::Obsidian);
|
||||
while let Some(mat) = current {
|
||||
if self.robots_for(mat) < blueprint.max_robots[mat]
|
||||
&& self.material(mat) <= 4 * blueprint.max_robots[mat] / 3
|
||||
{
|
||||
if let Some(next) = self.check_create(blueprint, mat) {
|
||||
result.push(next);
|
||||
}
|
||||
}
|
||||
current = mat.next();
|
||||
}
|
||||
if result.len() <= 1 {
|
||||
result.push(self.no_production());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Simulation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.time == other.time && self.material == other.material
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Simulation {}
|
||||
|
||||
impl PartialOrd for Simulation {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Simulation {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.time.cmp(&other.time) {
|
||||
core::cmp::Ordering::Equal => {}
|
||||
ord => return ord,
|
||||
}
|
||||
self.material.cmp(&other.material)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(33);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(56 * 62);
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() -> Result<()> {
|
||||
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||
let expected = Blueprint::new(
|
||||
1,
|
||||
[
|
||||
Ingredients([0, 7, 0, 2]),
|
||||
Ingredients([0, 0, 14, 3]),
|
||||
Ingredients([0, 0, 0, 2]),
|
||||
Ingredients([0, 0, 0, 4]),
|
||||
],
|
||||
);
|
||||
let result: Blueprint = line.parse()?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_faulty() {
|
||||
let line = "Blueprint 1: Each ore robot costs 4 ore. Each ore robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||
let result: Result<Blueprint, _> = line.parse();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_many() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let cabinet: Cabinet = lines.parse()?;
|
||||
assert_eq!(cabinet.0.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulate_one() -> Result<()> {
|
||||
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||
let blueprint: Blueprint = line.parse()?;
|
||||
let result = blueprint.simulate(24)?;
|
||||
assert_eq!(result, (1, 9));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulate_two() -> Result<()> {
|
||||
let line = "Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.";
|
||||
let blueprint: Blueprint = line.parse()?;
|
||||
let result = blueprint.simulate(24)?;
|
||||
assert_eq!(result, (2, 12));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulate_one_two() -> Result<()> {
|
||||
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||
let blueprint: Blueprint = line.parse()?;
|
||||
let result = blueprint.simulate(32)?;
|
||||
assert_eq!(result, (1, 56));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulate_two_two() -> Result<()> {
|
||||
let line = "Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.";
|
||||
|
||||
let blueprint: Blueprint = line.parse()?;
|
||||
let result = blueprint.simulate(32)?;
|
||||
assert_eq!(result, (2, 62));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 0;
|
||||
const DAY_NUMBER: usize = todo!();
|
||||
|
||||
pub struct Day;
|
||||
|
||||
|
|
@ -9,25 +10,31 @@ impl DayTrait for Day {
|
|||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, _lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
Ok(ResultType::Nothing)
|
||||
}
|
||||
|
||||
fn part2(&self, _lines: &[String]) -> anyhow::Result<ResultType> {
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
Ok(ResultType::Nothing)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum DayError {
|
||||
#[error("Dummy")]
|
||||
Dummy,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_lines;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Nothing;
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
@ -38,7 +45,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_lines(day.get_day_number(), "example01.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Nothing;
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ mod day15;
|
|||
mod day16;
|
||||
mod day17;
|
||||
mod day18;
|
||||
mod day19;
|
||||
mod template;
|
||||
|
||||
pub use template::DayTrait;
|
||||
|
|
@ -25,7 +26,7 @@ pub mod day_provider {
|
|||
use super::*;
|
||||
use thiserror::Error;
|
||||
|
||||
const MAX_DAY: usize = 18;
|
||||
const MAX_DAY: usize = 19;
|
||||
|
||||
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
||||
match day_num {
|
||||
|
|
@ -47,6 +48,7 @@ pub mod day_provider {
|
|||
16 => Ok(Box::new(day16::Day)),
|
||||
17 => Ok(Box::new(day17::Day)),
|
||||
18 => Ok(Box::new(day18::Day)),
|
||||
19 => Ok(Box::new(day19::Day)),
|
||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ pub enum ResultType {
|
|||
|
||||
pub trait DayTrait {
|
||||
fn get_day_number(&self) -> usize;
|
||||
fn part1(&self, lines: &[String]) -> Result<ResultType>;
|
||||
fn part2(&self, lines: &[String]) -> Result<ResultType>;
|
||||
fn part1(&self, lines: &str) -> Result<ResultType>;
|
||||
fn part2(&self, lines: &str) -> Result<ResultType>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ mod days;
|
|||
mod macros;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::file::read_lines;
|
||||
use common::file::read_string;
|
||||
use days::{day_provider, DayTrait, ResultType};
|
||||
use std::{
|
||||
env,
|
||||
|
|
@ -47,7 +47,7 @@ fn output(day: usize, part: usize, result: ResultType, time: Duration) {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_part(day: &dyn DayTrait, is_part1: bool, lines: &[String]) -> Result<Duration> {
|
||||
fn run_part(day: &dyn DayTrait, is_part1: bool, lines: &str) -> Result<Duration> {
|
||||
let now = Instant::now();
|
||||
let result = if is_part1 {
|
||||
day.part1(lines)?
|
||||
|
|
@ -70,7 +70,7 @@ fn run_part(day: &dyn DayTrait, is_part1: bool, lines: &[String]) -> Result<Dura
|
|||
}
|
||||
|
||||
fn run(day: &dyn DayTrait, part1: bool, part2: bool) -> Result<Duration> {
|
||||
let lines = read_lines(day.get_day_number(), "input.txt")?;
|
||||
let lines = read_string(day.get_day_number(), "input.txt")?;
|
||||
let elapsed1 = if part1 {
|
||||
run_part(day, true, &lines)?
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue