use std::collections::HashMap; use std::fs; use lazy_static::lazy_static; use regex::Regex; fn main() -> Result<(), Box> { let input = fs::read_to_string("input")?; let rules = parse_rules(&input)?; // Part 1 println!("{}", count_containing_bags(&rules, "shiny gold")); // Part 2 println!("{}", count_contained_bags(&rules, "shiny gold")); Ok(()) } fn parse_rule(input: &str) -> Result, std::num::ParseIntError> { // Rules look something like: // // bags?, bags?. // // or: // // no other bags. lazy_static! { static ref RULE_RE: Regex = Regex::new(r"(\d+) (.+?) bags?").expect("Should compile"); }; if let "no other bags." = input { Ok(HashMap::new()) } else { RULE_RE .captures_iter(input) .map(|captures| Ok((captures[2].to_string(), captures[1].parse::()?))) .collect() } } fn parse_rules(input: &str) -> Result>, String> { input .lines() .map(|line| { // Each line looks something like: // // bags contain // // So we simply split on " bags contain " and pass the // rest to the rule parsing function. // Note this will be a lot easier once str::split_once is stable let (key, rule) = { let temp: Vec<&str> = line.split(" bags contain ").collect(); if temp.len() != 2 { Err(format!("Invalid format for bag rule: {}", line)) } else { Ok((temp[0], temp[1])) } }?; Ok(( key, parse_rule(rule).map_err(|e| format!("Could not parse rule '{}': {}", rule, e))?, )) }) .collect() } fn count_containing_bags(rules: &HashMap<&str, HashMap>, color: &str) -> usize { fn contains_recursively( rules: &HashMap<&str, HashMap>, rule: &HashMap, color: &str, ) -> bool { // A bag contains the given color recursively, if either it // contains the color, or any bags it contains contain the bag // recursively rule.contains_key(color) || rule.keys().any(|bag| { if let Some(rule) = rules.get(bag.as_str()) { contains_recursively(rules, rule, color) } else { // The bag should always be in our index, but in // case it's not, we treat it as if it contains no other bags. false } }) } // Count from how many bags the given bag is reachable rules .iter() .filter(|(_, rule)| contains_recursively(rules, rule, color)) .count() } fn count_contained_bags(rules: &HashMap<&str, HashMap>, color: &str) -> usize { if let Some(rule) = rules.get(color) { // Count how many bags are reachable from this bag; except // multiply the numbers by the number of each bag contained rule.iter() .map(|(contained, number)| number + number * count_contained_bags(rules, contained)) .sum() } else { // The bag should always be in our index, but in case it's // not, we treat it as if it contains 0 other bags. 0 } } #[cfg(test)] mod tests { use super::*; use indoc::indoc; #[test] fn test_simple() -> Result<(), Box> { let input = indoc!( " light red bags contain 1 bright white bag, 2 muted yellow bags. dark orange bags contain 3 bright white bags, 4 muted yellow bags. bright white bags contain 1 shiny gold bag. muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. dark olive bags contain 3 faded blue bags, 4 dotted black bags. vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. faded blue bags contain no other bags. dotted black bags contain no other bags. " ); let rules = parse_rules(&input)?; assert_eq!(count_containing_bags(&rules, "shiny gold"), 4); Ok(()) } #[test] fn test_simple2() -> Result<(), Box> { let input = indoc!( " light red bags contain 1 bright white bag, 2 muted yellow bags. dark orange bags contain 3 bright white bags, 4 muted yellow bags. bright white bags contain 1 shiny gold bag. muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. dark olive bags contain 3 faded blue bags, 4 dotted black bags. vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. faded blue bags contain no other bags. dotted black bags contain no other bags. " ); let rules = parse_rules(&input)?; assert_eq!(count_contained_bags(&rules, "shiny gold"), 32); Ok(()) } #[test] fn test_simple3() -> Result<(), Box> { let input = indoc!( " shiny gold bags contain 2 dark red bags. dark red bags contain 2 dark orange bags. dark orange bags contain 2 dark yellow bags. dark yellow bags contain 2 dark green bags. dark green bags contain 2 dark blue bags. dark blue bags contain 2 dark violet bags. dark violet bags contain no other bags. " ); let rules = parse_rules(&input)?; assert_eq!(count_contained_bags(&rules, "shiny gold"), 126); Ok(()) } }