~janbaudisch/aoc-2020

6e5cbaeadad46c790ecccfee2c1dd3e14e7356c9 — Jan Baudisch 6 months ago 02d7ae1
add day 7
5 files changed, 181 insertions(+), 1 deletions(-)

M Cargo.lock
M Cargo.toml
A day_07/Cargo.toml
A day_07/src/main.rs
A day_07/src/rule.rs
M Cargo.lock => Cargo.lock +7 -0
@@ 45,3 45,10 @@ version = "1.0.0"
dependencies = [
 "common",
]

[[package]]
name = "day_07"
version = "1.0.0"
dependencies = [
 "common",
]

M Cargo.toml => Cargo.toml +2 -1
@@ 6,7 6,8 @@ members = [
    "day_03",
    "day_04",
    "day_05",
    "day_06"
    "day_06",
    "day_07"
]

[profile.release]

A day_07/Cargo.toml => day_07/Cargo.toml +12 -0
@@ 0,0 1,12 @@
[package]
name = "day_07"
version = "1.0.0"
description = "Day 7 - Advent of Code 2020"
authors = ["Jan Baudisch <jan@baudisch.xyz>"]
license = "GPL-3.0-or-later"
readme = "../README.md"
edition = "2018"
workspace = ".."

[dependencies]
common = { path = "../common" }

A day_07/src/main.rs => day_07/src/main.rs +114 -0
@@ 0,0 1,114 @@
mod rule;

use common::input;
use rule::Rule;

fn main() {
    let rules: Vec<Rule> = input::read_lines().iter().map(|x| x.into()).collect();

    let count: u32 = rules
        .iter()
        .map(|x| can_contain(x, &rules, "shiny gold") as u32)
        .sum();

    println!("[PART ONE] possible number of outer bags: {}", count);

    let count = must_contain(&rules, "shiny gold");

    println!("[PART TWO] number of bags: {}", count);
}

fn can_contain(rule: &Rule, rules: &[Rule], color: &str) -> bool {
    if rule.contents.contains_key(color) {
        return true;
    }

    for (rule_color, _) in rule.clone().contents {
        if can_contain(
            rules.iter().find(|x| x.color == rule_color).unwrap(),
            rules,
            &color,
        ) {
            return true;
        }
    }

    false
}

fn must_contain(rules: &[Rule], color: &str) -> u32 {
    let mut count = 0;

    let rule = rules.iter().find(|x| x.color == color).unwrap();

    for (rule_color, rule_count) in rule.clone().contents {
        count += rule_count;
        count += must_contain(rules, &rule_color) * rule_count;
    }

    count
}

#[cfg(test)]
mod tests {
    use super::{can_contain, must_contain, Rule};

    fn generate_input1() -> Vec<&'static str> {
        let input = vec![
            "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.",
        ];

        input.into_iter().map(|x| x.into()).collect()
    }

    fn generate_input2() -> Vec<&'static str> {
        let input = vec![
            "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.",
        ];

        input.into_iter().map(|x| x.into()).collect()
    }

    fn parse_rules(input: Vec<&str>) -> Vec<Rule> {
        input.into_iter().map(|x| x.into()).collect()
    }

    #[test]
    fn part_one() {
        let rules = parse_rules(generate_input1());
        assert_eq!(
            rules
                .iter()
                .map(|x| can_contain(x, &rules, "shiny gold") as u32)
                .sum::<u32>(),
            4
        );
    }

    #[test]
    fn part_two() {
        assert_eq!(
            must_contain(&parse_rules(generate_input1()), "shiny gold"),
            32
        );

        assert_eq!(
            must_contain(&parse_rules(generate_input2()), "shiny gold"),
            126
        );
    }
}

A day_07/src/rule.rs => day_07/src/rule.rs +46 -0
@@ 0,0 1,46 @@
use std::collections::HashMap;

#[derive(Clone)]
pub struct Rule {
    pub color: String,
    pub contents: HashMap<String, u32>,
}

impl From<&str> for Rule {
    fn from(input: &str) -> Self {
        let mut words = input.split_whitespace();

        let mut color = words.next().unwrap().to_string();
        color.push(' ');
        color.push_str(words.next().unwrap());

        let _ = words.next();
        let _ = words.next();

        let mut contents = HashMap::new();

        while let Some(count) = words.next() {
            if count == "no" {
                break;
            }

            let count = u32::from_str_radix(count, 10).unwrap();

            let mut color = words.next().unwrap().to_string();
            color.push(' ');
            color.push_str(words.next().unwrap());

            contents.insert(color, count);

            let _ = words.next();
        }

        Self { color, contents }
    }
}

impl From<&String> for Rule {
    fn from(input: &String) -> Self {
        Rule::from(input.as_str())
    }
}