diff --git a/Cargo.toml b/Cargo.toml index cf6e37d..cbeeee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ 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" diff --git a/data/day19/example01.txt b/data/day19/example01.txt deleted file mode 100644 index 6f9d45b..0000000 --- a/data/day19/example01.txt +++ /dev/null @@ -1,11 +0,0 @@ -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. \ No newline at end of file diff --git a/data/day19/input.txt b/data/day19/input.txt deleted file mode 100644 index f8f652c..0000000 --- a/data/day19/input.txt +++ /dev/null @@ -1,30 +0,0 @@ -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. \ No newline at end of file diff --git a/src/common/file.rs b/src/common/file.rs index 3c5b86c..5712052 100644 --- a/src/common/file.rs +++ b/src/common/file.rs @@ -5,16 +5,13 @@ fn format_path(day_num: usize, file: &str) -> String { format!("data/day{day_num:02}/{file}") } -pub fn read_string(day_num: usize, file: &str) -> io::Result { - Ok(fs::read_to_string(format_path(day_num, file))?) -} - -pub fn split_lines<'a>(lines: &'a str) -> impl Iterator + 'a { - lines +pub fn read_lines(day_num: usize, file: &str) -> io::Result> { + Ok(fs::read_to_string(format_path(day_num, file))? .split('\n') .with_position() .filter_map(|(pos, line)| match pos { itertools::Position::Last if line.is_empty() => None, - _ => Some(line), + _ => Some(line.to_owned()), }) + .collect()) } diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a2379d..3c42571 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,6 +3,5 @@ pub mod direction; pub mod file; pub mod helper; pub mod math; -pub mod parser; pub mod pos; pub mod turn; diff --git a/src/common/parser.rs b/src/common/parser.rs deleted file mode 100644 index fa6b022..0000000 --- a/src/common/parser.rs +++ /dev/null @@ -1,70 +0,0 @@ -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>( - mut parser: F, -) -> impl FnMut(I) -> Result> -where - F: FnMut(I) -> IResult, -{ - move |input: I| parser(input).map(|(_, value)| value) -} - -pub fn ignore>(mut parser: F) -> impl FnMut(I) -> Result> -where - F: FnMut(I) -> IResult, -{ - 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> { - 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) -} diff --git a/src/days/day01/mod.rs b/src/days/day01/mod.rs index 6419b99..0bda729 100644 --- a/src/days/day01/mod.rs +++ b/src/days/day01/mod.rs @@ -3,8 +3,6 @@ use std::num::ParseIntError; use thiserror::Error; -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; pub struct Day; @@ -15,15 +13,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let lines = split_lines(lines); + fn part1(&self, lines: &[String]) -> anyhow::Result { let vector = Day::parse(lines)?; let max = vector.iter().max().ok_or(CalorieError::Empty)?; Ok(ResultType::Integer(*max)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let lines = split_lines(lines); + fn part2(&self, lines: &[String]) -> anyhow::Result { let vector = Day::parse(lines)?; let sum = vector.iter().sorted_by(|a, b| Ord::cmp(b, a)).take(3).sum(); Ok(ResultType::Integer(sum)) @@ -31,8 +27,9 @@ impl DayTrait for Day { } impl Day { - fn parse<'a>(lines: impl Iterator) -> Result, CalorieError> { + fn parse(lines: &[String]) -> Result, CalorieError> { Ok(lines + .iter() .batching(|it| { let result = it .take_while(|line| !line.is_empty()) @@ -60,13 +57,13 @@ pub enum CalorieError { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(24_000); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -77,7 +74,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(45_000); let result = day.part2(&lines)?; assert_eq!(result, expected); @@ -87,10 +84,9 @@ mod test { #[test] fn test_parse() -> Result<()> { - let lines = read_string(DAY_NUMBER, "example01.txt")?; - let lines = split_lines(&lines); + let lines = read_lines(DAY_NUMBER, "example01.txt")?; let expected = vec![6000, 4000, 11000, 24000, 10000]; - let result = Day::parse(lines)?; + let result = Day::parse(&lines)?; assert_eq!(result, expected); Ok(()) diff --git a/src/days/day02/mod.rs b/src/days/day02/mod.rs index 9f19241..73fc25e 100644 --- a/src/days/day02/mod.rs +++ b/src/days/day02/mod.rs @@ -1,7 +1,5 @@ use std::cmp::Ordering; -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use thiserror::Error; @@ -14,8 +12,9 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part1(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .map(|line| Rps::parse_line(line)) .collect::, _>>()? .into_iter() @@ -25,8 +24,9 @@ impl DayTrait for Day { Ok(ResultType::Integer(sum)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part2(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .map(|line| Strategy::parse_line(line)) .collect::, _>>()? .into_iter() @@ -155,7 +155,7 @@ impl TryFrom<&str> for Strategy { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -182,7 +182,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(12); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day03/mod.rs b/src/days/day03/mod.rs index a86653c..f263f63 100644 --- a/src/days/day03/mod.rs +++ b/src/days/day03/mod.rs @@ -1,5 +1,3 @@ -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use itertools::Itertools; use thiserror::Error; @@ -13,8 +11,9 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part1(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .map(|line| find_double(line)) .map_ok(priority) .flatten_ok() @@ -22,11 +21,12 @@ impl DayTrait for Day { Ok(ResultType::Integer(sum)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part2(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .chunks(3) .into_iter() - .map(|chunk| find_badge(&chunk.collect_vec())) + .map(|chunk| find_badge(&chunk.collect::>())) .map_ok(priority) .flatten_ok() .fold_ok(0, |a, b| a + b)?; @@ -71,7 +71,7 @@ fn find_double(content: &str) -> Result { Err(RucksackError::NoDoubleFound)? } -fn find_badge(elves: &[&str]) -> Result { +fn find_badge(elves: &[&String]) -> Result { if elves.len() < 2 { return Err(RucksackError::NeedAtLeastTwo); }; @@ -92,7 +92,7 @@ fn find_badge(elves: &[&str]) -> Result { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -108,7 +108,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(70); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day04/mod.rs b/src/days/day04/mod.rs index 62a84a9..2696dc7 100644 --- a/src/days/day04/mod.rs +++ b/src/days/day04/mod.rs @@ -1,5 +1,3 @@ -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use itertools::Itertools; use std::num::ParseIntError; @@ -14,16 +12,18 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part1(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .map(|line| parse(line)) .filter_ok(fully_contained) .fold_ok(0i64, |a, _| a + 1)?; Ok(ResultType::Integer(sum)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let sum = split_lines(lines) + fn part2(&self, lines: &[String]) -> anyhow::Result { + let sum = lines + .iter() .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_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -93,7 +93,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(4); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day05/mod.rs b/src/days/day05/mod.rs index 57429eb..98a292f 100644 --- a/src/days/day05/mod.rs +++ b/src/days/day05/mod.rs @@ -1,7 +1,4 @@ -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; -use itertools::Itertools; use std::num::ParseIntError; use thiserror::Error; @@ -14,7 +11,7 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { + fn part1(&self, lines: &[String]) -> anyhow::Result { let (mut stacks, commands) = parse(lines)?; for command in commands { command.with_turn(&mut stacks)?; @@ -22,7 +19,7 @@ impl DayTrait for Day { Ok(ResultType::String(get_word(&stacks))) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { let (mut stacks, commands) = parse(lines)?; for command in commands { command.without_turn(&mut stacks)?; @@ -60,13 +57,11 @@ fn parse_crate(line: &str) -> Vec> { .collect() } -fn parse_all_crates<'a>( - line_it: &mut dyn Iterator, -) -> Result, CrateError> { - let rows = line_it +fn parse_all_crates(line_it: &mut dyn Iterator) -> Result, CrateError> { + let rows: Vec<_> = line_it .take_while(|line| !line.is_empty()) .map(|row| parse_crate(row)) - .collect_vec(); + .collect(); let Some(stacks_count) = rows.iter().map(|row| row.len()).max() else { return Err(CrateError::NoCargoStacksGiven); }; @@ -84,11 +79,11 @@ fn parse_all_crates<'a>( Ok(stacks) } -fn parse<'a>(lines: &'a str) -> Result<(Vec, Vec), CrateError> { - let mut iter = split_lines(lines); +fn parse(line: &[String]) -> Result<(Vec, Vec), CrateError> { + let mut iter = line.iter(); let stacks = parse_all_crates(&mut iter)?; let moves: Vec<_> = iter - .map(|command| Move::try_from(command)) + .map(|command| Move::try_from(command.as_str())) .collect::>()?; Ok((stacks, moves)) } @@ -172,7 +167,7 @@ impl TryFrom<&str> for Move { mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -196,15 +191,14 @@ mod test { #[test] fn test_parse_all_crates() -> Result<()> { let lines = vec![ - " [D] ", - "[N] [C] ", - "[Z] [M] [P]", - " 1 2 3 ", - "", - "move 1 from to 2 1", - ] - .join("\n"); - let mut input = split_lines(&lines); + " [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(); let expected = vec!["NZ1".to_owned(), "DCM2".to_owned(), "P3".to_owned()]; let result = parse_all_crates(&mut input)?; @@ -227,7 +221,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::String("CMZ".to_owned()); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -249,7 +243,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::String("MCD".to_owned()); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day06/mod.rs b/src/days/day06/mod.rs index 6a4bc51..7a04a3f 100644 --- a/src/days/day06/mod.rs +++ b/src/days/day06/mod.rs @@ -10,13 +10,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let count = find_marker(lines.trim_end(), 4)?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let count = find_marker(&lines[0], 4)?; Ok(ResultType::Integer(count as i64)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let count = find_marker(lines.trim_end(), 14)?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let count = find_marker(&lines[0], 14)?; Ok(ResultType::Integer(count as i64)) } } @@ -82,7 +82,7 @@ fn find_marker(word: &str, marker_length: usize) -> Result { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -98,7 +98,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(29); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day07/mod.rs b/src/days/day07/mod.rs index beb77c6..8c4c791 100644 --- a/src/days/day07/mod.rs +++ b/src/days/day07/mod.rs @@ -4,8 +4,6 @@ use std::{ rc::{Rc, Weak}, }; -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use thiserror::Error; @@ -18,8 +16,7 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let lines = split_lines(lines); + fn part1(&self, lines: &[String]) -> anyhow::Result { let root = Directory::parse(lines)?; let result = root .iter() @@ -35,10 +32,9 @@ impl DayTrait for Day { Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { 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 @@ -171,7 +167,7 @@ impl Directory { sub_node.map(|node| Directory::create(node.clone())) } - pub fn parse<'a>(lines: impl Iterator) -> Result { + pub fn parse(lines: &[String]) -> Result { let root = Directory::root(); let mut current = root.clone(); for line in lines { @@ -273,7 +269,7 @@ impl Iterator for DirectoryIterator { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -302,9 +298,8 @@ mod test { #[test] fn test_total_size() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; - let lines = split_lines(&lines); - let dir = Directory::parse(lines)?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let dir = Directory::parse(&lines)?; assert_eq!(dir.size(), 48381165); Ok(()) @@ -313,10 +308,9 @@ mod test { #[test] fn test_iterator() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; - let lines = split_lines(&lines); + let lines = read_lines(day.get_day_number(), "example01.txt")?; 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); @@ -326,7 +320,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(95437); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -337,7 +331,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(24933642); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day08/mod.rs b/src/days/day08/mod.rs index c031b2a..77b5fcc 100644 --- a/src/days/day08/mod.rs +++ b/src/days/day08/mod.rs @@ -1,7 +1,3 @@ -use std::str::FromStr; - -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use itertools::{iproduct, FoldWhile, Itertools}; use thiserror::Error; @@ -15,14 +11,14 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let forest: Forest = lines.parse()?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let forest = Forest::parse(lines)?; let result = forest.count_visible(); Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let forest: Forest = lines.parse()?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let forest = Forest::parse(lines)?; let result = forest.best_score(); Ok(ResultType::Integer(result)) } @@ -71,6 +67,18 @@ impl Forest { }) } + pub fn parse(lines: &[String]) -> Result { + lines + .iter() + .map(|line| { + line.chars() + .map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height))) + .collect::, _>>() + }) + .collect::, _>>() + .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]; @@ -183,32 +191,17 @@ impl Forest { } } -impl FromStr for Forest { - type Err = ForestError; - - fn from_str(lines: &str) -> Result { - split_lines(lines) - .map(|line| { - line.chars() - .map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height))) - .collect::, _>>() - }) - .collect::, _>>() - .and_then(Forest::create) - } -} - #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; - let forest: Forest = lines.parse()?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let forest = Forest::parse(&lines)?; assert_eq!(forest.width, 5); assert_eq!(forest.height, 5); @@ -218,7 +211,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(21); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -229,7 +222,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(8); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day09/mod.rs b/src/days/day09/mod.rs index 7f11710..533acf4 100644 --- a/src/days/day09/mod.rs +++ b/src/days/day09/mod.rs @@ -1,6 +1,5 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{direction::Direction, file::split_lines, pos::Pos}; -use itertools::Itertools; +use crate::common::{direction::Direction, pos::Pos}; use std::{collections::HashSet, num::ParseIntError}; use thiserror::Error; @@ -13,14 +12,14 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { + fn part1(&self, lines: &[String]) -> anyhow::Result { let commands = parse_commands(lines)?; let mut rope = Rope::create(2); let result = rope.run(&commands); Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { let commands = parse_commands(lines)?; let mut rope = Rope::create(10); let result = rope.run(&commands); @@ -40,12 +39,12 @@ enum RopeError { StepParseError(#[from] ParseIntError), } -fn parse_commands(lines: &str) -> Result, RopeError> { - split_lines(lines).map(parse_line).try_collect() +fn parse_commands(lines: &[String]) -> Result, RopeError> { + lines.iter().map(parse_line).collect::>() } -fn parse_line(line: &str) -> Result<(Direction, usize), RopeError> { - match line.split_whitespace().collect_vec()[..] { +fn parse_line(line: &String) -> Result<(Direction, usize), RopeError> { + match line.split_whitespace().collect::>()[..] { [direction, steps] => { let direction = match direction { "U" => Direction::North, @@ -126,13 +125,13 @@ impl Rope { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(13); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -143,7 +142,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example02.txt")?; + let lines = read_lines(day.get_day_number(), "example02.txt")?; let expected = ResultType::Integer(36); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day10/mod.rs b/src/days/day10/mod.rs index 255231b..13bfdcf 100644 --- a/src/days/day10/mod.rs +++ b/src/days/day10/mod.rs @@ -1,5 +1,3 @@ -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use std::{num::ParseIntError, slice::Iter}; use thiserror::Error; @@ -13,16 +11,18 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let instructions = split_lines(lines) + fn part1(&self, lines: &[String]) -> anyhow::Result { + let instructions = lines + .iter() .map(|line| Instruction::parse(line)) .collect::, _>>()?; let result = CpuCycles::signal_strength(&instructions, &[20, 60, 100, 140, 180, 220]); Ok(ResultType::Integer(result as i64)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let instructions = split_lines(lines) + fn part2(&self, lines: &[String]) -> anyhow::Result { + let instructions = lines + .iter() .map(|line| Instruction::parse(line)) .collect::, _>>()?; let result = CpuCycles::draw(&instructions); @@ -128,9 +128,8 @@ impl<'a> Iterator for CpuCycles<'a> { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; - use itertools::Itertools; #[test] fn test_simple() -> Result<()> { @@ -145,7 +144,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(13140); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -156,12 +155,8 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - 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 lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Lines(read_lines(day.get_day_number(), "expected01.txt")?); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day11/mod.rs b/src/days/day11/mod.rs index ddb6f62..2e8b3c7 100644 --- a/src/days/day11/mod.rs +++ b/src/days/day11/mod.rs @@ -1,19 +1,7 @@ -use crate::common::parser::{ - empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false, -}; - use super::template::{DayTrait, ResultType}; -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 once_cell::sync::Lazy; +use regex::Regex; +use std::{iter::zip, num::ParseIntError}; use thiserror::Error; const DAY_NUMBER: usize = 11; @@ -25,51 +13,51 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let troop: Troop = lines.parse()?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let troop = Troop::parse(lines)?; Ok(ResultType::Integer(troop.play(20))) } - fn part2(&self, lines: &str) -> anyhow::Result { - let troop: Troop = lines.parse()?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let troop = Troop::parse(lines)?; Ok(ResultType::Integer(troop.play_again(10_000))) } } #[derive(Debug, Error)] enum MonkeyError { - #[error("Error while parsing: {0}")] - ParsingError(String), + #[error("Not an Integer")] + NotAnInteger(#[from] ParseIntError), + + #[error("Can't parse line: {0}")] + UnknownLine(String), + + #[error("Did not expect end of Input")] + PrematureEndOfInput, } -impl From>> for MonkeyError { - fn from(value: Err>) -> Self { - MonkeyError::ParsingError(value.to_string()) - } -} - -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq)] enum Operation { - Squared, - Times(i64), Plus(i64), + Times(i64), + Squared, } impl Operation { - 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 parse(line: &str) -> Operation { + match line.split_whitespace().collect::>()[..] { + ["*", "old"] => Operation::Squared, + ["*", value] => Operation::Times(value.parse().unwrap()), + ["+", value] => Operation::Plus(value.parse().unwrap()), + _ => unreachable!(), + } } pub fn calc(&self, old: i64) -> i64 { match self { - Operation::Squared => old.pow(2), + Operation::Plus(value) => old + *value, Operation::Times(value) => old * value, - Operation::Plus(value) => old + value, + Operation::Squared => old.pow(2), } } } @@ -84,94 +72,84 @@ struct Monkey { bad_monkey: usize, } +static MONKEY: Lazy = Lazy::new(|| Regex::new(r"Monkey (\d+)").unwrap()); +static STARTING: Lazy = + Lazy::new(|| Regex::new(r"Starting items: (\d+(?:, \d+)*)").unwrap()); +static OP: Lazy = + Lazy::new(|| Regex::new(r"Operation: new = old ([+*] \d+|\* old)").unwrap()); +static TEST: Lazy = Lazy::new(|| Regex::new(r"Test: divisible by (\d+)").unwrap()); +static NEXT: Lazy = + Lazy::new(|| Regex::new(r"If (?:true|false): throw to monkey (\d+)").unwrap()); + impl Monkey { - 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)) + 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 starting_items_parse(input: &str) -> IResult<&str, Vec> { - let input = ignore(tag("Starting items:"))(input)?; - separated_list1(char(','), trim0(i64))(input) - } + pub fn parse_one( + iterator: &mut dyn Iterator, + ) -> Result, MonkeyError> { + if let Some(line) = iterator.next() { + let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?; - 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 start = Monkey::parse_line(&STARTING, line)?; + let items = start + .split(", ") + .map(|item| item.parse::()) + .collect::, _>>()?; - 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 operation = Operation::parse(Monkey::parse_line(&OP, line)?); - 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 divisor: i64 = Monkey::parse_line(&TEST, 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 good_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; - Ok(( - input, - Monkey { + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let bad_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; + + iterator.next(); + + Ok(Some(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 { - Ok(extract_result(Monkey::parse)(lines)?) - } } struct Troop { monkeys: Vec, } -impl FromStr for Troop { - type Err = MonkeyError; - - fn from_str(lines: &str) -> Result { - Ok(extract_result(Troop::parse)(lines)?) - } -} - impl Troop { - fn parse(input: &str) -> IResult<&str, Troop> { - map(many1(Monkey::parse), |monkeys| Troop { monkeys })(input) + pub fn parse(lines: &[String]) -> Result { + let mut iter = lines.iter(); + let mut monkeys = Vec::new(); + while let Some(monkey) = Monkey::parse_one(&mut iter)? { + monkeys.push(monkey); + } + Ok(Troop { monkeys }) } fn do_play(&self, rounds: usize, alter: F) -> i64 @@ -213,13 +191,13 @@ impl Troop { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = Monkey { number: 0, items: vec![79, 98], @@ -228,7 +206,7 @@ mod test { good_monkey: 2, bad_monkey: 3, }; - let result = Monkey::parse_one(&lines)?; + let result = Monkey::parse_one(&mut lines.iter())?.unwrap(); assert_eq!(result, expected); Ok(()) @@ -237,9 +215,9 @@ mod test { #[test] fn test_parse_all() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = 4; - let result: Troop = lines.parse()?; + let result = Troop::parse(&lines)?; assert_eq!(result.monkeys.len(), expected); Ok(()) @@ -248,7 +226,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(10605); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -259,7 +237,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(2713310158); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day12/mod.rs b/src/days/day12/mod.rs index 1c099d0..c3f6ab1 100644 --- a/src/days/day12/mod.rs +++ b/src/days/day12/mod.rs @@ -1,6 +1,6 @@ use std::collections::{BinaryHeap, HashSet}; -use crate::common::{file::split_lines, pos::Pos}; +use crate::common::pos::Pos; use super::template::{DayTrait, ResultType}; use thiserror::Error; @@ -14,13 +14,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let valley = Valley::try_from(lines)?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let valley = Valley::parse(lines)?; Ok(ResultType::Integer(valley.walk()? as i64)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let valley = Valley::try_from(lines)?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let valley = Valley::parse(lines)?; Ok(ResultType::Integer(valley.walk_short()? as i64)) } } @@ -158,16 +158,13 @@ struct Valley { width: usize, } -impl TryFrom<&str> for Valley { - type Error = ValleyError; - - fn try_from(lines: &str) -> Result { - let lines = split_lines(lines); +impl Valley { + pub fn parse(lines: &[String]) -> Result { let mut map = Vec::new(); let mut start = None; let mut exit = None; let mut valley_width = None; - for (y, row) in lines.enumerate() { + for (y, row) in lines.iter().enumerate() { let mut height_row = Vec::new(); for (x, height_char) in row.chars().enumerate() { match height_char { @@ -208,9 +205,7 @@ impl TryFrom<&str> for Valley { width, }) } -} -impl Valley { fn get_height(&self, pos: Pos) -> char { self.map[pos.y()][pos.x()] } @@ -260,14 +255,14 @@ impl Valley { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; - let valley = Valley::try_from(lines.as_str())?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let valley = Valley::parse(&lines)?; assert_eq!(valley.width, 8); assert_eq!(valley.start, Pos::new(0, 0)); assert_eq!(valley.exit, Pos::new(5, 2)); @@ -278,7 +273,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(31); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -289,7 +284,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(29); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day13/mod.rs b/src/days/day13/mod.rs index f6b146a..0e33a74 100644 --- a/src/days/day13/mod.rs +++ b/src/days/day13/mod.rs @@ -1,7 +1,5 @@ use std::cmp::Ordering; -use crate::common::file::split_lines; - use super::template::{DayTrait, ResultType}; use itertools::Itertools; use thiserror::Error; @@ -15,7 +13,7 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { + fn part1(&self, lines: &[String]) -> anyhow::Result { let packets = Packets::parse_all(lines)?; let result: i64 = packets .iter() @@ -32,7 +30,7 @@ impl DayTrait for Day { Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { let small = Packets::parse("[[2]]")?; let large = Packets::parse("[[6]]")?; let mut pos_small = 1; @@ -146,8 +144,9 @@ impl Packets { Err(PacketError::PrematureEndOfInput) } - pub fn parse_all(lines: &str) -> Result, PacketError> { - split_lines(lines) + pub fn parse_all(lines: &[String]) -> Result, PacketError> { + lines + .iter() .filter(|line| !line.is_empty()) .map(|line| Packets::parse(line)) .collect::, _>>() @@ -157,7 +156,7 @@ impl Packets { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] @@ -175,7 +174,7 @@ mod test { #[test] fn test_parse_all() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let result = Packets::parse_all(&lines)?; assert_eq!(result.len(), 16); Ok(()) @@ -184,7 +183,7 @@ mod test { #[test] fn test_compare_all() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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 @@ -200,7 +199,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(13); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -211,7 +210,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(140); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day14/mod.rs b/src/days/day14/mod.rs index 56a1e02..e8ace33 100644 --- a/src/days/day14/mod.rs +++ b/src/days/day14/mod.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use std::{collections::HashSet, num::ParseIntError}; use thiserror::Error; -use crate::common::{file::split_lines, pos::Pos}; +use crate::common::pos::Pos; use super::template::{DayTrait, ResultType}; @@ -15,13 +15,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let cave = Cave::try_from(lines)?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let cave = Cave::parse(lines)?; Ok(ResultType::Integer(cave.drop_bottomless() as i64)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let cave = Cave::try_from(lines)?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let cave = Cave::parse(lines)?; Ok(ResultType::Integer(cave.drop_floor())) } } @@ -51,46 +51,6 @@ struct Cave { height: i32, } -impl TryFrom<&str> for Cave { - type Error = CaveError; - - fn try_from(lines: &str) -> Result { - let lines = split_lines(lines); - let mut cave: HashSet> = 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], pos: Pos) -> bool { if pos.y() >= self.height || pos.x() < self.min_x || pos.x() > self.max_x { @@ -158,6 +118,41 @@ impl Cave { Ok(blocks) } + pub fn parse(lines: &[String]) -> Result { + let mut cave: HashSet> = 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(); @@ -248,7 +243,7 @@ impl Cave { #[cfg(test)] mod test { use super::*; - use crate::{common::file::read_string, hashset}; + use crate::{common::file::read_lines, hashset}; use anyhow::Result; #[test] @@ -264,8 +259,8 @@ mod test { #[test] fn test_parse_all() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; - let result = Cave::try_from(lines.as_str())?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let result = Cave::parse(&lines)?; assert_eq!(result.min_x, 494); assert_eq!(result.max_x, 503); assert_eq!(result.height, 10); @@ -276,7 +271,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(24); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -287,7 +282,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(93); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day15/mod.rs b/src/days/day15/mod.rs index 81e69f8..28e4ee4 100644 --- a/src/days/day15/mod.rs +++ b/src/days/day15/mod.rs @@ -1,44 +1,29 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{ - parser::{eol_terminated, extract_result, ignore, trim0}, - pos::Pos, -}; +use crate::common::pos::Pos; use itertools::Itertools; -use nom::{ - bytes::complete::tag, - character::complete::{char, i64}, - error::Error, - multi::many0, - sequence::{separated_pair, tuple}, - Err, IResult, -}; -use std::collections::HashSet; +use once_cell::sync::Lazy; +use regex::Regex; +use std::{collections::HashSet, num::ParseIntError}; use thiserror::Error; const DAY_NUMBER: usize = 15; pub struct Day; -fn parse_row(input: &str) -> IResult<&str, i64> { - eol_terminated(i64)(input) -} - impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let (lines, row) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?; - let (sensors, beacons) = Day::parse_all(lines)?; - + fn part1(&self, lines: &[String]) -> anyhow::Result { + let row = lines[0].parse()?; + let (sensors, beacons) = Day::parse_all(&lines[1..])?; let result = Day::count_coverage_at(&sensors, &beacons, row); Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let (lines, _) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?; - let (sensors, _) = Day::parse_all(lines)?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let (sensors, _) = Day::parse_all(&lines[1..])?; let mid_lines = Day::dividing_lines(&sensors); let points = mid_lines .iter() @@ -58,12 +43,11 @@ impl DayTrait for Day { } impl Day { - fn parse_all(lines: &str) -> Result<(HashSet, HashSet>), SensorError> { - let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?; - + fn parse_all(lines: &[String]) -> Result<(HashSet, HashSet>), SensorError> { let mut sensors = HashSet::new(); let mut beacons = HashSet::new(); - for (sensor, beacon) in data { + for line in lines { + let (sensor, beacon) = Sensor::parse(line)?; sensors.insert(sensor); beacons.insert(beacon); } @@ -99,20 +83,14 @@ impl Day { #[derive(Debug, Error)] enum SensorError { - #[error("Error Parsing Input: {0}")] - ParsingError(String), + #[error("Not an Integer")] + NotAnInt(#[from] ParseIntError), + + #[error("Unknown line: {0}")] + UnknownLine(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>> for SensorError { - fn from(error: Err>) -> Self { - SensorError::ParsingError(error.to_string()) - } } #[derive(Debug)] @@ -231,30 +209,28 @@ struct Sensor { radius: i64, } +static SENSOR: Lazy = + Lazy::new(|| Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap()); + impl Sensor { - 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> { - 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)> { - 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 parse(line: &str) -> Result<(Sensor, Pos), 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, + )) } pub fn range_at(&self, y: i64) -> Option { @@ -280,7 +256,7 @@ impl Sensor { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_parse() -> Result<()> { @@ -292,7 +268,7 @@ mod test { }, Pos::new(-2, 15), ); - let result = extract_result(Sensor::parse)(input)?; + let result = Sensor::parse(input)?; assert_eq!(result, expected); Ok(()) @@ -319,7 +295,7 @@ mod test { #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(26); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -330,7 +306,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(56000011); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day16/mod.rs b/src/days/day16/mod.rs index e068986..1e7cfb1 100644 --- a/src/days/day16/mod.rs +++ b/src/days/day16/mod.rs @@ -1,20 +1,15 @@ -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; @@ -26,13 +21,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { + fn part1(&self, lines: &[String]) -> anyhow::Result { let system = ValveSystem::build(lines, "AA")?; let result = system.maximum_flow(system.single_actor(Time(30)))?; Ok(ResultType::Integer(*result)) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { let system = ValveSystem::build(lines, "AA")?; let result = system.maximum_flow(system.double_actor(Time(26)))?; Ok(ResultType::Integer(*result)) @@ -132,8 +127,11 @@ impl Deref for Index { #[derive(Debug, Error)] enum ValveError { + #[error("Not an Integer")] + NotAnInt(#[from] ParseIntError), + #[error("Not a valid description: {0}")] - ParsingError(String), + CouldNotParse(String), #[error("Not a valid valve: {0}")] ValveNotFound(String), @@ -142,11 +140,17 @@ enum ValveError { NoOptimumFound, } -impl From>> for ValveError { - fn from(error: Err>) -> Self { - ValveError::ParsingError(error.to_string()) - } -} +const ID: &str = "[[:alpha:]]+"; +const COMMON: &str = concatcp!("^Valve (?", ID, r") has flow rate=(?\d+); "); +const PLURAL_STR: &str = concatcp!( + COMMON, + "tunnels lead to valves (?", + ID, + "(?:, ", + ID, + ")+)$" +); +const SINGULAR_STR: &str = concatcp!(COMMON, "tunnel leads to valve (?", ID, ")$"); #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct RawValve<'a> { @@ -164,45 +168,29 @@ impl<'a> RawValve<'a> { } } - fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> { - let input = ignore(tag("Valve"))(input)?; - let (input, valve) = trim1(alpha1)(input)?; - let input = ignore(tag("has flow rate="))(input)?; - let (input, flow) = trim0(i64)(input)?; - let input = ignore(tag(";"))(input)?; - Ok((input, (valve, Flow(flow)))) - } - - fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> { - let input = ignore(tag("tunnel leads to valve "))(input)?; - let (input, valve) = alpha1(input)?; - - Ok((input, vec![valve])) - } - - fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> { - let input = ignore(tag("tunnels lead to valves "))(input)?; - let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?; - - Ok((input, valve)) - } - - fn parse(input: &str) -> IResult<&str, RawValve> { - let (input, (id, flow)) = RawValve::parse_start(input)?; - let (input, neighbors) = trim_left1(eol_terminated(alt(( - RawValve::parse_plural, - RawValve::parse_singular, - ))))(input)?; - - Ok((input, RawValve::create(id, flow, neighbors))) + fn from_regex(regex: &Regex, line: &'a str) -> Option, ValveError>> { + regex.captures(line).map(|caps| { + match (caps.name("id"), caps.name("rate"), caps.name("exits")) { + (Some(id), Some(rate), Some(exits)) => { + let rate = rate.as_str().parse()?; + let neighbors = exits.as_str().split(",").map(|s| s.trim_start()).collect(); + Ok(RawValve::create(id.as_str(), Flow(rate), neighbors)) + } + _ => Err(ValveError::CouldNotParse(line.to_string())), + } + }) } } impl<'a> TryFrom<&'a str> for RawValve<'a> { type Error = ValveError; - fn try_from(value: &'a str) -> Result { - Ok(extract_result(RawValve::parse)(value)?) + fn try_from(value: &'a str) -> Result, Self::Error> { + static PLURAL: Lazy = Lazy::new(|| Regex::new(PLURAL_STR).unwrap()); + static SINGULAR: Lazy = 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()))) } } @@ -276,8 +264,11 @@ struct ValveSystem { } impl ValveSystem { - fn build(lines: &str, start: &str) -> Result { - let mut raw = extract_result(many1(RawValve::parse))(lines)?; + fn build(lines: &[String], start: &str) -> Result { + let mut raw = lines + .iter() + .map(|line| RawValve::try_from(line.as_str())) + .collect::, _>>()?; raw.sort_unstable_by_key(|valve| valve.id); let start_idx = raw @@ -921,13 +912,13 @@ impl Hash for HashableState { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(1651); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -939,7 +930,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 = line.try_into()?; + let result = RawValve::try_from(line)?; assert_eq!(expected, result); Ok(()) @@ -949,7 +940,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 = line.try_into()?; + let result = RawValve::try_from(line)?; assert_eq!(expected, result); Ok(()) @@ -958,7 +949,7 @@ mod test { #[test] fn parse_system() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let system = ValveSystem::build(&lines, "AA")?; assert_eq!(10, system.len()); @@ -975,7 +966,7 @@ mod test { #[test] fn single_state_init() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let system = ValveSystem::build(&lines, "AA")?; let actor = system.single_actor(Time(5)); @@ -989,7 +980,7 @@ mod test { #[test] fn next_single_actor_states() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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); @@ -1011,7 +1002,7 @@ mod test { #[test] fn next_single_states() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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); @@ -1032,7 +1023,7 @@ mod test { #[test] fn double_state_init() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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); @@ -1046,7 +1037,7 @@ mod test { #[test] fn next_double_actor_states() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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); @@ -1077,7 +1068,7 @@ mod test { #[test] fn next_double_states() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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); @@ -1099,7 +1090,7 @@ mod test { #[test] fn special_double_stats() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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 { @@ -1141,7 +1132,7 @@ mod test { #[test] fn double_long_run() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(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 { @@ -1167,7 +1158,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(1707); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day17/mod.rs b/src/days/day17/mod.rs index b64a7d6..07f954a 100644 --- a/src/days/day17/mod.rs +++ b/src/days/day17/mod.rs @@ -1,6 +1,6 @@ use std::{cell::Cell, marker::PhantomData, ops::Index}; -use crate::common::file::{read_string, split_lines}; +use crate::common::file::read_lines; use super::template::{DayTrait, ResultType}; use itertools::Itertools; @@ -16,28 +16,16 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let pushes = Dispenser::new( - lines - .trim_end() - .chars() - .map(|c| Push::parse(c)) - .try_collect()?, - )?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?); let result = Day::run(pushes, 2022)?; Ok(ResultType::Integer(result as i64)) } - fn part2(&self, lines: &str) -> anyhow::Result { - let pushes = Dispenser::new( - lines - .trim_end() - .chars() - .map(|c| Push::parse(c)) - .try_collect()?, - )?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?); let result = Day::run(pushes, 1000000000000)?; @@ -47,8 +35,8 @@ impl DayTrait for Day { impl Day { fn run(push_cycle: Dispenser, max_cycles: i64) -> Result { - let raw = read_string(DAY_NUMBER, "blocks.txt")?; - let rock_cycle = Dispenser::new(Rock::parse(&raw)?)?; + let raw = read_lines(DAY_NUMBER, "blocks.txt")?; + let rock_cycle = Dispenser::new(Rock::parse(&raw)?); let mut cycle = 0; let mut stack = Stack::new(); @@ -126,9 +114,6 @@ enum RockError { #[error("Unknown direction: {0}")] UnknownDirection(char), - - #[error("A Dispenser must never be empty")] - EmptyDispenserNotAllowed, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -173,8 +158,9 @@ impl Rock { }) } - pub fn parse(lines: &str) -> Result, RockError> { - split_lines(lines) + pub fn parse(lines: &[String]) -> Result, RockError> { + lines + .iter() .batching(|it| { let blocks = it .skip_while(|line| line.is_empty()) @@ -344,15 +330,11 @@ where } impl<'a, T> Dispenser<'a, T> { - pub fn new(data: Vec) -> Result { - if data.is_empty() { - Err(RockError::EmptyDispenserNotAllowed) - } else { - Ok(Dispenser { - data, - current: Cell::new(0), - _marker: PhantomData, - }) + pub fn new(data: Vec) -> Self { + Dispenser { + data, + current: Cell::new(0), + _marker: PhantomData, } } @@ -369,13 +351,13 @@ impl<'a, T> Dispenser<'a, T> { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(3068); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -386,7 +368,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(1514285714288); let result = day.part2(&lines)?; assert_eq!(result, expected); @@ -397,7 +379,7 @@ mod test { #[test] fn read_blocks() -> Result<()> { let day = Day {}; - let raw = read_string(day.get_day_number(), "blocks.txt")?; + let raw = read_lines(day.get_day_number(), "blocks.txt")?; let rocks = Rock::parse(&raw)?; let expected = Rock::new(vec![vec![true]; 4])?; assert_eq!(rocks[3], expected); @@ -414,16 +396,10 @@ mod test { #[test] fn drop_one() -> Result<()> { let day = Day {}; - 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 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 mut stack = Stack::new(); stack.one_rock(rocks.next(), &pushes); @@ -436,14 +412,8 @@ mod test { #[test] fn drop_some() -> Result<()> { let day = Day {}; - 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 lines = read_lines(day.get_day_number(), "example01.txt")?; + let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?); let result = Day::run(pushes, 10)?; diff --git a/src/days/day18/mod.rs b/src/days/day18/mod.rs index d5d339b..1885f3d 100644 --- a/src/days/day18/mod.rs +++ b/src/days/day18/mod.rs @@ -2,8 +2,6 @@ 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; @@ -15,13 +13,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { - let blob = Blob::try_from(lines)?; + fn part1(&self, lines: &[String]) -> anyhow::Result { + let blob = Blob::parse(lines)?; Ok(ResultType::Integer(blob.count_sides())) } - fn part2(&self, lines: &str) -> anyhow::Result { - let blob = Blob::try_from(lines)?; + fn part2(&self, lines: &[String]) -> anyhow::Result { + let blob = Blob::parse(lines)?; Ok(ResultType::Integer(blob.count_outside())) } } @@ -133,18 +131,15 @@ struct Blob { droplets: HashSet, } -impl TryFrom<&str> for Blob { - type Error = DropletError; - - fn try_from(lines: &str) -> Result { - let droplets = split_lines(lines) +impl Blob { + fn parse(lines: &[String]) -> Result { + let droplets = lines + .iter() .map(|line| Droplet::parse(line)) .try_collect()?; Ok(Blob { droplets }) } -} -impl Blob { fn extent(&self) -> Option { if self.droplets.is_empty() { return None; @@ -203,13 +198,13 @@ impl Blob { #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(64); let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -220,7 +215,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(58); let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/day19/mod.rs b/src/days/day19/mod.rs deleted file mode 100644 index 32bf5a4..0000000 --- a/src/days/day19/mod.rs +++ /dev/null @@ -1,587 +0,0 @@ -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 { - 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 { - 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>> for RobotError { - fn from(error: Err>) -> 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 { - 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 { - 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 for Ingredients { - type Output = i64; - - fn index(&self, index: Material) -> &Self::Output { - &self.0[index as usize] - } -} - -impl IndexMut 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 { - Ok(extract_result(Blueprint::parse)(input)?) - } -} - -struct Cabinet(Vec); - -impl FromStr for Cabinet { - type Err = RobotError; - - fn from_str(line: &str) -> Result { - let blueprints = extract_result(many0(preceded(multispace0, Blueprint::parse)))(line)?; - Ok(Cabinet(blueprints)) - } -} - -impl Cabinet { - pub fn quality_level(&self, max_time: usize) -> Result { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 = 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(()) - } -} diff --git a/src/days/day__/mod.rs b/src/days/day__/mod.rs index fe2759b..e67b4e2 100644 --- a/src/days/day__/mod.rs +++ b/src/days/day__/mod.rs @@ -1,7 +1,6 @@ use super::template::{DayTrait, ResultType}; -use thiserror::Error; -const DAY_NUMBER: usize = todo!(); +const DAY_NUMBER: usize = 0; pub struct Day; @@ -10,31 +9,25 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &str) -> anyhow::Result { + fn part1(&self, _lines: &[String]) -> anyhow::Result { Ok(ResultType::Nothing) } - fn part2(&self, lines: &str) -> anyhow::Result { + fn part2(&self, _lines: &[String]) -> anyhow::Result { Ok(ResultType::Nothing) } } -#[derive(Debug, Error)] -enum DayError { - #[error("Dummy")] - Dummy, -} - #[cfg(test)] mod test { use super::*; - use crate::common::file::read_string; + use crate::common::file::read_lines; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Nothing; let result = day.part1(&lines)?; assert_eq!(result, expected); @@ -45,7 +38,7 @@ mod test { #[test] fn test_part2() -> Result<()> { let day = Day {}; - let lines = read_string(day.get_day_number(), "example01.txt")?; + let lines = read_lines(day.get_day_number(), "example01.txt")?; let expected = ResultType::Nothing; let result = day.part2(&lines)?; assert_eq!(result, expected); diff --git a/src/days/mod.rs b/src/days/mod.rs index 80b70ea..d74c58b 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -16,7 +16,6 @@ mod day15; mod day16; mod day17; mod day18; -mod day19; mod template; pub use template::DayTrait; @@ -26,7 +25,7 @@ pub mod day_provider { use super::*; use thiserror::Error; - const MAX_DAY: usize = 19; + const MAX_DAY: usize = 18; pub fn get_day(day_num: usize) -> Result, ProviderError> { match day_num { @@ -48,7 +47,6 @@ 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)), } } diff --git a/src/days/template.rs b/src/days/template.rs index fe84e9a..73a779d 100644 --- a/src/days/template.rs +++ b/src/days/template.rs @@ -11,6 +11,6 @@ pub enum ResultType { pub trait DayTrait { fn get_day_number(&self) -> usize; - fn part1(&self, lines: &str) -> Result; - fn part2(&self, lines: &str) -> Result; + fn part1(&self, lines: &[String]) -> Result; + fn part2(&self, lines: &[String]) -> Result; } diff --git a/src/main.rs b/src/main.rs index 07a8ec7..7e90532 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod days; mod macros; use anyhow::Result; -use common::file::read_string; +use common::file::read_lines; 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: &str) -> Result { +fn run_part(day: &dyn DayTrait, is_part1: bool, lines: &[String]) -> Result { 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: &str) -> Result } fn run(day: &dyn DayTrait, part1: bool, part2: bool) -> Result { - let lines = read_string(day.get_day_number(), "input.txt")?; + let lines = read_lines(day.get_day_number(), "input.txt")?; let elapsed1 = if part1 { run_part(day, true, &lines)? } else {