This commit is contained in:
Joseph Montanaro 2021-12-28 15:14:08 -08:00
parent df55d78dec
commit dffb8a8673
3 changed files with 179 additions and 6 deletions

174
2021/src/day12.rs Normal file
View File

@ -0,0 +1,174 @@
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use color_eyre::eyre;
use eyre::eyre;
use crate::lib::IterExt;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum Size {
Big,
Small,
}
// this is a terrible way to do it, because it's completely tied to
// the dataset and needs to be redone in order to e.g. use a sample
// dataset. A better way would be to use [char; 2] to represent each
// cave and supply a method that computes size from letter case.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[allow(non_camel_case_types)]
enum Tag {
start,
de,
DN,
jv,
MU,
oa,
OC,
rj,
vd,
VP,
WK,
yb,
end,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Cave {
tag: Tag,
size: Size,
}
impl FromStr for Cave {
type Err = eyre::Report;
fn from_str(s: &str) -> eyre::Result<Self> {
let cave = match s {
"start" => Cave {tag: Tag::start, size: Size::Small},
"de" => Cave {tag: Tag::de, size: Size::Small},
"DN" => Cave {tag: Tag::DN, size: Size::Big},
"jv" => Cave {tag: Tag::jv, size: Size::Small},
"MU" => Cave {tag: Tag::MU, size: Size::Big},
"oa" => Cave {tag: Tag::oa, size: Size::Small},
"OC" => Cave {tag: Tag::OC, size: Size::Big},
"rj" => Cave {tag: Tag::rj, size: Size::Small},
"vd" => Cave {tag: Tag::vd, size: Size::Small},
"VP" => Cave {tag: Tag::VP, size: Size::Big},
"WK" => Cave {tag: Tag::WK, size: Size::Big},
"yb" => Cave {tag: Tag::yb, size: Size::Small},
"end" => Cave {tag: Tag::end, size: Size::Small},
_ => return Err(eyre!("Invalid cave tag: {}", s)),
};
Ok(cave)
}
}
type CaveMap = HashMap<Tag, Vec<Cave>>;
struct Path {
order: Vec<Cave>,
visited: HashSet<Cave>,
doubled: Option<Cave>
}
impl Path {
fn new() -> Self {
Path {
order: vec![Cave {tag: Tag::start, size: Size::Small}],
visited: HashSet::new(),
doubled: None,
}
}
fn push(&mut self, cave: Cave) {
self.order.push(cave);
if cave.size == Size::Small {
let inserted = self.visited.insert(cave);
if !inserted { // value was already present in set
self.doubled = Some(cave);
}
}
}
fn pop(&mut self) {
let cave = self.order.pop().unwrap();
if cave.size == Size::Small {
match self.doubled {
Some(d) if d == cave => {self.doubled = None},
_ => {self.visited.remove(&cave);}
}
}
}
fn last(&self) -> Option<&Cave> {
self.order.last()
}
}
fn load(data: &str) -> eyre::Result<CaveMap> {
let mut map = HashMap::new();
for line in data.lines() {
let (a, b) = line.split('-').take_pair()?;
let cave_a = a.parse::<Cave>()?;
let cave_b = b.parse::<Cave>()?;
let neighbors_a = map.entry(cave_a.tag).or_insert_with(|| vec![]);
neighbors_a.push(cave_b);
let neighbors_b = map.entry(cave_b.tag).or_insert_with(|| vec![]);
neighbors_b.push(cave_a);
}
Ok(map)
}
fn can_visit_part1(path: &Path, cave: &Cave) -> bool {
if cave.size == Size::Big {true}
else if cave.tag == Tag::start {false}
else if path.visited.contains(cave) {false}
else {true}
}
fn can_visit_part2(path: &Path, cave: &Cave) -> bool {
if cave.size == Size::Big {true}
else if cave.tag == Tag::start {false}
else if path.visited.contains(cave) && path.doubled.is_some() {false}
else {true}
}
fn search<F>(map: &CaveMap, path: &mut Path, can_visit: F) -> Vec<Vec<Cave>>
where F: Fn(&Path, &Cave) -> bool + Copy
{
let mut result = Vec::new();
for neighbor in &map[&path.last().unwrap().tag] {
if neighbor.tag == Tag::end {
path.push(*neighbor);
result.push(path.order.clone());
path.pop();
}
else if can_visit(path, neighbor) {
path.push(*neighbor);
let child_paths = search(map, path, can_visit);
result.extend(child_paths);
path.pop();
}
}
result
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let map = load(data)?;
let one = search(&map, &mut Path::new(), can_visit_part1);
let two = search(&map, &mut Path::new(), can_visit_part2);
Ok((one.len(), two.len()))
}

View File

@ -133,9 +133,8 @@ impl Iterator for NeighborCoords {
let row_offset = self.start_row + rel_row; let row_offset = self.start_row + rel_row;
let col_offset = self.start_col + rel_col; let col_offset = self.start_col + rel_col;
// the "neighbor window" is off by 1, so we make the comparisons off by 1 as well // the "neighbor window" is off by 1, so we make the comparisons off by 1 as well
if if row_offset > 0 && row_offset <= self.num_rows
(row_offset > 0 && row_offset <= self.num_rows && col_offset > 0 && col_offset <= self.num_cols
&& col_offset > 0 && col_offset <= self.num_cols)
{ {
Some((row_offset - 1, col_offset - 1)) Some((row_offset - 1, col_offset - 1))
} }

View File

@ -4,14 +4,14 @@ use color_eyre::eyre;
mod lib; mod lib;
use lib::load; use lib::load;
mod day11; mod day12;
fn main() -> eyre::Result<()> { fn main() -> eyre::Result<()> {
let data = load("data/11.txt")?; let data = load("data/12.txt")?;
let start = Instant::now(); let start = Instant::now();
let (one, two) = day11::run(&data)?; let (one, two) = day12::run(&data)?;
let (dur, unit) = format_ns(start.elapsed().as_nanos()); let (dur, unit) = format_ns(start.elapsed().as_nanos());
let precision = 2.0 - dur.log10().floor(); let precision = 2.0 - dur.log10().floor();