├── .github └── workflows │ ├── aoc_2021.yml │ ├── aoc_2022.yml │ ├── aoc_2023.yml │ ├── aoc_2024.yml │ ├── bin.yml │ └── scaffold.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── aoc_2021 ├── Cargo.toml ├── src │ ├── day_01.rs │ ├── day_02.rs │ ├── day_03.rs │ ├── day_04.rs │ ├── day_05.rs │ ├── day_06.rs │ ├── day_07.rs │ ├── day_08.rs │ ├── day_09.rs │ ├── day_10.rs │ ├── day_11.rs │ ├── day_12.rs │ ├── day_13.rs │ ├── day_14.rs │ ├── day_15.rs │ └── lib.rs └── todo.md ├── aoc_2022 ├── Cargo.toml └── src │ ├── day_01.rs │ ├── day_02.rs │ ├── day_03.rs │ ├── day_04.rs │ ├── day_05.rs │ ├── day_06.rs │ ├── day_07.rs │ ├── day_08.rs │ ├── day_09.rs │ ├── day_10.rs │ ├── day_11.rs │ ├── day_12.rs │ ├── day_13.rs │ ├── day_14.rs │ ├── day_15.rs │ ├── day_16.rs │ ├── day_17.rs │ ├── day_18.rs │ ├── day_19.rs │ ├── day_20.rs │ ├── day_21.rs │ ├── day_22.rs │ ├── day_23.rs │ ├── day_24.rs │ ├── day_25.rs │ └── lib.rs ├── aoc_2023 ├── Cargo.toml └── src │ ├── day_01.rs │ ├── day_02.rs │ ├── day_03.rs │ ├── day_04.rs │ ├── day_05.rs │ ├── day_06.rs │ ├── day_07.rs │ ├── day_08.rs │ ├── day_09.rs │ ├── day_10.rs │ ├── day_11.rs │ ├── day_12.rs │ ├── day_13.rs │ ├── day_14.rs │ ├── day_15.rs │ ├── day_16.rs │ ├── day_17.rs │ ├── day_18.rs │ ├── day_19.rs │ ├── day_20.rs │ ├── day_21.rs │ ├── day_22.rs │ ├── day_23.rs │ ├── day_24.rs │ ├── day_25.rs │ └── lib.rs ├── aoc_2024 ├── Cargo.toml └── src │ ├── day_01.rs │ ├── day_02.rs │ ├── day_03.rs │ ├── day_04.rs │ ├── day_05.rs │ ├── day_06.rs │ ├── day_07.rs │ ├── day_08.rs │ ├── day_09.rs │ ├── day_10.rs │ ├── day_11.rs │ ├── day_12.rs │ ├── day_13.rs │ ├── day_14.rs │ ├── day_15.rs │ ├── day_16.rs │ ├── day_17.rs │ ├── day_18.rs │ ├── day_19.rs │ ├── day_20.rs │ ├── day_21.rs │ ├── day_22.rs │ ├── day_23.rs │ ├── day_24.rs │ ├── day_25.rs │ └── lib.rs ├── aoc_lib ├── Cargo.toml └── src │ ├── direction │ ├── cardinal.rs │ ├── mod.rs │ └── ordinal.rs │ ├── lib.rs │ ├── math.rs │ ├── matrix.rs │ ├── regex.rs │ └── vector.rs ├── common ├── Cargo.toml └── src │ ├── answer.rs │ ├── lib.rs │ ├── misc.rs │ ├── part.rs │ └── solution.rs ├── scaffold ├── Cargo.toml ├── src │ ├── args.rs │ ├── commands │ │ ├── init.rs │ │ ├── mod.rs │ │ ├── submit.rs │ │ ├── timer.rs │ │ ├── token.rs │ │ └── verify.rs │ ├── formatter.rs │ ├── main.rs │ ├── misc.rs │ └── session.rs ├── template.txt └── todo.md └── src ├── args.rs ├── commands ├── list.rs ├── mod.rs ├── run.rs └── run_all.rs └── main.rs /.github/workflows/aoc_2021.yml: -------------------------------------------------------------------------------- 1 | name: aoc_2021 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "common/**" 7 | - "aoc_2021/**" 8 | 9 | env: 10 | CRATE: aoc_2021 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build -p ${{ env.CRATE }} 20 | - name: Test 21 | run: cargo test -p ${{ env.CRATE }} 22 | -------------------------------------------------------------------------------- /.github/workflows/aoc_2022.yml: -------------------------------------------------------------------------------- 1 | name: aoc_2022 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "common/**" 7 | - "aoc_2022/**" 8 | 9 | env: 10 | CRATE: aoc_2022 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build -p ${{ env.CRATE }} 20 | - name: Test 21 | run: cargo test -p ${{ env.CRATE }} 22 | -------------------------------------------------------------------------------- /.github/workflows/aoc_2023.yml: -------------------------------------------------------------------------------- 1 | name: aoc_2023 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "common/**" 7 | - "aoc_2023/**" 8 | 9 | env: 10 | CRATE: aoc_2023 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build -p ${{ env.CRATE }} 20 | - name: Test 21 | run: cargo test -p ${{ env.CRATE }} 22 | -------------------------------------------------------------------------------- /.github/workflows/aoc_2024.yml: -------------------------------------------------------------------------------- 1 | name: aoc_2024 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "common/**" 7 | - "aoc_2024/**" 8 | 9 | env: 10 | CRATE: aoc_2024 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build -p ${{ env.CRATE }} 20 | - name: Test 21 | run: cargo test -p ${{ env.CRATE }} 22 | -------------------------------------------------------------------------------- /.github/workflows/bin.yml: -------------------------------------------------------------------------------- 1 | name: bin 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "common/**" 7 | - "src/**" 8 | - "Cargo.toml" 9 | 10 | env: 11 | CRATE: advent_of_code 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build -p ${{ env.CRATE }} 21 | - name: Test 22 | run: cargo test -p ${{ env.CRATE }} 23 | -------------------------------------------------------------------------------- /.github/workflows/scaffold.yml: -------------------------------------------------------------------------------- 1 | name: scaffold 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths: 6 | - "scaffold/**" 7 | - "Cargo.toml" 8 | 9 | env: 10 | CRATE: scaffold 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build -p ${{ env.CRATE }} 20 | - name: Test 21 | run: cargo test -p ${{ env.CRATE }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | scripts/ 3 | todo.txt 4 | .env 5 | data 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "advent_of_code" 4 | version = "0.1.0" 5 | 6 | [workspace] 7 | members = [ 8 | "scaffold", 9 | "common", 10 | "aoc_lib", 11 | "aoc_2021", 12 | "aoc_2022", 13 | "aoc_2023", 14 | "aoc_2024", 15 | ] 16 | 17 | [dependencies] 18 | common = { path = "common" } 19 | aoc_2021 = { path = "aoc_2021" } 20 | aoc_2022 = { path = "aoc_2022" } 21 | aoc_2023 = { path = "aoc_2023" } 22 | aoc_2024 = { path = "aoc_2024" } 23 | 24 | clap = { version = "4.0.29", features = ["derive"] } 25 | chrono = "0.4.31" 26 | anyhow = "1.0.75" 27 | 28 | [profile.release] 29 | overflow-checks = true 30 | incremental = true 31 | -------------------------------------------------------------------------------- /aoc_2021/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc_2021" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | common = { path = "../common" } 8 | aoc_lib = { path = "../aoc_lib" } 9 | 10 | hashbrown = "0.13.1" 11 | indoc = "2.0.4" 12 | nd_vec = { git = "https://github.com/connorslade/nd-vec.git" } 13 | petgraph = "0.6.4" 14 | -------------------------------------------------------------------------------- /aoc_2021/src/day_01.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Sonar Sweep", 1); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let data = input 7 | .lines() 8 | .map(|x| x.parse::().unwrap()) 9 | .collect::>(); 10 | 11 | data.windows(2).filter(|x| x[0] < x[1]).count().into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | let d = input 16 | .lines() 17 | .map(|x| x.parse::().unwrap()) 18 | .collect::>(); 19 | 20 | d.windows(4).filter(|x| x[2] > x[0]).count().into() 21 | } 22 | -------------------------------------------------------------------------------- /aoc_2021/src/day_02.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Dive!", 2); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let mut dep: u32 = 0; 7 | let mut hor: u32 = 0; 8 | 9 | for i in input.lines() { 10 | let seg = i.split(' ').collect::>(); 11 | let x = seg[1].parse::().unwrap(); 12 | 13 | match seg[0] { 14 | "forward" => hor += x, 15 | "up" => dep -= x, 16 | "down" => dep += x, 17 | _ => {} 18 | } 19 | } 20 | 21 | (dep * hor).into() 22 | } 23 | 24 | fn part_b(input: &str) -> Answer { 25 | let mut dep: u32 = 0; 26 | let mut hor: u32 = 0; 27 | let mut aim: u32 = 0; 28 | 29 | for i in input.lines() { 30 | let seg = i.split(' ').collect::>(); 31 | let x = seg[1].parse::().unwrap(); 32 | 33 | match seg[0] { 34 | "forward" => { 35 | hor += x; 36 | dep += aim * x; 37 | } 38 | "up" => aim -= x, 39 | "down" => aim += x, 40 | _ => {} 41 | } 42 | } 43 | 44 | (dep * hor).into() 45 | } 46 | -------------------------------------------------------------------------------- /aoc_2021/src/day_03.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Binary Diagnostic", 3); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let num_len = input.lines().next().unwrap().len(); 7 | 8 | let mut gamma = vec![0; num_len]; 9 | let mut epsilon = vec![1; num_len]; 10 | 11 | for i in 0..num_len { 12 | let mut z = 0; 13 | let mut o = 0; 14 | 15 | input.lines().for_each(|j| match j.chars().nth(i).unwrap() { 16 | '0' => z += 1, 17 | '1' => o += 1, 18 | _ => {} 19 | }); 20 | 21 | if o > z { 22 | epsilon[i] = 0; 23 | gamma[i] = 1; 24 | } 25 | } 26 | 27 | let gamma = int_from_bin(&gamma).unwrap(); 28 | let epsilon = int_from_bin(&epsilon).unwrap(); 29 | 30 | (epsilon * gamma).into() 31 | } 32 | 33 | fn part_b(input: &str) -> Answer { 34 | let num_len = input.lines().next().unwrap().len(); 35 | 36 | let mut oxygen_keep = input.lines().collect::>(); 37 | let mut oxygen_raw = vec![[0, 0]; num_len]; 38 | let mut oxygen_gen = 0; 39 | 40 | let mut co2_keep = oxygen_keep.clone(); 41 | let mut co2_raw = oxygen_raw.clone(); 42 | let mut co2_scrub = 0; 43 | 44 | for i in 0..num_len { 45 | // Filter Oxygen 46 | let imax = get_imax(&oxygen_raw, i); 47 | oxygen_raw = gen_raw(oxygen_raw, num_len, &oxygen_keep); 48 | oxygen_keep.retain(|x| x.chars().nth(i).unwrap() == imax); 49 | 50 | // Filter Co2 51 | let imax = get_imax(&co2_raw, i); 52 | co2_raw = gen_raw(co2_raw, num_len, &co2_keep); 53 | co2_keep.retain(|x| x.chars().nth(i).unwrap() != imax); 54 | 55 | if oxygen_keep.len() == 1 { 56 | oxygen_gen = isize::from_str_radix(oxygen_keep.first().unwrap(), 2).unwrap(); 57 | } 58 | 59 | if co2_keep.len() == 1 { 60 | co2_scrub = isize::from_str_radix(co2_keep.first().unwrap(), 2).unwrap(); 61 | } 62 | } 63 | 64 | (oxygen_gen * co2_scrub).into() 65 | } 66 | 67 | fn int_from_bin(inp: &[u32]) -> Option { 68 | usize::from_str_radix( 69 | &inp.iter() 70 | .map(|x| x.to_string()) 71 | .collect::>() 72 | .join(""), 73 | 2, 74 | ) 75 | .ok() 76 | } 77 | 78 | fn gen_raw(mut old: Vec<[u32; 2]>, num_len: usize, keep: &[&str]) -> Vec<[u32; 2]> { 79 | for i in 0..num_len { 80 | let mut z = 0; 81 | let mut o = 0; 82 | for j in keep { 83 | match j.chars().nth(i).unwrap() { 84 | '0' => z += 1, 85 | '1' => o += 1, 86 | _ => {} 87 | } 88 | } 89 | *old.get_mut(i).unwrap() = [z, o]; 90 | } 91 | 92 | old 93 | } 94 | 95 | fn get_imax(raw: &[[u32; 2]], i: usize) -> char { 96 | if raw[i][0] > raw[i][1] { 97 | return '0'; 98 | }; 99 | '1' 100 | } 101 | -------------------------------------------------------------------------------- /aoc_2021/src/day_05.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | use hashbrown::HashMap; 4 | 5 | solution!("Hydrothermal Venture", 5); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | run(input, false).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | run(input, true).into() 13 | } 14 | 15 | /// dig -> Weather to include Diagonal Lines 16 | fn run(input: &str, dig: bool) -> u32 { 17 | let data = Segment::parse_inp(input, dig).unwrap(); 18 | let mut all_loc = HashMap::new(); 19 | 20 | for x in data { 21 | let dump = x.dump(); 22 | for y in dump { 23 | if all_loc.contains_key(&y) { 24 | *all_loc.get_mut(&y).unwrap() += 1; 25 | continue; 26 | } 27 | all_loc.insert(y, 1); 28 | } 29 | } 30 | 31 | all_loc.iter().filter(|x| *x.1 >= 2).count() as u32 32 | } 33 | 34 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 35 | struct Segment { 36 | x1: u32, 37 | y1: u32, 38 | 39 | x2: u32, 40 | y2: u32, 41 | } 42 | 43 | impl Segment { 44 | fn parse_inp(inp: &str, dig: bool) -> Option> { 45 | let mut out = Vec::new(); 46 | 47 | for line in inp.lines() { 48 | let mut parts = line.split(" -> "); 49 | let mut one = parts.next()?.split(','); 50 | let mut two = parts.next()?.split(','); 51 | 52 | let x1 = one.next()?.parse().ok()?; 53 | let y1 = one.next()?.parse().ok()?; 54 | let x2 = two.next()?.parse().ok()?; 55 | let y2 = two.next()?.parse().ok()?; 56 | 57 | if x1 != x2 && y1 != y2 && !dig { 58 | continue; 59 | } 60 | 61 | out.push(Segment { x1, y1, x2, y2 }) 62 | } 63 | 64 | Some(out) 65 | } 66 | 67 | fn dump(&self) -> Vec<(u32, u32)> { 68 | let mut out = vec![(self.x1, self.y1)]; 69 | let mut x = self.x1; 70 | let mut y = self.y1; 71 | 72 | while x != self.x2 || y != self.y2 { 73 | x -= (x > self.x2) as u32; 74 | x += (x < self.x2) as u32; 75 | y -= (y > self.y2) as u32; 76 | y += (y < self.y2) as u32; 77 | out.push((x, y)); 78 | } 79 | 80 | out 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /aoc_2021/src/day_06.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | use std::hash::Hash; 4 | 5 | use hashbrown::HashMap; 6 | 7 | solution!("Lanternfish", 6); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let data = Fish::parse_inp(input); 11 | Fish::sim(data, 80).into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | let data = Fish::parse_inp(input); 16 | Fish::sim(data, 256).into() 17 | } 18 | 19 | #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] 20 | struct Fish { 21 | timer: u32, 22 | } 23 | 24 | impl Fish { 25 | fn new(timer: u32) -> Fish { 26 | Fish { timer } 27 | } 28 | 29 | fn parse_inp(inp: &str) -> Vec { 30 | inp.lines() 31 | .next() 32 | .unwrap() 33 | .split(',') 34 | .map(|x| Fish::new(x.parse().unwrap())) 35 | .collect() 36 | } 37 | 38 | fn sim(fish_vec: Vec, days: u32) -> usize { 39 | let mut fish = HashMap::new(); 40 | 41 | for i in fish_vec { 42 | *fish.entry(i).or_insert(0) += 1; 43 | } 44 | 45 | for _ in 0..days { 46 | let mut new_fish: HashMap = HashMap::new(); 47 | for i in &fish { 48 | if i.0.timer == 0 { 49 | *new_fish.entry(Fish::new(6)).or_insert(0) += *i.1; 50 | *new_fish.entry(Fish::new(8)).or_insert(0) += *i.1; 51 | continue; 52 | } 53 | 54 | *new_fish.entry(Fish::new(i.0.timer - 1)).or_insert(0) += *i.1; 55 | } 56 | fish = new_fish; 57 | } 58 | 59 | fish.values().sum() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /aoc_2021/src/day_07.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("The Treachery of Whales", 7); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let data = parse_crabs(input); 7 | 8 | let min = data.iter().min().unwrap(); 9 | let max = data.iter().max().unwrap(); 10 | 11 | let mut this_min = u32::MAX; 12 | for i in *min..=*max { 13 | let cost = move_crabs(&data, i); 14 | if cost < this_min { 15 | this_min = cost; 16 | } 17 | } 18 | 19 | this_min.into() 20 | } 21 | 22 | fn part_b(input: &str) -> Answer { 23 | let data = parse_crabs(input); 24 | 25 | let min = data.iter().min().unwrap(); 26 | let max = data.iter().max().unwrap(); 27 | 28 | let mut this_min = u32::MAX; 29 | for i in *min..=*max { 30 | let cost = move_crabs_b(&data, i); 31 | if cost < this_min { 32 | this_min = cost; 33 | } 34 | } 35 | 36 | this_min.into() 37 | } 38 | 39 | fn parse_crabs(inp: &str) -> Vec { 40 | inp.lines() 41 | .next() 42 | .unwrap() 43 | .split(',') 44 | .map(|x| x.parse().unwrap()) 45 | .collect::>() 46 | } 47 | 48 | fn move_crabs(crabs: &[u32], to: u32) -> u32 { 49 | let mut cost = 0; 50 | for i in crabs { 51 | cost += (*i as i32 - to as i32).abs(); 52 | } 53 | 54 | cost as u32 55 | } 56 | 57 | fn move_crabs_b(crabs: &[u32], to: u32) -> u32 { 58 | let mut cost = 0; 59 | for crab in crabs { 60 | cost += (0..=(*crab as i32 - to as i32).abs()).sum::(); 61 | } 62 | 63 | cost as u32 64 | } 65 | -------------------------------------------------------------------------------- /aoc_2021/src/day_09.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Smoke Basin", 9); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let data = parse(input); 7 | let low = lowest(data); 8 | 9 | low.iter().map(|x| *x + 1).sum::().into() 10 | } 11 | 12 | fn part_b(input: &str) -> Answer { 13 | let data = parse(input); 14 | let basins = basins(data); 15 | 16 | basins.iter().rev().take(3).product::().into() 17 | } 18 | 19 | fn parse(inp: &str) -> Vec> { 20 | inp.lines() 21 | .map(|x| x.chars().map(|f| f.to_digit(10).unwrap()).collect()) 22 | .collect() 23 | } 24 | 25 | fn lowest(inp: Vec>) -> Vec { 26 | inp.iter() 27 | .enumerate() 28 | .flat_map(|(i, line)| { 29 | let inp = &inp; 30 | line.iter().enumerate().filter_map(move |(j, &h)| { 31 | if (i == 0 || h < inp[i - 1][j]) 32 | && (i == inp.len() - 1 || h < inp[i + 1][j]) 33 | && (j == 0 || h < inp[i][j - 1]) 34 | && (j == line.len() - 1 || h < inp[i][j + 1]) 35 | { 36 | return Some(h); 37 | } 38 | None 39 | }) 40 | }) 41 | .collect::>() 42 | } 43 | 44 | fn basins(mut inp: Vec>) -> Vec { 45 | let mut basins = Vec::new(); 46 | for i in 0..inp.len() { 47 | for j in 0..inp[0].len() { 48 | if inp[i][j] < 9 { 49 | basins.push(basin(&mut inp, j, i)); 50 | } 51 | } 52 | } 53 | 54 | basins.sort_unstable(); 55 | basins 56 | } 57 | 58 | fn basin(map: &mut Vec>, x: usize, y: usize) -> u32 { 59 | map[y][x] = 9; 60 | [(0, -1), (0, 1), (-1, 0), (1, 0)] 61 | .iter() 62 | .map(|(xx, yy)| ((x as isize + xx) as usize, (y as isize + yy) as usize)) 63 | .fold(1, |inc, (x, y)| { 64 | if map.get(y).and_then(|l| l.get(x)).map(|&n| n < 9) == Some(true) { 65 | return inc + basin(map, x, y); 66 | } 67 | inc 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /aoc_2021/src/day_10.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | const CHARS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')]; 4 | 5 | solution!("Syntax Scoring", 10); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let data = parse(input); 9 | 10 | let mut total = 0; 11 | for i in data { 12 | let mut closeing = Vec::new(); 13 | for j in i.chars() { 14 | if char_contains_key(j) { 15 | closeing.push(char_for_char(j)); 16 | continue; 17 | } 18 | 19 | if closeing.is_empty() || j != closeing.pop().unwrap() { 20 | total += match j { 21 | ')' => 3, 22 | ']' => 57, 23 | '}' => 1197, 24 | '>' => 25137, 25 | _ => unreachable!(), 26 | }; 27 | break; 28 | } 29 | } 30 | } 31 | 32 | total.into() 33 | } 34 | 35 | fn part_b(input: &str) -> Answer { 36 | let data = parse(input); 37 | 38 | let mut scores = Vec::new(); 39 | for i in data { 40 | let mut queue = Vec::new(); 41 | let mut is_corrupted = false; 42 | for j in i.chars() { 43 | if char_contains_key(j) { 44 | queue.push(char_for_char(j)); 45 | continue; 46 | } 47 | 48 | if queue.is_empty() || j != queue.pop().unwrap() { 49 | is_corrupted = true; 50 | break; 51 | } 52 | } 53 | 54 | if !is_corrupted { 55 | let mut score = 0u64; 56 | while let Some(ch) = queue.pop() { 57 | score = 5 * score 58 | + match ch { 59 | ')' => 1, 60 | ']' => 2, 61 | '}' => 3, 62 | '>' => 4, 63 | _ => unreachable!(), 64 | }; 65 | } 66 | scores.push(score); 67 | } 68 | } 69 | 70 | scores.sort_unstable(); 71 | let mid = scores.len() / 2; 72 | scores[mid].into() 73 | } 74 | 75 | fn parse(lines: &str) -> Vec { 76 | lines 77 | .lines() 78 | .map(|x| x.to_string()) 79 | .collect::>() 80 | } 81 | 82 | fn char_for_char(inp: char) -> char { 83 | for i in CHARS { 84 | if i.0 == inp { 85 | return i.1; 86 | } 87 | 88 | if i.1 == inp { 89 | return i.0; 90 | } 91 | } 92 | 93 | unreachable!() 94 | } 95 | 96 | fn char_contains_key(inp: char) -> bool { 97 | for i in CHARS { 98 | if i.0 == inp { 99 | return true; 100 | } 101 | } 102 | false 103 | } 104 | -------------------------------------------------------------------------------- /aoc_2021/src/day_11.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Dumbo Octopus", 11); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let mut octopi = parse(input); 7 | 8 | (0..100) 9 | .map(|_| step_octopi(&mut octopi)) 10 | .sum::() 11 | .into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | let mut octopi = parse(input); 16 | let octopi_count = octopi.len() * octopi[0].len(); 17 | let mut i = 0; 18 | 19 | loop { 20 | i += 1; 21 | 22 | let flash = step_octopi(&mut octopi); 23 | if flash == octopi_count { 24 | break; 25 | } 26 | } 27 | 28 | i.into() 29 | } 30 | 31 | struct Octopus { 32 | state: u8, 33 | flashed: bool, 34 | } 35 | 36 | impl Octopus { 37 | fn tick(&mut self) -> bool { 38 | if self.flashed { 39 | return false; 40 | } 41 | 42 | self.state += 1; 43 | if self.state > 9 { 44 | self.state = 0; 45 | self.flashed = true; 46 | return true; 47 | } 48 | 49 | false 50 | } 51 | } 52 | 53 | fn step_octopi(octopi: &mut Vec>) -> usize { 54 | for y in 0..octopi.len() { 55 | for x in 0..octopi[0].len() { 56 | tick_octopi(octopi, x, y); 57 | } 58 | } 59 | 60 | let mut flash = 0; 61 | for i in octopi.iter_mut() { 62 | for j in i.iter_mut() { 63 | if j.flashed { 64 | flash += 1; 65 | } 66 | 67 | j.flashed = false; 68 | } 69 | } 70 | 71 | flash 72 | } 73 | 74 | fn tick_octopi(octopi: &mut Vec>, x: usize, y: usize) { 75 | let flash = octopi[y][x].tick(); 76 | if !flash { 77 | return; 78 | } 79 | 80 | for i in octo_neighbors(octopi, x, y) { 81 | tick_octopi(octopi, i.0, i.1); 82 | } 83 | } 84 | 85 | fn octo_neighbors(octopi: &[Vec], x: usize, y: usize) -> Vec<(usize, usize)> { 86 | let mut out = Vec::new(); 87 | let (lenx, leny) = (octopi[0].len() as isize, octopi.len() as isize); 88 | let (x, y) = (x as isize, y as isize); 89 | 90 | for i in 0..3 { 91 | for j in 0..3 { 92 | if i == 1 && j == 1 { 93 | continue; 94 | } 95 | 96 | let x = x + i - 1; 97 | let y = y + j - 1; 98 | 99 | if x < 0 || y < 0 || x >= lenx || y >= leny { 100 | continue; 101 | } 102 | 103 | out.push((x as usize, y as usize)); 104 | } 105 | } 106 | 107 | out 108 | } 109 | 110 | fn parse(raw: &str) -> Vec> { 111 | let mut out = Vec::new(); 112 | 113 | for i in raw.lines() { 114 | out.push( 115 | i.chars() 116 | .map(|x| Octopus { 117 | state: x.to_digit(10).unwrap() as u8, 118 | flashed: false, 119 | }) 120 | .collect(), 121 | ); 122 | } 123 | 124 | out 125 | } 126 | -------------------------------------------------------------------------------- /aoc_2021/src/day_13.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | use common::{solution, Answer}; 4 | use nd_vec::vector; 5 | 6 | type Point = nd_vec::Vec2; 7 | 8 | solution!("Transparent Origami", 13); 9 | 10 | fn part_a(input: &str) -> Answer { 11 | let mut paper = Paper::parse(input); 12 | paper.fold(0); 13 | 14 | paper.data.len().into() 15 | } 16 | 17 | fn part_b(input: &str) -> Answer { 18 | let mut paper = Paper::parse(input); 19 | (0..paper.folds.len()).for_each(|x| paper.fold(x)); 20 | 21 | paper.print().into() 22 | } 23 | 24 | #[derive(Debug)] 25 | struct Paper { 26 | data: HashSet, 27 | folds: Vec, 28 | } 29 | 30 | #[derive(Debug)] 31 | struct Fold { 32 | direction: Direction, 33 | position: usize, 34 | } 35 | 36 | #[derive(Debug)] 37 | enum Direction { 38 | Horizontal, 39 | Vertical, 40 | } 41 | 42 | impl Paper { 43 | fn parse(raw: &str) -> Self { 44 | let mut parts = raw.split("\n\n"); 45 | let data = parts.next().unwrap().lines().map(parse_point).collect(); 46 | let folds = parts.next().unwrap().lines().map(parse_fold).collect(); 47 | 48 | Self { data, folds } 49 | } 50 | 51 | // Cordantes go from 0 onwards 52 | fn fold(&mut self, ins: usize) { 53 | let ins = &self.folds[ins]; 54 | 55 | match ins.direction { 56 | Direction::Horizontal => { 57 | for i in self.data.clone().iter().filter(|x| x.x() > ins.position) { 58 | self.data.remove(i); 59 | self.data.insert(vector!(ins.position * 2 - i.x(), i.y())); 60 | } 61 | } 62 | Direction::Vertical => { 63 | for i in self.data.clone().iter().filter(|x| x.y() > ins.position) { 64 | self.data.remove(i); 65 | self.data.insert(vector!(i.x(), ins.position * 2 - i.y())); 66 | } 67 | } 68 | } 69 | } 70 | 71 | fn bounds(&self) -> (usize, usize) { 72 | let x = self.data.iter().map(|x| x.x()).max().unwrap(); 73 | let y = self.data.iter().map(|x| x.y()).max().unwrap(); 74 | (x, y) 75 | } 76 | 77 | fn print(&self) -> String { 78 | let (mx, my) = self.bounds(); 79 | let mut out = "\n".to_owned(); 80 | 81 | for y in 0..=my { 82 | for x in 0..=mx { 83 | let point = vector!(x, y); 84 | if self.data.contains(&point) { 85 | out.push('#'); 86 | } else { 87 | out.push(' '); 88 | } 89 | } 90 | out.push('\n'); 91 | } 92 | 93 | out 94 | } 95 | } 96 | 97 | fn parse_point(raw: &str) -> Point { 98 | let parts = raw.split_once(',').unwrap(); 99 | let x = parts.0.parse().unwrap(); 100 | let y = parts.1.parse().unwrap(); 101 | vector!(x, y) 102 | } 103 | 104 | fn parse_fold(raw: &str) -> Fold { 105 | let parts = raw.rsplit_once(' ').unwrap().1.split_once('=').unwrap(); 106 | let position = parts.1.parse().unwrap(); 107 | let direction = match parts.0 { 108 | "x" => Direction::Horizontal, 109 | "y" => Direction::Vertical, 110 | _ => unreachable!(), 111 | }; 112 | 113 | Fold { 114 | direction, 115 | position, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /aoc_2021/src/day_14.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Extended Polymerization", 14); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | process(input, 10).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | process(input, 40).into() 13 | } 14 | 15 | fn process(raw: &str, steps: usize) -> u64 { 16 | let counts = Polymer::parse(raw).process(steps); 17 | counts.iter().max().unwrap() - counts.iter().filter(|&&x| x != 0).min().unwrap() 18 | } 19 | 20 | #[derive(Debug)] 21 | struct Polymer { 22 | units: Vec, 23 | key: HashMap<[char; 2], char>, 24 | } 25 | 26 | impl Polymer { 27 | fn process(&mut self, steps: usize) -> [u64; 26] { 28 | fn index(unit: char) -> usize { 29 | unit as usize - 'A' as usize 30 | } 31 | 32 | let mut pairs = HashMap::<_, u64>::new(); 33 | let mut counts = [0; 26]; 34 | 35 | counts[index(*self.units.last().unwrap())] += 1; 36 | for units in self.units.windows(2) { 37 | counts[index(units[0])] += 1; 38 | *pairs.entry([units[0], units[1]]).or_default() += 1; 39 | } 40 | 41 | // AB -> C 42 | // (A, B) -> (A, C), (C, B) 43 | // C += 1 44 | for _ in 0..steps { 45 | let mut new_pairs = HashMap::new(); 46 | 47 | for (pair, count) in pairs.iter() { 48 | let mapping = self.key[pair]; 49 | 50 | *new_pairs.entry([pair[0], mapping]).or_default() += count; 51 | *new_pairs.entry([mapping, pair[1]]).or_default() += count; 52 | counts[index(mapping)] += count; 53 | } 54 | 55 | pairs = new_pairs; 56 | } 57 | 58 | counts 59 | } 60 | 61 | fn parse(raw: &str) -> Self { 62 | let (start, key) = raw.split_once("\n\n").unwrap(); 63 | let mut key_out = HashMap::new(); 64 | 65 | for i in key.lines() { 66 | let (k, v) = i.split_once(" -> ").unwrap(); 67 | let mut k = k.chars(); 68 | key_out.insert( 69 | [k.next().unwrap(), k.next().unwrap()], 70 | v.chars().next().unwrap(), 71 | ); 72 | } 73 | 74 | Self { 75 | units: start.chars().collect(), 76 | key: key_out, 77 | } 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod test { 83 | use indoc::indoc; 84 | 85 | const CASE: &str = indoc! {" 86 | NNCB 87 | 88 | CH -> B 89 | HH -> N 90 | CB -> H 91 | NH -> C 92 | HB -> C 93 | HC -> B 94 | HN -> C 95 | NN -> C 96 | BH -> H 97 | NC -> B 98 | NB -> B 99 | BN -> B 100 | BB -> N 101 | BC -> B 102 | CC -> N 103 | CN -> C 104 | "}; 105 | 106 | #[test] 107 | fn part_a() { 108 | assert_eq!(super::part_a(CASE), 1588.into()); 109 | } 110 | 111 | #[test] 112 | fn part_b() { 113 | assert_eq!(super::part_b(CASE), 2188189693529_u64.into()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /aoc_2021/src/day_15.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | 3 | use hashbrown::HashMap; 4 | use nd_vec::{vector, Vector}; 5 | 6 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 7 | use common::{solution, Answer}; 8 | 9 | solution!("Chiton", 15); 10 | 11 | type Point = Vector; 12 | 13 | fn part_a(input: &str) -> Answer { 14 | let matrix = Grid::parse(input, |chr| chr.to_digit(10).unwrap() as u8); 15 | solve(matrix.size, |pos| matrix.get(pos).copied()).into() 16 | } 17 | 18 | fn part_b(input: &str) -> Answer { 19 | let matrix = Grid::parse(input, |chr| chr.to_digit(10).unwrap() as u8); 20 | solve(matrix.size * 5, |pos| { 21 | let (cx, cy) = (pos.x() / matrix.size.x(), pos.y() / matrix.size.y()); 22 | if cx > 4 || cy > 4 { 23 | return None; 24 | }; 25 | 26 | let pos = vector!(pos.x() % matrix.size.x(), pos.y() % matrix.size.y()); 27 | matrix 28 | .get(pos) 29 | .map(|x| (x + cx as u8 + cy as u8 - 1) % 9 + 1) 30 | }) 31 | .into() 32 | } 33 | 34 | fn solve(size: Point, get: impl Fn(Point) -> Option) -> u32 { 35 | let mut seen = HashMap::new(); 36 | let mut queue = BinaryHeap::new(); 37 | 38 | seen.insert(vector!(0, 0), 0); 39 | queue.push(QueueItem { 40 | pos: vector!(0, 0), 41 | distance: 0, 42 | }); 43 | 44 | while let Some(item) = queue.pop() { 45 | if item.pos == size - vector!(1, 1) { 46 | return item.distance; 47 | } 48 | 49 | for dir in Direction::ALL { 50 | let Some((pos, cost)) = dir.try_advance(item.pos).and_then(|x| Some((x, get(x)?))) 51 | else { 52 | continue; 53 | }; 54 | 55 | let dist = seen.entry(pos).or_insert(u32::MAX); 56 | let next_dist = item.distance + cost as u32; 57 | 58 | if next_dist < *dist { 59 | *dist = next_dist; 60 | queue.push(QueueItem { 61 | pos, 62 | distance: next_dist, 63 | }); 64 | } 65 | } 66 | } 67 | 68 | unreachable!() 69 | } 70 | 71 | #[derive(PartialEq, Eq)] 72 | struct QueueItem { 73 | pos: Point, 74 | distance: u32, 75 | } 76 | 77 | impl Ord for QueueItem { 78 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 79 | other.distance.cmp(&self.distance) 80 | } 81 | } 82 | 83 | impl PartialOrd for QueueItem { 84 | fn partial_cmp(&self, other: &Self) -> Option { 85 | Some(self.cmp(other)) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod test { 91 | use indoc::indoc; 92 | 93 | const CASE: &str = indoc! {" 94 | 1163751742 95 | 1381373672 96 | 2136511328 97 | 3694931569 98 | 7463417111 99 | 1319128137 100 | 1359912421 101 | 3125421639 102 | 1293138521 103 | 2311944581 104 | "}; 105 | 106 | #[test] 107 | fn part_a() { 108 | assert_eq!(super::part_a(CASE), 40.into()); 109 | } 110 | 111 | #[test] 112 | fn part_b() { 113 | assert_eq!(super::part_b(CASE), 315.into()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /aoc_2021/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use common::solution; 2 | use common::Solution; 3 | 4 | mod day_01; 5 | mod day_02; 6 | mod day_03; 7 | mod day_04; 8 | mod day_05; 9 | mod day_06; 10 | mod day_07; 11 | mod day_08; 12 | mod day_09; 13 | mod day_10; 14 | mod day_11; 15 | mod day_12; 16 | mod day_13; 17 | mod day_14; 18 | mod day_15; 19 | // [import_marker] 20 | 21 | pub const SOLUTIONS: &[Solution] = &[ 22 | day_01::SOLUTION, 23 | day_02::SOLUTION, 24 | day_03::SOLUTION, 25 | day_04::SOLUTION, 26 | day_05::SOLUTION, 27 | day_06::SOLUTION, 28 | day_07::SOLUTION, 29 | day_08::SOLUTION, 30 | day_09::SOLUTION, 31 | day_10::SOLUTION, 32 | day_11::SOLUTION, 33 | day_12::SOLUTION, 34 | day_13::SOLUTION, 35 | day_14::SOLUTION, 36 | day_15::SOLUTION, 37 | // [list_marker] 38 | ]; 39 | -------------------------------------------------------------------------------- /aoc_2021/todo.md: -------------------------------------------------------------------------------- 1 | # AOC 2021 Todo 2 | 3 | - [x] Day 1 4 | - [x] Day 2 5 | - [x] Day 3 6 | - [x] Day 4 7 | - [x] Day 5 8 | - [x] Day 6 9 | - [x] Day 7 10 | - [x] Day 8 11 | - [x] Day 9 12 | - [x] Day 10 13 | - [x] Day 11 14 | - [ ] Day 12 15 | - [x] Day 13 16 | - [x] Day 14 17 | - [ ] Day 15 18 | - [ ] Day 16 19 | - [ ] Day 17 20 | - [ ] Day 18 21 | - [ ] Day 19 22 | - [ ] Day 20 23 | - [ ] Day 21 24 | - [ ] Day 22 25 | - [ ] Day 23 26 | - [ ] Day 24 27 | - [ ] Day 25 28 | -------------------------------------------------------------------------------- /aoc_2022/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc_2022" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | common = { path = "../common" } 8 | derive_more = "0.99.17" 9 | hashbrown = "0.13.1" 10 | indoc = "2.0.4" 11 | # nd_vec = "0.4.0" 12 | nd_vec = { git = "https://github.com/connorslade/nd-vec" } 13 | num-traits = "0.2.15" 14 | pathfinding = "4.3.3" 15 | petgraph = "0.6.2" 16 | rayon = "1.6.1" 17 | -------------------------------------------------------------------------------- /aoc_2022/src/day_01.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Calorie Counting", 1); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let elfs = get_elfs(input); 7 | 8 | (*elfs.last().unwrap()).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | let elfs = get_elfs(input); 13 | 14 | elfs.iter().rev().take(3).sum::().into() 15 | } 16 | 17 | fn get_elfs(data: &str) -> Vec { 18 | let mut out = data 19 | .replace('\r', "") 20 | .split("\n\n") 21 | .map(|x| x.lines().map(|x| x.parse::().unwrap()).sum()) 22 | .collect::>(); 23 | out.sort(); 24 | out 25 | } 26 | -------------------------------------------------------------------------------- /aoc_2022/src/day_02.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Rock Paper Scissors", 2); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let mut score = 0; 7 | 8 | for (other, self_) in input 9 | .lines() 10 | .filter(|x| !x.is_empty()) 11 | .map(|x| x.split_once(' ').unwrap()) 12 | { 13 | let other_move = Move::from_str(other); 14 | let self_move = Move::from_str(self_); 15 | 16 | score += self_move as u32 + 1; 17 | score += score_round(other_move, self_move).to_score(); 18 | } 19 | 20 | score.into() 21 | } 22 | 23 | fn part_b(input: &str) -> Answer { 24 | let mut score = 0; 25 | 26 | for (other, self_) in input 27 | .lines() 28 | .filter(|x| !x.is_empty()) 29 | .map(|x| x.split_once(' ').unwrap()) 30 | { 31 | let other_move = Move::from_str(other); 32 | let self_move = match self_ { 33 | "X" => other_move.derive(false), 34 | "Y" => other_move, 35 | "Z" => other_move.derive(true), 36 | _ => unreachable!(), 37 | }; 38 | 39 | score += self_move as u32 + 1; 40 | score += score_round(other_move, self_move).to_score(); 41 | } 42 | 43 | score.into() 44 | } 45 | 46 | #[derive(Debug, Clone, Copy, PartialEq)] 47 | enum Move { 48 | Rock, 49 | Paper, 50 | Scissors, 51 | } 52 | 53 | #[derive(Debug)] 54 | enum Outcome { 55 | Win, 56 | Lose, 57 | Tie, 58 | } 59 | 60 | impl Move { 61 | fn from_str(s: &str) -> Self { 62 | match s { 63 | "A" | "X" => Move::Rock, 64 | "B" | "Y" => Move::Paper, 65 | "C" | "Z" => Move::Scissors, 66 | _ => unreachable!(), 67 | } 68 | } 69 | 70 | fn from_index(i: usize) -> Self { 71 | match i { 72 | 0 => Move::Rock, 73 | 1 => Move::Paper, 74 | 2 => Move::Scissors, 75 | _ => unreachable!(), 76 | } 77 | } 78 | 79 | fn derive(&self, win: bool) -> Self { 80 | Move::from_index((*self as usize + if win { 1 } else { 2 }) % 3) 81 | } 82 | } 83 | 84 | impl Outcome { 85 | fn to_score(&self) -> u32 { 86 | match self { 87 | Outcome::Lose => 0, 88 | Outcome::Tie => 3, 89 | Outcome::Win => 6, 90 | } 91 | } 92 | } 93 | 94 | fn score_round(other: Move, self_: Move) -> Outcome { 95 | if other == self_ { 96 | return Outcome::Tie; 97 | } 98 | 99 | if (other as u32 + 1) % 3 == self_ as u32 { 100 | return Outcome::Win; 101 | } 102 | 103 | Outcome::Lose 104 | } 105 | -------------------------------------------------------------------------------- /aoc_2022/src/day_03.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Rucksack Reorganization", 3); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let mut out = 0; 9 | 10 | for i in input.trim().lines() { 11 | let mut both = i[0..i.len() / 2].chars().collect::>(); 12 | let pocket_2 = i[i.len() / 2..].chars().collect::>(); 13 | both.retain(|x| pocket_2.contains(x)); 14 | both.dedup(); 15 | 16 | debug_assert!(both.len() == 1); 17 | out += score_item(both[0]) as usize; 18 | } 19 | 20 | out.into() 21 | } 22 | 23 | fn part_b(input: &str) -> Answer { 24 | let mut out = 0; 25 | 26 | for i in input.trim().lines().collect::>().chunks(3) { 27 | let mut all = HashSet::new(); 28 | i.iter().for_each(|x| all.extend(x.chars())); 29 | i.iter().for_each(|x| all.retain(|y| x.contains(*y))); 30 | 31 | debug_assert!(all.len() == 1); 32 | out += score_item(*all.iter().next().unwrap()) as usize; 33 | } 34 | 35 | out.into() 36 | } 37 | 38 | fn score_item(char_: char) -> u8 { 39 | match char_ as u8 { 40 | 97..=122 => char_ as u8 - 96, 41 | 65..=90 => char_ as u8 - 38, 42 | _ => unreachable!(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /aoc_2022/src/day_04.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Camp Cleanup", 4); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let mut out = 0; 7 | 8 | for (p1, p2) in assignment_loop(input) { 9 | out += ((p1.0 >= p2.0 && p1.1 <= p2.1) || (p2.0 >= p1.0 && p2.1 <= p1.1)) as usize; 10 | } 11 | 12 | out.into() 13 | } 14 | 15 | fn part_b(input: &str) -> Answer { 16 | let mut out = 0; 17 | 18 | for (p1, p2) in assignment_loop(input) { 19 | out += (p1.0.max(p2.0) <= p1.1.min(p2.1)) as usize; 20 | } 21 | 22 | out.into() 23 | } 24 | 25 | fn assignment_loop(raw: &str) -> Vec<((u32, u32), (u32, u32))> { 26 | raw.trim() 27 | .lines() 28 | .map(|x| x.split_once(',').unwrap()) 29 | .map(|(a, b)| (split_range(a), split_range(b))) 30 | .collect() 31 | } 32 | 33 | fn split_range(range: &str) -> (u32, u32) { 34 | let mut range = range.split('-').map(|x| x.parse::().unwrap()); 35 | (range.next().unwrap(), range.next().unwrap()) 36 | } 37 | -------------------------------------------------------------------------------- /aoc_2022/src/day_05.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Supply Stacks", 5); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | process(input, true).into() 7 | } 8 | 9 | fn part_b(input: &str) -> Answer { 10 | process(input, false).into() 11 | } 12 | 13 | fn process(raw: &str, part: bool) -> String { 14 | let raw = raw.replace('\r', ""); 15 | let (crates, orders) = raw.split_once("\n\n").unwrap(); 16 | let mut crates = parse_crates(crates); 17 | 18 | for i in orders.trim().lines() { 19 | let parts = i.split_whitespace().collect::>(); 20 | let count = parts[1].parse::().unwrap(); 21 | let from = parts[3].parse::().unwrap() - 1; 22 | let to = parts[5].parse::().unwrap() - 1; 23 | 24 | let count = crates[from].len() - count..; 25 | let mut working = crates[from].drain(count).collect::>(); 26 | if part { 27 | working.reverse(); 28 | } 29 | 30 | crates[to].extend(working); 31 | } 32 | 33 | crates 34 | .into_iter() 35 | .filter(|x| !x.is_empty()) 36 | .map(|mut x| x.pop().unwrap()) 37 | .collect() 38 | } 39 | 40 | fn parse_crates(raw: &str) -> Vec> { 41 | let raw_len = raw.lines().next().unwrap().len() + 1; 42 | let mut out = vec![Vec::new(); raw_len / 4]; 43 | 44 | for i in raw.lines().filter(|x| !x.starts_with(" 1")) { 45 | for (ji, i) in i.chars().enumerate().filter(|x| x.1.is_ascii_uppercase()) { 46 | out[(ji as f32 / 4.).ceil() as usize - 1].insert(0, i); 47 | } 48 | } 49 | 50 | out 51 | } 52 | -------------------------------------------------------------------------------- /aoc_2022/src/day_06.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Tuning Trouble", 6); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | process(input, 4).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | process(input, 14).into() 13 | } 14 | 15 | fn process(input: &str, size: usize) -> usize { 16 | let mut chars = HashSet::new(); 17 | 'o: for i in input.chars().enumerate().collect::>().windows(size) { 18 | for j in i { 19 | if !chars.insert(j.1) { 20 | chars.clear(); 21 | continue 'o; 22 | } 23 | } 24 | 25 | return i[size - 1].0 + 1; 26 | } 27 | 28 | unreachable!() 29 | } 30 | -------------------------------------------------------------------------------- /aoc_2022/src/day_07.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("No Space Left On Device", 7); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | process(input) 9 | .get_all_children() 10 | .iter() 11 | .filter(|x| x.is_dir && x.size <= 100000) 12 | .fold(0, |a, x| a + x.size) 13 | .into() 14 | } 15 | 16 | fn part_b(input: &str) -> Answer { 17 | let folders = process(input); 18 | let needed_space = 30000000 - (70000000 - folders.size); 19 | 20 | let folder_vec = folders.get_all_children(); 21 | let mut folder_vec = folder_vec.iter().collect::>(); 22 | folder_vec.sort_by(|a, b| a.size.cmp(&b.size)); 23 | 24 | folder_vec 25 | .iter() 26 | .find(|x| x.is_dir && x.size > needed_space) 27 | .unwrap() 28 | .size 29 | .into() 30 | } 31 | 32 | fn process(raw: &str) -> File { 33 | let mut tree = File::new("root"); 34 | let mut path = Vec::new(); 35 | for line in raw.lines() { 36 | let parts = line.split_whitespace().collect::>(); 37 | 38 | if parts[..2] == ["$", "cd"] { 39 | match parts[2] { 40 | "/" => continue, 41 | ".." => { 42 | path.pop().unwrap(); 43 | continue; 44 | } 45 | _ => {} 46 | } 47 | 48 | let parent = tree.get_path(&path); 49 | path.push(parts[2].to_owned()); 50 | if parent.children.iter().any(|x| x.name == parts[2]) { 51 | continue; 52 | } 53 | 54 | parent.children.push(File::new(parts[2])); 55 | continue; 56 | } 57 | 58 | if parts[0] == "dir" { 59 | let parent = tree.get_path(&path); 60 | if let Some(i) = parent.children.iter_mut().find(|x| x.name == parts[1]) { 61 | i.is_dir = true; 62 | continue; 63 | } 64 | 65 | let mut child = File::new(parts[1]); 66 | child.is_dir = true; 67 | parent.children.push(child); 68 | continue; 69 | } 70 | 71 | if let Ok(i) = parts[0].parse::() { 72 | let mut child = File::new(parts[1]); 73 | child.size = i; 74 | tree.get_path(&path).children.push(child); 75 | } 76 | } 77 | tree.propagate_size(); 78 | 79 | tree 80 | } 81 | 82 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 83 | struct File { 84 | name: String, 85 | size: usize, 86 | children: Vec, 87 | is_dir: bool, 88 | } 89 | 90 | impl File { 91 | fn new(name: &str) -> Self { 92 | Self { 93 | name: name.to_string(), 94 | size: 0, 95 | children: Vec::new(), 96 | is_dir: false, 97 | } 98 | } 99 | 100 | fn get_path(&mut self, path: &[String]) -> &mut Self { 101 | let mut current = self; 102 | for part in path { 103 | current = current 104 | .children 105 | .iter_mut() 106 | .find(|f| f.name == *part) 107 | .unwrap(); 108 | } 109 | 110 | current 111 | } 112 | 113 | fn propagate_size(&mut self) -> usize { 114 | for i in &mut self.children { 115 | self.size += i.propagate_size(); 116 | } 117 | 118 | self.size 119 | } 120 | 121 | fn get_all_children(&self) -> HashSet { 122 | let mut out = HashSet::new(); 123 | 124 | for i in &self.children { 125 | out.insert(i.clone()); 126 | out.extend(i.get_all_children()); 127 | } 128 | 129 | out 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /aoc_2022/src/day_08.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Treetop Tree House", 8); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let trees = parse_trees(input); 7 | let mut count = 0; 8 | 9 | for row in 0..trees.len() { 10 | for col in 0..trees[row].len() { 11 | let height = trees[row][col]; 12 | if trees[..row].iter().all(|x| x[col] < height) 13 | || trees[row][..col].iter().all(|x| x < &height) 14 | || trees[row + 1..].iter().all(|x| x[col] < height) 15 | || trees[row][col + 1..].iter().all(|x| x < &height) 16 | { 17 | count += 1; 18 | } 19 | } 20 | } 21 | 22 | count.into() 23 | } 24 | 25 | fn part_b(input: &str) -> Answer { 26 | let trees = parse_trees(input); 27 | let mut count = 0; 28 | 29 | for row in 0..trees.len() { 30 | for col in 0..trees[row].len() { 31 | let mut ctx = (1, trees[row][col]); 32 | process_slice(&mut ctx, trees[..row].iter().map(|x| x[col]).rev()); 33 | process_slice(&mut ctx, trees[row][..col].iter().rev().copied()); 34 | process_slice(&mut ctx, trees[row + 1..].iter().map(|x| x[col])); 35 | process_slice(&mut ctx, trees[row][col + 1..].iter().copied()); 36 | count = count.max(ctx.0); 37 | } 38 | } 39 | 40 | count.into() 41 | } 42 | 43 | fn parse_trees(inp: &str) -> Vec> { 44 | inp.lines() 45 | .map(|i| { 46 | i.chars() 47 | .filter(|x| x.is_ascii_digit()) 48 | .filter_map(|x| x.to_digit(10)) 49 | .map(|x| x as usize) 50 | .collect() 51 | }) 52 | .collect() 53 | } 54 | 55 | fn process_slice((local_best, height): &mut (usize, usize), iter: impl Iterator) { 56 | let mut score = 0; 57 | 58 | for i in iter { 59 | score += 1; 60 | if i >= *height { 61 | break; 62 | } 63 | } 64 | 65 | *local_best *= score; 66 | } 67 | -------------------------------------------------------------------------------- /aoc_2022/src/day_09.rs: -------------------------------------------------------------------------------- 1 | use std::vec; 2 | 3 | use hashbrown::HashSet; 4 | 5 | use common::{solution, Answer}; 6 | use nd_vec::vector; 7 | 8 | type Point = nd_vec::Vec2; 9 | 10 | solution!("Rope Bridge", 9); 11 | 12 | fn part_a(input: &str) -> Answer { 13 | process(input, 1).into() 14 | } 15 | 16 | fn part_b(input: &str) -> Answer { 17 | process(input, 9).into() 18 | } 19 | 20 | fn process(raw: &str, count: usize) -> usize { 21 | let orders = raw.lines().map(from_line).collect::>(); 22 | let mut tail_pios = HashSet::new(); 23 | let mut knots = vec![vector!(0, 0); count + 1]; 24 | 25 | tail_pios.insert(*knots.last().unwrap()); 26 | for (dir, count) in orders { 27 | for _ in 0..count { 28 | knots[0] += dir; 29 | 30 | for i in 1..knots.len() { 31 | let diff = knots[i - 1] - knots[i]; 32 | if diff.abs().max_component() <= 1 { 33 | continue; 34 | } 35 | 36 | knots[i] += diff.signum(); 37 | } 38 | tail_pios.insert(*knots.last().unwrap()); 39 | } 40 | } 41 | 42 | tail_pios.len() 43 | } 44 | 45 | // Direction, count 46 | fn from_line(imp: &str) -> (Point, u32) { 47 | let (direction, count) = imp.split_once(' ').unwrap(); 48 | let count = count.parse::().unwrap(); 49 | 50 | let out = match direction { 51 | "R" => vector!(1, 0), 52 | "L" => vector!(-1, 0), 53 | "U" => vector!(0, -1), 54 | "D" => vector!(0, 1), 55 | _ => panic!("Invalid direction"), 56 | }; 57 | 58 | (out, count as u32) 59 | } 60 | -------------------------------------------------------------------------------- /aoc_2022/src/day_10.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Cathode-Ray Tube", 10); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let instructions = parse(input); 7 | let cycles = cycle(&instructions); 8 | 9 | let mut out = 0; 10 | for i in (0..6).map(|x| 20 + 40 * x) { 11 | out += cycles[0..i].iter().sum::() * i as i32; 12 | } 13 | 14 | out.into() 15 | } 16 | 17 | fn part_b(input: &str) -> Answer { 18 | let instructions = parse(input); 19 | let mut out = "\n".to_owned(); 20 | let mut sprite = 1; 21 | let mut cycle = 0; 22 | 23 | for i in instructions { 24 | let (goto, inc) = i.info(); 25 | for i in cycle..goto + cycle { 26 | let diff = i % 40_i32 - sprite; 27 | if diff.abs() < 2 { 28 | out.push('#'); 29 | continue; 30 | } 31 | out.push(' '); 32 | } 33 | 34 | cycle += goto; 35 | sprite += inc; 36 | } 37 | 38 | make_lines(&out, 40).into() 39 | } 40 | 41 | #[derive(Debug, Clone, Copy)] 42 | enum Instruction { 43 | Noop, 44 | Addx(i32), 45 | } 46 | 47 | impl Instruction { 48 | fn info(&self) -> (i32, i32) { 49 | match self { 50 | Instruction::Noop => (1, 0), 51 | Instruction::Addx(x) => (2, *x), 52 | } 53 | } 54 | } 55 | 56 | fn make_lines(raw: &str, width: usize) -> String { 57 | raw.char_indices() 58 | .map(|(i, c)| { 59 | if i % width == 0 { 60 | return format!("{}\n", c); 61 | } 62 | c.to_string() 63 | }) 64 | .collect() 65 | } 66 | 67 | fn cycle(ins: &[Instruction]) -> Vec { 68 | let mut cycle = 0; 69 | let mut cycles = vec![0; 240]; 70 | cycles[0] = 1; 71 | 72 | for i in ins { 73 | match i { 74 | Instruction::Noop => cycle += 1, 75 | Instruction::Addx(x) => { 76 | cycles[cycle + 2] += x; 77 | cycle += 2; 78 | } 79 | } 80 | } 81 | 82 | cycles 83 | } 84 | 85 | fn parse(raw: &str) -> Vec { 86 | let mut out = Vec::new(); 87 | 88 | for line in raw.lines() { 89 | let mut parts = line.split_whitespace(); 90 | let ins = parts.next().unwrap(); 91 | match ins { 92 | "addx" => out.push(Instruction::Addx( 93 | parts.next().unwrap().parse::().unwrap(), 94 | )), 95 | "noop" => out.push(Instruction::Noop), 96 | _ => panic!("Unknown instruction: {}", ins), 97 | } 98 | } 99 | 100 | out 101 | } 102 | -------------------------------------------------------------------------------- /aoc_2022/src/day_11.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Monkey in the Middle", 11); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let monkeys = parse_monkeys(input); 9 | 10 | process(monkeys, 20, |x| x / 3).into() 11 | } 12 | 13 | fn part_b(input: &str) -> Answer { 14 | let monkeys = parse_monkeys(input); 15 | 16 | let magic = monkeys.iter().map(|x| x.test.divisor).product::(); 17 | process(monkeys, 10000, |x| x % magic).into() 18 | } 19 | 20 | struct Monkey { 21 | items: VecDeque, 22 | inspected: usize, 23 | operation: Operation, 24 | test: Test, 25 | } 26 | 27 | enum Operation { 28 | Add(u64), 29 | Multiply(u64), 30 | Square, 31 | } 32 | 33 | struct Test { 34 | divisor: u64, 35 | // [true, false] 36 | monkey: [usize; 2], 37 | } 38 | 39 | fn process(mut monkeys: Vec, iter: usize, proc: impl Fn(u64) -> u64) -> usize { 40 | for _ in 0..iter { 41 | for m in 0..monkeys.len() { 42 | while let Some(item) = monkeys[m].items.pop_front() { 43 | monkeys[m].inspected += 1; 44 | let item = proc(monkeys[m].operation.process(item)); 45 | let goto = monkeys[m].test.process(item); 46 | monkeys[goto].items.push_back(item); 47 | } 48 | } 49 | } 50 | 51 | monkeys.sort_unstable_by_key(|x| x.inspected); 52 | monkeys.pop().unwrap().inspected * monkeys.pop().unwrap().inspected 53 | } 54 | 55 | fn parse_monkeys(raw: &str) -> Vec { 56 | let mut out = Vec::new(); 57 | 58 | for i in raw.lines().collect::>().chunks(7) { 59 | let items = i[1] 60 | .split_once(": ") 61 | .unwrap() 62 | .1 63 | .split(", ") 64 | .map(|x| x.parse::().unwrap()) 65 | .collect::>(); 66 | 67 | let operation = Operation::parse(i[2].split_once(" = ").unwrap().1); 68 | let test = Test::parse(&i[3..6]); 69 | 70 | out.push(Monkey { 71 | items, 72 | inspected: 0, 73 | operation, 74 | test, 75 | }); 76 | } 77 | 78 | out 79 | } 80 | 81 | impl Operation { 82 | fn parse(inp: &str) -> Self { 83 | let mut parts = inp.split_whitespace(); 84 | assert_eq!(parts.next().unwrap(), "old"); 85 | 86 | let op = parts.next().unwrap(); 87 | let value = parts.next().unwrap(); 88 | match op { 89 | "*" => match value { 90 | "old" => Self::Square, 91 | _ => Self::Multiply(value.parse::().unwrap()), 92 | }, 93 | "+" => Self::Add(value.parse::().unwrap()), 94 | _ => panic!("Unsuppored operation"), 95 | } 96 | } 97 | 98 | fn process(&self, old: u64) -> u64 { 99 | match self { 100 | Self::Add(x) => old + x, 101 | Self::Multiply(x) => old * x, 102 | Self::Square => old * old, 103 | } 104 | } 105 | } 106 | 107 | impl Test { 108 | fn parse(inp: &[&str]) -> Self { 109 | let divisor = inp[0].split_once("by ").unwrap().1.parse::().unwrap(); 110 | 111 | let mut monkey = [0; 2]; 112 | for (i, line) in inp[1..].iter().enumerate() { 113 | let monkey_id = line 114 | .split_once("monkey ") 115 | .unwrap() 116 | .1 117 | .parse::() 118 | .unwrap(); 119 | monkey[i] = monkey_id; 120 | } 121 | 122 | Self { divisor, monkey } 123 | } 124 | 125 | fn process(&self, item: u64) -> usize { 126 | if item % self.divisor == 0 { 127 | return self.monkey[0]; 128 | } 129 | self.monkey[1] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /aoc_2022/src/day_12.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use common::{solution, Answer}; 4 | use nd_vec::vector; 5 | 6 | type Point = nd_vec::Vec2; 7 | 8 | solution!("Hill Climbing Algorithm", 12); 9 | 10 | fn part_a(input: &str) -> Answer { 11 | let map = parse(input); 12 | 13 | run_path(&map, |a, b| a <= b + 1, |c| c == map.end) 14 | .unwrap() 15 | .into() 16 | } 17 | 18 | fn part_b(input: &str) -> Answer { 19 | let mut map = parse(input); 20 | map.start = map.end; 21 | map.current = map.start; 22 | 23 | run_path(&map, |a, b| b <= a + 1, |c| map.data[c.y()][c.x()] == 0) 24 | .expect("No path found!?") 25 | .into() 26 | } 27 | 28 | fn run_path( 29 | map: &HeightMap, 30 | allow: fn(u8, u8) -> bool, 31 | solve: impl Fn(Point) -> bool, 32 | ) -> Option { 33 | let mut visited = vec![vec![false; map.data[0].len()]; map.data.len()]; 34 | let mut queue = VecDeque::new(); 35 | queue.push_back((map.current, Vec::new())); 36 | 37 | while !queue.is_empty() { 38 | let (current, history) = queue.pop_front().unwrap(); 39 | if solve(current) { 40 | return Some(history.len()); 41 | } 42 | 43 | let current_height = map.data[current.y()][current.x()]; 44 | let mut check_neighbor = |x: usize, y: usize| { 45 | if x >= map.data[0].len() 46 | || y >= map.data.len() 47 | || visited[y][x] 48 | || !allow(map.data[y][x], current_height) 49 | { 50 | return; 51 | } 52 | 53 | visited[y][x] = true; 54 | let mut new_history = history.clone(); 55 | new_history.push(current); 56 | queue.push_back((vector!(x, y), new_history)); 57 | }; 58 | 59 | let (cx, cy) = (current.x(), current.y()); 60 | check_neighbor(cx + 1, cy); 61 | check_neighbor(cx, cy + 1); 62 | check_neighbor(cx.wrapping_sub(1), cy); 63 | check_neighbor(cx, cy.wrapping_sub(1)); 64 | } 65 | 66 | None 67 | } 68 | 69 | #[derive(Debug, Clone)] 70 | struct HeightMap { 71 | data: Vec>, 72 | current: Point, 73 | 74 | start: Point, 75 | end: Point, 76 | } 77 | 78 | fn parse(raw: &str) -> HeightMap { 79 | let mut out = Vec::new(); 80 | let mut start = vector!(0, 0); 81 | let mut end = vector!(0, 0); 82 | 83 | for i in raw.lines() { 84 | let mut row = Vec::new(); 85 | 86 | for j in i.chars() { 87 | match j { 88 | 'S' => { 89 | row.push(0); 90 | start = vector!(row.len() - 1, out.len()); 91 | } 92 | 'E' => { 93 | row.push(25); 94 | end = vector!(row.len() - 1, out.len()); 95 | } 96 | _ => row.push(j as u8 - 97), 97 | } 98 | } 99 | 100 | out.push(row); 101 | } 102 | 103 | HeightMap { 104 | data: out, 105 | current: start, 106 | start, 107 | end, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /aoc_2022/src/day_13.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Distress Signal", 13); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let signals = parse(input); 9 | 10 | signals 11 | .chunks(2) 12 | .enumerate() 13 | .filter(|x| x.1[0].cmp(&x.1[1]) == Ordering::Less) 14 | .map(|x| 1 + x.0) 15 | .sum::() 16 | .into() 17 | } 18 | 19 | fn part_b(input: &str) -> Answer { 20 | let mut signals = parse(input); 21 | let div = [Token::Number(6), Token::Number(2)]; 22 | signals.extend(div.clone()); 23 | signals.sort(); 24 | 25 | signals 26 | .iter() 27 | .enumerate() 28 | .filter(|x| div.contains(x.1)) 29 | .map(|x| x.0 + 1) 30 | .product::() 31 | .into() 32 | } 33 | 34 | #[derive(Debug, Clone, PartialEq, Eq)] 35 | enum Token { 36 | Number(u32), 37 | List(Vec), 38 | } 39 | 40 | fn parse(raw: &str) -> Vec { 41 | raw.split("\n\n") 42 | .flat_map(|x| { 43 | x.lines() 44 | .map(tokenisze) 45 | .map(Token::List) 46 | .collect::>() 47 | }) 48 | .collect() 49 | } 50 | 51 | impl Ord for Token { 52 | fn cmp(&self, other: &Self) -> Ordering { 53 | match (self, other) { 54 | (Token::Number(l), Token::Number(r)) => l.cmp(r), 55 | (Token::List(l), Token::List(r)) => l.cmp(r), 56 | (Token::Number(l), Token::List(r)) => vec![Token::Number(*l)].cmp(r), 57 | (Token::List(l), Token::Number(r)) => l.cmp(&vec![Token::Number(*r)]), 58 | } 59 | } 60 | } 61 | 62 | impl PartialOrd for Token { 63 | fn partial_cmp(&self, other: &Self) -> Option { 64 | Some(self.cmp(other)) 65 | } 66 | } 67 | 68 | fn tokenisze(raw: &str) -> Vec { 69 | let mut out = Vec::new(); 70 | let mut working = String::new(); 71 | let mut in_list = 0; 72 | 73 | for i in raw.chars().filter(|x| !x.is_ascii_whitespace()) { 74 | match i { 75 | '[' if in_list > 0 => in_list += 1, 76 | ']' if in_list > 0 => in_list -= 1, 77 | _ => {} 78 | } 79 | 80 | match i { 81 | '[' if in_list == 0 => in_list += 1, 82 | ']' if in_list == 1 => { 83 | flush(&mut working, &mut out); 84 | in_list -= 1; 85 | } 86 | _ if in_list > 0 => working.push(i), 87 | 88 | ',' => flush(&mut working, &mut out), 89 | i if i.is_ascii_digit() => working.push(i), 90 | _ => {} 91 | } 92 | } 93 | 94 | flush(&mut working, &mut out); 95 | out 96 | } 97 | 98 | fn flush(working: &mut String, out: &mut Vec) { 99 | if working.is_empty() { 100 | return; 101 | } 102 | 103 | match working.parse::() { 104 | Ok(i) => out.push(Token::Number(i)), 105 | Err(_) => out.push(Token::List(tokenisze(working))), 106 | } 107 | working.clear(); 108 | } 109 | -------------------------------------------------------------------------------- /aoc_2022/src/day_15.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | use nd_vec::vector; 4 | use rayon::prelude::*; 5 | 6 | solution!("Beacon Exclusion Zone", 15); 7 | 8 | type Point = nd_vec::Vec2; 9 | 10 | fn part_a(input: &str) -> Answer { 11 | let world = World::parse(input); 12 | let y_level = 2000000; // 10 for example 13 | 14 | let blocked = (world.bounds.0.x()..=world.bounds.1.x()) 15 | .into_par_iter() 16 | .map(|x| vector!(x, y_level)) 17 | .filter(|x| world.is_sensed(*x)) 18 | .count(); 19 | 20 | (blocked - 1).into() 21 | } 22 | 23 | fn part_b(input: &str) -> Answer { 24 | let world = World::parse(input); 25 | let bounds = 4000000; 26 | 27 | let distress = world.search(bounds).expect("No distress beacon found"); 28 | (distress.x() * 4000000 + distress.y()).into() 29 | } 30 | 31 | struct World { 32 | sensors: Vec, 33 | bounds: (Point, Point), 34 | } 35 | 36 | #[derive(Debug)] 37 | struct Sensor { 38 | pos: Point, 39 | distance: isize, 40 | } 41 | 42 | impl World { 43 | fn parse(raw: &str) -> Self { 44 | let mut sensors = Vec::new(); 45 | let (mut min_bound, mut max_bound) = (Point::default(), Point::default()); 46 | 47 | for i in raw.lines() { 48 | let sensor = Sensor::parse(i); 49 | min_bound = min_bound.min(&(sensor.pos - sensor.distance)); 50 | max_bound = max_bound.max(&(sensor.pos + sensor.distance)); 51 | sensors.push(sensor); 52 | } 53 | 54 | Self { 55 | sensors, 56 | bounds: (min_bound, max_bound), 57 | } 58 | } 59 | 60 | fn search(&self, bounds: isize) -> Option { 61 | (0..=bounds).into_par_iter().find_map_any(|y| { 62 | for s in &self.sensors { 63 | let x1 = s.pos.x() - (s.distance - (s.pos.y() - y).abs()) - 1; 64 | let x2 = s.pos.x() + (s.distance - (s.pos.y() - y).abs()) + 1; 65 | 66 | if x1 > 0 && x1 < bounds && !self.is_sensed(vector!(x1, y)) { 67 | return Some(vector!(x1, y)); 68 | } 69 | 70 | if x2 > 0 && x2 < bounds && !self.is_sensed(vector!(x2, y)) { 71 | return Some(vector!(x2, y)); 72 | } 73 | } 74 | 75 | None 76 | }) 77 | } 78 | 79 | fn is_sensed(&self, point: Point) -> bool { 80 | self.sensors 81 | .iter() 82 | .any(|x| x.pos.manhattan_distance(&point) <= x.distance) 83 | } 84 | } 85 | 86 | impl Sensor { 87 | fn parse(raw: &str) -> Self { 88 | let parts = raw.split("at ").collect::>(); 89 | let parse_pos = |pos: &str| { 90 | let parts = pos.split(", ").collect::>(); 91 | vector!( 92 | parts[0].trim_start_matches("x=").parse().unwrap(), 93 | parts[1] 94 | .trim_start_matches("y=") 95 | .split(':') 96 | .next() 97 | .unwrap() 98 | .parse() 99 | .unwrap() 100 | ) 101 | }; 102 | 103 | let pos = parse_pos(parts[1]); 104 | let beacon = parse_pos(parts[2]); 105 | 106 | Self { 107 | pos, 108 | distance: pos.manhattan_distance(&beacon), 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /aoc_2022/src/day_18.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | use common::{solution, Answer}; 4 | use nd_vec::{vector, Vector}; 5 | 6 | solution!("Boiling Boulders", 18); 7 | 8 | type Pos = nd_vec::Vec3; 9 | 10 | fn part_a(input: &str) -> Answer { 11 | let world = World::parse(input); 12 | 13 | let mut open_faces = 0; 14 | 15 | for i in &world.points { 16 | open_faces += 6 - world.neighbors(i); 17 | } 18 | 19 | open_faces.into() 20 | } 21 | 22 | fn part_b(input: &str) -> Answer { 23 | let world = World::parse(input); 24 | 25 | let outside = world.flood_fill(Vector::default()); 26 | let mut out = 0; 27 | for i in &world.points { 28 | for j in NEIGHBORS { 29 | let n = *i + j; 30 | if !world.points.contains(&n) && outside.contains(&n) { 31 | out += 1; 32 | } 33 | } 34 | } 35 | 36 | out.into() 37 | } 38 | 39 | struct World { 40 | points: HashSet, 41 | } 42 | 43 | const NEIGHBORS: [Pos; 6] = [ 44 | vector!(1, 0, 0), 45 | vector!(-1, 0, 0), 46 | vector!(0, 1, 0), 47 | vector!(0, -1, 0), 48 | vector!(0, 0, 1), 49 | vector!(0, 0, -1), 50 | ]; 51 | 52 | impl World { 53 | fn parse(raw: &str) -> Self { 54 | Self { 55 | points: HashSet::from_iter(parse(raw)), 56 | } 57 | } 58 | 59 | fn neighbors(&self, point: &Pos) -> usize { 60 | let mut out = 0; 61 | 62 | for i in NEIGHBORS { 63 | out += self.points.contains(&(*point + i)) as usize; 64 | } 65 | 66 | out 67 | } 68 | 69 | fn bounds(&self) -> (Pos, Pos) { 70 | let mut min = vector!(i32::MAX, i32::MAX, i32::MAX); 71 | let mut max = vector!(i32::MIN, i32::MIN, i32::MIN); 72 | 73 | for i in &self.points { 74 | min = min.min(i); 75 | max = max.max(i); 76 | } 77 | 78 | (min, max) 79 | } 80 | 81 | fn flood_fill(&self, start: Pos) -> HashSet { 82 | let bounds = self.bounds(); 83 | let mut steam = HashSet::new(); 84 | let mut new = vec![start]; 85 | 86 | while let Some(s) = new.pop() { 87 | steam.insert(s); 88 | for n in NEIGHBORS { 89 | let n = s + n; 90 | if n.x() > bounds.1.x() + 1 91 | || n.x() < bounds.0.x() - 1 92 | || n.y() > bounds.1.y() + 1 93 | || n.y() < bounds.0.y() - 1 94 | || n.z() > bounds.1.z() + 1 95 | || n.z() < bounds.0.z() - 1 96 | || self.points.contains(&n) 97 | || steam.contains(&n) 98 | || new.contains(&n) 99 | { 100 | continue; 101 | } 102 | 103 | new.push(n); 104 | } 105 | } 106 | 107 | steam 108 | } 109 | } 110 | 111 | fn parse(raw: &str) -> Vec { 112 | let mut out = Vec::new(); 113 | 114 | for i in raw.lines() { 115 | let mut parts = i.split(','); 116 | out.push(vector!( 117 | parts.next().unwrap().parse().unwrap(), 118 | parts.next().unwrap().parse().unwrap(), 119 | parts.next().unwrap().parse().unwrap() 120 | )); 121 | } 122 | 123 | out 124 | } 125 | -------------------------------------------------------------------------------- /aoc_2022/src/day_20.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Grove Positioning System", 20); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let mut file = File::new(input); 7 | file.mix(); 8 | file.coordinates().into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | let mut file = File::new(input).multiply(811589153); 13 | (0..10).for_each(|_| file.mix()); 14 | file.coordinates().into() 15 | } 16 | 17 | #[derive(Debug)] 18 | struct File { 19 | // (value, index) 20 | list: Vec<(i64, usize)>, 21 | } 22 | 23 | impl File { 24 | fn new(raw: &str) -> Self { 25 | Self { 26 | list: raw 27 | .lines() 28 | .map(|x| x.parse().unwrap()) 29 | .enumerate() 30 | .map(|(i, v)| (v, i)) 31 | .collect(), 32 | } 33 | } 34 | 35 | fn coordinates(&self) -> i64 { 36 | let zero = self.list.iter().position(|x| x.0 == 0).unwrap() as isize; 37 | self.get(zero + 1000).unwrap() 38 | + self.get(zero + 2000).unwrap() 39 | + self.get(zero + 3000).unwrap() 40 | } 41 | 42 | fn multiply(self, val: i64) -> Self { 43 | Self { 44 | list: self.list.into_iter().map(|x| (x.0 * val, x.1)).collect(), 45 | } 46 | } 47 | 48 | fn get(&self, index: isize) -> Option<&i64> { 49 | self.list 50 | .get(index as usize % self.list.len()) 51 | .map(|(v, _)| v) 52 | } 53 | 54 | fn mix(&mut self) { 55 | for i in 0..self.list.len() { 56 | let (index, value) = self 57 | .list 58 | .iter() 59 | .enumerate() 60 | .find(|x| x.1 .1 == i) 61 | .map(|x| (x.0, *x.1)) 62 | .unwrap(); 63 | 64 | self.list.remove(index); 65 | let raw_i = index as i64 + value.0; 66 | let new_i = if value.0 > 0 { 67 | raw_i % self.list.len() as i64 68 | } else if raw_i < 0 { 69 | self.list.len() as i64 - (raw_i.abs() % self.list.len() as i64) 70 | } else { 71 | raw_i 72 | }; 73 | self.list.insert(new_i as usize, value); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /aoc_2022/src/day_25.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Full of Hot Air", 25); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | snafu::encode(input.lines().map(snafu::decode).sum::()).into() 7 | } 8 | 9 | fn part_b(_input: &str) -> Answer { 10 | // No part b for day 25! 11 | Answer::Unimplemented 12 | } 13 | 14 | mod snafu { 15 | pub fn decode(s: &str) -> i64 { 16 | let mut value = 0; 17 | 18 | for (i, c) in s.chars().rev().enumerate() { 19 | value += match c { 20 | '0'..='2' => c as i64 - '0' as i64, 21 | '-' => -1, 22 | '=' => -2, 23 | _ => panic!("Invalid character"), 24 | } * 5_i64.pow(i as u32); 25 | } 26 | 27 | value 28 | } 29 | 30 | pub fn encode(real: i64) -> String { 31 | let mut out = String::new(); 32 | let mut num = real; 33 | 34 | while num > 0 { 35 | let index = (num % 5) as usize; 36 | out.push("012=-".as_bytes()[index] as char); 37 | num -= [0, 1, 2, -2, -1][index]; 38 | num /= 5; 39 | } 40 | 41 | out.chars().rev().collect::() 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod test { 47 | use indoc::indoc; 48 | 49 | const CASE: &str = indoc! {r" 50 | 1=-0-2 51 | 12111 52 | 2=0= 53 | 21 54 | 2=01 55 | 111 56 | 20012 57 | 112 58 | 1=-1= 59 | 1-12 60 | 12 61 | 1= 62 | 122 63 | "}; 64 | 65 | #[test] 66 | fn part_a() { 67 | assert_eq!(super::part_a(CASE), "2=-1=0".into()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /aoc_2022/src/lib.rs: -------------------------------------------------------------------------------- 1 | use common::Solution; 2 | 3 | mod day_01; 4 | mod day_02; 5 | mod day_03; 6 | mod day_04; 7 | mod day_05; 8 | mod day_06; 9 | mod day_07; 10 | mod day_08; 11 | mod day_09; 12 | mod day_10; 13 | mod day_11; 14 | mod day_12; 15 | mod day_13; 16 | mod day_14; 17 | mod day_15; 18 | mod day_16; 19 | mod day_17; 20 | mod day_18; 21 | mod day_19; 22 | mod day_20; 23 | mod day_21; 24 | mod day_22; 25 | mod day_23; 26 | mod day_24; 27 | mod day_25; 28 | 29 | pub const SOLUTIONS: &[Solution] = &[ 30 | day_01::SOLUTION, 31 | day_02::SOLUTION, 32 | day_03::SOLUTION, 33 | day_04::SOLUTION, 34 | day_05::SOLUTION, 35 | day_06::SOLUTION, 36 | day_07::SOLUTION, 37 | day_08::SOLUTION, 38 | day_09::SOLUTION, 39 | day_10::SOLUTION, 40 | day_11::SOLUTION, 41 | day_12::SOLUTION, 42 | day_13::SOLUTION, 43 | day_14::SOLUTION, 44 | day_15::SOLUTION, 45 | day_16::SOLUTION, 46 | day_17::SOLUTION, 47 | day_18::SOLUTION, 48 | day_19::SOLUTION, 49 | day_20::SOLUTION, 50 | day_21::SOLUTION, 51 | day_22::SOLUTION, 52 | day_23::SOLUTION, 53 | day_24::SOLUTION, 54 | day_25::SOLUTION, 55 | ]; 56 | -------------------------------------------------------------------------------- /aoc_2023/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc_2023" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | aoc_lib = { path = "../aoc_lib" } 8 | common = { path = "../common" } 9 | 10 | bitvec = "1.0.1" 11 | indoc = "2.0.4" 12 | itertools = "0.12.0" 13 | nd_vec = { git = "https://github.com/connorslade/nd-vec.git" } 14 | num-traits = "0.2.17" 15 | once_cell = "1.18.0" 16 | petgraph = "0.6.4" 17 | polynomial = "0.2.6" 18 | rayon = "1.8.0" 19 | regex = "1.10.2" 20 | rustworkx-core = "0.13.2" 21 | -------------------------------------------------------------------------------- /aoc_2023/src/day_01.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Trebuchet?!", 1); 4 | 5 | const DIGITS: [&str; 9] = [ 6 | "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", 7 | ]; 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let mut sum = 0; 11 | for line in input.lines() { 12 | let mut digits = line.chars().filter_map(|c| c.to_digit(10)); 13 | let first = digits.next().unwrap(); 14 | let last = digits.last().unwrap_or(first); 15 | sum += first * 10 + last; 16 | } 17 | 18 | sum.into() 19 | } 20 | 21 | fn part_b(input: &str) -> Answer { 22 | let mut sum = 0; 23 | for line in input.lines() { 24 | let digits = digits(line); 25 | sum += digits[0] * 10 + digits[1]; 26 | } 27 | 28 | sum.into() 29 | } 30 | 31 | fn digits(i: &str) -> [u32; 2] { 32 | let mut first = None; 33 | let mut last = 0; 34 | 35 | let mut digit = |c| { 36 | first = first.or(Some(c)); 37 | last = c; 38 | }; 39 | 40 | let chars = i.as_bytes(); 41 | for (i, c) in chars.iter().enumerate() { 42 | if c.is_ascii_digit() { 43 | digit((c - b'0') as u32); 44 | } else { 45 | for (j, d) in DIGITS.iter().enumerate() { 46 | if chars[i..].starts_with(d.as_bytes()) { 47 | digit(j as u32 + 1); 48 | } 49 | } 50 | } 51 | } 52 | 53 | [first.unwrap(), last] 54 | } 55 | 56 | #[cfg(test)] 57 | mod test { 58 | use indoc::indoc; 59 | 60 | const CASE_A: &str = indoc! {" 61 | 1abc2 62 | pqr3stu8vwx 63 | a1b2c3d4e5f 64 | treb7uchet 65 | "}; 66 | 67 | const CASE_B: &str = indoc! {" 68 | two1nine 69 | eightwothree 70 | abcone2threexyz 71 | xtwone3four 72 | 4nineeightseven2 73 | zoneight234 74 | 7pqrstsixteen 75 | "}; 76 | 77 | #[test] 78 | fn part_a() { 79 | assert_eq!(super::part_a(CASE_A), 142.into()); 80 | } 81 | 82 | #[test] 83 | fn part_b() { 84 | assert_eq!(super::part_b(CASE_B), 281.into()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /aoc_2023/src/day_02.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Cube Conundrum", 2); 4 | 5 | // 12 red cubes, 13 green cubes, and 14 blue cubes 6 | const MAX_CUBES: [u32; 3] = [12, 13, 14]; 7 | 8 | fn part_a(input: &str) -> Answer { 9 | parse(input) 10 | .iter() 11 | .enumerate() 12 | .filter(|(_, games)| games.iter().all(|game| game.is_possible())) 13 | .map(|x| x.0 + 1) 14 | .sum::() 15 | .into() 16 | } 17 | 18 | fn part_b(input: &str) -> Answer { 19 | parse(input) 20 | .iter() 21 | .map(|games| { 22 | let mut max = CubeSet::default(); 23 | for game in games { 24 | max = max.max(game); 25 | } 26 | max.red * max.green * max.blue 27 | }) 28 | .sum::() 29 | .into() 30 | } 31 | 32 | fn parse(input: &str) -> Vec> { 33 | input 34 | .lines() 35 | .map(|line| { 36 | let cubes = line.split_once(':').unwrap().1; 37 | 38 | let mut sets = Vec::new(); 39 | for game in cubes.split(';') { 40 | let mut cubes = CubeSet::default(); 41 | for i in game.split(',') { 42 | let mut iter = i.split_whitespace(); 43 | let count = iter.next().unwrap().parse::().unwrap(); 44 | let color = iter.next().unwrap(); 45 | 46 | match color { 47 | "red" => cubes.red += count, 48 | "green" => cubes.green += count, 49 | "blue" => cubes.blue += count, 50 | _ => unreachable!(), 51 | } 52 | } 53 | sets.push(cubes); 54 | } 55 | 56 | sets 57 | }) 58 | .collect() 59 | } 60 | 61 | #[derive(Debug, Default)] 62 | struct CubeSet { 63 | red: u32, 64 | green: u32, 65 | blue: u32, 66 | } 67 | 68 | impl CubeSet { 69 | fn max(&self, other: &Self) -> Self { 70 | Self { 71 | red: self.red.max(other.red), 72 | green: self.green.max(other.green), 73 | blue: self.blue.max(other.blue), 74 | } 75 | } 76 | 77 | fn is_possible(&self) -> bool { 78 | self.red <= MAX_CUBES[0] && self.green <= MAX_CUBES[1] && self.blue <= MAX_CUBES[2] 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod test { 84 | use indoc::indoc; 85 | 86 | const CASE: &str = indoc! {" 87 | Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 88 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 89 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 90 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 91 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green 92 | "}; 93 | 94 | #[test] 95 | fn part_a() { 96 | assert_eq!(super::part_a(CASE), 8.into()); 97 | } 98 | 99 | #[test] 100 | fn part_b() { 101 | assert_eq!(super::part_b(CASE), 2286.into()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /aoc_2023/src/day_03.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use aoc_lib::regex; 4 | use common::{solution, Answer}; 5 | use nd_vec::{vector, Vec2}; 6 | 7 | type Pos = Vec2; 8 | 9 | solution!("Gear Ratios", 3); 10 | 11 | fn part_a(input: &str) -> Answer { 12 | parse(input) 13 | .gears 14 | .iter() 15 | .filter(|x| x.part_number) 16 | .map(|x| x.value) 17 | .sum::() 18 | .into() 19 | } 20 | 21 | fn part_b(input: &str) -> Answer { 22 | parse(input) 23 | .ratios 24 | .iter() 25 | .filter(|(_, vals)| vals.len() == 2) 26 | .map(|(_, vals)| vals[0] * vals[1]) 27 | .sum::() 28 | .into() 29 | } 30 | 31 | struct ParseResult { 32 | gears: Vec, 33 | ratios: HashMap>, 34 | } 35 | 36 | fn parse(input: &str) -> ParseResult { 37 | let mut symbols = HashMap::new(); 38 | for (y, line) in input.lines().enumerate() { 39 | for (x, c) in line.char_indices() { 40 | if !c.is_ascii_digit() && c != '.' { 41 | symbols.insert(vector!(x, y), c); 42 | } 43 | } 44 | } 45 | 46 | let mut gears = Vec::new(); 47 | let mut ratios = HashMap::new(); 48 | for (y, line) in input.lines().enumerate() { 49 | for m in regex!(r"\d+").find_iter(line) { 50 | let value = m.as_str().parse().unwrap(); 51 | 52 | let mut part_number = false; 53 | for nx in m.start().saturating_sub(1)..=m.end() { 54 | for ny in y.saturating_sub(1)..=y + 1 { 55 | let pos = vector!(nx, ny); 56 | let symbol = symbols.get(&pos); 57 | part_number |= symbol.is_some(); 58 | 59 | if symbol == Some(&'*') { 60 | ratios.entry(pos).or_insert(Vec::new()).push(value); 61 | } 62 | } 63 | } 64 | 65 | gears.push(Gear { value, part_number }); 66 | } 67 | } 68 | 69 | ParseResult { gears, ratios } 70 | } 71 | 72 | #[derive(Debug)] 73 | struct Gear { 74 | value: u32, 75 | part_number: bool, 76 | } 77 | 78 | #[cfg(test)] 79 | mod test { 80 | use indoc::indoc; 81 | 82 | const CASE: &str = indoc! {" 83 | 467..114.. 84 | ...*...... 85 | ..35..633. 86 | ......#... 87 | 617*...... 88 | .....+.58. 89 | ..592..... 90 | ......755. 91 | ...$.*.... 92 | .664.598.. 93 | "}; 94 | 95 | #[test] 96 | fn part_a() { 97 | assert_eq!(super::part_a(CASE), 4361.into()); 98 | } 99 | 100 | #[test] 101 | fn part_b() { 102 | assert_eq!(super::part_b(CASE), 467835.into()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /aoc_2023/src/day_04.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Scratchcards", 4); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let cards = parse(input); 7 | cards 8 | .iter() 9 | .filter(|x| x.wins > 0) 10 | .map(|x| 2u32.pow(x.wins.saturating_sub(1) as u32)) 11 | .sum::() 12 | .into() 13 | } 14 | 15 | fn part_b(input: &str) -> Answer { 16 | let cards = parse(input); 17 | 18 | let mut queue = (0..cards.len()).collect::>(); 19 | let mut visited = 0; 20 | 21 | while let Some(i) = queue.pop() { 22 | visited += 1; 23 | 24 | let card = &cards[i]; 25 | if card.wins == 0 { 26 | continue; 27 | } 28 | 29 | for j in 0..card.wins as usize { 30 | queue.push(j + i + 1); 31 | } 32 | } 33 | 34 | visited.into() 35 | } 36 | 37 | struct Card { 38 | wins: u8, 39 | } 40 | 41 | fn parse(input: &str) -> Vec { 42 | let mut cards = Vec::new(); 43 | for line in input.lines() { 44 | let (_, line) = line.split_once(": ").unwrap(); 45 | let (winning, scratch) = line.split_once(" | ").unwrap(); 46 | let parse = |s: &str| { 47 | s.split_whitespace() 48 | .map(|x| x.parse().unwrap()) 49 | .collect::>() 50 | }; 51 | 52 | let winning = parse(winning); 53 | let scratch = parse(scratch); 54 | cards.push(Card { 55 | wins: scratch.iter().filter(|x| winning.contains(x)).count() as u8, 56 | }); 57 | } 58 | 59 | cards 60 | } 61 | 62 | #[cfg(test)] 63 | mod test { 64 | use indoc::indoc; 65 | 66 | const CASE: &str = indoc! {" 67 | Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 68 | Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 69 | Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 70 | Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 71 | Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 72 | Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 73 | "}; 74 | 75 | #[test] 76 | fn part_a() { 77 | assert_eq!(super::part_a(CASE), 13.into()); 78 | } 79 | 80 | #[test] 81 | fn part_b() { 82 | assert_eq!(super::part_b(CASE), 30.into()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /aoc_2023/src/day_05.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | use rayon::{iter::ParallelIterator, slice::ParallelSlice}; 3 | 4 | solution!("If You Give A Seed A Fertilizer", 5); 5 | 6 | fn part_a(input: &str) -> Answer { 7 | let seeds = parse(input); 8 | 9 | let mut min = u32::MAX; 10 | for mut seed in seeds.seeds { 11 | for map in &seeds.maps { 12 | seed = map.map(seed); 13 | } 14 | min = min.min(seed); 15 | } 16 | 17 | min.into() 18 | } 19 | 20 | fn part_b(input: &str) -> Answer { 21 | let seeds = parse(input); 22 | 23 | // eh its fast enough 24 | // ~1min on my machine 25 | seeds 26 | .seeds 27 | .par_chunks_exact(2) 28 | .map(|seed| { 29 | let mut min = u32::MAX; 30 | for mut seed in seed[0]..=seed[0] + seed[1] { 31 | for map in &seeds.maps { 32 | seed = map.map(seed); 33 | } 34 | 35 | min = min.min(seed); 36 | } 37 | min 38 | }) 39 | .min() 40 | .unwrap() 41 | .into() 42 | } 43 | 44 | #[derive(Debug)] 45 | struct Map { 46 | ranges: Vec, 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | struct Range { 51 | end: u32, 52 | start: u32, 53 | length: u32, 54 | } 55 | 56 | struct ParseResult { 57 | maps: Vec, 58 | seeds: Vec, 59 | } 60 | 61 | fn parse(input: &str) -> ParseResult { 62 | let mut maps = Vec::new(); 63 | 64 | let mut sections = input.split("\n\n"); 65 | 66 | let seeds = sections 67 | .next() 68 | .unwrap() 69 | .split_whitespace() 70 | .skip(1) 71 | .map(|s| s.parse().unwrap()) 72 | .collect(); 73 | 74 | for section in sections.filter(|x| !x.is_empty()) { 75 | let lines = section.lines(); 76 | let mut ranges = Vec::new(); 77 | 78 | for line in lines.skip(1) { 79 | let mut parts = line.split_whitespace(); 80 | 81 | let end = parts.next().unwrap().parse().unwrap(); 82 | let start = parts.next().unwrap().parse().unwrap(); 83 | let length = parts.next().unwrap().parse().unwrap(); 84 | 85 | ranges.push(Range { start, end, length }); 86 | } 87 | 88 | maps.push(Map { ranges }); 89 | } 90 | 91 | ParseResult { maps, seeds } 92 | } 93 | 94 | impl Range { 95 | fn start_contains(&self, value: u32) -> bool { 96 | self.start <= value && value < self.start.wrapping_add(self.length) 97 | } 98 | } 99 | 100 | impl Map { 101 | fn map(&self, value: u32) -> u32 { 102 | for range in &self.ranges { 103 | if range.start_contains(value) { 104 | return range.end.wrapping_add(value).wrapping_sub(range.start); 105 | } 106 | } 107 | 108 | value 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod test { 114 | use indoc::indoc; 115 | 116 | const CASE: &str = indoc! {" 117 | seeds: 79 14 55 13 118 | 119 | seed-to-soil map: 120 | 50 98 2 121 | 52 50 48 122 | 123 | soil-to-fertilizer map: 124 | 0 15 37 125 | 37 52 2 126 | 39 0 15 127 | 128 | fertilizer-to-water map: 129 | 49 53 8 130 | 0 11 42 131 | 42 0 7 132 | 57 7 4 133 | 134 | water-to-light map: 135 | 88 18 7 136 | 18 25 70 137 | 138 | light-to-temperature map: 139 | 45 77 23 140 | 81 45 19 141 | 68 64 13 142 | 143 | temperature-to-humidity map: 144 | 0 69 1 145 | 1 0 69 146 | 147 | humidity-to-location map: 148 | 60 56 37 149 | 56 93 4 150 | 151 | "}; 152 | 153 | #[test] 154 | fn part_a() { 155 | assert_eq!(super::part_a(CASE), 35.into()); 156 | } 157 | 158 | #[test] 159 | fn part_b() { 160 | assert_eq!(super::part_b(CASE), 46.into()); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /aoc_2023/src/day_06.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | use itertools::Itertools; 3 | 4 | solution!("Wait For It", 6); 5 | 6 | fn part_a(input: &str) -> Answer { 7 | parse_a(input) 8 | .iter() 9 | .map(Race::ways_to_win) 10 | .product::() 11 | .into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | parse_b(input).ways_to_win().into() 16 | } 17 | 18 | #[derive(Debug)] 19 | struct Race { 20 | time: u64, 21 | distance: u64, 22 | } 23 | 24 | fn parse_a(input: &str) -> Vec { 25 | let (a, b) = input 26 | .lines() 27 | .map(|x| { 28 | x.split_whitespace() 29 | .skip(1) 30 | .map(|x| x.parse::().unwrap()) 31 | }) 32 | .next_tuple() 33 | .unwrap(); 34 | a.zip(b) 35 | .map(|(time, distance)| Race { time, distance }) 36 | .collect::>() 37 | } 38 | 39 | fn parse_b(input: &str) -> Race { 40 | let (time, distance) = input 41 | .lines() 42 | .map(|x| { 43 | x.split_whitespace() 44 | .skip(1) 45 | .collect::() 46 | .parse::() 47 | .unwrap() 48 | }) 49 | .next_tuple() 50 | .unwrap(); 51 | 52 | Race { time, distance } 53 | } 54 | 55 | impl Race { 56 | fn ways_to_win(&self) -> u64 { 57 | let a = ((self.time * self.time - 4 * self.distance) as f32).sqrt(); 58 | let x1 = ((self.time as f32 - a) / 2.0 + 1.0).floor(); 59 | let x2 = ((self.time as f32 + a) / 2.0 - 1.0).ceil(); 60 | (x2 - x1 + 1.0) as u64 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod test { 66 | use indoc::indoc; 67 | 68 | const CASE: &str = indoc! {" 69 | Time: 7 15 30 70 | Distance: 9 40 200 71 | "}; 72 | 73 | #[test] 74 | fn part_a() { 75 | assert_eq!(super::part_a(CASE), 288.into()); 76 | } 77 | 78 | #[test] 79 | fn part_b() { 80 | assert_eq!(super::part_b(CASE), 71503.into()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /aoc_2023/src/day_08.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use aoc_lib::math::lcm; 4 | use common::{solution, Answer}; 5 | 6 | solution!("Haunted Wasteland", 8); 7 | 8 | /// Just start at `AAA` and follow the instructions until you reach `ZZZ`. 9 | fn part_a(input: &str) -> Answer { 10 | let map = parse(input); 11 | 12 | let mut i = 0; 13 | let mut pos = "AAA"; 14 | loop { 15 | pos = map.get(pos, i); 16 | i += 1; 17 | 18 | if pos == "ZZZ" { 19 | break; 20 | } 21 | } 22 | 23 | i.into() 24 | } 25 | 26 | /// Get the cycle length for each starting position, this is the number of positions you need to get from `AAA` to `ZZZ`. 27 | /// Calculate the least common multiple of all cycle lengths to get the number of steps needed to get from `AAA` to `ZZZ` for all starting positions. 28 | fn part_b(input: &str) -> Answer { 29 | let map = parse(input); 30 | 31 | let mut pos = Vec::new(); 32 | for &id in map.nodes.keys() { 33 | if id.ends_with('A') { 34 | pos.push(id); 35 | } 36 | } 37 | 38 | let mut cycles = Vec::new(); 39 | for mut pos in pos { 40 | let mut cycle_len = 0_u64; 41 | let mut i = 0; 42 | loop { 43 | pos = map.get(pos, i); 44 | i += 1; 45 | 46 | cycle_len += 1; 47 | if pos.ends_with('Z') { 48 | cycles.push(cycle_len); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | cycles.into_iter().reduce(lcm).unwrap().into() 55 | } 56 | 57 | #[derive(Debug)] 58 | struct Map<'a> { 59 | // Char array of 'L's and 'R's 60 | instructions: &'a [u8], 61 | // Node => (Left, Right) 62 | nodes: HashMap<&'a str, (&'a str, &'a str)>, 63 | } 64 | 65 | impl<'a> Map<'a> { 66 | fn get(&self, pos: &'a str, i: usize) -> &'a str { 67 | let (left, right) = self.nodes.get(pos).unwrap(); 68 | match self.instructions[i % self.instructions.len()] as char { 69 | 'L' => left, 70 | 'R' => right, 71 | _ => unreachable!(), 72 | } 73 | } 74 | } 75 | 76 | fn parse(input: &str) -> Map { 77 | let (instructions, node_list) = input.split_once("\n\n").unwrap(); 78 | 79 | let mut nodes = HashMap::new(); 80 | for node in node_list.lines() { 81 | let (id, children) = node.split_once(" = ").unwrap(); 82 | let children = children 83 | .trim_start_matches('(') 84 | .trim_end_matches(')') 85 | .split_once(", ") 86 | .unwrap(); 87 | nodes.insert(id, children); 88 | } 89 | 90 | Map { 91 | instructions: instructions.as_bytes(), 92 | nodes, 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod test { 98 | use indoc::indoc; 99 | 100 | const CASE_A: &str = indoc! {" 101 | LLR 102 | 103 | AAA = (BBB, BBB) 104 | BBB = (AAA, ZZZ) 105 | ZZZ = (ZZZ, ZZZ) 106 | "}; 107 | 108 | const CASE_B: &str = indoc! {" 109 | LR 110 | 111 | 11A = (11B, XXX) 112 | 11B = (XXX, 11Z) 113 | 11Z = (11B, XXX) 114 | 22A = (22B, XXX) 115 | 22B = (22C, 22C) 116 | 22C = (22Z, 22Z) 117 | 22Z = (22B, 22B) 118 | XXX = (XXX, XXX) 119 | "}; 120 | 121 | #[test] 122 | fn part_a() { 123 | assert_eq!(super::part_a(CASE_A), 6.into()); 124 | } 125 | 126 | #[test] 127 | fn part_b() { 128 | assert_eq!(super::part_b(CASE_B), 6.into()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /aoc_2023/src/day_09.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Mirage Maintenance", 9); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | parse(input) 7 | .iter() 8 | .map(Sequence::predict) 9 | .sum::() 10 | .into() 11 | } 12 | 13 | fn part_b(input: &str) -> Answer { 14 | parse(input) 15 | .into_iter() 16 | .map(|x| x.reverse().predict()) 17 | .sum::() 18 | .into() 19 | } 20 | 21 | struct Sequence { 22 | values: Vec, 23 | } 24 | 25 | fn parse(input: &str) -> Vec { 26 | let mut out = Vec::new(); 27 | 28 | for line in input.lines() { 29 | let values = line 30 | .split_whitespace() 31 | .map(|v| v.parse().unwrap()) 32 | .collect(); 33 | out.push(Sequence { values }); 34 | } 35 | 36 | out 37 | } 38 | 39 | impl Sequence { 40 | fn derive(&self) -> Vec> { 41 | let mut derived = vec![self.values.clone()]; 42 | 43 | while !derived.last().unwrap().iter().all(|&x| x == 0) { 44 | let last = derived.last().unwrap(); 45 | let mut next = Vec::new(); 46 | 47 | for i in 1..last.len() { 48 | next.push(last[i] - last[i - 1]); 49 | } 50 | 51 | derived.push(next); 52 | } 53 | 54 | derived 55 | } 56 | 57 | fn reverse(mut self) -> Self { 58 | self.values.reverse(); 59 | self 60 | } 61 | 62 | fn predict(&self) -> i64 { 63 | self.derive().iter().filter_map(|v| v.last()).sum() 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod test { 69 | use indoc::indoc; 70 | 71 | const CASE: &str = indoc! {" 72 | 0 3 6 9 12 15 73 | 1 3 6 10 15 21 74 | 10 13 16 21 30 45 75 | "}; 76 | 77 | #[test] 78 | fn part_a() { 79 | assert_eq!(super::part_a(CASE), 114.into()); 80 | } 81 | 82 | #[test] 83 | fn part_b() { 84 | assert_eq!(super::part_b(CASE), 2.into()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /aoc_2023/src/day_11.rs: -------------------------------------------------------------------------------- 1 | use bitvec::{bitvec, vec::BitVec}; 2 | use itertools::Itertools; 3 | use nd_vec::{vector, Vec2}; 4 | 5 | use common::{solution, Answer}; 6 | 7 | type Pos = Vec2; 8 | 9 | solution!("Cosmic Expansion", 11); 10 | 11 | fn part_a(input: &str) -> Answer { 12 | let mut galaxies = parse(input); 13 | galaxies.expand(2); 14 | galaxies.total_distance().into() 15 | } 16 | 17 | fn part_b(input: &str) -> Answer { 18 | let mut galaxies = parse(input); 19 | galaxies.expand(1000000); 20 | galaxies.total_distance().into() 21 | } 22 | 23 | struct Galaxies { 24 | galaxies: Vec, 25 | rows: BitVec, 26 | cols: BitVec, 27 | } 28 | 29 | fn parse(input: &str) -> Galaxies { 30 | let lines = input.lines().collect::>(); 31 | let mut galaxies = Vec::new(); 32 | 33 | let mut rows = bitvec![0; lines[0].len()]; 34 | let mut cols = bitvec![0; lines.len()]; 35 | 36 | for (y, line) in lines.iter().enumerate() { 37 | for (x, c) in line.chars().enumerate() { 38 | if c == '#' { 39 | galaxies.push(vector!(x, y)); 40 | rows.set(y, true); 41 | cols.set(x, true); 42 | } 43 | } 44 | } 45 | 46 | Galaxies { 47 | galaxies, 48 | rows, 49 | cols, 50 | } 51 | } 52 | 53 | impl Galaxies { 54 | fn expand(&mut self, mut multiplier: usize) { 55 | multiplier -= 1; 56 | 57 | for (y, _) in self.rows.iter().enumerate().rev().filter(|x| !x.1.as_ref()) { 58 | for pos in self.galaxies.iter_mut().filter(|pos| pos.y() > y) { 59 | *pos += vector!(0, multiplier) 60 | } 61 | } 62 | 63 | for (x, _) in self.cols.iter().enumerate().rev().filter(|x| !x.1.as_ref()) { 64 | for pos in self.galaxies.iter_mut().filter(|pos| pos.x() > x) { 65 | *pos += vector!(multiplier, 0) 66 | } 67 | } 68 | } 69 | 70 | fn total_distance(&self) -> usize { 71 | self.galaxies 72 | .iter() 73 | .map(|x| x.num_cast::().unwrap()) 74 | .tuple_combinations() 75 | .map(|(a, b)| a.manhattan_distance(&b) as usize) 76 | .sum() 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod test { 82 | use indoc::indoc; 83 | 84 | const CASE: &str = indoc! {" 85 | ...#...... 86 | .......#.. 87 | #......... 88 | .......... 89 | ......#... 90 | .#........ 91 | .........# 92 | .......... 93 | .......#.. 94 | #...#..... 95 | "}; 96 | 97 | #[test] 98 | fn part_a() { 99 | assert_eq!(super::part_a(CASE), 374.into()); 100 | } 101 | 102 | #[test] 103 | fn part_b() { 104 | assert_eq!(super::part_b(CASE), 82000210.into()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /aoc_2023/src/day_12.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Hot Springs", 12); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | parse(input) 9 | .iter() 10 | .map(|s| s.arrangements()) 11 | .sum::() 12 | .into() 13 | } 14 | 15 | fn part_b(input: &str) -> Answer { 16 | parse(input) 17 | .iter() 18 | .map(|s| s.expand().arrangements()) 19 | .sum::() 20 | .into() 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | struct Spring { 25 | field: Vec, 26 | springs: Vec, 27 | } 28 | 29 | fn parse(input: &str) -> Vec { 30 | let mut out = Vec::new(); 31 | 32 | for line in input.lines() { 33 | let (field, springs) = line.split_once(' ').unwrap(); 34 | let springs = springs 35 | .split(',') 36 | .map(|s| s.parse().unwrap()) 37 | .collect::>(); 38 | let mut field = field.chars().collect::>(); 39 | field.push('.'); 40 | out.push(Spring { field, springs }); 41 | } 42 | 43 | out 44 | } 45 | 46 | impl Spring { 47 | fn arrangements(&self) -> usize { 48 | fn count( 49 | memo: &mut HashMap<(usize, usize, usize), usize>, 50 | spring: &Spring, 51 | pos: usize, 52 | block: usize, 53 | sequences: usize, 54 | ) -> usize { 55 | if let Some(&res) = memo.get(&(pos, block, sequences)) { 56 | return res; 57 | } 58 | 59 | let mut res = 0; 60 | if pos == spring.field.len() { 61 | res = (sequences == spring.springs.len()) as usize; 62 | } else if spring.field[pos] == '#' { 63 | res = count(memo, spring, pos + 1, block + 1, sequences) 64 | } else if spring.field[pos] == '.' || sequences == spring.springs.len() { 65 | if sequences < spring.springs.len() && block == spring.springs[sequences] { 66 | res = count(memo, spring, pos + 1, 0, sequences + 1) 67 | } else if block == 0 { 68 | res = count(memo, spring, pos + 1, 0, sequences) 69 | } 70 | } else { 71 | res += count(memo, spring, pos + 1, block + 1, sequences); 72 | if block == spring.springs[sequences] { 73 | res += count(memo, spring, pos + 1, 0, sequences + 1) 74 | } else if block == 0 { 75 | res += count(memo, spring, pos + 1, 0, sequences) 76 | } 77 | } 78 | 79 | memo.insert((pos, block, sequences), res); 80 | res 81 | } 82 | 83 | count(&mut HashMap::new(), self, 0, 0, 0) 84 | } 85 | 86 | fn expand(&self) -> Self { 87 | let mut new_field = self.field.clone(); 88 | *new_field.last_mut().unwrap() = '?'; 89 | 90 | Self { 91 | field: new_field.repeat(5), 92 | springs: self.springs.repeat(5), 93 | } 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod test { 99 | use indoc::indoc; 100 | 101 | const CASE: &str = indoc! {" 102 | ???.### 1,1,3 103 | .??..??...?##. 1,1,3 104 | ?#?#?#?#?#?#?#? 1,3,1,6 105 | ????.#...#... 4,1,1 106 | ????.######..#####. 1,6,5 107 | ?###???????? 3,2,1 108 | "}; 109 | 110 | #[test] 111 | fn part_a() { 112 | assert_eq!(super::part_a(CASE), 21.into()); 113 | } 114 | 115 | #[test] 116 | fn part_b() { 117 | assert_eq!(super::part_b(CASE), 525152.into()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /aoc_2023/src/day_13.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Point of Incidence", 13); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let valleys = parse(input); 7 | solve(&valleys, 0).into() 8 | } 9 | 10 | fn part_b(input: &str) -> Answer { 11 | let valleys = parse(input); 12 | solve(&valleys, 1).into() 13 | } 14 | 15 | fn solve(valleys: &[Valley], limit: usize) -> usize { 16 | valleys 17 | .iter() 18 | .filter_map(|valley| { 19 | valley 20 | .horizontal_reflection(limit) 21 | .map(|x| 100 * x) 22 | .or_else(|| valley.vertical_reflection(limit)) 23 | }) 24 | .sum() 25 | } 26 | 27 | struct Valley { 28 | tiles: Vec>, 29 | } 30 | 31 | fn parse(input: &str) -> Vec { 32 | let mut out = Vec::new(); 33 | 34 | for set in input.split("\n\n") { 35 | let tiles = set.lines().map(|line| line.chars().collect()).collect(); 36 | out.push(Valley { tiles }); 37 | } 38 | 39 | out 40 | } 41 | 42 | impl Valley { 43 | // Find a horizontal reflection in the valley. 44 | // Horizontal reflections is from left to right. 45 | fn horizontal_reflection(&self, error: usize) -> Option { 46 | for mid in 1..=self.tiles.len() - 1 { 47 | let side_len = mid.min(self.tiles.len() - mid); 48 | let start = mid - side_len; 49 | 50 | let mut diff = 0; 51 | for a in start..mid { 52 | let b = mid * 2 - a - 1; 53 | diff += (0..self.tiles[a].len()) 54 | .filter(|&i| self.tiles[a][i] != self.tiles[b][i]) 55 | .count(); 56 | } 57 | 58 | if diff == error { 59 | return Some(mid); 60 | } 61 | } 62 | 63 | None 64 | } 65 | 66 | fn vertical_reflection(&self, error: usize) -> Option { 67 | for mid in 1..=self.tiles[0].len() - 1 { 68 | let side_len = mid.min(self.tiles[0].len() - mid); 69 | let start = mid - side_len; 70 | 71 | let mut diff = 0; 72 | for a in start..mid { 73 | let b = mid * 2 - a - 1; 74 | diff += (0..self.tiles.len()) 75 | .filter(|&i| self.tiles[i][a] != self.tiles[i][b]) 76 | .count(); 77 | } 78 | 79 | if diff == error { 80 | return Some(mid); 81 | } 82 | } 83 | 84 | None 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod test { 90 | use indoc::indoc; 91 | 92 | const CASE: &str = indoc! {" 93 | #.##..##. 94 | ..#.##.#. 95 | ##......# 96 | ##......# 97 | ..#.##.#. 98 | ..##..##. 99 | #.#.##.#. 100 | 101 | #...##..# 102 | #....#..# 103 | ..##..### 104 | #####.##. 105 | #####.##. 106 | ..##..### 107 | #....#..# 108 | "}; 109 | 110 | #[test] 111 | fn part_a() { 112 | assert_eq!(super::part_a(CASE), 405.into()); 113 | } 114 | 115 | #[test] 116 | fn part_b() { 117 | assert_eq!(super::part_b(CASE), 400.into()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /aoc_2023/src/day_14.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, convert::identity, hash::Hash}; 2 | 3 | use aoc_lib::matrix::Grid; 4 | use common::{solution, Answer}; 5 | use nd_vec::{vector, Vec2}; 6 | 7 | type Pos = Vec2; 8 | 9 | solution!("Parabolic Reflector Dish", 14); 10 | 11 | fn part_a(input: &str) -> Answer { 12 | let mut dish = parse(input); 13 | dish.tilt(vector!(0, -1)); 14 | dish.score().into() 15 | } 16 | 17 | fn part_b(input: &str) -> Answer { 18 | let mut dish = parse(input); 19 | 20 | const ITERS: usize = 1000000000; 21 | let mut seen = HashMap::new(); 22 | for i in 0..ITERS { 23 | if let Some(prev) = seen.get(&dish) { 24 | if (ITERS - i) % (i - prev) == 0 { 25 | return dish.score().into(); 26 | } 27 | } 28 | 29 | seen.insert(dish.clone(), i); 30 | dish.spin(); 31 | } 32 | 33 | dish.score().into() 34 | } 35 | 36 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 37 | struct Dish { 38 | tiles: Grid, 39 | } 40 | 41 | fn parse(input: &str) -> Dish { 42 | Dish { 43 | tiles: Grid::parse(input, identity), 44 | } 45 | } 46 | 47 | impl Dish { 48 | fn tilt(&mut self, tilt: Pos) { 49 | let tiles = &mut self.tiles; 50 | loop { 51 | let mut moved = false; 52 | for y in 0..tiles.size.y() { 53 | for x in 0..tiles.size.x() { 54 | let pos = vector!(x as isize, y as isize); 55 | if tiles[pos] != 'O' { 56 | continue; 57 | } 58 | 59 | let new_pos = vector!(x, y).num_cast().unwrap() + tilt; 60 | if !tiles.contains(new_pos) || tiles[new_pos] != '.' { 61 | continue; 62 | } 63 | 64 | let tile = tiles[pos]; 65 | tiles.set(pos, '.'); 66 | tiles.set(new_pos, tile); 67 | moved = true; 68 | } 69 | } 70 | 71 | if !moved { 72 | break; 73 | } 74 | } 75 | } 76 | 77 | fn spin(&mut self) { 78 | for pos in [vector!(0, -1), vector!(-1, 0), vector!(0, 1), vector!(1, 0)] { 79 | self.tilt(pos); 80 | } 81 | } 82 | 83 | fn score(&self) -> usize { 84 | let tiles = &self.tiles; 85 | let mut acc = 0; 86 | 87 | for y in 0..tiles.size.y() { 88 | for x in 0..tiles.size.x() { 89 | if tiles[[x, y]] == 'O' { 90 | acc += tiles.size.y() - y; 91 | } 92 | } 93 | } 94 | 95 | acc 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod test { 101 | use indoc::indoc; 102 | 103 | const CASE: &str = indoc! {" 104 | O....#.... 105 | O.OO#....# 106 | .....##... 107 | OO.#O....O 108 | .O.....O#. 109 | O.#..O.#.# 110 | ..O..#O..O 111 | .......O.. 112 | #....###.. 113 | #OO..#.... 114 | "}; 115 | 116 | #[test] 117 | fn part_a() { 118 | assert_eq!(super::part_a(CASE), 136.into()); 119 | } 120 | 121 | #[test] 122 | fn part_b() { 123 | assert_eq!(super::part_b(CASE), 64.into()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /aoc_2023/src/day_15.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | use itertools::Itertools; 3 | 4 | solution!("Lens Library", 15); 5 | 6 | fn part_a(input: &str) -> Answer { 7 | input 8 | .trim() 9 | .split(',') 10 | .map(|x| hash(x) as u32) 11 | .sum::() 12 | .into() 13 | } 14 | 15 | fn part_b(input: &str) -> Answer { 16 | let input = parse(input); 17 | let mut boxes = vec![Vec::new(); 256]; 18 | 19 | for (label, focal_len) in input { 20 | let key = hash(label) as usize; 21 | if let Some(focal_len) = focal_len { 22 | if let Some((_, e)) = boxes[key] 23 | .iter_mut() 24 | .find(|x: &&mut (&str, u32)| x.0 == label) 25 | { 26 | *e = focal_len; 27 | } else { 28 | boxes[key].push((label, focal_len)); 29 | } 30 | } else { 31 | boxes[key].retain(|x| x.0 != label); 32 | } 33 | } 34 | 35 | let mut acc = 0; 36 | for (i, e) in boxes.iter().enumerate() { 37 | for (j, f) in e.iter().enumerate() { 38 | acc += (i + 1) * (j + 1) * f.1 as usize; 39 | } 40 | } 41 | 42 | acc.into() 43 | } 44 | 45 | fn parse(input: &str) -> Vec<(&str, Option)> { 46 | let mut out = Vec::new(); 47 | 48 | for i in input.trim().split(',') { 49 | let (label, focal_len) = i.split(['=', '-'].as_ref()).collect_tuple().unwrap(); 50 | out.push((label, focal_len.parse::().ok())); 51 | } 52 | 53 | out 54 | } 55 | 56 | fn hash(input: &str) -> u8 { 57 | let mut out = 0u8; 58 | for c in input.chars() { 59 | out = out.wrapping_add(c as u8).wrapping_mul(17); 60 | } 61 | 62 | out 63 | } 64 | 65 | #[cfg(test)] 66 | mod test { 67 | use indoc::indoc; 68 | 69 | const CASE: &str = indoc! {" 70 | rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 71 | "}; 72 | 73 | #[test] 74 | fn part_a() { 75 | assert_eq!(super::part_a(CASE), 1320.into()); 76 | } 77 | 78 | #[test] 79 | fn part_b() { 80 | assert_eq!(super::part_b(CASE), 145.into()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /aoc_2023/src/day_17.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::{BinaryHeap, HashMap}, 4 | }; 5 | 6 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 7 | use common::{solution, Answer}; 8 | use nd_vec::{vector, Vec2}; 9 | 10 | solution!("Clumsy Crucible", 17); 11 | 12 | type Pos = Vec2; 13 | 14 | fn part_a(input: &str) -> Answer { 15 | pathfind(parse(input), 1, 3).into() 16 | } 17 | 18 | fn part_b(input: &str) -> Answer { 19 | pathfind(parse(input), 4, 10).into() 20 | } 21 | 22 | fn parse(input: &str) -> Grid { 23 | Grid::parse(input, |c| c as u8 - b'0') 24 | } 25 | 26 | fn pathfind(board: Grid, min_dist: u8, max_dist: u8) -> u32 { 27 | let mut queue = BinaryHeap::new(); 28 | let mut visited = HashMap::new(); 29 | let mut res = u32::MAX; 30 | 31 | let end = board.size() - vector!(1, 1); 32 | for dir in [Direction::Down, Direction::Right] { 33 | let state = State::new(vector!(0, 0), dir, 1); 34 | queue.push(QueueItem { state, cost: 0 }); 35 | visited.insert(state, 0); 36 | } 37 | 38 | while let Some(item) = queue.pop() { 39 | let state = item.state; 40 | let mut explore = |facing: Direction, turn_distance: u8| { 41 | if let Some(pos) = facing 42 | .try_advance(state.pos) 43 | .filter(|pos| board.contains(*pos)) 44 | { 45 | let state = State::new(pos, facing, turn_distance); 46 | let cost = item.cost + board[pos] as u32; 47 | 48 | if !visited.contains_key(&state) || visited.get(&state).unwrap() > &cost { 49 | queue.push(QueueItem { state, cost }); 50 | visited.insert(state, cost); 51 | } 52 | } 53 | }; 54 | 55 | if state.pos == end && state.turn_distance >= min_dist { 56 | res = res.min(item.cost); 57 | continue; 58 | } 59 | 60 | if state.turn_distance < max_dist { 61 | explore(state.facing, state.turn_distance + 1); 62 | } 63 | 64 | if state.turn_distance >= min_dist { 65 | explore(state.facing.turn_left(), 1); 66 | explore(state.facing.turn_right(), 1); 67 | } 68 | } 69 | 70 | res 71 | } 72 | 73 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 74 | struct State { 75 | pos: Pos, 76 | facing: Direction, 77 | turn_distance: u8, 78 | } 79 | 80 | #[derive(PartialEq, Eq)] 81 | struct QueueItem { 82 | state: State, 83 | cost: u32, 84 | } 85 | 86 | impl State { 87 | fn new(pos: Pos, facing: Direction, turn_distance: u8) -> Self { 88 | Self { 89 | pos, 90 | facing, 91 | turn_distance, 92 | } 93 | } 94 | } 95 | 96 | impl Ord for QueueItem { 97 | fn cmp(&self, other: &Self) -> Ordering { 98 | other.cost.cmp(&self.cost) 99 | } 100 | } 101 | 102 | impl PartialOrd for QueueItem { 103 | fn partial_cmp(&self, other: &Self) -> Option { 104 | Some(self.cmp(other)) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod test { 110 | use indoc::indoc; 111 | 112 | const CASE: &str = indoc! {" 113 | 2413432311323 114 | 3215453535623 115 | 3255245654254 116 | 3446585845452 117 | 4546657867536 118 | 1438598798454 119 | 4457876987766 120 | 3637877979653 121 | 4654967986887 122 | 4564679986453 123 | 1224686865563 124 | 2546548887735 125 | 4322674655533 126 | "}; 127 | 128 | #[test] 129 | fn part_a() { 130 | assert_eq!(super::part_a(CASE), 102.into()); 131 | } 132 | 133 | #[test] 134 | fn part_b() { 135 | assert_eq!(super::part_b(CASE), 94.into()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /aoc_2023/src/day_18.rs: -------------------------------------------------------------------------------- 1 | use aoc_lib::direction::cardinal::Direction; 2 | use common::{solution, Answer}; 3 | use nd_vec::vector; 4 | 5 | solution!("Lavaduct Lagoon", 18); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | solve(parse_a(input)).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | solve(parse_b(input)).into() 13 | } 14 | 15 | fn solve(instructions: Vec<(Direction, u32)>) -> i64 { 16 | let mut pos = vector!(0, 0); 17 | let mut perimeter = 0; 18 | let mut area = 0; 19 | 20 | // Shoelace formula (Trapezoid formula) to get area of polygon. 21 | // (The perimeter is also calculated here by just adding all the side lengths) 22 | for (dir, steps) in instructions.into_iter() { 23 | // Update the perimeter. 24 | perimeter += steps as i64; 25 | 26 | // Get the change in position from the direction and steps. 27 | let cng = dir.as_vector() * (steps as i64); 28 | // Update the position. 29 | pos += cng; 30 | // Update the area using the new x coordinate and the change in y. 31 | area += pos.x() * cng.y(); 32 | } 33 | 34 | // Uses Pick's theorem to calculate the area of the polygon. 35 | // This is because we are looking for the number of cube segments the lagoon can hold rather than the volume. 36 | // 37 | // area = inner + perimeter / 2 - 1 38 | // inner = area - perimeter / 2 + 1 39 | // inner + perimeter = area + perimeter / 2 + 1 40 | area + perimeter / 2 + 1 41 | } 42 | 43 | fn parse_b(input: &str) -> Vec<(Direction, u32)> { 44 | input 45 | .lines() 46 | .map(|line| { 47 | let hex = &line[line.find('#').unwrap() + 1..line.len() - 1]; 48 | let steps = u32::from_str_radix(&hex[0..5], 16).unwrap(); 49 | let dir = match &hex[5..6] { 50 | "0" => Direction::Right, 51 | "1" => Direction::Down, 52 | "2" => Direction::Left, 53 | "3" => Direction::Up, 54 | _ => panic!("Invalid direction"), 55 | }; 56 | (dir, steps) 57 | }) 58 | .collect() 59 | } 60 | 61 | fn parse_a(input: &str) -> Vec<(Direction, u32)> { 62 | input 63 | .lines() 64 | .map(|line| { 65 | let mut parts = line.split_whitespace(); 66 | let dir = match parts.next().unwrap() { 67 | "R" => Direction::Right, 68 | "L" => Direction::Left, 69 | "U" => Direction::Up, 70 | "D" => Direction::Down, 71 | _ => panic!("Invalid direction"), 72 | }; 73 | let steps = parts.next().unwrap(); 74 | (dir, steps.parse().unwrap()) 75 | }) 76 | .collect() 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use indoc::indoc; 82 | 83 | const CASE: &str = indoc! {" 84 | R 6 (#70c710) 85 | D 5 (#0dc571) 86 | L 2 (#5713f0) 87 | D 2 (#d2c081) 88 | R 2 (#59c680) 89 | D 2 (#411b91) 90 | L 5 (#8ceee2) 91 | U 2 (#caa173) 92 | L 1 (#1b58a2) 93 | U 2 (#caa171) 94 | R 2 (#7807d2) 95 | U 3 (#a77fa3) 96 | L 2 (#015232) 97 | U 2 (#7a21e3) 98 | "}; 99 | 100 | #[test] 101 | fn part_a() { 102 | assert_eq!(super::part_a(CASE), 62.into()); 103 | } 104 | 105 | #[test] 106 | fn part_b() { 107 | assert_eq!(super::part_b(CASE), 952408144115i64.into()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /aoc_2023/src/day_21.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use nd_vec::{vector, Vec2}; 6 | 7 | use polynomial::Polynomial; 8 | 9 | solution!("Step Counter", 21); 10 | 11 | fn part_a(input: &str) -> Answer { 12 | let map = parse(input); 13 | let mut pos = HashSet::new(); 14 | pos.insert(map.find(Tile::Start).unwrap().num_cast::().unwrap()); 15 | 16 | for _ in 0..64 { 17 | let mut new_pos = HashSet::new(); 18 | 19 | for p in pos { 20 | for dir in Direction::ALL { 21 | let new_p = dir.advance(p); 22 | if !map.contains(new_p) 23 | || *map.get(new_p.num_cast().unwrap()).unwrap() != Tile::Wall 24 | { 25 | new_pos.insert(new_p); 26 | } 27 | } 28 | } 29 | 30 | pos = new_pos; 31 | } 32 | 33 | pos.len().into() 34 | } 35 | 36 | fn part_b(input: &str) -> Answer { 37 | let map = parse(input); 38 | let start = map.find(Tile::Start).unwrap().num_cast::().unwrap(); 39 | let size = map.size.num_cast::().unwrap(); 40 | 41 | let mut pos = HashSet::new(); 42 | pos.insert(start); 43 | 44 | let mut points = Vec::new(); 45 | for i in 0.. { 46 | let mut new_pos = HashSet::new(); 47 | 48 | if i % size.x() == 65 { 49 | points.push((i, pos.len())); 50 | if points.len() >= 3 { 51 | break; 52 | } 53 | } 54 | 55 | for p in pos { 56 | for dir in Direction::ALL { 57 | let new_p = dir.advance(p); 58 | let mapped = map_pos(new_p, size); 59 | if *map.get(mapped).unwrap() != Tile::Wall { 60 | new_pos.insert(new_p); 61 | } 62 | } 63 | } 64 | 65 | pos = new_pos; 66 | } 67 | 68 | let x = points.iter().map(|x| x.0 as f64).collect::>(); 69 | let y = points.iter().map(|x| x.1 as f64).collect::>(); 70 | let poly = Polynomial::lagrange(&x, &y).unwrap(); 71 | 72 | poly.eval(26501365.0).round().into() 73 | } 74 | 75 | fn map_pos(pos: Vec2, size: Vec2) -> Vec2 { 76 | let mut mapped = pos; 77 | mapped = vector!((size.x() + mapped.x() % size.x()) % size.x(), mapped.y()); 78 | mapped = vector!(mapped.x(), (size.y() + mapped.y() % size.y()) % size.y()); 79 | mapped.num_cast().unwrap() 80 | } 81 | 82 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 83 | enum Tile { 84 | Garden, 85 | Wall, 86 | Start, 87 | } 88 | 89 | fn parse(input: &str) -> Grid { 90 | Grid::parse(input, |x| match x { 91 | '#' => Tile::Wall, 92 | '.' => Tile::Garden, 93 | 'S' => Tile::Start, 94 | _ => panic!("Invalid input"), 95 | }) 96 | } 97 | 98 | #[cfg(test)] 99 | mod test { 100 | use indoc::indoc; 101 | 102 | const CASE: &str = indoc! {" 103 | ........... 104 | .....###.#. 105 | .###.##..#. 106 | ..#.#...#.. 107 | ....#.#.... 108 | .##..S####. 109 | .##..#...#. 110 | .......##.. 111 | .##.#.####. 112 | .##..##.##. 113 | ........... 114 | "}; 115 | 116 | #[test] 117 | fn part_a() { 118 | assert_eq!(super::part_a(CASE), 4056.into()); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /aoc_2023/src/day_22.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, convert::identity}; 2 | 3 | use common::{solution, Answer}; 4 | use nd_vec::{vector, Vec3}; 5 | 6 | solution!("Sand Slabs", 22); 7 | 8 | fn part_a(input: &str) -> Answer { 9 | solve(parse(input), false, |x| (x == 0) as u32).into() 10 | } 11 | 12 | fn part_b(input: &str) -> Answer { 13 | solve(parse(input), true, identity).into() 14 | } 15 | 16 | fn solve(mut map: Vec, exhaustive: bool, count: fn(u32) -> u32) -> u32 { 17 | // Shift all boxes down as far as possible 18 | while shift_down(&mut map, false) != 0 {} 19 | 20 | // For each box, remove it and shift all other boxes down as far as possible 21 | let mut out = 0; 22 | for i in 0..map.len() { 23 | let mut map_clone = map.clone(); 24 | map_clone.remove(i); 25 | out += count(shift_down(&mut map_clone, exhaustive)); 26 | } 27 | 28 | out 29 | } 30 | 31 | fn shift_down(map: &mut [Box], exhaustive: bool) -> u32 { 32 | let mut moved = HashSet::new(); 33 | let mut dirty = true; 34 | while dirty { 35 | dirty = false; 36 | 'outer: for i in 0..map.len() { 37 | // If no other box below this one, move it down 38 | let item = map[i]; 39 | for x in item.a.x()..=item.b.x() { 40 | for y in item.a.y()..=item.b.y() { 41 | if item.a.z() == 1 42 | || map 43 | .iter() 44 | .take(i) 45 | .rev() 46 | .any(|b| b.contains(vector!(x, y, item.a.z() - 1))) 47 | { 48 | continue 'outer; 49 | } 50 | } 51 | } 52 | 53 | map[i].a -= vector!(0, 0, 1); 54 | map[i].b -= vector!(0, 0, 1); 55 | moved.insert(i); 56 | dirty = exhaustive; 57 | } 58 | } 59 | moved.len() as u32 60 | } 61 | 62 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 63 | struct Box { 64 | a: Vec3, 65 | b: Vec3, 66 | } 67 | 68 | impl Box { 69 | fn contains(&self, p: Vec3) -> bool { 70 | p.x() >= self.a.x() 71 | && p.x() <= self.b.x() 72 | && p.y() >= self.a.y() 73 | && p.y() <= self.b.y() 74 | && p.z() >= self.a.z() 75 | && p.z() <= self.b.z() 76 | } 77 | } 78 | 79 | fn parse(input: &str) -> Vec { 80 | let mut out = Vec::new(); 81 | for line in input.lines() { 82 | let parse = |s: &str| { 83 | let a = s.split(',').collect::>(); 84 | vector!( 85 | a[0].parse().unwrap(), 86 | a[1].parse().unwrap(), 87 | a[2].parse().unwrap() 88 | ) 89 | }; 90 | let (a, b) = line.split_once('~').unwrap(); 91 | let (a, b) = (parse(a), parse(b)); 92 | out.push(Box { 93 | a: a.min(&b), 94 | b: a.max(&b), 95 | }); 96 | } 97 | 98 | out.sort_unstable_by_key(|b| b.a.z()); 99 | out 100 | } 101 | 102 | #[cfg(test)] 103 | mod test { 104 | use indoc::indoc; 105 | 106 | const CASE: &str = indoc! {" 107 | 1,0,1~1,2,1 108 | 0,0,2~2,0,2 109 | 0,2,3~2,2,3 110 | 0,0,4~0,2,4 111 | 2,0,5~2,2,5 112 | 0,1,6~2,1,6 113 | 1,1,8~1,1,9 114 | "}; 115 | 116 | #[test] 117 | fn part_a() { 118 | assert_eq!(super::part_a(CASE), 5.into()); 119 | } 120 | 121 | #[test] 122 | fn part_b() { 123 | assert_eq!(super::part_b(CASE), 7.into()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /aoc_2023/src/day_25.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use common::{solution, Answer}; 4 | use petgraph::{graph::UnGraph, stable_graph::NodeIndex, Graph, Undirected}; 5 | use rustworkx_core::connectivity::stoer_wagner_min_cut; 6 | 7 | solution!("Snowverload", 25); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let wires = parse(input); 11 | 12 | let total = wires.wire.node_count(); 13 | let (len, side) = stoer_wagner_min_cut(&wires.wire, |_| Ok::(1)) 14 | .unwrap() 15 | .unwrap(); 16 | 17 | assert_eq!(len, 3); 18 | ((total - side.len()) * side.len()).into() 19 | } 20 | 21 | fn part_b(_input: &str) -> Answer { 22 | Answer::Unimplemented 23 | } 24 | 25 | struct Wires<'a> { 26 | wire: Graph<&'a str, (), Undirected>, 27 | } 28 | 29 | fn parse(input: &str) -> Wires { 30 | let mut nodes = HashMap::new(); 31 | let mut wire = UnGraph::new_undirected(); 32 | 33 | fn get_node<'a>( 34 | nodes: &mut HashMap<&'a str, NodeIndex>, 35 | wire: &mut Graph<&'a str, (), Undirected>, 36 | name: &'a str, 37 | ) -> NodeIndex { 38 | *nodes.entry(name).or_insert_with(|| wire.add_node(name)) 39 | } 40 | 41 | for line in input.lines() { 42 | let mut parts = line.split(": "); 43 | let key = parts.next().unwrap(); 44 | let values = parts.next().unwrap().split_whitespace(); 45 | 46 | let node = get_node(&mut nodes, &mut wire, key); 47 | for value in values { 48 | let value = get_node(&mut nodes, &mut wire, value); 49 | wire.add_edge(node, value, ()); 50 | } 51 | } 52 | Wires { wire } 53 | } 54 | 55 | #[cfg(test)] 56 | mod test { 57 | use indoc::indoc; 58 | 59 | const CASE: &str = indoc! {" 60 | jqt: rhn xhk nvd 61 | rsh: frs pzl lsr 62 | xhk: hfx 63 | cmg: qnr nvd lhk bvb 64 | rhn: xhk bvb hfx 65 | bvb: xhk hfx 66 | pzl: lsr hfx nvd 67 | qnr: nvd 68 | ntq: jqt hfx bvb xhk 69 | nvd: lhk 70 | lsr: lhk 71 | rzs: qnr cmg lsr rsh 72 | frs: qnr lhk lsr 73 | "}; 74 | 75 | #[test] 76 | fn part_a() { 77 | assert_eq!(super::part_a(CASE), 54.into()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /aoc_2023/src/lib.rs: -------------------------------------------------------------------------------- 1 | use common::Solution; 2 | 3 | mod day_01; 4 | mod day_02; 5 | mod day_03; 6 | mod day_04; 7 | mod day_05; 8 | mod day_06; 9 | mod day_07; 10 | mod day_08; 11 | mod day_09; 12 | mod day_10; 13 | mod day_11; 14 | mod day_12; 15 | mod day_13; 16 | mod day_14; 17 | mod day_15; 18 | mod day_16; 19 | mod day_17; 20 | mod day_18; 21 | mod day_19; 22 | mod day_20; 23 | mod day_21; 24 | mod day_22; 25 | mod day_23; 26 | mod day_24; 27 | mod day_25; 28 | // [import_marker] 29 | 30 | pub const SOLUTIONS: &[Solution] = &[ 31 | day_01::SOLUTION, 32 | day_02::SOLUTION, 33 | day_03::SOLUTION, 34 | day_04::SOLUTION, 35 | day_05::SOLUTION, 36 | day_06::SOLUTION, 37 | day_07::SOLUTION, 38 | day_08::SOLUTION, 39 | day_09::SOLUTION, 40 | day_10::SOLUTION, 41 | day_11::SOLUTION, 42 | day_12::SOLUTION, 43 | day_13::SOLUTION, 44 | day_14::SOLUTION, 45 | day_15::SOLUTION, 46 | day_16::SOLUTION, 47 | day_17::SOLUTION, 48 | day_18::SOLUTION, 49 | day_19::SOLUTION, 50 | day_20::SOLUTION, 51 | day_21::SOLUTION, 52 | day_22::SOLUTION, 53 | day_23::SOLUTION, 54 | day_24::SOLUTION, 55 | day_25::SOLUTION, 56 | // [list_marker] 57 | ]; 58 | -------------------------------------------------------------------------------- /aoc_2024/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc_2024" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | aoc_lib = { path = "../aoc_lib" } 8 | common = { path = "../common" } 9 | 10 | indoc = "2.0.4" 11 | itertools = "0.13.0" 12 | nd_vec = { git = "https://github.com/connorslade/nd-vec.git" } 13 | rayon = "1.10.0" 14 | -------------------------------------------------------------------------------- /aoc_2024/src/day_01.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Historian Hysteria", 1); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | let (mut list_a, mut list_b) = parse(input); 7 | list_a.sort(); 8 | list_b.sort(); 9 | 10 | list_a 11 | .into_iter() 12 | .zip(list_b) 13 | .map(|(a, b)| a.abs_diff(b)) 14 | .sum::() 15 | .into() 16 | } 17 | 18 | fn part_b(input: &str) -> Answer { 19 | let (list_a, list_b) = parse(input); 20 | 21 | list_a 22 | .into_iter() 23 | .map(|x| { 24 | let count = list_b.iter().filter(|&&y| y == x).count(); 25 | x * count as u32 26 | }) 27 | .sum::() 28 | .into() 29 | } 30 | 31 | fn parse(input: &str) -> (Vec, Vec) { 32 | let (mut a, mut b) = (Vec::new(), Vec::new()); 33 | 34 | for x in input.lines() { 35 | let mut parts = x.split_whitespace(); 36 | a.push(parts.next().unwrap().parse::().unwrap()); 37 | b.push(parts.next().unwrap().parse::().unwrap()); 38 | } 39 | 40 | (a, b) 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | use indoc::indoc; 46 | 47 | const CASE: &str = indoc! {" 48 | 3 4 49 | 4 3 50 | 2 5 51 | 1 3 52 | 3 9 53 | 3 3 54 | "}; 55 | 56 | #[test] 57 | fn part_a() { 58 | assert_eq!(super::part_a(CASE), 11.into()); 59 | } 60 | 61 | #[test] 62 | fn part_b() { 63 | assert_eq!(super::part_b(CASE), 31.into()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /aoc_2024/src/day_02.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | use itertools::Itertools; 3 | 4 | solution!("Red-Nosed Reports", 2); 5 | 6 | fn part_a(input: &str) -> Answer { 7 | let reports = parse(input); 8 | reports.iter().filter(|x| is_safe(x)).count().into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | let reports = parse(input); 13 | reports.iter().filter(|x| is_safe_b(x, None)).count().into() 14 | } 15 | 16 | fn is_safe(input: &[i32]) -> bool { 17 | let sig = (input[0] - input[1]).signum(); 18 | input 19 | .iter() 20 | .tuple_windows() 21 | .map(|(a, b)| a - b) 22 | .all(|x| (1..=3).contains(&x.abs()) && x.signum() == sig) 23 | } 24 | 25 | fn is_safe_b(input: &[i32], skip: Option) -> bool { 26 | let vals = input 27 | .iter() 28 | .enumerate() 29 | .filter(|(idx, _)| skip.is_none() || Some(*idx) != skip) 30 | .map(|(_, &x)| x); 31 | let mut diffs = vals.tuple_windows().map(|(a, b)| a - b).peekable(); 32 | 33 | let sig = diffs.peek().unwrap().signum(); 34 | let first_invalid = diffs.position(|x| !(1..=3).contains(&x.abs()) || x.signum() != sig); 35 | 36 | match first_invalid { 37 | Some(x) if skip.is_none() => { 38 | is_safe_b(input, Some(x + 1)) 39 | || is_safe_b(input, Some(x.saturating_sub(1))) 40 | || is_safe_b(input, Some(x)) 41 | } 42 | None => true, 43 | _ => false, 44 | } 45 | } 46 | 47 | fn parse(input: &str) -> Vec> { 48 | input 49 | .lines() 50 | .map(|x| { 51 | x.split_whitespace() 52 | .map(|x| x.parse::().unwrap()) 53 | .collect::>() 54 | }) 55 | .collect::>() 56 | } 57 | 58 | #[cfg(test)] 59 | mod test { 60 | use indoc::indoc; 61 | 62 | const CASE: &str = indoc! {" 63 | 7 6 4 2 1 64 | 1 2 7 8 9 65 | 9 7 6 2 1 66 | 1 3 2 4 5 67 | 8 6 4 4 1 68 | 1 3 6 7 9 69 | "}; 70 | 71 | #[test] 72 | fn part_a() { 73 | assert_eq!(super::part_a(CASE), 2.into()); 74 | } 75 | 76 | #[test] 77 | fn part_b() { 78 | assert_eq!(super::part_b(CASE), 4.into()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /aoc_2024/src/day_03.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | 3 | solution!("Mull It Over", 3); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | solve(input, false).into() 7 | } 8 | 9 | fn part_b(input: &str) -> Answer { 10 | solve(input, true).into() 11 | } 12 | 13 | fn solve(input: &str, part_b: bool) -> u32 { 14 | let mut out = 0; 15 | 16 | let mut parser = Parser::new(input); 17 | let mut active = true; 18 | 19 | while !parser.is_eof() { 20 | active |= parser.expect("do()"); 21 | active &= !parser.expect("don't()"); 22 | 23 | if parser.expect("mul(") { 24 | let Some(a) = parser.number() else { continue }; 25 | if !parser.expect(",") { 26 | continue; 27 | } 28 | let Some(b) = parser.number() else { continue }; 29 | if !parser.expect(")") { 30 | continue; 31 | } 32 | 33 | if active || !part_b { 34 | out += a * b; 35 | } 36 | } else { 37 | parser.advance(1); 38 | } 39 | } 40 | 41 | out 42 | } 43 | 44 | struct Parser { 45 | chars: Vec, 46 | idx: usize, 47 | } 48 | 49 | impl Parser { 50 | pub fn new(input: &str) -> Self { 51 | Self { 52 | chars: input.chars().collect(), 53 | idx: 0, 54 | } 55 | } 56 | 57 | pub fn expect(&mut self, str: &str) -> bool { 58 | let valid = self.idx + str.len() < self.chars.len() 59 | && self.chars[self.idx..self.idx + str.len()] 60 | .iter() 61 | .zip(str.chars()) 62 | .all(|(&a, b)| a == b); 63 | 64 | if valid { 65 | self.idx += str.len(); 66 | } 67 | 68 | valid 69 | } 70 | 71 | pub fn number(&mut self) -> Option { 72 | let mut working = String::new(); 73 | while self.chars[self.idx].is_ascii_digit() && self.idx < self.chars.len() { 74 | working.push(self.chars[self.idx]); 75 | self.idx += 1; 76 | } 77 | working.parse::().ok() 78 | } 79 | 80 | pub fn advance(&mut self, count: usize) { 81 | self.idx += count; 82 | } 83 | 84 | pub fn is_eof(&self) -> bool { 85 | self.idx >= self.chars.len() 86 | } 87 | } 88 | #[cfg(test)] 89 | mod test { 90 | use indoc::indoc; 91 | 92 | const CASE_A: &str = indoc! {" 93 | xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5)) 94 | "}; 95 | 96 | const CASE_B: &str = indoc! {" 97 | xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) 98 | "}; 99 | 100 | #[test] 101 | fn part_a() { 102 | assert_eq!(super::part_a(CASE_A), 161.into()); 103 | } 104 | 105 | #[test] 106 | fn part_b() { 107 | assert_eq!(super::part_b(CASE_B), 48.into()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /aoc_2024/src/day_04.rs: -------------------------------------------------------------------------------- 1 | use std::convert::identity; 2 | 3 | use aoc_lib::{direction::ordinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use nd_vec::vector; 6 | 7 | solution!("Ceres Search", 4); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let matrix = Grid::parse(input, identity); 11 | let mut count = 0; 12 | 13 | for y in 0..matrix.size.y() { 14 | for x in 0..matrix.size.x() { 15 | let start = vector!(x, y); 16 | if *matrix.get(start).unwrap() != 'X' { 17 | continue; 18 | } 19 | 20 | 'outer: for dir in Direction::ALL { 21 | let mut pos = start; 22 | for expected in ['M', 'A', 'S'] { 23 | let next = dir.try_advance(pos); 24 | let Some(next) = next else { continue 'outer }; 25 | pos = next; 26 | 27 | if Some(&expected) != matrix.get(pos) { 28 | continue 'outer; 29 | }; 30 | } 31 | 32 | count += 1; 33 | } 34 | } 35 | } 36 | 37 | count.into() 38 | } 39 | 40 | /// The directions to advance from the middle 'A' for each MAS instance. 41 | const MAS_DIRECTIONS: [[Direction; 2]; 2] = [ 42 | [Direction::NorthEast, Direction::SouthWest], 43 | [Direction::SouthEast, Direction::NorthWest], 44 | ]; 45 | 46 | fn part_b(input: &str) -> Answer { 47 | let matrix = Grid::parse(input, identity); 48 | let mut count = 0; 49 | 50 | for y in 0..matrix.size.y() { 51 | 'outer: for x in 0..matrix.size.x() { 52 | let start = vector!(x, y); 53 | if *matrix.get(start).unwrap() != 'A' { 54 | continue; 55 | } 56 | 57 | for mas in MAS_DIRECTIONS { 58 | let (mut m, mut s) = (false, false); 59 | for dir in mas { 60 | let Some(&chr) = dir.try_advance(start).and_then(|x| matrix.get(x)) else { 61 | continue 'outer; 62 | }; 63 | 64 | m |= chr == 'M'; 65 | s |= chr == 'S'; 66 | } 67 | 68 | if !(m && s) { 69 | continue 'outer; 70 | } 71 | } 72 | 73 | count += 1; 74 | } 75 | } 76 | 77 | count.into() 78 | } 79 | 80 | #[cfg(test)] 81 | mod test { 82 | use indoc::indoc; 83 | 84 | const CASE: &str = indoc! {" 85 | MMMSXXMASM 86 | MSAMXMSMSA 87 | AMXSXMAAMM 88 | MSAMASMSMX 89 | XMASAMXAMM 90 | XXAMMXXAMA 91 | SMSMSASXSS 92 | SAXAMASAAA 93 | MAMMMXMMMM 94 | MXMXAXMASX 95 | "}; 96 | 97 | #[test] 98 | fn part_a() { 99 | assert_eq!(super::part_a(CASE), 18.into()); 100 | } 101 | 102 | #[test] 103 | fn part_b() { 104 | assert_eq!(super::part_b(CASE), 9.into()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /aoc_2024/src/day_05.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Print Queue", 5); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let problem = PrintQueue::parse(input); 9 | 10 | // Sum the middle value of all valid page lists. 11 | (0..problem.updates.len()) 12 | .filter(|&x| problem.is_valid(x)) 13 | .map(|x| &problem.updates[x]) 14 | .map(|x| x[x.len() / 2]) 15 | .sum::() 16 | .into() 17 | } 18 | 19 | fn part_b(input: &str) -> Answer { 20 | let problem = PrintQueue::parse(input); 21 | 22 | // Sum the middle value of the sorted versions of all page lists that are 23 | // not sorted correctly. 24 | (0..problem.updates.len()) 25 | .filter(|&x| !problem.is_valid(x)) 26 | .map(|x| problem.sort_pages(x)) 27 | .map(|x| x[x.len() / 2]) 28 | .sum::() 29 | .into() 30 | } 31 | 32 | struct PrintQueue { 33 | /// Maps a page to the pages that must come before it. 34 | rule_map: HashMap>, 35 | updates: Vec>, 36 | } 37 | 38 | impl PrintQueue { 39 | fn parse(input: &str) -> Self { 40 | let (rules, updates) = input.split_once("\n\n").unwrap(); 41 | 42 | // a|b => a comes before b 43 | // For each rule stating that some page a comes before some page b, add 44 | // a to the list of pages that come before b. 45 | let mut rule_map: HashMap> = HashMap::new(); 46 | for (a, b) in rules.lines().map(|x| { 47 | let (a, b) = x.split_once('|').unwrap(); 48 | (a.parse::().unwrap(), b.parse::().unwrap()) 49 | }) { 50 | rule_map.entry(b).or_default().insert(a); 51 | } 52 | 53 | let updates = updates 54 | .lines() 55 | .map(|x| x.split(',').map(|x| x.parse::().unwrap()).collect()) 56 | .collect(); 57 | 58 | Self { rule_map, updates } 59 | } 60 | 61 | fn is_valid(&self, idx: usize) -> bool { 62 | let line = &self.updates[idx]; 63 | // A line is sorted if to the left of every page is a page that should 64 | // be before it. If b is not in the rule map, that means that there are 65 | // no pages that come before it and since b is at least the 2nd item in 66 | // the line, the line is therefore invalid. 67 | line.is_sorted_by(|a, b| self.rule_map.contains_key(b) && self.rule_map[b].contains(a)) 68 | } 69 | 70 | fn sort_pages(&self, idx: usize) -> Vec { 71 | let mut line = self.updates[idx].clone(); 72 | // Just the same expression from before plugged into a sorting algo to 73 | // put our pages in order! 74 | line.sort_unstable_by(|a, b| { 75 | (self.rule_map.contains_key(b) && self.rule_map[b].contains(a)).cmp(&true) 76 | }); 77 | line 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod test { 83 | use indoc::indoc; 84 | 85 | const CASE: &str = indoc! {" 86 | 47|53 87 | 97|13 88 | 97|61 89 | 97|47 90 | 75|29 91 | 61|13 92 | 75|53 93 | 29|13 94 | 97|29 95 | 53|29 96 | 61|53 97 | 97|53 98 | 61|29 99 | 47|13 100 | 75|47 101 | 97|75 102 | 47|61 103 | 75|61 104 | 47|29 105 | 75|13 106 | 53|13 107 | 108 | 75,47,61,53,29 109 | 97,61,53,29,13 110 | 75,29,13 111 | 75,97,47,61,53 112 | 61,13,29 113 | 97,13,75,29,47 114 | "}; 115 | 116 | #[test] 117 | fn part_a() { 118 | assert_eq!(super::part_a(CASE), 143.into()); 119 | } 120 | 121 | #[test] 122 | fn part_b() { 123 | assert_eq!(super::part_b(CASE), 123.into()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /aoc_2024/src/day_06.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use nd_vec::Vec2; 6 | use rayon::iter::{ParallelBridge, ParallelIterator}; 7 | 8 | solution!("Guard Gallivant", 6); 9 | 10 | fn part_a(input: &str) -> Answer { 11 | Map::new(input).visited().len().into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | let map = Map::new(input); 16 | 17 | map.visited() 18 | .into_iter() 19 | .par_bridge() 20 | .map(|x| map.loops(x) as usize) 21 | .sum::() 22 | .into() 23 | } 24 | 25 | #[derive(Debug, PartialEq, Eq)] 26 | enum Tile { 27 | Obstacle, 28 | Start, 29 | None, 30 | } 31 | 32 | struct Map { 33 | map: Grid, 34 | start: Vec2, 35 | } 36 | 37 | impl Map { 38 | fn new(input: &str) -> Self { 39 | let map = Grid::parse(input, |x| match x { 40 | '#' => Tile::Obstacle, 41 | '^' => Tile::Start, 42 | _ => Tile::None, 43 | }); 44 | let start = map.find(Tile::Start).unwrap(); 45 | 46 | Self { map, start } 47 | } 48 | 49 | fn visited(&self) -> HashSet> { 50 | let mut seen = HashSet::new(); 51 | let mut pos = self.start; 52 | let mut direction = Direction::Up; 53 | 54 | loop { 55 | seen.insert(pos); 56 | 57 | let Some(mut next) = direction.try_advance(pos) else { 58 | break; 59 | }; 60 | let Some(tile) = self.map.get(next) else { 61 | break; 62 | }; 63 | if *tile == Tile::Obstacle { 64 | direction = direction.turn_right(); 65 | next = match direction.try_advance(pos) { 66 | Some(x) => x, 67 | None => break, 68 | } 69 | } 70 | 71 | pos = next; 72 | } 73 | 74 | seen 75 | } 76 | 77 | fn loops(&self, obstacle: Vec2) -> bool { 78 | let mut seen = HashSet::new(); 79 | let mut pos = self.start; 80 | let mut direction = Direction::Up; 81 | 82 | loop { 83 | let Some(tile) = self.map.get(pos) else { 84 | break; 85 | }; 86 | if obstacle == pos || *tile == Tile::Obstacle { 87 | pos = direction.opposite().advance(pos); 88 | direction = direction.turn_right(); 89 | } 90 | 91 | if !seen.insert((pos, direction)) { 92 | return true; 93 | } 94 | 95 | pos = match direction.try_advance(pos) { 96 | Some(x) => x, 97 | None => break, 98 | }; 99 | } 100 | 101 | false 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | use indoc::indoc; 108 | 109 | const CASE: &str = indoc! {" 110 | ....#..... 111 | .........# 112 | .......... 113 | ..#....... 114 | .......#.. 115 | .......... 116 | .#..^..... 117 | ........#. 118 | #......... 119 | ......#... 120 | "}; 121 | 122 | #[test] 123 | fn part_a() { 124 | assert_eq!(super::part_a(CASE), 41.into()); 125 | } 126 | 127 | #[test] 128 | fn part_b() { 129 | assert_eq!(super::part_b(CASE), 6.into()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /aoc_2024/src/day_08.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use aoc_lib::matrix::Grid; 4 | use common::{solution, Answer}; 5 | use nd_vec::Vec2; 6 | 7 | use itertools::Itertools; 8 | 9 | solution!("Resonant Collinearity", 8); 10 | 11 | fn part_a(input: &str) -> Answer { 12 | // Parse the input into a hash table that maps antenna frequencies to positions. 13 | let map = AntennaMap::parse(input); 14 | 15 | let mut out = HashSet::new(); 16 | for (_freq, pos) in map.freqs { 17 | // Because a line is defined by two points, we find all point 18 | // combinations with two points. To get the two antinode points, we add 19 | // the difference between both points to the first point and subtract 20 | // the difference from the second. 21 | for (a, b) in pos.into_iter().tuple_combinations() { 22 | out.extend( 23 | [a + (a - b), b + (b - a)] 24 | .into_iter() 25 | .filter(|&x| map.world.contains(x)), 26 | ); 27 | } 28 | } 29 | 30 | out.len().into() 31 | } 32 | 33 | fn part_b(input: &str) -> Answer { 34 | let map = AntennaMap::parse(input); 35 | 36 | let mut out = HashSet::new(); 37 | for (_freq, pos) in map.freqs { 38 | for (a, b) in pos.into_iter().tuple_combinations() { 39 | // For part be we just need to keep adding / subtracting the 40 | // diffract between the two points to the starting position until 41 | // the result is out of bounds. 42 | for (mut start, delta) in [(a, a - b), (b, b - a)] { 43 | while map.world.contains(start) { 44 | out.insert(start); 45 | start += delta; 46 | } 47 | } 48 | } 49 | } 50 | 51 | out.len().into() 52 | } 53 | 54 | struct AntennaMap { 55 | world: Grid, 56 | freqs: HashMap>>, 57 | } 58 | 59 | enum Tile { 60 | Emitter(char), 61 | Empty, 62 | } 63 | 64 | impl AntennaMap { 65 | fn parse(input: &str) -> Self { 66 | let world = Grid::parse(input, |x| match x { 67 | 'a'..='z' | 'A'..='Z' | '0'..='9' => Tile::Emitter(x), 68 | _ => Tile::Empty, 69 | }); 70 | 71 | let mut freqs = HashMap::>>::new(); 72 | for (pos, tile) in world.iter() { 73 | if let Tile::Emitter(chr) = tile { 74 | freqs 75 | .entry(*chr) 76 | .or_default() 77 | .push(pos.try_cast::().unwrap()); 78 | } 79 | } 80 | 81 | Self { world, freqs } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod test { 87 | use indoc::indoc; 88 | 89 | const CASE: &str = indoc! {" 90 | ............ 91 | ........0... 92 | .....0...... 93 | .......0.... 94 | ....0....... 95 | ......A..... 96 | ............ 97 | ............ 98 | ........A... 99 | .........A.. 100 | ............ 101 | ............ 102 | "}; 103 | 104 | #[test] 105 | fn part_a() { 106 | assert_eq!(super::part_a(CASE), 14.into()); 107 | } 108 | 109 | #[test] 110 | fn part_b() { 111 | assert_eq!(super::part_b(CASE), 34.into()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /aoc_2024/src/day_09.rs: -------------------------------------------------------------------------------- 1 | use common::{solution, Answer}; 2 | use itertools::{repeat_n, Itertools}; 3 | 4 | solution!("Disk Fragmenter", 9); 5 | 6 | fn part_a(input: &str) -> Answer { 7 | let mut problem = Blocks::parse(input); 8 | problem.sort(); 9 | problem.score().into() 10 | } 11 | 12 | fn part_b(input: &str) -> Answer { 13 | let mut problem = Files::parse(input); 14 | problem.sort(); 15 | problem.score().into() 16 | } 17 | 18 | struct Blocks { 19 | blocks: Vec>, 20 | } 21 | 22 | struct Files { 23 | files: Vec, 24 | } 25 | 26 | struct File { 27 | pos: u32, 28 | size: u8, 29 | id: u32, 30 | } 31 | 32 | impl Blocks { 33 | fn parse(input: &str) -> Self { 34 | let mut blocks = Vec::new(); 35 | 36 | let mut id = 0; 37 | for (idx, chr) in input.trim().char_indices() { 38 | let count = chr.to_digit(10).unwrap() as u8; 39 | 40 | let is_block = idx % 2 == 0; 41 | let item = is_block.then_some(id); 42 | 43 | blocks.extend(repeat_n(item, count as usize)); 44 | id += is_block as u32; 45 | } 46 | 47 | Self { blocks } 48 | } 49 | 50 | fn sort(&mut self) { 51 | loop { 52 | let empty = self.blocks.iter().position(|x| x.is_none()).unwrap(); 53 | let last = self.blocks.iter().rposition(|x| x.is_some()).unwrap(); 54 | 55 | if last <= empty { 56 | break; 57 | } 58 | 59 | self.blocks.swap(empty, last); 60 | } 61 | } 62 | 63 | fn score(&self) -> u64 { 64 | self.blocks 65 | .iter() 66 | .enumerate() 67 | .map(|(idx, id)| idx as u64 * id.unwrap_or_default() as u64) 68 | .sum() 69 | } 70 | } 71 | 72 | impl Files { 73 | fn parse(input: &str) -> Self { 74 | let mut files = Vec::new(); 75 | let (mut id, mut pos) = (0, 0); 76 | 77 | for (idx, chr) in input.trim().char_indices() { 78 | let size = chr.to_digit(10).unwrap() as u8; 79 | 80 | if idx % 2 == 0 { 81 | files.push(File { pos, size, id }); 82 | id += 1; 83 | } 84 | 85 | pos += size as u32; 86 | } 87 | 88 | Self { files } 89 | } 90 | 91 | fn sort(&mut self) { 92 | let max_id = self.files.last().unwrap().id; 93 | for id in (0..=max_id).rev() { 94 | let file_idx = self.files.iter().position(|x| x.id == id).unwrap(); 95 | let file = &self.files[file_idx]; 96 | 97 | let mut new_pos = None; 98 | for (a, b) in self.files.iter().tuple_windows() { 99 | let free = (b.pos) - (a.pos + a.size as u32); 100 | let pos = a.pos + a.size as u32; 101 | 102 | if pos > file.pos { 103 | break; 104 | } 105 | 106 | if free >= file.size as u32 { 107 | new_pos = Some(pos); 108 | break; 109 | } 110 | } 111 | 112 | if let Some(new_pos) = new_pos { 113 | self.files[file_idx].pos = new_pos; 114 | } 115 | 116 | self.files.sort_by_key(|x| x.pos); 117 | } 118 | } 119 | 120 | fn score(&self) -> u64 { 121 | let mut sum = 0; 122 | let mut last = 0; 123 | let mut idx = 0; 124 | 125 | for x in &self.files { 126 | idx += x.pos - last; 127 | 128 | sum += (x.id as u64 * x.size as u64 * (x.size as u64 + 2 * idx as u64 - 1)) / 2; 129 | idx += x.size as u32; 130 | 131 | last = x.pos + x.size as u32; 132 | } 133 | 134 | sum 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod test { 140 | use indoc::indoc; 141 | 142 | const CASE: &str = indoc! {" 143 | 2333133121414131402 144 | "}; 145 | 146 | #[test] 147 | fn part_a() { 148 | assert_eq!(super::part_a(CASE), 1928.into()); 149 | } 150 | 151 | #[test] 152 | fn part_b() { 153 | assert_eq!(super::part_b(CASE), 2858.into()); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /aoc_2024/src/day_10.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use nd_vec::Vec2; 6 | 7 | solution!("Hoof It", 10); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | solve(input, false).into() 11 | } 12 | 13 | fn part_b(input: &str) -> Answer { 14 | solve(input, true).into() 15 | } 16 | 17 | fn solve(input: &str, part_b: bool) -> usize { 18 | let map = Map::parse(input); 19 | map.trailheads() 20 | .map(|x| map.score(x, !part_b)) 21 | .sum::() 22 | } 23 | 24 | struct Map { 25 | board: Grid, 26 | } 27 | 28 | impl Map { 29 | fn parse(input: &str) -> Self { 30 | let board = Grid::parse(input, |x| x.to_digit(10).unwrap()); 31 | Self { board } 32 | } 33 | 34 | // Find the coordinates of all 0s 35 | fn trailheads(&self) -> impl Iterator> + '_ { 36 | self.board 37 | .iter() 38 | .filter(|(_, &tile)| tile == 0) 39 | .map(|(pos, _)| pos) 40 | } 41 | 42 | // Simple BFS for pathfinding, where we don't avoid going to already 43 | // explored tiles if on part B. 44 | fn score(&self, pos: Vec2, no_repeats: bool) -> usize { 45 | let mut queue = VecDeque::new(); 46 | let mut seen = HashSet::new(); 47 | 48 | queue.push_back(pos); 49 | seen.insert(pos); 50 | 51 | let mut score = 0; 52 | while let Some(pos) = queue.pop_front() { 53 | let value = *self.board.get(pos).unwrap(); 54 | score += (value == 9) as usize; 55 | 56 | queue.extend( 57 | Direction::ALL 58 | .iter() 59 | .filter_map(|&dir| dir.try_advance(pos)) 60 | .filter(|&next| { 61 | self.board.contains(next) 62 | && *self.board.get(next).unwrap() == value + 1 63 | && (!no_repeats || seen.insert(next)) 64 | }), 65 | ); 66 | } 67 | 68 | score 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use indoc::indoc; 75 | 76 | const CASE: &str = indoc! {" 77 | 89010123 78 | 78121874 79 | 87430965 80 | 96549874 81 | 45678903 82 | 32019012 83 | 01329801 84 | 10456732 85 | "}; 86 | 87 | #[test] 88 | fn part_a() { 89 | assert_eq!(super::part_a(CASE), 36.into()); 90 | } 91 | 92 | #[test] 93 | fn part_b() { 94 | assert_eq!(super::part_b(CASE), 81.into()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /aoc_2024/src/day_11.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Plutonian Pebbles", 11); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | solve(parse(input), 25).into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | solve(parse(input), 75).into() 13 | } 14 | 15 | fn solve(nums: Vec, iters: usize) -> u64 { 16 | // Store the counts of each stone type 17 | let mut counts = HashMap::::new(); 18 | nums.into_iter() 19 | .for_each(|x| *counts.entry(x).or_default() += 1); 20 | 21 | // For each iteration, create a new count map by applying the rules for each 22 | // stone to get a new key and adding the previous count to it. 23 | for _ in 0..iters { 24 | let mut next = HashMap::new(); 25 | for (stone, count) in counts { 26 | if stone == 0 { 27 | *next.entry(1).or_default() += count; 28 | } else if let Some((a, b)) = split_digits(stone) { 29 | *next.entry(a).or_default() += count; 30 | *next.entry(b).or_default() += count; 31 | } else { 32 | *next.entry(stone * 2024).or_default() += count; 33 | } 34 | } 35 | counts = next; 36 | } 37 | 38 | counts.values().sum::() 39 | } 40 | 41 | fn parse(input: &str) -> Vec { 42 | input 43 | .split_ascii_whitespace() 44 | .map(|x| x.parse().unwrap()) 45 | .collect() 46 | } 47 | 48 | /// Given an integer, this function will return None if it has an odd number of 49 | /// base 10 digits, otherwise the first half and second half of the digits will 50 | /// be returned separately. 51 | fn split_digits(num: u64) -> Option<(u64, u64)> { 52 | let digits = num.ilog10() + 1; 53 | let pow = u64::pow(10, digits / 2); 54 | (digits & 1 == 0).then(|| (num / pow, num % pow)) 55 | } 56 | 57 | #[cfg(test)] 58 | mod test { 59 | use indoc::indoc; 60 | 61 | const CASE: &str = indoc! {" 62 | 125 17 63 | "}; 64 | 65 | #[test] 66 | fn part_a() { 67 | assert_eq!(super::part_a(CASE), 55312.into()); 68 | } 69 | 70 | #[test] 71 | fn part_b() { 72 | assert_eq!(super::part_b(CASE), 65601038650482_u64.into()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /aoc_2024/src/day_12.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, convert::identity}; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use itertools::Itertools; 6 | use nd_vec::{vector, Vec2}; 7 | 8 | solution!("Garden Groups", 12); 9 | 10 | fn part_a(input: &str) -> Answer { 11 | let mut garden = Garden::parse(input); 12 | garden 13 | .points() 14 | .filter_map(|x| garden.flood(x)) 15 | .map(|(area, perimeter)| area.len() * perimeter) 16 | .sum::() 17 | .into() 18 | } 19 | 20 | fn part_b(input: &str) -> Answer { 21 | let mut garden = Garden::parse(input); 22 | 23 | let mut sum = 0; 24 | for (area, _) in garden.points().filter_map(|x| garden.flood(x)) { 25 | let mut corners = 0; 26 | 27 | for &point in area.iter() { 28 | // Count convex corners by checking to see that the wall is not in 29 | // any cardinal direction and a direction orthogonal to that 30 | for a in Direction::ALL { 31 | corners += (!area.contains(&a.wrapping_advance(point)) 32 | && !area.contains(&a.turn_right().wrapping_advance(point))) 33 | as u32; 34 | } 35 | 36 | // Count the concave angles by looking for when both the orthogonal 37 | // directions are in the area, but not the diagonal between them. 38 | for a in Direction::ALL { 39 | let b = a.turn_right(); 40 | corners += (area.contains(&a.wrapping_advance(point)) 41 | && area.contains(&b.wrapping_advance(point)) 42 | && !area.contains(&b.wrapping_advance(a.wrapping_advance(point)))) 43 | as u32; 44 | } 45 | } 46 | 47 | sum += area.len() as u32 * corners; 48 | } 49 | 50 | sum.into() 51 | } 52 | 53 | struct Garden { 54 | matrix: Grid, 55 | 56 | seen: HashSet>, 57 | } 58 | 59 | impl Garden { 60 | fn parse(input: &str) -> Self { 61 | let matrix = Grid::parse(input, identity); 62 | Self { 63 | matrix, 64 | seen: HashSet::new(), 65 | } 66 | } 67 | 68 | fn points(&self) -> impl Iterator> { 69 | let size = self.matrix.size; 70 | (0..size.x()) 71 | .cartesian_product(0..size.y()) 72 | .map(|(x, y)| vector!(x, y)) 73 | } 74 | 75 | fn flood(&mut self, start: Vec2) -> Option<(HashSet>, usize)> { 76 | if !self.seen.insert(start) { 77 | return None; 78 | } 79 | 80 | let mut area = HashSet::new(); 81 | let mut perimeter = 0; 82 | 83 | let mut queue = Vec::new(); 84 | let plant = self.matrix.get(start).unwrap(); 85 | 86 | area.insert(start); 87 | queue.push(start); 88 | 89 | while let Some(pos) = queue.pop() { 90 | for next in Direction::ALL.map(|x| x.wrapping_advance(pos)) { 91 | if !self.matrix.contains(next) { 92 | perimeter += 1; 93 | continue; 94 | } 95 | 96 | if self.matrix.get(next).unwrap() == plant { 97 | if self.seen.insert(next) { 98 | area.insert(next); 99 | queue.push(next); 100 | } 101 | } else { 102 | perimeter += 1; 103 | } 104 | } 105 | } 106 | 107 | Some((area, perimeter)) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod test { 113 | use indoc::indoc; 114 | 115 | const CASE: &str = indoc! {" 116 | RRRRIICCFF 117 | RRRRIICCCF 118 | VVRRRCCFFF 119 | VVRCCCJFFF 120 | VVVVCJJCFE 121 | VVIVCCJJEE 122 | VVIIICJJEE 123 | MIIIIIJJEE 124 | MIIISIJEEE 125 | MMMISSJEEE 126 | "}; 127 | 128 | #[test] 129 | fn part_a() { 130 | assert_eq!(super::part_a(CASE), 1930.into()); 131 | } 132 | 133 | #[test] 134 | fn part_b() { 135 | assert_eq!(super::part_b(CASE), 1206.into()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /aoc_2024/src/day_13.rs: -------------------------------------------------------------------------------- 1 | use aoc_lib::vector::IntoTuple2; 2 | use common::{solution, Answer}; 3 | use nd_vec::{vector, Vec2}; 4 | 5 | solution!("Claw Contraption", 13); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | Problem::parse(input).solve().into() 9 | } 10 | 11 | fn part_b(input: &str) -> Answer { 12 | Problem::parse(input).part_b().solve().into() 13 | } 14 | 15 | struct Problem { 16 | cases: Vec, 17 | } 18 | 19 | struct Case { 20 | a_button: Vec2, 21 | b_button: Vec2, 22 | goal: Vec2, 23 | } 24 | 25 | impl Case { 26 | fn cheapest(&self) -> u64 { 27 | let cast = |x: Vec2| x.try_cast::().unwrap().into_tuple(); 28 | let ((gx, gy), (ax, ay), (bx, by)) = 29 | (cast(self.goal), cast(self.a_button), cast(self.b_button)); 30 | 31 | // The best a and b values for a case are the solutions to the following 32 | // system of equations, where g is the goal position, a/b are the number 33 | // of times you press the corespondent buttons (what we are solving 34 | // for), and (a|b)(x|y) are the offsets applied by each button press. 35 | // 36 | // gx = ax * a + bx * b 37 | // gy = ay * a + by * b 38 | // 39 | // By plugging those into Wolfram Alpha, I got the below equation for a, 40 | // then used that to find the equation of b. Because this is integer 41 | // math, we need to verify it by making sure a and b are greater than 42 | // zero and checking if the solution actually solves the system. If it 43 | // does, we return 3a + b, the price. 44 | 45 | let a = (by * gx - bx * gy) / (ax * by - ay * bx); 46 | let b = (gx - ax * a) / bx; 47 | 48 | if a <= 0 || b <= 0 || self.goal != self.a_button * a as u64 + self.b_button * b as u64 { 49 | return 0; 50 | } 51 | 52 | a as u64 * 3 + b as u64 53 | } 54 | } 55 | 56 | impl Problem { 57 | fn parse(input: &str) -> Self { 58 | let cases = input 59 | .split("\n\n") 60 | .map(|x| { 61 | let mut lines = x.split('\n'); 62 | let a_button = parse_button(lines.next().unwrap()); 63 | let b_button = parse_button(lines.next().unwrap()); 64 | 65 | let (_, prize) = lines.next().unwrap().rsplit_once(": ").unwrap(); 66 | let (x, y) = prize.split_once(", ").unwrap(); 67 | let prize = vector!(x[2..].parse().unwrap(), y[2..].parse().unwrap()); 68 | 69 | Case { 70 | a_button, 71 | b_button, 72 | goal: prize, 73 | } 74 | }) 75 | .collect(); 76 | Self { cases } 77 | } 78 | 79 | fn solve(&self) -> u64 { 80 | self.cases.iter().map(|x| x.cheapest()).sum::() 81 | } 82 | 83 | fn part_b(mut self) -> Self { 84 | self.cases 85 | .iter_mut() 86 | .for_each(|case| case.goal += vector!(10000000000000, 10000000000000)); 87 | self 88 | } 89 | } 90 | 91 | fn parse_button(input: &str) -> Vec2 { 92 | let (_, parts) = input.rsplit_once(": ").unwrap(); 93 | let (x, y) = parts.split_once(", ").unwrap(); 94 | vector!(x[1..].parse().unwrap(), y[1..].parse().unwrap()) 95 | } 96 | 97 | #[cfg(test)] 98 | mod test { 99 | use indoc::indoc; 100 | 101 | const CASE: &str = indoc! {" 102 | Button A: X+94, Y+34 103 | Button B: X+22, Y+67 104 | Prize: X=8400, Y=5400 105 | 106 | Button A: X+26, Y+66 107 | Button B: X+67, Y+21 108 | Prize: X=12748, Y=12176 109 | 110 | Button A: X+17, Y+86 111 | Button B: X+84, Y+37 112 | Prize: X=7870, Y=6450 113 | 114 | Button A: X+69, Y+23 115 | Button B: X+27, Y+71 116 | Prize: X=18641, Y=10279 117 | "}; 118 | 119 | #[test] 120 | fn part_a() { 121 | assert_eq!(super::part_a(CASE), 480.into()); 122 | } 123 | 124 | #[test] 125 | fn part_b() { 126 | assert_eq!(super::part_b(CASE), 875318608908_u64.into()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /aoc_2024/src/day_18.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use nd_vec::{vector, Vec2}; 6 | 7 | solution!("RAM Run", 18); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let mut map = Map::parse(input, vector!(71, 71)); 11 | map.fill_to(1023); 12 | map.shortest_path().into() 13 | } 14 | 15 | fn part_b(input: &str) -> Answer { 16 | let mut map = Map::parse(input, vector!(71, 71)); 17 | 18 | let (mut start, mut end) = (0, map.falling.len() - 1); 19 | 20 | while start <= end { 21 | let mid = (start + end) / 2; 22 | let next = map.fill_to(mid); 23 | 24 | let works = map.shortest_path() != usize::MAX; 25 | 26 | if !works && { 27 | map.fill_to(mid - 1); 28 | map.shortest_path() != usize::MAX 29 | } { 30 | return format!("{},{}", next.x(), next.y()).into(); 31 | } 32 | 33 | if works { 34 | start = mid + 1; 35 | } else { 36 | end = mid - 1; 37 | } 38 | } 39 | 40 | panic!("No solution found") 41 | } 42 | 43 | struct Map { 44 | board: Grid, 45 | falling: Vec>, 46 | } 47 | 48 | impl Map { 49 | fn parse(input: &str, size: Vec2) -> Self { 50 | let falling = input 51 | .lines() 52 | .map(|x| { 53 | let (a, b) = x.split_once(',').unwrap(); 54 | vector!(a.parse::().unwrap(), b.parse::().unwrap()) 55 | }) 56 | .collect::>(); 57 | 58 | let board = Grid::new(size, false); 59 | 60 | Self { falling, board } 61 | } 62 | 63 | fn fill_to(&mut self, end: usize) -> Vec2 { 64 | self.board.fill(false); 65 | for ins in &self.falling[0..=end] { 66 | self.board.set(*ins, true); 67 | } 68 | 69 | self.falling[end] 70 | } 71 | 72 | fn shortest_path(&self) -> usize { 73 | let mut queue = VecDeque::new(); 74 | let mut seen = HashSet::new(); 75 | queue.push_back((vector!(0, 0), 0)); 76 | 77 | while let Some((pos, depth)) = queue.pop_front() { 78 | if pos + vector!(1, 1) == self.board.size() { 79 | return depth; 80 | } 81 | 82 | if !seen.insert(pos) { 83 | continue; 84 | } 85 | 86 | for dir in Direction::ALL { 87 | let next = dir.wrapping_advance(pos); 88 | if self.board.get(next) == Some(&false) { 89 | queue.push_back((next, depth + 1)); 90 | } 91 | } 92 | } 93 | 94 | usize::MAX 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod test { 100 | use indoc::indoc; 101 | use nd_vec::vector; 102 | 103 | use super::Map; 104 | 105 | const CASE: &str = indoc! {" 106 | 5,4 107 | 4,2 108 | 4,5 109 | 3,0 110 | 2,1 111 | 6,3 112 | 2,4 113 | 1,5 114 | 0,6 115 | 3,3 116 | 2,6 117 | 5,1 118 | 1,2 119 | 5,5 120 | 2,5 121 | 6,5 122 | 1,4 123 | 0,4 124 | 6,4 125 | 1,1 126 | 6,1 127 | 1,0 128 | 0,5 129 | 1,6 130 | 2,0 131 | "}; 132 | 133 | #[test] 134 | fn part_a() { 135 | let mut map = Map::parse(CASE, vector!(7, 7)); 136 | map.fill_to(11); 137 | assert_eq!(map.shortest_path(), 22); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /aoc_2024/src/day_19.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use common::{solution, Answer}; 4 | 5 | solution!("Linen Layout", 19); 6 | 7 | fn part_a(input: &str) -> Answer { 8 | let problem = Onsen::parse(input); 9 | problem.possible().into() 10 | } 11 | 12 | fn part_b(input: &str) -> Answer { 13 | let problem = Onsen::parse(input); 14 | problem.ways().into() 15 | } 16 | 17 | struct Onsen<'a> { 18 | segments: Vec<&'a str>, 19 | towels: Vec<&'a str>, 20 | } 21 | 22 | impl<'a> Onsen<'a> { 23 | fn parse(input: &'a str) -> Self { 24 | let (sources, needed) = input.split_once("\n\n").unwrap(); 25 | let segments = sources.split(", ").collect(); 26 | let towels = needed.lines().collect(); 27 | 28 | Self { segments, towels } 29 | } 30 | 31 | /// Returns the number of possible towel designs by counting all the towels 32 | /// that can be made a non-zero number of ways. 33 | fn possible(&self) -> usize { 34 | self.towels 35 | .iter() 36 | .filter(|x| count_ways(&mut HashMap::new(), x, &self.segments) != 0) 37 | .count() 38 | } 39 | 40 | /// Here we just sum up the number of ways each towel can be made. 41 | fn ways(&self) -> u64 { 42 | self.towels 43 | .iter() 44 | .map(|x| count_ways(&mut HashMap::new(), x, &self.segments)) 45 | .sum() 46 | } 47 | } 48 | 49 | fn count_ways<'a>(memo: &mut HashMap<&'a str, u64>, expected: &'a str, sources: &[&'a str]) -> u64 { 50 | if let Some(&cache) = memo.get(expected) { 51 | return cache; 52 | } 53 | 54 | // If there is no more towel to find designs for, we have found one way to 55 | // make the towel. 56 | if expected.is_empty() { 57 | return 1; 58 | } 59 | 60 | // Otherwise, we will sum up the number of ways the towel can be made from 61 | // adding each of the available segments to the current towel, but only the 62 | // ones that match the current pattern. 63 | let mut ways = 0; 64 | for source in sources { 65 | if expected.len() >= source.len() && expected.starts_with(source) { 66 | ways += count_ways(memo, &expected[source.len()..], sources); 67 | } 68 | } 69 | 70 | // Memoization!!! This is what allows us to avoid evaluating huge segments 71 | // of the tree and get good performance. 72 | memo.insert(expected, ways); 73 | ways 74 | } 75 | 76 | #[cfg(test)] 77 | mod test { 78 | use indoc::indoc; 79 | 80 | const CASE: &str = indoc! {" 81 | r, wr, b, g, bwu, rb, gb, br 82 | 83 | brwrr 84 | bggr 85 | gbbr 86 | rrbgbr 87 | ubwu 88 | bwurrg 89 | brgr 90 | bbrgwb 91 | "}; 92 | 93 | #[test] 94 | fn part_a() { 95 | assert_eq!(super::part_a(CASE), 6.into()); 96 | } 97 | 98 | #[test] 99 | fn part_b() { 100 | assert_eq!(super::part_b(CASE), 16.into()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /aoc_2024/src/day_20.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, convert::identity}; 2 | 3 | use aoc_lib::{direction::cardinal::Direction, matrix::Grid}; 4 | use common::{solution, Answer}; 5 | use itertools::Itertools; 6 | use nd_vec::{vector, Vec2}; 7 | 8 | solution!("Race Condition", 20); 9 | 10 | fn part_a(input: &str) -> Answer { 11 | Problem::parse(input).solve(2).into() 12 | } 13 | 14 | fn part_b(input: &str) -> Answer { 15 | Problem::parse(input).solve(20).into() 16 | } 17 | 18 | struct Problem { 19 | board: Grid, 20 | start: Vec2, 21 | end: Vec2, 22 | } 23 | 24 | impl Problem { 25 | fn parse(input: &str) -> Self { 26 | let board = Grid::parse(input, identity); 27 | 28 | let start = board.find('S').unwrap(); 29 | let end = board.find('E').unwrap(); 30 | 31 | Self { board, start, end } 32 | } 33 | 34 | fn solve(&self, max_skip: i32) -> u32 { 35 | let (sc, ec) = (self.cost_map(self.start), self.cost_map(self.end)); 36 | let base_cost = sc[self.end]; 37 | 38 | let mut out = 0; 39 | 40 | for (pos, tile) in self.board.iter() { 41 | if *tile == '#' || sc[pos] == u32::MAX { 42 | continue; 43 | } 44 | 45 | for (x, y) in (-max_skip..=max_skip).cartesian_product(-max_skip..=max_skip) { 46 | let offset = vector!(x, y); 47 | let dist = offset.manhattan_distance(&Vec2::zero()); 48 | if dist > max_skip { 49 | continue; 50 | } 51 | 52 | let end = pos.try_cast::().unwrap() + offset; 53 | if !self.board.contains(end) || self.board[end] == '#' || ec[end] == u32::MAX { 54 | continue; 55 | } 56 | 57 | let cost = sc[pos] + ec[end] + dist as u32; 58 | out += (cost + 100 <= base_cost) as u32; 59 | } 60 | } 61 | 62 | out 63 | } 64 | 65 | fn cost_map(&self, start: Vec2) -> Grid { 66 | let mut costs = Grid::new(self.board.size, u32::MAX); 67 | let mut queue = VecDeque::new(); 68 | queue.push_back((start, 0)); 69 | 70 | while let Some((pos, dist)) = queue.pop_front() { 71 | if costs[pos] != u32::MAX { 72 | continue; 73 | } 74 | 75 | costs[pos] = dist; 76 | for dir in Direction::ALL { 77 | let next = dir.wrapping_advance(pos); 78 | if let Some(tile) = self.board.get(next) { 79 | if matches!(tile, '.' | 'E') { 80 | queue.push_back((next, dist + 1)); 81 | } 82 | } 83 | } 84 | } 85 | 86 | costs 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /aoc_2024/src/day_22.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{once, repeat}; 2 | 3 | use common::{solution, Answer}; 4 | use itertools::Itertools; 5 | use rayon::iter::{ParallelBridge, ParallelIterator}; 6 | 7 | solution!("Monkey Market", 22); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let mut sum = 0; 11 | 12 | for num in input.lines() { 13 | let mut num = num.parse::().unwrap(); 14 | (0..2000).for_each(|_| { 15 | next(&mut num); 16 | }); 17 | sum += num; 18 | } 19 | 20 | sum.into() 21 | } 22 | 23 | fn part_b(input: &str) -> Answer { 24 | let mut buyers = Vec::new(); 25 | for num in input.lines() { 26 | let mut num = num.parse::().unwrap(); 27 | let seq = once(num) 28 | .chain((0..2000).map(|_| next(&mut num))) 29 | .collect::>(); 30 | buyers.push(seq); 31 | } 32 | 33 | let diffs = buyers 34 | .iter() 35 | .map(|buyer| { 36 | buyer 37 | .iter() 38 | .tuple_windows() 39 | .map(|(&a, &b)| (b % 10) as i8 - (a % 10) as i8) 40 | .collect::>() 41 | }) 42 | .collect::>(); 43 | 44 | let out = repeat(-9..=9) 45 | .take(4) 46 | .multi_cartesian_product() 47 | .map(|x| (x[0], x[1], x[2], x[3])) 48 | .par_bridge() 49 | .map(|(a, b, c, d)| { 50 | let mut sum = 0; 51 | 52 | for (diff, nums) in diffs.iter().zip(buyers.iter()) { 53 | if let Some(idx) = find_sequence(diff, (a, b, c, d)) { 54 | sum += nums[idx + 4] % 10; 55 | } 56 | } 57 | 58 | sum 59 | }) 60 | .max() 61 | .unwrap(); 62 | 63 | out.into() 64 | } 65 | 66 | fn next(num: &mut u64) -> u64 { 67 | *num ^= *num * 64; 68 | *num %= 16777216; 69 | 70 | *num ^= *num / 32; 71 | *num %= 16777216; 72 | 73 | *num ^= *num * 2048; 74 | *num %= 16777216; 75 | 76 | *num 77 | } 78 | 79 | fn find_sequence(haystack: &[i8], (a, b, c, d): (i8, i8, i8, i8)) -> Option { 80 | haystack 81 | .iter() 82 | .tuple_windows() 83 | .position(|(&ax, &bx, &cx, &dx)| ax == a && bx == b && cx == c && dx == d) 84 | } 85 | 86 | #[cfg(test)] 87 | mod test { 88 | use indoc::indoc; 89 | 90 | const CASE: &str = indoc! {" 91 | 1 92 | 10 93 | 100 94 | 2024 95 | "}; 96 | 97 | #[test] 98 | fn part_a() { 99 | assert_eq!(super::part_a(CASE), 37327623.into()); 100 | } 101 | 102 | #[test] 103 | fn part_b() { 104 | assert_eq!(super::part_b(CASE), 24.into()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /aoc_2024/src/day_23.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use common::{solution, Answer}; 4 | use itertools::Itertools; 5 | 6 | solution!("LAN Party", 23); 7 | 8 | fn part_a(input: &str) -> Answer { 9 | let graph = parse(input); 10 | 11 | let mut triplets = HashSet::new(); 12 | for key in graph.keys() { 13 | let neighbors = &graph[key]; 14 | 15 | for x in neighbors { 16 | for y in neighbors.iter().skip(1) { 17 | if graph[x].contains(y) { 18 | let mut sorted = vec![key, x, y]; 19 | sorted.sort(); 20 | triplets.insert((sorted[0], sorted[1], sorted[2])); 21 | } 22 | } 23 | } 24 | } 25 | 26 | triplets 27 | .iter() 28 | .filter(|(a, b, c)| a.starts_with('t') || b.starts_with('t') || c.starts_with('t')) 29 | .count() 30 | .into() 31 | } 32 | 33 | fn part_b(input: &str) -> Answer { 34 | let graph = parse(input); 35 | 36 | let mut cliques = Vec::new(); 37 | bron_kerbosch( 38 | &graph, 39 | &mut HashSet::new(), 40 | &mut graph.keys().cloned().collect(), 41 | &mut HashSet::new(), 42 | &mut cliques, 43 | ); 44 | 45 | let max = cliques.iter().max_by_key(|x| x.len()).unwrap(); 46 | max.iter().sorted().join(",").into() 47 | } 48 | 49 | fn parse(input: &str) -> HashMap<&str, HashSet<&str>> { 50 | let mut out: HashMap<&str, HashSet<&str>> = HashMap::new(); 51 | 52 | for line in input.lines() { 53 | let (a, b) = line.split_once('-').unwrap(); 54 | out.entry(a).or_default().insert(b); 55 | out.entry(b).or_default().insert(a); 56 | } 57 | 58 | out 59 | } 60 | 61 | fn bron_kerbosch<'a>( 62 | graph: &HashMap<&'a str, HashSet<&'a str>>, 63 | r: &mut HashSet<&'a str>, 64 | p: &mut HashSet<&'a str>, 65 | x: &mut HashSet<&'a str>, 66 | cliques: &mut Vec>, 67 | ) { 68 | if p.is_empty() && x.is_empty() { 69 | cliques.push(r.clone()); 70 | return; 71 | } 72 | 73 | let pivot = p.iter().chain(x.iter()).next(); 74 | 75 | for &v in p.difference(&pivot.map_or_else(HashSet::new, |p| { 76 | graph.get(p).cloned().unwrap_or(HashSet::new()) 77 | })) { 78 | let mut r = r.clone(); 79 | r.insert(v); 80 | 81 | let mut p = p 82 | .intersection(graph.get(&v).unwrap_or(&HashSet::new())) 83 | .cloned() 84 | .collect(); 85 | let mut x = x 86 | .intersection(graph.get(&v).unwrap_or(&HashSet::new())) 87 | .cloned() 88 | .collect(); 89 | 90 | bron_kerbosch(graph, &mut r, &mut p, &mut x, cliques); 91 | 92 | p.remove(&v); 93 | x.insert(v); 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod test { 99 | use indoc::indoc; 100 | 101 | const CASE: &str = indoc! {" 102 | kh-tc 103 | qp-kh 104 | de-cg 105 | ka-co 106 | yn-aq 107 | qp-ub 108 | cg-tb 109 | vc-aq 110 | tb-ka 111 | wh-tc 112 | yn-cg 113 | kh-ub 114 | ta-co 115 | de-co 116 | tc-td 117 | tb-wq 118 | wh-td 119 | ta-ka 120 | td-qp 121 | aq-cg 122 | wq-ub 123 | ub-vc 124 | de-ta 125 | wq-aq 126 | wq-vc 127 | wh-yn 128 | ka-de 129 | kh-ta 130 | co-tc 131 | wh-qp 132 | tb-vc 133 | td-yn 134 | "}; 135 | 136 | #[test] 137 | fn part_a() { 138 | assert_eq!(super::part_a(CASE), 7.into()); 139 | } 140 | 141 | #[test] 142 | fn part_b() { 143 | assert_eq!(super::part_b(CASE), "co,de,ka,ta".into()); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /aoc_2024/src/day_25.rs: -------------------------------------------------------------------------------- 1 | use std::convert::identity; 2 | 3 | use aoc_lib::matrix::Grid; 4 | use common::{solution, Answer}; 5 | use nd_vec::vector; 6 | 7 | solution!("Code Chronicle", 25); 8 | 9 | fn part_a(input: &str) -> Answer { 10 | let mut keys = Vec::new(); 11 | let mut locks = Vec::new(); 12 | 13 | for item in input.split("\n\n") { 14 | let (item, lock) = Key::parse(item); 15 | if lock { 16 | locks.push(item); 17 | } else { 18 | keys.push(item); 19 | } 20 | } 21 | 22 | let mut out = 0; 23 | 24 | for key in keys { 25 | for lock in locks.iter() { 26 | if key.fits(&lock) { 27 | out += 1; 28 | } 29 | } 30 | } 31 | 32 | out.into() 33 | } 34 | 35 | fn part_b(input: &str) -> Answer { 36 | Answer::Unimplemented 37 | } 38 | 39 | #[derive(Debug)] 40 | struct Key { 41 | heights: [u8; 5], 42 | } 43 | 44 | impl Key { 45 | fn parse(input: &str) -> (Self, bool) { 46 | let grid = Grid::parse(input, identity); 47 | 48 | let mut heights = [0; 5]; 49 | let mut is_lock = true; 50 | 51 | for x in 0..5 { 52 | let mut height = 0; 53 | for y in 0..7 { 54 | if y == 0 && grid[vector!(x, y)] != '#' { 55 | is_lock = false; 56 | } 57 | 58 | if grid[vector!(x, y)] == '#' { 59 | height += 1; 60 | } 61 | } 62 | 63 | heights[x] = height - 1; 64 | } 65 | 66 | (Self { heights }, is_lock) 67 | } 68 | 69 | fn fits(&self, other: &Self) -> bool { 70 | for (a, b) in self.heights.iter().zip(other.heights.iter()) { 71 | if a + b > 5 { 72 | return false; 73 | } 74 | } 75 | 76 | true 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod test { 82 | use indoc::indoc; 83 | 84 | const CASE: &str = indoc! {" 85 | ##### 86 | .#### 87 | .#### 88 | .#### 89 | .#.#. 90 | .#... 91 | ..... 92 | 93 | ##### 94 | ##.## 95 | .#.## 96 | ...## 97 | ...#. 98 | ...#. 99 | ..... 100 | 101 | ..... 102 | #.... 103 | #.... 104 | #...# 105 | #.#.# 106 | #.### 107 | ##### 108 | 109 | ..... 110 | ..... 111 | #.#.. 112 | ###.. 113 | ###.# 114 | ###.# 115 | ##### 116 | 117 | ..... 118 | ..... 119 | ..... 120 | #.... 121 | #.#.. 122 | #.#.# 123 | ##### 124 | "}; 125 | 126 | #[test] 127 | fn part_a() { 128 | assert_eq!(super::part_a(CASE), 3.into()); 129 | } 130 | 131 | #[test] 132 | fn part_b() { 133 | assert_eq!(super::part_b(CASE), ().into()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /aoc_2024/src/lib.rs: -------------------------------------------------------------------------------- 1 | use common::Solution; 2 | 3 | mod day_01; 4 | mod day_02; 5 | mod day_03; 6 | mod day_04; 7 | mod day_05; 8 | mod day_06; 9 | mod day_07; 10 | mod day_08; 11 | mod day_09; 12 | mod day_10; 13 | mod day_11; 14 | mod day_12; 15 | mod day_13; 16 | mod day_14; 17 | mod day_15; 18 | mod day_16; 19 | mod day_17; 20 | mod day_18; 21 | mod day_19; 22 | mod day_20; 23 | mod day_21; 24 | mod day_22; 25 | mod day_23; 26 | mod day_24; 27 | mod day_25; 28 | // [import_marker] 29 | 30 | pub const SOLUTIONS: &[Solution] = &[ 31 | day_01::SOLUTION, 32 | day_02::SOLUTION, 33 | day_03::SOLUTION, 34 | day_04::SOLUTION, 35 | day_05::SOLUTION, 36 | day_06::SOLUTION, 37 | day_07::SOLUTION, 38 | day_08::SOLUTION, 39 | day_09::SOLUTION, 40 | day_10::SOLUTION, 41 | day_11::SOLUTION, 42 | day_12::SOLUTION, 43 | day_13::SOLUTION, 44 | day_14::SOLUTION, 45 | day_15::SOLUTION, 46 | day_16::SOLUTION, 47 | day_17::SOLUTION, 48 | day_18::SOLUTION, 49 | day_19::SOLUTION, 50 | day_20::SOLUTION, 51 | day_21::SOLUTION, 52 | day_22::SOLUTION, 53 | day_23::SOLUTION, 54 | day_24::SOLUTION, 55 | day_25::SOLUTION, 56 | // [list_marker] 57 | ]; 58 | -------------------------------------------------------------------------------- /aoc_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc_lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | nd_vec = { git = "https://github.com/connorslade/nd-vec.git" } 8 | num-traits = "0.2.17" 9 | once_cell = "1.18.0" 10 | regex = "1.10.2" 11 | -------------------------------------------------------------------------------- /aoc_lib/src/direction/cardinal.rs: -------------------------------------------------------------------------------- 1 | use nd_vec::{vector, Vec2}; 2 | use num_traits::{Num, Signed, WrappingSub}; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 5 | #[repr(u8)] 6 | pub enum Direction { 7 | Up, 8 | Down, 9 | Left, 10 | Right, 11 | } 12 | 13 | #[allow(dead_code)] 14 | impl Direction { 15 | pub const ALL: [Direction; 4] = [ 16 | Direction::Up, 17 | Direction::Down, 18 | Direction::Left, 19 | Direction::Right, 20 | ]; 21 | 22 | pub fn as_vector(&self) -> Vec2 { 23 | match self { 24 | Self::Up => vector!(T::zero(), -T::one()), 25 | Self::Down => vector!(T::zero(), T::one()), 26 | Self::Left => vector!(-T::one(), T::zero()), 27 | Self::Right => vector!(T::one(), T::zero()), 28 | } 29 | } 30 | 31 | #[rustfmt::skip] 32 | pub fn try_advance(&self, pos: Vec2) -> Option> { 33 | Some(match self { 34 | Self::Up if pos.y() > T::zero() => vector!(pos.x(), pos.y() - T::one()), 35 | Self::Down => vector!(pos.x(), pos.y() + T::one()), 36 | Self::Left if pos.x() > T::zero() => vector!(pos.x() - T::one(), pos.y()), 37 | Self::Right => vector!(pos.x() + T::one(), pos.y()), 38 | _ => return None, 39 | }) 40 | } 41 | 42 | #[rustfmt::skip] 43 | pub fn advance(&self, pos: Vec2) -> Vec2 { 44 | match self { 45 | Self::Up => vector!(pos.x(), pos.y() - T::one()), 46 | Self::Down => vector!(pos.x(), pos.y() + T::one()), 47 | Self::Left => vector!(pos.x() - T::one(), pos.y()), 48 | Self::Right => vector!(pos.x() + T::one(), pos.y()), 49 | } 50 | } 51 | 52 | pub fn wrapping_advance(&self, pos: Vec2) -> Vec2 { 53 | match self { 54 | Self::Up => vector!(pos.x(), pos.y().wrapping_sub(&T::one())), 55 | Self::Down => vector!(pos.x(), pos.y() + T::one()), 56 | Self::Left => vector!(pos.x().wrapping_sub(&T::one()), pos.y()), 57 | Self::Right => vector!(pos.x() + T::one(), pos.y()), 58 | } 59 | } 60 | 61 | pub fn opposite(&self) -> Self { 62 | match self { 63 | Self::Up => Self::Down, 64 | Self::Down => Self::Up, 65 | Self::Left => Self::Right, 66 | Self::Right => Self::Left, 67 | } 68 | } 69 | 70 | pub fn turn_left(&self) -> Self { 71 | match self { 72 | Self::Up => Self::Left, 73 | Self::Down => Self::Right, 74 | Self::Left => Self::Down, 75 | Self::Right => Self::Up, 76 | } 77 | } 78 | 79 | pub fn turn_right(&self) -> Self { 80 | match self { 81 | Self::Up => Self::Right, 82 | Self::Down => Self::Left, 83 | Self::Left => Self::Up, 84 | Self::Right => Self::Down, 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /aoc_lib/src/direction/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cardinal; 2 | pub mod ordinal; 3 | -------------------------------------------------------------------------------- /aoc_lib/src/direction/ordinal.rs: -------------------------------------------------------------------------------- 1 | use nd_vec::{vector, Vec2}; 2 | use num_traits::Num; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 5 | pub enum Direction { 6 | North, 7 | NorthEast, 8 | East, 9 | SouthEast, 10 | South, 11 | SouthWest, 12 | West, 13 | NorthWest, 14 | } 15 | 16 | impl Direction { 17 | pub const ALL: [Direction; 8] = [ 18 | Direction::North, 19 | Direction::NorthEast, 20 | Direction::East, 21 | Direction::SouthEast, 22 | Direction::South, 23 | Direction::SouthWest, 24 | Direction::West, 25 | Direction::NorthWest, 26 | ]; 27 | 28 | pub fn try_advance(&self, pos: Vec2) -> Option> { 29 | Some(match self { 30 | Self::North => vector!(pos.x(), pos.y() + T::one()), 31 | Self::NorthEast => vector!(pos.x() + T::one(), pos.y() + T::one()), 32 | Self::East => vector!(pos.x() + T::one(), pos.y()), 33 | Self::SouthEast if pos.y() > T::zero() => { 34 | vector!(pos.x() + T::one(), pos.y() - T::one()) 35 | } 36 | Self::South if pos.y() > T::zero() => vector!(pos.x(), pos.y() - T::one()), 37 | Self::SouthWest if pos.x() > T::zero() && pos.y() > T::zero() => { 38 | vector!(pos.x() - T::one(), pos.y() - T::one()) 39 | } 40 | Self::West if pos.x() > T::zero() => vector!(pos.x() - T::one(), pos.y()), 41 | Self::NorthWest if pos.x() > T::zero() => { 42 | vector!(pos.x() - T::one(), pos.y() + T::one()) 43 | } 44 | _ => return None, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /aoc_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod regex; 3 | pub mod direction; 4 | pub mod math; 5 | pub mod matrix; 6 | pub mod vector; -------------------------------------------------------------------------------- /aoc_lib/src/math.rs: -------------------------------------------------------------------------------- 1 | use num_traits::Num; 2 | 3 | pub fn gcd(mut a: T, mut b: T) -> T { 4 | while b != T::zero() { 5 | let tmp = b; 6 | b = a % b; 7 | a = tmp; 8 | } 9 | 10 | a 11 | } 12 | 13 | pub fn lcm(a: T, b: T) -> T { 14 | a * b / gcd(a, b) 15 | } 16 | -------------------------------------------------------------------------------- /aoc_lib/src/regex.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! regex { 3 | ($raw:expr) => {{ 4 | static REGEX: once_cell::sync::OnceCell<::regex::Regex> = once_cell::sync::OnceCell::new(); 5 | REGEX.get_or_init(|| ::regex::Regex::new($raw).unwrap()) 6 | }}; 7 | } 8 | -------------------------------------------------------------------------------- /aoc_lib/src/vector.rs: -------------------------------------------------------------------------------- 1 | use nd_vec::Vec2; 2 | 3 | pub trait IntoTuple2 { 4 | fn into_tuple(self) -> (T, T); 5 | } 6 | impl IntoTuple2 for Vec2 { 7 | fn into_tuple(self) -> (T, T) { 8 | (self.x(), self.y()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /common/src/answer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Answer { 5 | String(String), 6 | Number(u64), 7 | Float(f64), 8 | Unimplemented, 9 | } 10 | 11 | impl Display for Answer { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | match self { 14 | Answer::String(s) => write!(f, "{s}"), 15 | Answer::Number(n) => write!(f, "{n}"), 16 | Answer::Float(n) => write!(f, "{n}"), 17 | Answer::Unimplemented => write!(f, "Unimplemented"), 18 | } 19 | } 20 | } 21 | 22 | impl From for Answer { 23 | fn from(s: String) -> Self { 24 | Self::String(s) 25 | } 26 | } 27 | 28 | impl From<&str> for Answer { 29 | fn from(s: &str) -> Self { 30 | Self::String(s.to_string()) 31 | } 32 | } 33 | 34 | impl From<()> for Answer { 35 | fn from(_value: ()) -> Self { 36 | Self::Unimplemented 37 | } 38 | } 39 | 40 | macro_rules! answer_impl { 41 | ($($answer:ident, $answer_type:ty => { $($type:ty),* }),*) => { 42 | $($(impl From<$type> for Answer { 43 | fn from(n: $type) -> Self { 44 | Self::$answer(n as $answer_type) 45 | } 46 | })*)* 47 | }; 48 | } 49 | 50 | answer_impl!( 51 | Number, u64 => { u8, u16, u32, u64, usize, i8, i16, i32, i64, isize }, 52 | Float, f64 => { f32, f64 } 53 | ); 54 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod answer; 2 | mod misc; 3 | mod part; 4 | #[macro_use] 5 | pub mod solution; 6 | 7 | pub use answer::Answer; 8 | pub use misc::load; 9 | pub use part::Part; 10 | pub use solution::Solution; 11 | -------------------------------------------------------------------------------- /common/src/misc.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | /// Load the input for the given year and day. 4 | /// Removes carriage returns and trims leading and trailing whitespace. 5 | pub fn load(year: u16, day: u32) -> io::Result { 6 | load_raw(year, day).map(|x| x.trim().replace('\r', "")) 7 | } 8 | 9 | /// Load the input for the given year and day. 10 | pub fn load_raw(year: u16, day: u32) -> io::Result { 11 | let file = format!("data/{year}/{:02}.txt", day); 12 | fs::read_to_string(file) 13 | } 14 | -------------------------------------------------------------------------------- /common/src/part.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Display, Write}, 3 | str::FromStr, 4 | }; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum Part { 8 | A, 9 | B, 10 | } 11 | 12 | impl FromStr for Part { 13 | type Err = String; 14 | 15 | fn from_str(s: &str) -> Result { 16 | match s { 17 | "a" => Ok(Part::A), 18 | "b" => Ok(Part::B), 19 | _ => Err("part must be `a` or `b`".to_owned()), 20 | } 21 | } 22 | } 23 | 24 | impl Display for Part { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | f.write_char(match self { 27 | Part::A => 'a', 28 | Part::B => 'b', 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/solution.rs: -------------------------------------------------------------------------------- 1 | use crate::Answer; 2 | 3 | pub struct Solution { 4 | pub name: &'static str, 5 | pub day: u8, 6 | 7 | pub part_a: fn(&str) -> Answer, 8 | pub part_b: fn(&str) -> Answer, 9 | } 10 | 11 | #[macro_export] 12 | macro_rules! solution { 13 | ($name:expr, $day:expr) => { 14 | pub const SOLUTION: $crate::Solution = $crate::Solution { 15 | name: $name, 16 | day: $day, 17 | 18 | part_a, 19 | part_b, 20 | }; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /scaffold/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scaffold" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.75" 10 | chrono = "0.4.31" 11 | clap = { version = "4.4.8", features = ["derive"] } 12 | colored = "2.0.4" 13 | common = { path = "../common" } 14 | dotenv = "0.15.0" 15 | globalenv = "0.4.2" 16 | humantime = "2.1.0" 17 | once_cell = "1.18.0" 18 | regex = "1.10.2" 19 | scraper = "0.18.1" 20 | shell-words = "1.1.0" 21 | ureq = "2.9.1" 22 | url = { version = "2.5.0", features = ["serde"] } 23 | which = "5.0.0" 24 | -------------------------------------------------------------------------------- /scaffold/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod init; 2 | pub mod submit; 3 | pub mod timer; 4 | pub mod token; 5 | pub mod verify; 6 | -------------------------------------------------------------------------------- /scaffold/src/commands/submit.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | process::{Command, Stdio}, 3 | time::Instant, 4 | }; 5 | 6 | use anyhow::{Context, Result}; 7 | use scraper::Html; 8 | 9 | use crate::{ 10 | args::{Args, SubmitArgs}, 11 | formatter::Formatter, 12 | session::{Authenticated, Session}, 13 | }; 14 | 15 | pub fn submit(session: &Session, cmd: &SubmitArgs, args: &Args) -> Result<()> { 16 | let answer = get_answer(cmd).context("Getting answer")?; 17 | 18 | if cmd.dry_run { 19 | println!("[*] Aborting due to dry run"); 20 | return Ok(()); 21 | } 22 | 23 | submit_answer(session, cmd, args, &answer).context("Submitting answer")?; 24 | Ok(()) 25 | } 26 | 27 | fn get_answer(cmd: &SubmitArgs) -> Result { 28 | let formats: &[(&str, String)] = &[ 29 | ("day", cmd.day.to_string()), 30 | ("year", cmd.year.to_string()), 31 | ("part", cmd.part.to_string()), 32 | ]; 33 | let command = Formatter::new(&cmd.command)?.format(formats)?; 34 | let args = shell_words::split(&command)?; 35 | let executable = which::which(&args[0])?; 36 | 37 | if cmd.dry_run { 38 | println!("[*] Running command: {}", command); 39 | } 40 | 41 | let executable = Command::new(&executable) 42 | .args(&args[1..]) 43 | .stdout(Stdio::piped()) 44 | .stderr(Stdio::piped()) 45 | .spawn()?; 46 | 47 | let start = Instant::now(); 48 | let output = executable.wait_with_output()?; 49 | let time = start.elapsed(); 50 | 51 | if output.status.code() != Some(0) { 52 | anyhow::bail!( 53 | "Command failed with status code {}\n{}", 54 | output.status, 55 | String::from_utf8_lossy(&output.stderr) 56 | ); 57 | } 58 | 59 | let output = String::from_utf8_lossy(&output.stdout); 60 | let answer = cmd 61 | .extraction_regex 62 | .captures(&output) 63 | .context("Failed to extract answer, regex didn't match")? 64 | .get(cmd.extraction_group) 65 | .context("Failed to extract answer, too few capture groups")? 66 | .as_str() 67 | .trim() 68 | .to_owned(); 69 | 70 | println!("[*] Answer: `{answer}` ({time:.2?})"); 71 | 72 | Ok(answer) 73 | } 74 | 75 | fn submit_answer(session: &Session, cmd: &SubmitArgs, args: &Args, answer: &str) -> Result<()> { 76 | let url = args 77 | .address 78 | .join(&format!("{}/day/{}/answer", cmd.year, cmd.day))?; 79 | 80 | // POST https://adventofcode.com/{{year}}/day/{{day}}/answer 81 | // level={{part:int}}&answer={{answer}} 82 | let result = ureq::post(url.as_str()) 83 | .authenticated(session) 84 | .send_form(&[ 85 | ("level", &(cmd.part as u8 + 1).to_string()), 86 | ("answer", answer), 87 | ])?; 88 | 89 | let document = Html::parse_document(&result.into_string()?); 90 | let result = document 91 | .select(selector!("article p")) 92 | .next() 93 | .context("No response message found")?; 94 | let result_text = result.text().collect::>().join(""); 95 | 96 | // Remove duplicate whitespace 97 | let result_text = regex!(r"[\[\(].*?[\]\)]").replace_all(&result_text, ""); 98 | let result_text = regex!(r"\s+").replace_all(&result_text, " "); 99 | 100 | println!("[*] {result_text}"); 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /scaffold/src/commands/timer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Write}, 3 | thread, 4 | time::Duration, 5 | }; 6 | 7 | use anyhow::{Context, Result}; 8 | use chrono::{DateTime, Datelike, Utc}; 9 | 10 | use crate::args::TimerArgs; 11 | 12 | /// The timezone of the Advent of Code release. 13 | const AOC_TIMEZONE: u32 = 5; 14 | 15 | pub fn timer(cmd: &TimerArgs) -> Result<()> { 16 | let mut stop_time = next_release()?; 17 | 18 | if let Some(offset) = cmd.offset { 19 | stop_time += chrono::Duration::seconds(offset as i64); 20 | } 21 | 22 | if Utc::now() >= stop_time { 23 | println!("[*] The next puzzle has already been released."); 24 | 25 | if cmd.offset.is_some() { 26 | println!("[*] Note: This may be because of the offset you set"); 27 | } 28 | 29 | return Ok(()); 30 | } 31 | 32 | if cmd.quiet { 33 | println!("[*] Waiting..."); 34 | } else { 35 | println!("[*] The next puzzle will be released in:"); 36 | } 37 | 38 | loop { 39 | let now = Utc::now(); 40 | if now >= stop_time { 41 | break; 42 | } 43 | 44 | if !cmd.quiet { 45 | let time_left = (stop_time - now).to_std()?; 46 | let time_left = Duration::new(time_left.as_secs(), 0); 47 | print!("\r\x1b[0K[*] {}", humantime::format_duration(time_left)); 48 | io::stdout().flush()?; 49 | } 50 | 51 | thread::sleep(Duration::from_secs_f32(cmd.frequency)); 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | fn next_release() -> Result> { 58 | let mut next = Utc::now() 59 | .date_naive() 60 | .and_hms_opt(AOC_TIMEZONE, 0, 0) 61 | .unwrap() 62 | .and_utc(); 63 | 64 | let before_event = next.month() != 12; 65 | let after_event = next.month() == 12 && next.day() > 25; 66 | if after_event || before_event { 67 | next = next 68 | .with_month(12) 69 | .and_then(|x| x.with_day(1)) 70 | .context("Can not represent the next first of December.")?; 71 | } 72 | 73 | if after_event { 74 | next = next 75 | .with_year(next.year() + 1) 76 | .context("Can not represent the next first of December.")?; 77 | } 78 | 79 | if Utc::now() > next { 80 | next = next 81 | .date_naive() 82 | .succ_opt() 83 | .unwrap() 84 | .and_time(next.time()) 85 | .and_utc(); 86 | } 87 | 88 | Ok(next) 89 | } 90 | -------------------------------------------------------------------------------- /scaffold/src/commands/token.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::{ 4 | args::{Args, TokenArgs}, 5 | commands::verify::verify_inner, 6 | session::Session, 7 | TOKEN_VAR, 8 | }; 9 | 10 | pub fn token(session: &Option, cmd: &TokenArgs, args: &Args) -> Result<()> { 11 | if cmd.token.len() != 128 { 12 | anyhow::bail!("Invalid token length of {}, should be 128", cmd.token.len()); 13 | } 14 | 15 | println!("[*] Validating session token..."); 16 | let new_session = Session::new(&cmd.token); 17 | verify_inner(&new_session, &args.address)?; 18 | println!("[*] Session token is valid."); 19 | 20 | if session.is_some() && session.as_ref().unwrap().is_from_env() { 21 | println!("[*] Updating session token"); 22 | } else { 23 | println!("[*] Setting session token"); 24 | } 25 | 26 | globalenv::set_var(TOKEN_VAR, &cmd.token)?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /scaffold/src/commands/verify.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use colored::Colorize; 3 | use url::Url; 4 | 5 | use crate::session::{Session, SessionVerification}; 6 | 7 | pub fn verify(session: &Session, address: &Url) -> Result<()> { 8 | println!("[*] Verifying session token..."); 9 | let verification = verify_inner(session, address)?; 10 | 11 | println!("[*] Hello, {}!", verification.name.underline()); 12 | println!("{}", "[*] Session token is valid.".green()); 13 | Ok(()) 14 | } 15 | 16 | pub fn verify_inner(session: &Session, address: &Url) -> Result { 17 | match session.verify(address) { 18 | Ok(Some(verification)) => Ok(verification), 19 | Ok(None) => { 20 | bail!("Session token is invalid. Sign in again and update with `aoc token`.") 21 | } 22 | Err(err) => bail!("Failed to verify session: {}", err), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scaffold/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use args::{Args, SubCommand}; 3 | use clap::Parser; 4 | 5 | use crate::session::Session; 6 | 7 | mod args; 8 | #[macro_use] 9 | mod misc; 10 | mod commands; 11 | mod formatter; 12 | mod session; 13 | 14 | const TOKEN_VAR: &str = "AOC_TOKEN"; 15 | 16 | fn main() -> Result<()> { 17 | let args = Args::parse(); 18 | 19 | let _ = dotenv::dotenv(); 20 | let session = match &args.token { 21 | Some(token) => Ok(Session::new(token)), 22 | None => Session::from_env(), 23 | }; 24 | 25 | match &args.subcommand { 26 | SubCommand::Verify => commands::verify::verify(&session?, &args.address)?, 27 | SubCommand::Token(e) => commands::token::token(&session.ok(), e, &args)?, 28 | SubCommand::Timer(e) => commands::timer::timer(e)?, 29 | SubCommand::Init(e) => commands::init::init(&session?, e, &args)?, 30 | SubCommand::Submit(e) => commands::submit::submit(&session?, e, &args)?, 31 | } 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /scaffold/src/misc.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Datelike, Utc}; 2 | 3 | macro_rules! selector { 4 | ($raw:expr) => {{ 5 | static SELECTOR: once_cell::sync::OnceCell = 6 | once_cell::sync::OnceCell::new(); 7 | SELECTOR.get_or_init(|| scraper::Selector::parse($raw).unwrap()) 8 | }}; 9 | } 10 | 11 | macro_rules! regex { 12 | ($raw:expr) => {{ 13 | static REGEX: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); 14 | REGEX.get_or_init(|| regex::Regex::new($raw).unwrap()) 15 | }}; 16 | } 17 | 18 | pub fn current_year() -> u16 { 19 | Utc::now().year() as u16 20 | } 21 | -------------------------------------------------------------------------------- /scaffold/src/session.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use anyhow::{Context, Result}; 4 | use scraper::Html; 5 | use ureq::Request; 6 | use url::Url; 7 | 8 | use crate::TOKEN_VAR; 9 | 10 | pub struct Session { 11 | token: String, 12 | from_env: bool, 13 | } 14 | 15 | impl Session { 16 | pub fn new(token: &str) -> Self { 17 | Self { 18 | token: token.to_owned(), 19 | from_env: false, 20 | } 21 | } 22 | 23 | pub fn from_env() -> Result { 24 | let token = env::var(TOKEN_VAR)?; 25 | Ok(Self { 26 | token, 27 | from_env: true, 28 | }) 29 | } 30 | 31 | pub fn is_from_env(&self) -> bool { 32 | self.from_env 33 | } 34 | 35 | pub fn verify(&self, address: &Url) -> Result> { 36 | let body = ureq::get(address.as_str()) 37 | .set("Cookie", &format!("session={}", self.token)) 38 | .call()? 39 | .into_string()?; 40 | 41 | let document = Html::parse_document(&body); 42 | let user = match document.select(selector!(".user")).next() { 43 | Some(user) => user, 44 | None => return Ok(None), 45 | }; 46 | let name = user 47 | .text() 48 | .next() 49 | .context("No username found")? 50 | .trim() 51 | .to_owned(); 52 | 53 | Ok(Some(SessionVerification { name })) 54 | } 55 | } 56 | 57 | pub struct SessionVerification { 58 | pub name: String, 59 | } 60 | 61 | pub trait Authenticated { 62 | fn authenticated(self, session: &Session) -> Request; 63 | } 64 | 65 | impl Authenticated for Request { 66 | fn authenticated(self, session: &Session) -> Request { 67 | self.set("Cookie", &format!("session={}", session.token)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scaffold/template.txt: -------------------------------------------------------------------------------- 1 | use common::{Answer, solution}; 2 | 3 | solution!("{{problem_name}}", {{day}}); 4 | 5 | fn part_a(input: &str) -> Answer { 6 | Answer::Unimplemented 7 | } 8 | 9 | fn part_b(input: &str) -> Answer { 10 | Answer::Unimplemented 11 | } 12 | 13 | #[cfg(test)] 14 | mod test { 15 | use indoc::indoc; 16 | 17 | const CASE: &str = indoc! {" 18 | ... 19 | "}; 20 | 21 | #[test] 22 | fn part_a() { 23 | assert_eq!(super::part_a(CASE), ().into()); 24 | } 25 | 26 | #[test] 27 | fn part_b() { 28 | assert_eq!(super::part_b(CASE), ().into()); 29 | } 30 | } -------------------------------------------------------------------------------- /scaffold/todo.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | - [x] Formatter 4 | - [x] Commands 5 | - [x] Init 6 | - [x] Scaffold 7 | - [x] Module 8 | - [x] Input 9 | - [x] Verify 10 | - [x] Token 11 | - [x] Timer 12 | - [x] Submit 13 | 14 | ```sh 15 | scaffold timer -o 2 && scaffold init 1 --auto-open 16 | cargo t -p aoc_2023 -- day_01::test::part_a && scaffold submit 1 a 17 | ``` 18 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use chrono::{Datelike, Utc}; 4 | use clap::{Parser, Subcommand}; 5 | use common::Part; 6 | 7 | #[derive(Parser)] 8 | #[command( 9 | name = "advent_of_code", 10 | author = "Connor Slade " 11 | )] 12 | pub struct Args { 13 | #[command(subcommand)] 14 | pub command: Commands, 15 | } 16 | 17 | #[derive(Subcommand)] 18 | pub enum Commands { 19 | /// Run a solution to a problem 20 | Run(RunArgs), 21 | /// Run all solutions in a given year 22 | RunAll(RunAllArgs), 23 | /// List all solutions for a given year 24 | List(ListArgs), 25 | } 26 | 27 | #[derive(Parser)] 28 | pub struct RunArgs { 29 | /// The day to run 30 | pub day: u8, 31 | /// The part to run, a or b 32 | pub part: Part, 33 | /// The year to run 34 | #[arg(default_value_t = current_year())] 35 | pub year: u16, 36 | /// The location of the input file, will default to `data/{year:pad(2)}/{day:pad(2)}.txt` 37 | #[arg(short, long)] 38 | pub input: Option, 39 | /// Wether just the answer should be printed, not the execution time or other information 40 | #[arg(short, long)] 41 | pub raw: bool, 42 | } 43 | 44 | #[derive(Parser)] 45 | pub struct RunAllArgs { 46 | /// The year to run 47 | #[arg(default_value_t = current_year())] 48 | pub year: u16, 49 | } 50 | 51 | #[derive(Parser)] 52 | pub struct ListArgs { 53 | /// The year to list solutions for 54 | #[arg(default_value_t = current_year())] 55 | pub year: u16, 56 | } 57 | 58 | pub fn current_year() -> u16 { 59 | Utc::now().year() as u16 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/list.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::{args::ListArgs, get_year}; 4 | 5 | pub fn list(cmd: &ListArgs) -> Result<()> { 6 | let solutions = get_year(cmd.year); 7 | println!("[*] Solutions for {}:", cmd.year); 8 | 9 | for (i, e) in solutions.iter().enumerate() { 10 | let last = i + 1 == solutions.len(); 11 | println!( 12 | " {} Day {}: {}", 13 | if last { "└" } else { "├" }, 14 | e.day, 15 | e.name 16 | ); 17 | } 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod list; 2 | pub mod run; 3 | pub mod run_all; 4 | -------------------------------------------------------------------------------- /src/commands/run.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fs, path::PathBuf, time::Instant}; 2 | 3 | use anyhow::{bail, Context, Result}; 4 | use common::Part; 5 | 6 | use crate::{args::RunArgs, get_year}; 7 | 8 | pub fn run(cmd: &RunArgs) -> Result<()> { 9 | let solutions = get_year(cmd.year); 10 | let solution = solutions 11 | .iter() 12 | .find(|x| x.day == cmd.day) 13 | .with_context(|| format!("No solution for day {} in year {}", cmd.day, cmd.year))?; 14 | 15 | println!( 16 | "[*] Running: {} ({})", 17 | solution.name, 18 | cmd.part.to_string().to_uppercase() 19 | ); 20 | 21 | let path = match &cmd.input { 22 | Some(path) => Cow::Borrowed(path), 23 | None => { 24 | let path = PathBuf::from(format!("data/{}/{:02}.txt", cmd.year, cmd.day)); 25 | if !path.exists() { 26 | bail!("Default input file does not exist. Use --input to specify a path."); 27 | } 28 | Cow::Owned(path) 29 | } 30 | }; 31 | 32 | let input = fs::read_to_string(&*path)?.trim().replace('\r', ""); 33 | 34 | let start = Instant::now(); 35 | let out = match cmd.part { 36 | Part::A => (solution.part_a)(&input), 37 | Part::B => (solution.part_b)(&input), 38 | }; 39 | 40 | if cmd.raw { 41 | println!("{out}"); 42 | } else { 43 | println!("[+] OUT: {} ({:.2?})", out, start.elapsed()); 44 | } 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /src/commands/run_all.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::PathBuf, time::Instant}; 2 | 3 | use anyhow::Result; 4 | use common::Part; 5 | 6 | use crate::{args::RunAllArgs, get_year}; 7 | 8 | pub fn run(cmd: &RunAllArgs) -> Result<()> { 9 | let solutions = get_year(cmd.year); 10 | 11 | println!("[*] Running all {} solutions\n", cmd.year); 12 | 13 | for solution in solutions { 14 | let path = PathBuf::from(format!("data/{}/{:02}.txt", cmd.year, solution.day)); 15 | let input = fs::read_to_string(&*path)?.trim().replace('\r', ""); 16 | 17 | println!("[*] Day {}", solution.day); 18 | 19 | for part in [Part::A, Part::B] { 20 | let start = Instant::now(); 21 | let out = match part { 22 | Part::A => (solution.part_a)(&input), 23 | Part::B => (solution.part_b)(&input), 24 | }; 25 | 26 | println!(" | Part {part}: {} ({:.2?})", out, start.elapsed()); 27 | } 28 | } 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use args::{Args, Commands}; 5 | use common::Solution; 6 | mod args; 7 | mod commands; 8 | 9 | fn main() -> Result<()> { 10 | let args = Args::parse(); 11 | 12 | match &args.command { 13 | Commands::Run(cmd) => commands::run::run(cmd)?, 14 | Commands::RunAll(cmd) => commands::run_all::run(cmd)?, 15 | Commands::List(cmd) => commands::list::list(cmd)?, 16 | } 17 | 18 | Ok(()) 19 | } 20 | 21 | fn get_year(year: u16) -> &'static [Solution] { 22 | match year { 23 | 2021 => aoc_2021::SOLUTIONS, 24 | 2022 => aoc_2022::SOLUTIONS, 25 | 2023 => aoc_2023::SOLUTIONS, 26 | 2024 => aoc_2024::SOLUTIONS, 27 | _ => &[], 28 | } 29 | } 30 | --------------------------------------------------------------------------------