Compare commits
2 commits
c473ee4fc7
...
d73485111d
Author | SHA1 | Date | |
---|---|---|---|
d73485111d | |||
49cfe7a7cb |
63
src/day_12/mod.rs
Normal file
63
src/day_12/mod.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
mod model;
|
||||
mod parsing;
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
pub use model::{Cave, CaveSystem};
|
||||
pub use parsing::parse_cave_system;
|
||||
use yap::IntoTokens;
|
||||
|
||||
#[aoc_generator(day12)]
|
||||
pub fn parse_input(input: &str) -> CaveSystem {
|
||||
parse_cave_system(&mut input.into_tokens()).unwrap()
|
||||
}
|
||||
|
||||
#[aoc(day12, part1)]
|
||||
pub fn part1(input: &CaveSystem) -> usize {
|
||||
input.list_paths(false).len()
|
||||
}
|
||||
|
||||
#[aoc(day12, part2)]
|
||||
pub fn part2(input: &CaveSystem) -> usize {
|
||||
input.list_paths(true).len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
const EXAMPLE_INPUT: &str = "fs-end
|
||||
he-DX
|
||||
fs-he
|
||||
start-DX
|
||||
pj-DX
|
||||
end-zg
|
||||
zg-sl
|
||||
zg-pj
|
||||
pj-he
|
||||
RW-he
|
||||
fs-DX
|
||||
pj-RW
|
||||
zg-RW
|
||||
start-pj
|
||||
he-WI
|
||||
zg-he
|
||||
pj-fs
|
||||
start-RW";
|
||||
const RESULT_PART_1: usize = 226;
|
||||
const RESULT_PART_2: usize = 3509;
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
let result = super::part1(&super::parse_input(EXAMPLE_INPUT));
|
||||
assert_eq!(result, RESULT_PART_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
let result = super::part2(&super::parse_input(EXAMPLE_INPUT));
|
||||
assert_eq!(result, RESULT_PART_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_example() {
|
||||
let _example = super::parse_input(EXAMPLE_INPUT);
|
||||
}
|
||||
}
|
156
src/day_12/model.rs
Normal file
156
src/day_12/model.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub struct CaveSystem {
|
||||
pub adjacencies: HashMap<Cave, HashSet<Cave>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum Cave {
|
||||
Big(String),
|
||||
Small(String),
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
impl ToString for Cave {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Cave::Big(cave) => cave.clone(),
|
||||
Cave::Small(cave) => cave.clone(),
|
||||
Cave::Start => String::from("start"),
|
||||
Cave::End => String::from("end"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CaveSystem {
|
||||
pub fn list_paths(&self, may_visit_twice: bool) -> Vec<Vec<Cave>> {
|
||||
self.recurse_path(Cave::Start, vec![Cave::Start], may_visit_twice)
|
||||
}
|
||||
|
||||
fn recurse_path(
|
||||
&self,
|
||||
current: Cave,
|
||||
path: Vec<Cave>,
|
||||
may_visit_twice: bool,
|
||||
) -> Vec<Vec<Cave>> {
|
||||
let mut ret_val = Vec::new();
|
||||
for cave in self.adjacencies.get(¤t).unwrap_or(&HashSet::new()) {
|
||||
match cave {
|
||||
Cave::Big(_) => {
|
||||
let mut path = path.clone();
|
||||
path.push(cave.clone());
|
||||
ret_val.append(&mut self.recurse_path(cave.clone(), path, may_visit_twice));
|
||||
}
|
||||
Cave::Small(_) => {
|
||||
let mut may_visit_twice = may_visit_twice;
|
||||
if path.contains(cave) {
|
||||
if may_visit_twice {
|
||||
may_visit_twice = false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut path = path.clone();
|
||||
path.push(cave.clone());
|
||||
ret_val.append(&mut self.recurse_path(cave.clone(), path, may_visit_twice));
|
||||
}
|
||||
Cave::Start => continue,
|
||||
Cave::End => {
|
||||
let mut path = path.clone();
|
||||
path.push(cave.clone());
|
||||
ret_val.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret_val
|
||||
}
|
||||
|
||||
pub fn new(edges: Vec<[Cave; 2]>) -> Result<CaveSystem, &'static str> {
|
||||
let mut adjacencies: HashMap<Cave, HashSet<Cave>> = Default::default();
|
||||
for edge in edges {
|
||||
adjacencies
|
||||
.entry(edge[0].clone())
|
||||
.or_default()
|
||||
.insert(edge[1].clone());
|
||||
adjacencies
|
||||
.entry(edge[1].clone())
|
||||
.or_default()
|
||||
.insert(edge[0].clone());
|
||||
}
|
||||
if !adjacencies.contains_key(&Cave::Start) {
|
||||
Err("Cave system does not contain start cave")
|
||||
} else if !adjacencies.contains_key(&Cave::End) {
|
||||
Err("Cave system does not contain end cave")
|
||||
} else {
|
||||
Ok(CaveSystem { adjacencies })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[test]
|
||||
fn small_example_part_2() {
|
||||
let input: super::CaveSystem = super::super::parse_input(
|
||||
"start-A
|
||||
start-b
|
||||
A-c
|
||||
A-b
|
||||
b-d
|
||||
A-end
|
||||
b-end",
|
||||
);
|
||||
let paths: HashSet<String> = input
|
||||
.list_paths(true)
|
||||
.iter()
|
||||
.map(|path| path.iter().map(|cave| cave.to_string()).join(","))
|
||||
.collect();
|
||||
|
||||
let expected_paths: HashSet<String> = "start,A,b,A,b,A,c,A,end
|
||||
start,A,b,A,b,A,end
|
||||
start,A,b,A,b,end
|
||||
start,A,b,A,c,A,b,A,end
|
||||
start,A,b,A,c,A,b,end
|
||||
start,A,b,A,c,A,c,A,end
|
||||
start,A,b,A,c,A,end
|
||||
start,A,b,A,end
|
||||
start,A,b,d,b,A,c,A,end
|
||||
start,A,b,d,b,A,end
|
||||
start,A,b,d,b,end
|
||||
start,A,b,end
|
||||
start,A,c,A,b,A,b,A,end
|
||||
start,A,c,A,b,A,b,end
|
||||
start,A,c,A,b,A,c,A,end
|
||||
start,A,c,A,b,A,end
|
||||
start,A,c,A,b,d,b,A,end
|
||||
start,A,c,A,b,d,b,end
|
||||
start,A,c,A,b,end
|
||||
start,A,c,A,c,A,b,A,end
|
||||
start,A,c,A,c,A,b,end
|
||||
start,A,c,A,c,A,end
|
||||
start,A,c,A,end
|
||||
start,A,end
|
||||
start,b,A,b,A,c,A,end
|
||||
start,b,A,b,A,end
|
||||
start,b,A,b,end
|
||||
start,b,A,c,A,b,A,end
|
||||
start,b,A,c,A,b,end
|
||||
start,b,A,c,A,c,A,end
|
||||
start,b,A,c,A,end
|
||||
start,b,A,end
|
||||
start,b,d,b,A,c,A,end
|
||||
start,b,d,b,A,end
|
||||
start,b,d,b,end
|
||||
start,b,end"
|
||||
.lines()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
dbg!(paths.symmetric_difference(&expected_paths));
|
||||
assert_eq!(paths, expected_paths);
|
||||
}
|
||||
}
|
72
src/day_12/parsing.rs
Normal file
72
src/day_12/parsing.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use yap::Tokens;
|
||||
|
||||
use crate::parsing::{newline, parse_n};
|
||||
|
||||
use super::{Cave, CaveSystem};
|
||||
|
||||
pub fn parse_cave_system(tokens: &mut impl Tokens<Item = char>) -> Option<CaveSystem> {
|
||||
let edges = tokens.sep_by(|t| parse_edge(t), |t| newline(t)).collect();
|
||||
CaveSystem::new(edges).ok()
|
||||
}
|
||||
|
||||
pub fn parse_edge(tokens: &mut impl Tokens<Item = char>) -> Option<[Cave; 2]> {
|
||||
parse_n(tokens, &|t| parse_cave(t), &|t| t.token('-'))
|
||||
}
|
||||
|
||||
pub fn parse_cave(tokens: &mut impl Tokens<Item = char>) -> Option<Cave> {
|
||||
yap::one_of!(ts from tokens;
|
||||
parse_start(ts),
|
||||
parse_end(ts),
|
||||
parse_big(ts),
|
||||
parse_small(ts),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn parse_start(tokens: &mut impl Tokens<Item = char>) -> Option<Cave> {
|
||||
tokens.tokens("start".chars()).then(|| Cave::Start)
|
||||
}
|
||||
|
||||
pub fn parse_end(tokens: &mut impl Tokens<Item = char>) -> Option<Cave> {
|
||||
tokens.tokens("end".chars()).then(|| Cave::End)
|
||||
}
|
||||
|
||||
pub fn parse_big(tokens: &mut impl Tokens<Item = char>) -> Option<Cave> {
|
||||
let cave: String = tokens.tokens_while(|c| c.is_ascii_uppercase()).collect();
|
||||
(!cave.is_empty()).then(|| Cave::Big(cave))
|
||||
}
|
||||
|
||||
pub fn parse_small(tokens: &mut impl Tokens<Item = char>) -> Option<Cave> {
|
||||
let cave: String = tokens.tokens_while(|c| c.is_ascii_lowercase()).collect();
|
||||
(!cave.is_empty()).then(|| Cave::Small(cave))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use yap::IntoTokens;
|
||||
|
||||
use super::super::Cave;
|
||||
|
||||
#[test]
|
||||
fn parse_cave() {
|
||||
assert_eq!(
|
||||
super::parse_cave(&mut "start".into_tokens()),
|
||||
Some(Cave::Start)
|
||||
);
|
||||
assert_eq!(super::parse_cave(&mut "end".into_tokens()), Some(Cave::End));
|
||||
assert_eq!(
|
||||
super::parse_cave(&mut "a".into_tokens()),
|
||||
Some(Cave::Small(String::from("a")))
|
||||
);
|
||||
assert_eq!(
|
||||
super::parse_cave(&mut "BBBB".into_tokens()),
|
||||
Some(Cave::Big(String::from("BBBB")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_edge() {
|
||||
assert!(super::parse_edge(&mut "start-DX".into_tokens()).is_some());
|
||||
assert!(super::parse_edge(&mut "a-DX".into_tokens()).is_some());
|
||||
assert!(super::parse_edge(&mut "AAA-b".into_tokens()).is_some());
|
||||
}
|
||||
}
|
|
@ -13,5 +13,6 @@ pub mod day_08;
|
|||
pub mod day_09;
|
||||
pub mod day_10;
|
||||
pub mod day_11;
|
||||
pub mod day_12;
|
||||
|
||||
aoc_lib! { year = 2021 }
|
||||
|
|
|
@ -42,12 +42,11 @@ pub fn parse_n<T: Debug, P: Tokens<Item = char>, const N: usize>(
|
|||
parser: &dyn Fn(&mut P) -> Option<T>,
|
||||
separator: &dyn Fn(&mut P) -> bool,
|
||||
) -> Option<[T; N]> {
|
||||
let elements: Vec<T> = tokens.sep_by(parser, separator).collect();
|
||||
if elements.len() != 10 {
|
||||
None
|
||||
} else {
|
||||
Some(elements.try_into().unwrap())
|
||||
}
|
||||
tokens
|
||||
.sep_by(parser, separator)
|
||||
.collect::<Vec<T>>()
|
||||
.try_into()
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue