commit 745524e95d34e597a62a3d0c8fdb5a16ef203dfb
parent 7dda43d51a149382991f9c053a1963fab85b21c3
Author: Louis Burda <quent.burda@gmail.com>
Date: Fri, 17 Dec 2021 19:46:02 +0100
Add solution wrapper and run / debug commands
Diffstat:
10 files changed, 448 insertions(+), 33 deletions(-)
diff --git a/lib/aoc/Cargo.toml b/lib/aoc/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "aoc"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/lib/aoc/src/lib.rs b/lib/aoc/src/lib.rs
@@ -0,0 +1,94 @@
+use std::env;
+use std::fs;
+use std::process::exit;
+
+pub enum Loglvl {
+ LogNone = 0,
+ LogVerb,
+ LogAll
+}
+
+pub struct Info {
+ pub debug: Loglvl,
+ pub filename: String,
+ pub input: String,
+ pub solution: Option<String>,
+ pub answer: Option<String>,
+ pub part: u32
+}
+
+#[macro_export]
+macro_rules! die {
+ ($( $arg:expr ),*) => {
+ eprintln!($( $arg ), *);
+ exit(1);
+ }
+}
+
+pub fn to_loglvl(val: u32) -> Loglvl {
+ match val {
+ 0 => return Loglvl::LogNone,
+ 1 => return Loglvl::LogVerb,
+ 2 => return Loglvl::LogAll,
+ _ => panic!("Invalid log level {}", val)
+ }
+}
+
+pub fn run(part1: fn(info: &mut Info), part2: fn(info: &mut Info)) {
+ let mut info = Info {
+ filename: "input".to_string(),
+ debug: Loglvl::LogNone,
+ input: "".to_string(),
+ solution: None,
+ answer: None,
+ part: 0
+ };
+
+ let input_file = env::var("AOCINPUT");
+ if input_file.is_ok() {
+ info.filename = input_file.unwrap();
+ }
+
+ let content = fs::read_to_string(info.filename.clone());
+ if content.is_err() {
+ die!("Failed to read from file: {}", info.filename);
+ }
+ info.input = content.unwrap();
+
+ let debug = env::var("AOCDEBUG");
+ if debug.is_ok() {
+ let num = debug.unwrap().parse::<u32>().unwrap();
+ info.debug = to_loglvl(num);
+ }
+
+ let args: Vec<String> = env::args().collect();
+ if args.len() > 1 {
+ let num = args[1].parse();
+ if num.is_err() {
+ die!("Invalid part number: {}", args[1]);
+ }
+
+ info.part = num.unwrap();
+ } else {
+ die!("Usage: aoc run PART");
+ }
+
+ if info.part == 1 {
+ part1(&mut info);
+ } else if info.part == 2 {
+ part2(&mut info);
+ } else {
+ die!("No such part: {}", info.part);
+ }
+
+ if info.answer.is_some() {
+ let answer = info.answer.unwrap();
+ println!("{}", answer);
+ if info.solution.is_some() {
+ let solution = info.solution.unwrap();
+ if answer != solution {
+ die!("Incorrect solution!");
+ }
+ }
+ }
+}
diff --git a/src/01/Cargo.toml b/src/01/Cargo.toml
@@ -1,7 +1,7 @@
[package]
-name = "aoc"
+name = "day01"
version = "0.1.0"
edition = "2021"
[dependencies]
-
+aoc = { path = "../../lib/aoc" }
diff --git a/src/01/src/main.rs b/src/01/src/main.rs
@@ -1,27 +1,10 @@
-use std::fs;
-use std::env;
-
-fn main() {
- let args: Vec<String> = env::args().collect();
- if args[1] == "1" {
- part1();
- } else if args[1] == "2" {
- part2();
- } else {
- eprintln!("No such part\n");
- }
-}
-
-fn part1() {
- let vals : Vec<u32> = fs::read_to_string("input")
- .expect("no file")
- .lines()
- .map(|x| x.parse().expect("no int"))
+fn part1(info: &mut aoc::Info) {
+ let vals : Vec<u32> = info.input.lines()
+ .map(|x| x.parse().unwrap())
.collect();
- let mut prev : u32 = vals[0];
let mut count : u32 = 0;
-
+ let mut prev : u32 = vals[0];
for v in vals {
if v > prev {
count += 1;
@@ -29,26 +12,30 @@ fn part1() {
prev = v;
}
- println!("{}", count);
+ info.solution = Some("1692".to_string());
+ info.answer = Some(format!("{}", count));
}
-fn part2() {
- let vals : Vec<u32> = fs::read_to_string("input")
- .expect("no file")
- .lines()
- .map(|x| x.parse().expect("no int"))
+fn part2(info: &mut aoc::Info) {
+ let vals : Vec<u32> = info.input.lines()
+ .map(|x| x.parse().unwrap())
.collect();
- let mut prev : Option<u32> = None;
let mut count : u32 = 0;
-
+ let mut prev : Option<u32> = None;
for i in 0..vals.len()-2 {
let sum = vals[i..i+3].iter().sum();
- if prev != None && sum > prev.expect("wtf") {
+ if prev.is_some() && sum > prev.unwrap() {
count += 1;
}
prev = Some(sum);
}
- println!("{}", count);
+ info.solution = Some("1724".to_string());
+ info.answer = Some(format!("{}", count));
}
+
+fn main() {
+ aoc::run(part1, part2);
+}
+
diff --git a/src/01/Cargo.toml b/src/12/Cargo.toml
diff --git a/src/12/input b/src/12/input
@@ -0,0 +1,26 @@
+xq-XZ
+zo-yr
+CT-zo
+yr-xq
+yr-LD
+xq-ra
+np-zo
+end-LD
+np-LD
+xq-kq
+start-ra
+np-kq
+LO-end
+start-xq
+zo-ra
+LO-np
+XZ-start
+zo-kq
+LO-yr
+kq-XZ
+zo-LD
+kq-ra
+XZ-yr
+LD-ws
+np-end
+kq-yr
diff --git a/src/12/input.test b/src/12/input.test
@@ -0,0 +1,10 @@
+dc-end
+HN-start
+start-kj
+dc-start
+dc-HN
+LN-dc
+HN-end
+kj-sa
+kj-HN
+kj-dc
diff --git a/src/12/part1 b/src/12/part1
@@ -0,0 +1,115 @@
+--- Day 12: Passage Pathing ---
+
+With your submarine's subterranean subsystems subsisting suboptimally, the only way you're getting
+out of this cave anytime soon is by finding a path yourself. Not just [1m[37ma[0m path - the only way to know
+if you've found the [1m[37mbest[0m path is to find [1m[37mall[0m of them.
+
+Fortunately, the sensors are still mostly working, and so you build a rough map of the remaining
+caves (your puzzle input). For example:
+
+start-A
+start-b
+A-c
+A-b
+b-d
+A-end
+b-end
+
+This is a list of how all of the caves are connected. You start in the cave named start, and your
+destination is the cave named end. An entry like b-d means that cave b is connected to cave d - that
+is, you can move between them.
+
+So, the above cave system looks roughly like this:
+
+ start
+ / \
+c--A-----b--d
+ \ /
+ end
+
+Your goal is to find the number of distinct [1m[37mpaths[0m that start at start, end at end, and don't visit
+small caves more than once. There are two types of caves: [1m[37mbig[0m caves (written in uppercase, like A)
+and [1m[37msmall[0m caves (written in lowercase, like b). It would be a waste of time to visit any small cave
+more than once, but big caves are large enough that it might be worth visiting them multiple times.
+So, all paths you find should [1m[37mvisit small caves at most once[0m, and can [1m[37mvisit big caves any number of
+times[0m.
+
+Given these rules, there are [1m[37m10[0m paths through this example cave system:
+
+start,A,b,A,c,A,end
+start,A,b,A,end
+start,A,b,end
+start,A,c,A,b,A,end
+start,A,c,A,b,end
+start,A,c,A,end
+start,A,end
+start,b,A,c,A,end
+start,b,A,end
+start,b,end
+
+(Each line in the above list corresponds to a single path; the caves visited by that path are listed
+in the order they are visited and separated by commas.)
+
+Note that in this cave system, cave d is never visited by any path: to do so, cave b would need to
+be visited twice (once on the way to cave d and a second time when returning from cave d), and since
+cave b is small, this is not allowed.
+
+Here is a slightly larger example:
+
+dc-end
+HN-start
+start-kj
+dc-start
+dc-HN
+LN-dc
+HN-end
+kj-sa
+kj-HN
+kj-dc
+
+The 19 paths through it are as follows:
+
+start,HN,dc,HN,end
+start,HN,dc,HN,kj,HN,end
+start,HN,dc,end
+start,HN,dc,kj,HN,end
+start,HN,end
+start,HN,kj,HN,dc,HN,end
+start,HN,kj,HN,dc,end
+start,HN,kj,HN,end
+start,HN,kj,dc,HN,end
+start,HN,kj,dc,end
+start,dc,HN,end
+start,dc,HN,kj,HN,end
+start,dc,end
+start,dc,kj,HN,end
+start,kj,HN,dc,HN,end
+start,kj,HN,dc,end
+start,kj,HN,end
+start,kj,dc,HN,end
+start,kj,dc,end
+
+Finally, this even larger example has 226 paths through it:
+
+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
+
+[1m[37mHow many paths through this cave system are there that visit small caves at most once?[0m
+
+
diff --git a/src/12/part2 b/src/12/part2
@@ -0,0 +1,53 @@
+--- Part Two ---
+
+After reviewing the available paths, you realize you might have time to visit a single small cave
+[1m[37mtwice[0m. Specifically, big caves can be visited any number of times, a single small cave can be
+visited at most twice, and the remaining small caves can be visited at most once. However, the caves
+named start and end can only be visited [1m[37mexactly once each[0m: once you leave the start cave, you may
+not return to it, and once you reach the end cave, the path must end immediately.
+
+Now, the 36 possible paths through the first example above are:
+
+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
+
+The slightly larger example above now has 103 paths through it, and the even larger example now has
+3509 paths through it.
+
+Given these new rules, [1m[37mhow many paths through this cave system are there?[0m
+
+
diff --git a/src/12/src/main.rs b/src/12/src/main.rs
@@ -0,0 +1,124 @@
+use std::collections::HashMap;
+use std::env;
+use std::fs;
+
+/* assume no two big caves are next to each other
+ * => otherwise infinite paths possible */
+
+#[derive(PartialEq)]
+struct Node {
+ name: String,
+ small: bool,
+ adj: Vec<String>
+}
+
+struct NodeState<'a> {
+ node: &'a Node,
+ index: usize
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args[1] == "1" {
+ part1();
+ } else if args[1] == "2" {
+ part2();
+ } else {
+ eprintln!("No such part\n");
+ }
+}
+
+fn parse_input() -> HashMap<String, Node> {
+ let mut nodes: HashMap<String, Node> = HashMap::new();
+
+ let content = fs::read_to_string("input").expect("no file");
+ for line in content.lines() {
+ let parts: Vec<String> = line.split("-").map(|x| x.to_string()).collect();
+ for key in &parts {
+ if !nodes.contains_key(key) {
+ let tmp = Node {
+ name: key.clone(),
+ adj: Vec::new(),
+ small: key.chars().all(char::is_lowercase)
+ };
+ nodes.insert(key.clone(), tmp);
+ }
+ }
+
+ let (left, right) = (&parts[0], &parts[1]);
+ nodes.get_mut(left).unwrap().adj.push(right.to_string());
+ nodes.get_mut(right).unwrap().adj.push(left.to_string());
+ }
+
+ return nodes;
+}
+
+fn part1() {
+ let nodes = parse_input();
+
+ let mut path: Vec<&Node> = Vec::new();
+ let mut adj_index = 0;
+
+ path.push(nodes.get("start").unwrap());
+
+ let mut paths: usize = 0;
+ while path.len() > 0 {
+ let current = path.last().unwrap().clone();
+ if adj_index >= current.adj.len() {
+ /* backtrack */
+ path.pop();
+ if path.len() == 0 { break; }
+ let next = path.last().unwrap();
+ adj_index = next.adj.iter().position(|x| *x == current.name).unwrap() + 1;
+ } else {
+ let adj = nodes.get(¤t.adj[adj_index]).unwrap();
+ if adj.name == "end" {
+ // for p in &path {
+ // print!("{} -> ", p.name);
+ // }
+ // println!("end");
+ paths += 1;
+ adj_index += 1;
+ } else if !path.contains(&adj) || !adj.small {
+ path.push(adj);
+ adj_index = 0;
+ } else {
+ adj_index += 1;
+ }
+ }
+ }
+
+ println!("{}", paths);
+}
+
+fn part2() {
+ let nodes = parse_input();
+
+ let mut path: Vec<NodeState> = Vec::new();
+
+ path.push(NodeState { node: nodes.get("start").unwrap(), index: 0 });
+
+ let mut paths: usize = 0;
+ while path.len() > 0 {
+ let current = path.last_mut().unwrap();
+ if current.index < current.node.adj.len() {
+ let adj: &Node = nodes.get(¤t.node.adj[current.index]).unwrap();
+ current.index += 1;
+ if adj.name == "end" {
+ for p in &path {
+ print!("{} -> ", p.node.name);
+ }
+ println!("end");
+ paths += 1;
+ } else if adj.name != "start"
+ && path.iter().filter(|x| x.node == adj).count() <= 1 || !adj.small {
+ path.push(NodeState { node: adj, index: 0 } );
+ }
+ } else {
+ /* backtrack */
+ path.pop();
+ }
+ }
+
+ println!("{}", paths);
+}