diff --git a/Cargo.toml b/Cargo.toml index 8f0de84..d4af544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" [dependencies] anyhow = "1.0" +const_format = "0.2.31" itertools = "0.11" lazy_static = "1.4" num-traits = "0.2" +once_cell = "1.18.0" regex = "1.7" thiserror = "1.0" diff --git a/src/common/file.rs b/src/common/file.rs index 1c6368c..5712052 100644 --- a/src/common/file.rs +++ b/src/common/file.rs @@ -9,9 +9,9 @@ pub fn read_lines(day_num: usize, file: &str) -> io::Result> { Ok(fs::read_to_string(format_path(day_num, file))? .split('\n') .with_position() - .filter_map(|line| match line { - (itertools::Position::Last, line) if line.is_empty() => None, - (_, line) => Some(line.to_string()), + .filter_map(|(pos, line)| match pos { + itertools::Position::Last if line.is_empty() => None, + _ => Some(line.to_owned()), }) .collect()) } diff --git a/src/days/day16/mod.rs b/src/days/day16/mod.rs index b8dedb1..1f5e13b 100644 --- a/src/days/day16/mod.rs +++ b/src/days/day16/mod.rs @@ -1,4 +1,10 @@ +use std::num::ParseIntError; + use super::template::{DayTrait, ResultType}; +use const_format::concatcp; +use once_cell::sync::Lazy; +use regex::Regex; +use thiserror::Error; const DAY_NUMBER: usize = 16; @@ -9,15 +15,114 @@ impl DayTrait for Day { DAY_NUMBER } - fn part1(&self, _lines: &[String]) -> anyhow::Result { + fn part1(&self, lines: &[String]) -> anyhow::Result { Ok(ResultType::Nothing) } - fn part2(&self, _lines: &[String]) -> anyhow::Result { + fn part2(&self, lines: &[String]) -> anyhow::Result { Ok(ResultType::Nothing) } } +#[derive(Debug, Error)] +enum ValveError { + #[error("Not an Integer")] + NotAnInt(#[from] ParseIntError), + + #[error("Not a valid valve: {0}")] + NotAValidValve(String), +} + +const ID: &str = "[[:alpha:]]+"; +const COMMON: &str = concatcp!("^Valve (?", ID, r") has flow rate=(?\d+); "); +const PLURAL_STR: &str = concatcp!( + COMMON, + "tunnels lead to valves (?", + ID, + "(?:, ", + ID, + ")+)$" +); +const SINGULAR_STR: &str = concatcp!(COMMON, "tunnel leads to valve (?", ID, ")$"); + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Valve { + id: String, + rate: i64, + distances: Vec<(String, i64)>, +} + +impl Valve { + fn from_regex(regex: &Regex, line: &str) -> Option> { + 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 distances = exits + .as_str() + .split(",") + .map(|s| (s.trim_start().to_string(), 1)) + .collect(); + Ok(Valve { + id: id.as_str().to_string(), + rate, + distances, + }) + } + _ => Err(ValveError::NotAValidValve(line.to_string())), + } + }) + } + + pub fn known_distances(&self) -> usize { + self.distances.len() + 1 + } + + pub fn steps_to(&self, to: &str) -> Option { + self.distances + .iter() + .find_map(|(other, distance)| if other == to { Some(*distance) } else { None }) + .or_else(|| (to == self.id).then_some(0)) + } +} + +impl TryFrom<&str> for Valve { + type Error = ValveError; + + fn try_from(value: &str) -> Result { + static PLURAL: Lazy = Lazy::new(|| Regex::new(PLURAL_STR).unwrap()); + static SINGULAR: Lazy = Lazy::new(|| Regex::new(SINGULAR_STR).unwrap()); + Valve::from_regex(&PLURAL, value) + .or_else(|| Valve::from_regex(&SINGULAR, value)) + .unwrap_or_else(|| Err(ValveError::NotAValidValve(value.to_string()))) + } +} + +struct System { + valves: Vec, +} + +impl System { + pub fn get_valve(&self, id: &str) -> Option<&Valve> { + self.valves.iter().find(|valve| valve.id == id) + } + + pub fn len(&self) -> usize { + self.valves.len() + } +} + +impl System { + fn build(lines: &[String]) -> Result { + Ok(System { + valves: lines + .iter() + .map(|line| Valve::try_from(line.as_str())) + .collect::, _>>()?, + }) + } +} + #[cfg(test)] mod test { use super::*; @@ -45,4 +150,32 @@ mod test { Ok(()) } + + #[test] + fn parse_plural() -> Result<()> { + let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA"; + let expected = Valve { + id: "BB".to_string(), + rate: 13, + distances: vec![("CC".to_string(), 1), ("AA".to_string(), 1)], + }; + let result = Valve::try_from(line)?; + assert_eq!(expected, result); + + Ok(()) + } + + #[test] + fn parse_singular() -> Result<()> { + let line = "Valve HH has flow rate=22; tunnel leads to valve GG"; + let expected = Valve { + id: "HH".to_string(), + rate: 22, + distances: vec![("GG".to_string(), 1)], + }; + let result = Valve::try_from(line)?; + assert_eq!(expected, result); + + Ok(()) + } }