aoc-2021-rust

git clone https://git.sinitax.com/sinitax/aoc-2021-rust
Log | Files | Refs | README | sfeed.txt

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:
Alib/aoc/Cargo.toml | 6++++++
Alib/aoc/src/lib.rs | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/01/Cargo.toml | 4++--
Msrc/01/src/main.rs | 49++++++++++++++++++-------------------------------
Csrc/01/Cargo.toml -> src/12/Cargo.toml | 0
Asrc/12/input | 26++++++++++++++++++++++++++
Asrc/12/input.test | 10++++++++++
Asrc/12/part1 | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/12/part2 | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/12/src/main.rs | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 a path - the only way to know +if you've found the best path is to find all 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 paths that start at start, end at end, and don't visit +small caves more than once. There are two types of caves: big caves (written in uppercase, like A) +and small 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 visit small caves at most once, and can visit big caves any number of +times. + +Given these rules, there are 10 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 + +How many paths through this cave system are there that visit small caves at most once? + + 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 +twice. 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 exactly once each: 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, how many paths through this cave system are there? + + 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(&current.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(&current.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); +}