Completed day 21

Code is not great... clone-city basically, but it gets the job done and I want
to move on.
This commit is contained in:
2020-12-29 23:06:19 -05:00
parent c248cb0bbd
commit fe08f0087a
5 changed files with 260 additions and 0 deletions

147
day21/src/main.rs Normal file
View File

@@ -0,0 +1,147 @@
#[macro_use]
extern crate lazy_static;
use anyhow::Result;
use regex::Regex;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::time::Instant;
const INPUT: &str = "input/input.txt";
lazy_static! {
static ref INGREDIENT_LIST: Regex =
Regex::new(r"(?P<ingredients>(?:\w+ )+)\(contains (?P<allergens>(:?\w+(:?, )?)+)\)")
.unwrap();
}
fn map_from_reader<R: BufRead>(
reader: R,
) -> Result<(HashMap<String, u32>, HashMap<String, HashSet<String>>)> {
let mut all_ingredients = HashMap::new();
let mut allergen_ingredient_counts = HashMap::new();
for line in reader.lines() {
let line = line?;
for caps in INGREDIENT_LIST.captures_iter(&line) {
let ingredients: HashSet<String> = caps["ingredients"]
.split(' ')
.filter_map(|s| {
if s.is_empty() {
None
} else {
Some(s.to_owned())
}
})
.collect();
let allergens: HashSet<String> = caps["allergens"]
.split(", ")
.filter_map(|s| {
if s.is_empty() {
None
} else {
Some(s.to_owned())
}
})
.collect();
for allergen in allergens.iter() {
match allergen_ingredient_counts.entry(allergen.clone()) {
Entry::Occupied(entry) => {
let entry: &mut HashSet<String> = entry.into_mut();
*entry = entry.intersection(&ingredients).cloned().collect();
}
Entry::Vacant(entry) => {
entry.insert(ingredients.iter().cloned().collect());
}
}
}
for ingredient in ingredients.into_iter() {
let entry = all_ingredients.entry(ingredient).or_insert(0);
*entry += 1;
}
}
}
Ok((all_ingredients, allergen_ingredient_counts))
}
fn solve_part1(input_path: &str) -> Result<u32> {
let file = File::open(input_path)?;
let reader = BufReader::new(file);
let (mut all_ingredients, mut allergen_ingredient_counts) = map_from_reader(reader)?;
let mut ingredient;
loop {
if let Some(ingredients) = allergen_ingredient_counts
.values_mut()
.find(|ingredients| ingredients.len() == 1)
{
ingredient = ingredients.drain().next().expect("non-empty set");
all_ingredients.remove(&ingredient);
for ingredients in allergen_ingredient_counts.values_mut() {
ingredients.remove(&ingredient);
}
} else {
break;
}
}
Ok(all_ingredients.values().sum())
}
fn solve_part2(input_path: &str) -> Result<String> {
let file = File::open(input_path)?;
let reader = BufReader::new(file);
let mut dangerous_ingredients = Vec::new();
let (_, mut allergen_ingredient_counts) = map_from_reader(reader)?;
let mut ingredient;
loop {
if let Some((allergen, ingredients)) = allergen_ingredient_counts
.iter_mut()
.find(|(_, ingredients)| ingredients.len() == 1)
{
let allergen = allergen.clone();
ingredient = ingredients.drain().next().expect("non-empty set");
for ingredients in allergen_ingredient_counts.values_mut() {
ingredients.remove(&ingredient);
}
dangerous_ingredients.push((allergen, ingredient));
} else {
break;
}
}
dangerous_ingredients.sort_unstable_by_key(|(allergen, _)| allergen.clone());
Ok(dangerous_ingredients
.iter()
.map(|(_, ingredient)| ingredient.as_str())
.collect::<Vec<&str>>()
.join(","))
}
fn main() {
let mut now = Instant::now();
println!("Part 1: {}", solve_part1(INPUT).unwrap());
println!("(elapsed: {:?})", now.elapsed());
now = Instant::now();
println!("");
println!("Part 2: {}", solve_part2(INPUT).unwrap());
println!("(elapsed: {:?})", now.elapsed());
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_INPUT: &str = "input/test.txt";
#[test]
fn solves_part1() {
assert_eq!(solve_part1(TEST_INPUT).unwrap(), 5);
}
#[test]
fn solves_part2() {
assert_eq!(
solve_part2(TEST_INPUT).unwrap(),
"mxmxvkd,sqjhc,fvjkl".to_string()
);
}
}