use std::error::Error; use crate::rules::Rule; use crate::utils::GenericError; #[path = "../utils/mod.rs"] mod utils; fn main() -> Result<(), Box<dyn Error>> { let rules = utils::parse_input_lines::<Rule>()?; let count = rules::count_bags_which_contain(&rules, String::from("shiny gold")); println!("Amount of bags which contain a 'shiny gold' bag: {}", count); Ok(()) } mod rules { use std::collections::{HashMap, HashSet}; use std::str::FromStr; use crate::GenericError; pub struct Rule { bag: String, contains: HashMap<String, u16>, } pub fn count_bags_which_contain(rules: &Vec<Rule>, target_bag: String) -> usize { let mut containers = HashSet::new(); let mut queue = HashSet::new(); queue.insert(&target_bag); while !queue.is_empty() { for bag in queue.drain().collect::<Vec<&String>>() { for rule in rules { if rule.contains.contains_key(bag) && containers.insert(&rule.bag) { queue.insert(&rule.bag); } } } } return containers.len(); } impl FromStr for Rule { type Err = GenericError; fn from_str(s: &str) -> Result<Self, GenericError> { fn validate_bag(bag: &str) -> Result<String, GenericError> { return if bag.chars().filter(|c| *c == ' ').count() != 1 { Err(GenericError::new(format!("Invalid bag name: {}", bag))) } else { Ok(bag.to_string()) }; } let (bag, contains_str) = s.split_once(" bags contain ").ok_or(GenericError::new("Missing ' bags contain ' delimiter."))?; let mut rule = Rule { bag: validate_bag(bag)?, contains: HashMap::new(), }; if contains_str == "no other bags." { return Ok(rule); } fn strip<'a>(s: &'a str, suffix: &str) -> &'a str { s.strip_suffix(suffix).unwrap_or(s) } for entry_str in contains_str.split(", ") { let (count_str, contained_bag_str) = entry_str.split_once(' ').ok_or_else(|| GenericError::new(format!("Missing count in entry: {}", entry_str)))?; let count = count_str.parse::<u16>().map_err(|_| GenericError::new(format!("Cannot parse count: {}", count_str)))?; let contained_bag_str = strip(contained_bag_str, "."); let contained_bag_str = strip(contained_bag_str, " bags"); let contained_bag_str = strip(contained_bag_str, " bag"); rule.contains.insert(validate_bag(contained_bag_str)?, count); } return Ok(rule); } } }