From a5f19ecae1368ce0add6e0037f970270effdc165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ludwig?= Date: Sat, 29 Jul 2023 17:02:25 +0200 Subject: [PATCH 1/3] Made everything a bit more rustic --- src/common/file.rs | 11 +++-- src/days/day01/mod.rs | 22 ++++++---- src/days/day02/mod.rs | 18 ++++---- src/days/day03/mod.rs | 22 +++++----- src/days/day04/mod.rs | 18 ++++---- src/days/day05/mod.rs | 44 +++++++++++--------- src/days/day06/mod.rs | 14 +++---- src/days/day07/mod.rs | 26 +++++++----- src/days/day08/mod.rs | 47 +++++++++++---------- src/days/day09/mod.rs | 21 +++++----- src/days/day10/mod.rs | 25 +++++++----- src/days/day11/mod.rs | 37 ++++++++++------- src/days/day12/mod.rs | 31 ++++++++------ src/days/day13/mod.rs | 21 +++++----- src/days/day14/mod.rs | 95 +++++++++++++++++++++++-------------------- src/days/day15/mod.rs | 25 ++++++++---- src/days/day16/mod.rs | 37 +++++++++-------- src/days/day17/mod.rs | 80 ++++++++++++++++++++++++------------ src/days/day18/mod.rs | 27 +++++++----- src/days/day__/mod.rs | 19 ++++++--- src/days/template.rs | 4 +- src/main.rs | 6 +-- 22 files changed, 376 insertions(+), 274 deletions(-) diff --git a/src/common/file.rs b/src/common/file.rs index 5712052..3c5b86c 100644 --- a/src/common/file.rs +++ b/src/common/file.rs @@ -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> { - Ok(fs::read_to_string(format_path(day_num, 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 .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()) } diff --git a/src/days/day01/mod.rs b/src/days/day01/mod.rs index 0bda729..6419b99 100644 --- a/src/days/day01/mod.rs +++ b/src/days/day01/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> anyhow::Result { + 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 { + fn part2(&self, lines: &str) -> anyhow::Result { + 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, CalorieError> { + fn parse<'a>(lines: impl Iterator) -> Result, 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(()) diff --git a/src/days/day02/mod.rs b/src/days/day02/mod.rs index 73fc25e..9f19241 100644 --- a/src/days/day02/mod.rs +++ b/src/days/day02/mod.rs @@ -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 { - let sum = lines - .iter() + fn part1(&self, lines: &str) -> anyhow::Result { + let sum = split_lines(lines) .map(|line| Rps::parse_line(line)) .collect::, _>>()? .into_iter() @@ -24,9 +25,8 @@ impl DayTrait for Day { Ok(ResultType::Integer(sum)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let sum = lines - .iter() + fn part2(&self, lines: &str) -> anyhow::Result { + let sum = split_lines(lines) .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_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); diff --git a/src/days/day03/mod.rs b/src/days/day03/mod.rs index f263f63..a86653c 100644 --- a/src/days/day03/mod.rs +++ b/src/days/day03/mod.rs @@ -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 { - let sum = lines - .iter() + fn part1(&self, lines: &str) -> anyhow::Result { + 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 { - let sum = lines - .iter() + fn part2(&self, lines: &str) -> anyhow::Result { + let sum = split_lines(lines) .chunks(3) .into_iter() - .map(|chunk| find_badge(&chunk.collect::>())) + .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 { Err(RucksackError::NoDoubleFound)? } -fn find_badge(elves: &[&String]) -> Result { +fn find_badge(elves: &[&str]) -> Result { if elves.len() < 2 { return Err(RucksackError::NeedAtLeastTwo); }; @@ -92,7 +92,7 @@ fn find_badge(elves: &[&String]) -> Result { #[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); diff --git a/src/days/day04/mod.rs b/src/days/day04/mod.rs index 2696dc7..62a84a9 100644 --- a/src/days/day04/mod.rs +++ b/src/days/day04/mod.rs @@ -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 { - let sum = lines - .iter() + fn part1(&self, lines: &str) -> anyhow::Result { + 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 { - let sum = lines - .iter() + fn part2(&self, lines: &str) -> anyhow::Result { + 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); diff --git a/src/days/day05/mod.rs b/src/days/day05/mod.rs index 98a292f..57429eb 100644 --- a/src/days/day05/mod.rs +++ b/src/days/day05/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> anyhow::Result { 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 { + fn part2(&self, lines: &str) -> anyhow::Result { 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> { .collect() } -fn parse_all_crates(line_it: &mut dyn Iterator) -> Result, CrateError> { - let rows: Vec<_> = line_it +fn parse_all_crates<'a>( + line_it: &mut dyn Iterator, +) -> Result, 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) -> Result Result<(Vec, Vec), CrateError> { - let mut iter = line.iter(); +fn parse<'a>(lines: &'a str) -> Result<(Vec, Vec), 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::>()?; 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); diff --git a/src/days/day06/mod.rs b/src/days/day06/mod.rs index 7a04a3f..6a4bc51 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: &[String]) -> anyhow::Result { - let count = find_marker(&lines[0], 4)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let count = find_marker(lines.trim_end(), 4)?; Ok(ResultType::Integer(count as i64)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let count = find_marker(&lines[0], 14)?; + fn part2(&self, lines: &str) -> anyhow::Result { + 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 { #[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); diff --git a/src/days/day07/mod.rs b/src/days/day07/mod.rs index 8c4c791..beb77c6 100644 --- a/src/days/day07/mod.rs +++ b/src/days/day07/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> anyhow::Result { + 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 { + fn part2(&self, lines: &str) -> 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 @@ -167,7 +171,7 @@ impl Directory { sub_node.map(|node| Directory::create(node.clone())) } - pub fn parse(lines: &[String]) -> Result { + pub fn parse<'a>(lines: impl Iterator) -> Result { 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); diff --git a/src/days/day08/mod.rs b/src/days/day08/mod.rs index 77b5fcc..aa671ea 100644 --- a/src/days/day08/mod.rs +++ b/src/days/day08/mod.rs @@ -1,3 +1,5 @@ +use crate::common::file::split_lines; + use super::template::{DayTrait, ResultType}; use itertools::{iproduct, FoldWhile, Itertools}; use thiserror::Error; @@ -11,14 +13,14 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &[String]) -> anyhow::Result { - let forest = Forest::parse(lines)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let forest = Forest::try_from(lines)?; let result = forest.count_visible(); Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let forest = Forest::parse(lines)?; + fn part2(&self, lines: &str) -> anyhow::Result { + let forest = Forest::try_from(lines)?; let result = forest.best_score(); Ok(ResultType::Integer(result)) } @@ -67,18 +69,6 @@ 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]; @@ -191,17 +181,32 @@ impl Forest { } } +impl TryFrom<&str> for Forest { + type Error = ForestError; + + fn try_from(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_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::try_from(lines.as_str())?; assert_eq!(forest.width, 5); assert_eq!(forest.height, 5); @@ -211,7 +216,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 +227,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); diff --git a/src/days/day09/mod.rs b/src/days/day09/mod.rs index 533acf4..7f11710 100644 --- a/src/days/day09/mod.rs +++ b/src/days/day09/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> 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: &[String]) -> anyhow::Result { + fn part2(&self, lines: &str) -> anyhow::Result { 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, RopeError> { - lines.iter().map(parse_line).collect::>() +fn parse_commands(lines: &str) -> Result, RopeError> { + split_lines(lines).map(parse_line).try_collect() } -fn parse_line(line: &String) -> Result<(Direction, usize), RopeError> { - match line.split_whitespace().collect::>()[..] { +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); diff --git a/src/days/day10/mod.rs b/src/days/day10/mod.rs index 13bfdcf..255231b 100644 --- a/src/days/day10/mod.rs +++ b/src/days/day10/mod.rs @@ -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 { - let instructions = lines - .iter() + fn part1(&self, lines: &str) -> anyhow::Result { + let instructions = split_lines(lines) .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: &[String]) -> anyhow::Result { - let instructions = lines - .iter() + fn part2(&self, lines: &str) -> anyhow::Result { + let instructions = split_lines(lines) .map(|line| Instruction::parse(line)) .collect::, _>>()?; 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); diff --git a/src/days/day11/mod.rs b/src/days/day11/mod.rs index 2e8b3c7..058cb57 100644 --- a/src/days/day11/mod.rs +++ b/src/days/day11/mod.rs @@ -1,3 +1,5 @@ +use crate::common::file::split_lines; + use super::template::{DayTrait, ResultType}; use once_cell::sync::Lazy; use regex::Regex; @@ -13,13 +15,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &[String]) -> anyhow::Result { - let troop = Troop::parse(lines)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let troop = Troop::try_from(lines)?; Ok(ResultType::Integer(troop.play(20))) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let troop = Troop::parse(lines)?; + fn part2(&self, lines: &str) -> anyhow::Result { + let troop = Troop::try_from(lines)?; Ok(ResultType::Integer(troop.play_again(10_000))) } } @@ -90,7 +92,7 @@ impl Monkey { } pub fn parse_one( - iterator: &mut dyn Iterator, + iterator: &mut dyn Iterator, ) -> Result, MonkeyError> { if let Some(line) = iterator.next() { let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?; @@ -142,16 +144,20 @@ struct Troop { monkeys: Vec, } -impl Troop { - pub fn parse(lines: &[String]) -> Result { - let mut iter = lines.iter(); +impl TryFrom<&str> for Troop { + type Error = MonkeyError; + + fn try_from(value: &str) -> Result { + let mut iter = split_lines(value); let mut monkeys = Vec::new(); while let Some(monkey) = Monkey::parse_one(&mut iter)? { monkeys.push(monkey); } Ok(Troop { monkeys }) } +} +impl Troop { fn do_play(&self, rounds: usize, alter: F) -> i64 where F: Fn(i64) -> i64, @@ -191,13 +197,14 @@ 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 mut lines = split_lines(&lines); let expected = Monkey { number: 0, items: vec![79, 98], @@ -206,7 +213,7 @@ mod test { good_monkey: 2, bad_monkey: 3, }; - let result = Monkey::parse_one(&mut lines.iter())?.unwrap(); + let result = Monkey::parse_one(&mut lines)?.unwrap(); assert_eq!(result, expected); Ok(()) @@ -215,9 +222,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::try_from(lines.as_str())?; assert_eq!(result.monkeys.len(), expected); Ok(()) @@ -226,7 +233,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 +244,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); diff --git a/src/days/day12/mod.rs b/src/days/day12/mod.rs index c3f6ab1..1c099d0 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::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 { - let valley = Valley::parse(lines)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let valley = Valley::try_from(lines)?; Ok(ResultType::Integer(valley.walk()? as i64)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let valley = Valley::parse(lines)?; + fn part2(&self, lines: &str) -> anyhow::Result { + 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 { +impl TryFrom<&str> for Valley { + type Error = ValleyError; + + fn try_from(lines: &str) -> Result { + 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) -> 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); diff --git a/src/days/day13/mod.rs b/src/days/day13/mod.rs index 0e33a74..f6b146a 100644 --- a/src/days/day13/mod.rs +++ b/src/days/day13/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> anyhow::Result { 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 { + fn part2(&self, lines: &str) -> anyhow::Result { 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, PacketError> { - lines - .iter() + pub fn parse_all(lines: &str) -> Result, PacketError> { + split_lines(lines) .filter(|line| !line.is_empty()) .map(|line| Packets::parse(line)) .collect::, _>>() @@ -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); diff --git a/src/days/day14/mod.rs b/src/days/day14/mod.rs index e8ace33..56a1e02 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::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 { - let cave = Cave::parse(lines)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let cave = Cave::try_from(lines)?; Ok(ResultType::Integer(cave.drop_bottomless() as i64)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let cave = Cave::parse(lines)?; + fn part2(&self, lines: &str) -> anyhow::Result { + 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 { + 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 { @@ -118,41 +158,6 @@ 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(); @@ -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); diff --git a/src/days/day15/mod.rs b/src/days/day15/mod.rs index 28e4ee4..8292730 100644 --- a/src/days/day15/mod.rs +++ b/src/days/day15/mod.rs @@ -1,5 +1,5 @@ use super::template::{DayTrait, ResultType}; -use crate::common::pos::Pos; +use crate::common::{file::split_lines, pos::Pos}; use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; @@ -15,14 +15,22 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &[String]) -> anyhow::Result { + fn part1(&self, lines: &str) -> anyhow::Result { + let lines = split_lines(lines).collect_vec(); + if lines.len() < 2 { + Err(SensorError::ToFewLines)?; + } let row = lines[0].parse()?; let (sensors, beacons) = Day::parse_all(&lines[1..])?; let result = Day::count_coverage_at(&sensors, &beacons, row); Ok(ResultType::Integer(result)) } - fn part2(&self, lines: &[String]) -> anyhow::Result { + fn part2(&self, lines: &str) -> anyhow::Result { + let lines = split_lines(lines).collect_vec(); + if lines.len() < 2 { + Err(SensorError::ToFewLines)?; + } let (sensors, _) = Day::parse_all(&lines[1..])?; let mid_lines = Day::dividing_lines(&sensors); let points = mid_lines @@ -43,7 +51,7 @@ impl DayTrait for Day { } impl Day { - fn parse_all(lines: &[String]) -> Result<(HashSet, HashSet>), SensorError> { + fn parse_all(lines: &[&str]) -> Result<(HashSet, HashSet>), SensorError> { let mut sensors = HashSet::new(); let mut beacons = HashSet::new(); for line in lines { @@ -91,6 +99,9 @@ enum SensorError { #[error("Did not find exactly one point for the sensor: {0}")] NotExactlyOneResult(usize), + + #[error("Too few lines in input.txt")] + ToFewLines, } #[derive(Debug)] @@ -256,7 +267,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<()> { @@ -295,7 +306,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 +317,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); diff --git a/src/days/day16/mod.rs b/src/days/day16/mod.rs index 1e7cfb1..67dd832 100644 --- a/src/days/day16/mod.rs +++ b/src/days/day16/mod.rs @@ -5,6 +5,8 @@ use std::iter::Sum; use std::ops::{Add, Deref, Mul, Sub}; use std::{collections::BinaryHeap, num::ParseIntError}; +use crate::common::file::split_lines; + use super::template::{DayTrait, ResultType}; use const_format::concatcp; use itertools::Itertools; @@ -21,13 +23,13 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, lines: &[String]) -> anyhow::Result { + fn part1(&self, lines: &str) -> 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: &[String]) -> anyhow::Result { + fn part2(&self, lines: &str) -> anyhow::Result { let system = ValveSystem::build(lines, "AA")?; let result = system.maximum_flow(system.double_actor(Time(26)))?; Ok(ResultType::Integer(*result)) @@ -264,10 +266,9 @@ struct ValveSystem { } impl ValveSystem { - fn build(lines: &[String], start: &str) -> Result { - let mut raw = lines - .iter() - .map(|line| RawValve::try_from(line.as_str())) + fn build(lines: &str, start: &str) -> Result { + let mut raw = split_lines(lines) + .map(|line| RawValve::try_from(line)) .collect::, _>>()?; raw.sort_unstable_by_key(|valve| valve.id); @@ -912,13 +913,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); @@ -949,7 +950,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 +967,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 +981,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 +1003,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 +1024,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 +1038,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 +1069,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 +1091,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 +1133,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 +1159,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); diff --git a/src/days/day17/mod.rs b/src/days/day17/mod.rs index 07f954a..b64a7d6 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_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 { - let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?); + fn part1(&self, lines: &str) -> anyhow::Result { + 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 { - let pushes = Dispenser::new(lines[0].chars().map(|c| Push::parse(c)).try_collect()?); + fn part2(&self, lines: &str) -> anyhow::Result { + 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, max_cycles: i64) -> Result { - 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, RockError> { - lines - .iter() + pub fn parse(lines: &str) -> Result, 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) -> Self { - Dispenser { - data, - current: Cell::new(0), - _marker: PhantomData, + pub fn new(data: Vec) -> Result { + 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)?; diff --git a/src/days/day18/mod.rs b/src/days/day18/mod.rs index 1885f3d..d5d339b 100644 --- a/src/days/day18/mod.rs +++ b/src/days/day18/mod.rs @@ -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 { - let blob = Blob::parse(lines)?; + fn part1(&self, lines: &str) -> anyhow::Result { + let blob = Blob::try_from(lines)?; Ok(ResultType::Integer(blob.count_sides())) } - fn part2(&self, lines: &[String]) -> anyhow::Result { - let blob = Blob::parse(lines)?; + fn part2(&self, lines: &str) -> anyhow::Result { + let blob = Blob::try_from(lines)?; Ok(ResultType::Integer(blob.count_outside())) } } @@ -131,15 +133,18 @@ struct Blob { droplets: HashSet, } -impl Blob { - fn parse(lines: &[String]) -> Result { - let droplets = lines - .iter() +impl TryFrom<&str> for Blob { + type Error = DropletError; + + fn try_from(lines: &str) -> Result { + let droplets = split_lines(lines) .map(|line| Droplet::parse(line)) .try_collect()?; Ok(Blob { droplets }) } +} +impl Blob { fn extent(&self) -> Option { 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); diff --git a/src/days/day__/mod.rs b/src/days/day__/mod.rs index e67b4e2..fe2759b 100644 --- a/src/days/day__/mod.rs +++ b/src/days/day__/mod.rs @@ -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 { + fn part1(&self, lines: &str) -> anyhow::Result { Ok(ResultType::Nothing) } - fn part2(&self, _lines: &[String]) -> anyhow::Result { + fn part2(&self, lines: &str) -> anyhow::Result { 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); diff --git a/src/days/template.rs b/src/days/template.rs index 73a779d..fe84e9a 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: &[String]) -> Result; - fn part2(&self, lines: &[String]) -> Result; + fn part1(&self, lines: &str) -> Result; + fn part2(&self, lines: &str) -> Result; } diff --git a/src/main.rs b/src/main.rs index 7e90532..07a8ec7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 { +fn run_part(day: &dyn DayTrait, is_part1: bool, lines: &str) -> 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: &[String]) -> Result Result { - 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 { From b00835c25e24e12cf8bee89a2ed7f1d3d8c8f001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ludwig?= Date: Sun, 30 Jul 2023 16:42:17 +0200 Subject: [PATCH 2/3] switch to nom --- Cargo.toml | 4 +- src/common/mod.rs | 1 + src/common/parser.rs | 70 +++++++++++++++++ src/days/day08/mod.rs | 14 ++-- src/days/day11/mod.rs | 173 +++++++++++++++++++++++------------------- src/days/day15/mod.rs | 107 ++++++++++++++------------ src/days/day16/mod.rs | 100 +++++++++++++----------- 7 files changed, 288 insertions(+), 181 deletions(-) create mode 100644 src/common/parser.rs diff --git a/Cargo.toml b/Cargo.toml index cbeeee5..cf6e37d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/common/mod.rs b/src/common/mod.rs index 3c42571..3a2379d 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -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; diff --git a/src/common/parser.rs b/src/common/parser.rs new file mode 100644 index 0000000..fa6b022 --- /dev/null +++ b/src/common/parser.rs @@ -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>( + 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/day08/mod.rs b/src/days/day08/mod.rs index aa671ea..c031b2a 100644 --- a/src/days/day08/mod.rs +++ b/src/days/day08/mod.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use crate::common::file::split_lines; use super::template::{DayTrait, ResultType}; @@ -14,13 +16,13 @@ impl DayTrait for Day { } fn part1(&self, lines: &str) -> anyhow::Result { - let forest = Forest::try_from(lines)?; + let forest: Forest = lines.parse()?; let result = forest.count_visible(); Ok(ResultType::Integer(result)) } fn part2(&self, lines: &str) -> anyhow::Result { - let forest = Forest::try_from(lines)?; + let forest: Forest = lines.parse()?; let result = forest.best_score(); Ok(ResultType::Integer(result)) } @@ -181,10 +183,10 @@ impl Forest { } } -impl TryFrom<&str> for Forest { - type Error = ForestError; +impl FromStr for Forest { + type Err = ForestError; - fn try_from(lines: &str) -> Result { + fn from_str(lines: &str) -> Result { split_lines(lines) .map(|line| { line.chars() @@ -206,7 +208,7 @@ mod test { fn test_parse() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; - let forest = Forest::try_from(lines.as_str())?; + let forest: Forest = lines.parse()?; assert_eq!(forest.width, 5); assert_eq!(forest.height, 5); diff --git a/src/days/day11/mod.rs b/src/days/day11/mod.rs index 058cb57..ddb6f62 100644 --- a/src/days/day11/mod.rs +++ b/src/days/day11/mod.rs @@ -1,9 +1,19 @@ -use crate::common::file::split_lines; +use crate::common::parser::{ + empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false, +}; use super::template::{DayTrait, ResultType}; -use once_cell::sync::Lazy; -use regex::Regex; -use std::{iter::zip, num::ParseIntError}; +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{char, i64, u32}, + combinator::{map, value, verify}, + error::Error, + multi::{many1, separated_list1}, + sequence::{preceded, tuple}, + Err, IResult, Parser, +}; +use std::{iter::zip, str::FromStr}; use thiserror::Error; const DAY_NUMBER: usize = 11; @@ -16,50 +26,50 @@ impl DayTrait for Day { } fn part1(&self, lines: &str) -> anyhow::Result { - let troop = Troop::try_from(lines)?; + let troop: Troop = lines.parse()?; Ok(ResultType::Integer(troop.play(20))) } fn part2(&self, lines: &str) -> anyhow::Result { - let troop = Troop::try_from(lines)?; + let troop: Troop = lines.parse()?; Ok(ResultType::Integer(troop.play_again(10_000))) } } #[derive(Debug, Error)] enum MonkeyError { - #[error("Not an Integer")] - NotAnInteger(#[from] ParseIntError), - - #[error("Can't parse line: {0}")] - UnknownLine(String), - - #[error("Did not expect end of Input")] - PrematureEndOfInput, + #[error("Error while parsing: {0}")] + ParsingError(String), } -#[derive(Debug, PartialEq)] +impl From>> for MonkeyError { + fn from(value: Err>) -> 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::>()[..] { - ["*", "old"] => Operation::Squared, - ["*", value] => Operation::Times(value.parse().unwrap()), - ["+", value] => Operation::Plus(value.parse().unwrap()), - _ => unreachable!(), - } + fn parse(input: &str) -> IResult<&str, Self> { + let (input, _) = tag("new = old ")(input)?; + alt(( + value(Operation::Squared, tag("* old")), + preceded(char('*'), trim0(i64.map(|a| Operation::Times(a)))), + preceded(char('+'), trim0(i64.map(|a| Operation::Plus(a)))), + ))(input) } pub fn calc(&self, old: i64) -> i64 { match self { - Operation::Plus(value) => old + *value, - Operation::Times(value) => old * value, Operation::Squared => old.pow(2), + Operation::Times(value) => old * value, + Operation::Plus(value) => old + value, } } } @@ -74,90 +84,96 @@ struct Monkey { bad_monkey: usize, } -static MONKEY: Lazy = 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 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, - ) -> Result, 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> { + 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::()) - .collect::, _>>()?; + 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 { + Ok(extract_result(Monkey::parse)(lines)?) + } } struct Troop { monkeys: Vec, } -impl TryFrom<&str> for Troop { - type Error = MonkeyError; +impl FromStr for Troop { + type Err = MonkeyError; - fn try_from(value: &str) -> Result { - let mut iter = split_lines(value); - let mut monkeys = Vec::new(); - while let Some(monkey) = Monkey::parse_one(&mut iter)? { - monkeys.push(monkey); - } - Ok(Troop { monkeys }) + fn from_str(lines: &str) -> Result { + Ok(extract_result(Troop::parse)(lines)?) } } impl Troop { + fn parse(input: &str) -> IResult<&str, Troop> { + map(many1(Monkey::parse), |monkeys| Troop { monkeys })(input) + } + fn do_play(&self, rounds: usize, alter: F) -> i64 where F: Fn(i64) -> i64, @@ -204,7 +220,6 @@ mod test { fn test_parse() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; - let mut lines = split_lines(&lines); let expected = Monkey { number: 0, items: vec![79, 98], @@ -213,7 +228,7 @@ mod test { good_monkey: 2, bad_monkey: 3, }; - let result = Monkey::parse_one(&mut lines)?.unwrap(); + let result = Monkey::parse_one(&lines)?; assert_eq!(result, expected); Ok(()) @@ -224,7 +239,7 @@ mod test { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = 4; - let result = Troop::try_from(lines.as_str())?; + let result: Troop = lines.parse()?; assert_eq!(result.monkeys.len(), expected); Ok(()) diff --git a/src/days/day15/mod.rs b/src/days/day15/mod.rs index 8292730..81e69f8 100644 --- a/src/days/day15/mod.rs +++ b/src/days/day15/mod.rs @@ -1,37 +1,44 @@ use super::template::{DayTrait, ResultType}; -use crate::common::{file::split_lines, pos::Pos}; +use crate::common::{ + parser::{eol_terminated, extract_result, ignore, trim0}, + pos::Pos, +}; use itertools::Itertools; -use once_cell::sync::Lazy; -use regex::Regex; -use std::{collections::HashSet, num::ParseIntError}; +use nom::{ + bytes::complete::tag, + character::complete::{char, i64}, + error::Error, + multi::many0, + sequence::{separated_pair, tuple}, + Err, IResult, +}; +use std::collections::HashSet; use thiserror::Error; const DAY_NUMBER: usize = 15; pub struct Day; +fn parse_row(input: &str) -> IResult<&str, i64> { + eol_terminated(i64)(input) +} + impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } fn part1(&self, lines: &str) -> anyhow::Result { - let lines = split_lines(lines).collect_vec(); - if lines.len() < 2 { - Err(SensorError::ToFewLines)?; - } - let row = lines[0].parse()?; - let (sensors, beacons) = Day::parse_all(&lines[1..])?; + let (lines, row) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?; + let (sensors, beacons) = Day::parse_all(lines)?; + let result = Day::count_coverage_at(&sensors, &beacons, row); Ok(ResultType::Integer(result)) } fn part2(&self, lines: &str) -> anyhow::Result { - let lines = split_lines(lines).collect_vec(); - if lines.len() < 2 { - Err(SensorError::ToFewLines)?; - } - let (sensors, _) = Day::parse_all(&lines[1..])?; + let (lines, _) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?; + let (sensors, _) = Day::parse_all(lines)?; let mid_lines = Day::dividing_lines(&sensors); let points = mid_lines .iter() @@ -51,11 +58,12 @@ impl DayTrait for Day { } impl Day { - fn parse_all(lines: &[&str]) -> Result<(HashSet, HashSet>), SensorError> { + fn parse_all(lines: &str) -> Result<(HashSet, HashSet>), SensorError> { + let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?; + let mut sensors = HashSet::new(); let mut beacons = HashSet::new(); - for line in lines { - let (sensor, beacon) = Sensor::parse(line)?; + for (sensor, beacon) in data { sensors.insert(sensor); beacons.insert(beacon); } @@ -91,17 +99,20 @@ impl Day { #[derive(Debug, Error)] enum SensorError { - #[error("Not an Integer")] - NotAnInt(#[from] ParseIntError), - - #[error("Unknown line: {0}")] - UnknownLine(String), + #[error("Error Parsing Input: {0}")] + ParsingError(String), #[error("Did not find exactly one point for the sensor: {0}")] NotExactlyOneResult(usize), - #[error("Too few lines in input.txt")] - ToFewLines, + #[error("Row line not found at start of Input")] + RowLineNotFound, +} + +impl From>> for SensorError { + fn from(error: Err>) -> Self { + SensorError::ParsingError(error.to_string()) + } } #[derive(Debug)] @@ -220,28 +231,30 @@ struct Sensor { radius: i64, } -static SENSOR: Lazy = - Lazy::new(|| Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap()); - impl Sensor { - 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, - )) + 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 range_at(&self, y: i64) -> Option { @@ -279,7 +292,7 @@ mod test { }, Pos::new(-2, 15), ); - let result = Sensor::parse(input)?; + let result = extract_result(Sensor::parse)(input)?; assert_eq!(result, expected); Ok(()) diff --git a/src/days/day16/mod.rs b/src/days/day16/mod.rs index 67dd832..e068986 100644 --- a/src/days/day16/mod.rs +++ b/src/days/day16/mod.rs @@ -1,17 +1,20 @@ +use super::template::{DayTrait, ResultType}; +use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1}; +use itertools::Itertools; +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{alpha1, i64}, + error::Error, + multi::{many1, separated_list1}, + Err, IResult, +}; +use std::collections::BinaryHeap; use std::collections::HashMap; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::iter::Sum; use std::ops::{Add, Deref, Mul, Sub}; -use std::{collections::BinaryHeap, num::ParseIntError}; - -use crate::common::file::split_lines; - -use super::template::{DayTrait, ResultType}; -use const_format::concatcp; -use itertools::Itertools; -use once_cell::sync::Lazy; -use regex::Regex; use thiserror::Error; const DAY_NUMBER: usize = 16; @@ -129,11 +132,8 @@ impl Deref for Index { #[derive(Debug, Error)] enum ValveError { - #[error("Not an Integer")] - NotAnInt(#[from] ParseIntError), - #[error("Not a valid description: {0}")] - CouldNotParse(String), + ParsingError(String), #[error("Not a valid valve: {0}")] ValveNotFound(String), @@ -142,17 +142,11 @@ enum ValveError { NoOptimumFound, } -const ID: &str = "[[:alpha:]]+"; -const COMMON: &str = concatcp!("^Valve (?", ID, 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, ")$"); +impl From>> for ValveError { + fn from(error: Err>) -> Self { + ValveError::ParsingError(error.to_string()) + } +} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct RawValve<'a> { @@ -170,29 +164,45 @@ impl<'a> RawValve<'a> { } } - fn from_regex(regex: &Regex, line: &'a str) -> Option, ValveError>> { - regex.captures(line).map(|caps| { - match (caps.name("id"), caps.name("rate"), caps.name("exits")) { - (Some(id), Some(rate), Some(exits)) => { - let rate = rate.as_str().parse()?; - let neighbors = exits.as_str().split(",").map(|s| s.trim_start()).collect(); - Ok(RawValve::create(id.as_str(), Flow(rate), neighbors)) - } - _ => Err(ValveError::CouldNotParse(line.to_string())), - } - }) + fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> { + let input = ignore(tag("Valve"))(input)?; + let (input, valve) = trim1(alpha1)(input)?; + let input = ignore(tag("has flow rate="))(input)?; + let (input, flow) = trim0(i64)(input)?; + let input = ignore(tag(";"))(input)?; + Ok((input, (valve, Flow(flow)))) + } + + fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> { + let input = ignore(tag("tunnel leads to valve "))(input)?; + let (input, valve) = alpha1(input)?; + + Ok((input, vec![valve])) + } + + fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> { + let input = ignore(tag("tunnels lead to valves "))(input)?; + let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?; + + Ok((input, valve)) + } + + fn parse(input: &str) -> IResult<&str, RawValve> { + let (input, (id, flow)) = RawValve::parse_start(input)?; + let (input, neighbors) = trim_left1(eol_terminated(alt(( + RawValve::parse_plural, + RawValve::parse_singular, + ))))(input)?; + + Ok((input, RawValve::create(id, flow, neighbors))) } } impl<'a> TryFrom<&'a str> for RawValve<'a> { type Error = ValveError; - fn try_from(value: &'a str) -> Result, 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()))) + fn try_from(value: &'a str) -> Result { + Ok(extract_result(RawValve::parse)(value)?) } } @@ -267,9 +277,7 @@ struct ValveSystem { impl ValveSystem { fn build(lines: &str, start: &str) -> Result { - let mut raw = split_lines(lines) - .map(|line| RawValve::try_from(line)) - .collect::, _>>()?; + let mut raw = extract_result(many1(RawValve::parse))(lines)?; raw.sort_unstable_by_key(|valve| valve.id); let start_idx = raw @@ -931,7 +939,7 @@ mod test { fn parse_plural() -> Result<()> { let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA"; let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]); - let result = RawValve::try_from(line)?; + let result = line.try_into()?; assert_eq!(expected, result); Ok(()) @@ -941,7 +949,7 @@ mod test { fn parse_singular() -> Result<()> { let line = "Valve HH has flow rate=22; tunnel leads to valve GG"; let expected = RawValve::create("HH", Flow(22), vec!["GG"]); - let result = RawValve::try_from(line)?; + let result = line.try_into()?; assert_eq!(expected, result); Ok(()) From 062ede1df143dfba5f8453f42f287ae23d4a0c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ludwig?= Date: Wed, 2 Aug 2023 17:58:09 +0200 Subject: [PATCH 3/3] day 19 finished --- data/day19/example01.txt | 11 + data/day19/input.txt | 30 ++ src/days/day19/mod.rs | 587 +++++++++++++++++++++++++++++++++++++++ src/days/mod.rs | 4 +- 4 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 data/day19/example01.txt create mode 100644 data/day19/input.txt create mode 100644 src/days/day19/mod.rs diff --git a/data/day19/example01.txt b/data/day19/example01.txt new file mode 100644 index 0000000..6f9d45b --- /dev/null +++ b/data/day19/example01.txt @@ -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. \ No newline at end of file diff --git a/data/day19/input.txt b/data/day19/input.txt new file mode 100644 index 0000000..f8f652c --- /dev/null +++ b/data/day19/input.txt @@ -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. \ No newline at end of file diff --git a/src/days/day19/mod.rs b/src/days/day19/mod.rs new file mode 100644 index 0000000..32bf5a4 --- /dev/null +++ b/src/days/day19/mod.rs @@ -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 { + 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/mod.rs b/src/days/mod.rs index d74c58b..80b70ea 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -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, 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)), } }