├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── fetch.sh ├── leaderboard.png └── src ├── bin ├── 01.rs ├── 02.rs ├── 03.rs ├── 04.rs ├── 05.rs ├── 06.rs ├── 07.rs ├── 08.rs ├── 09.rs ├── 10.rs ├── 11.rs ├── 12.rs ├── 13.rs ├── 14.rs ├── 15.rs ├── 16.rs ├── 17.rs ├── 18.rs ├── 19.rs ├── 20.rs ├── 21.rs ├── 22.rs ├── 23.rs ├── 24.rs └── 25.rs ├── lib.rs ├── main.rs └── template.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /inputs/ 2 | /target/ 3 | Cargo.lock 4 | **/*.rs.bk 5 | *.swp 6 | /.vscode 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aoc" 3 | version = "0.1.0" 4 | authors = ["Axel Lindeberg"] 5 | edition = "2021" 6 | default-run = "aoc" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [profile.dev] 12 | overflow-checks = false 13 | 14 | [dependencies] 15 | itertools = "0.12" 16 | hashbrown = "0.14" 17 | z3 = "0.12" 18 | 19 | # lib proc-macro dependencies 20 | syn = { version = "2.0", features = ["full"] } 21 | quote = "1.0" 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DAYS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | TODAY := $(shell TZ=America/New_York date +%y%m%d) 3 | 4 | # if we're in the AOC month, set default goal to today's problem 5 | .DEFAULT_GOAL := $(or $(filter $(TODAY:2312%=%),$(DAYS)),help) 6 | .PHONY: $(DAYS) all help 7 | 8 | inputs/%.in: 9 | ./fetch.sh $* 10 | 11 | src/bin/%.rs: 12 | DAY=$* envsubst < src/template.rs > $@ 13 | 14 | $(DAYS): %: src/bin/%.rs inputs/%.in 15 | cargo run --quiet --release --bin $* 16 | 17 | all: $(patsubst src/bin/%.rs,inputs/%.in,$(wildcard src/bin/*.rs)) 18 | cargo run --quiet --release 19 | 20 | help: 21 | @echo 'usage: make [TARGET..]' 22 | @echo 'Automatically downloads input, sets up files, and runs solutions.' 23 | @echo 24 | @echo 'TARGET:' 25 | @echo ' {01..25} run a specific day, e.g 01' 26 | @echo ' all run all days' 27 | @echo ' help show this help text' 28 | @echo 29 | @echo "During the AoC month 'make' will run the current day's solution" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdventOfCode2023 :christmas_tree: 2 | Solutions to AoC 2023 in Rust :crab: 3 | 4 | ## Usage 5 | ```sh 6 | # to be able to download inputs (see fetch.sh) 7 | export AOC_SESSION=[value from session cookie] 8 | 9 | make # run todays (if in december 2023) 10 | make $DAY # run a specific day, e.g 01 11 | make all # run all days 12 | 13 | # directly with cargo, if you prefer 14 | cargo run --release --bin $DAY # run a specific day, e.g 01 15 | cargo run --release # run all days 16 | ``` 17 | 18 | ## All years 19 | - [2024](https://github.com/AxlLind/AdventOfCode2024/) in Rust :crab: 20 | - [2023](https://github.com/AxlLind/AdventOfCode2023/) in Rust :crab: 21 | - [2022](https://github.com/AxlLind/AdventOfCode2022/) in Rust :crab: 22 | - [2021](https://github.com/AxlLind/AdventOfCode2021/) in Rust :crab: 23 | - [2020](https://github.com/AxlLind/AdventOfCode2020/) in Rust :crab: 24 | - [2019](https://github.com/AxlLind/AdventOfCode2019/) in Rust :crab: 25 | - [2018](https://github.com/AxlLind/AdventOfCode2018/) in Python :snake: 26 | - [2017](https://github.com/AxlLind/AdventOfCode2017/) in Haskell λ 27 | - [2016](https://github.com/AxlLind/AdventOfCode2016/) in OCaml :camel: 28 | - [2015](https://github.com/AxlLind/AdventOfCode2015/) in Clojure λ 29 | 30 | ## Leaderboard 31 | Getting up at 5:50 every day is tough. Life got in the way a few times. Best placement this year was 111. Went really well this year, especially towards the end. 32 | 33 | ![leaderboard](./leaderboard.png) 34 | -------------------------------------------------------------------------------- /fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | SCRIPT_DIR=$(realpath "$(dirname "$0")") 4 | 5 | if [[ $# != 1 ]]; then 6 | echo "Please provide a day number." 7 | echo "usage: $0 DAY" 8 | exit 1 9 | fi 10 | 11 | if [[ ! "$1" =~ ^(0[1-9]|1[0-9]|2[0-5])$ ]]; then 12 | echo "Not a valid day: $1" 13 | exit 1 14 | fi 15 | 16 | if [[ -z "${AOC_SESSION-""}" ]]; then 17 | echo "\$AOC_SESSION not set" 18 | exit 1 19 | fi 20 | 21 | TMPFILE=$(mktemp) 22 | trap 'rm -f "$TMPFILE"' EXIT 23 | 24 | curl "https://adventofcode.com/2023/day/${1#0}/input" \ 25 | -s --fail --cookie "session=$AOC_SESSION" \ 26 | -A "Bash script at $(git remote -v | awk 'NR==1{print $2}')" \ 27 | | tee "$TMPFILE" 28 | 29 | mkdir -p "$SCRIPT_DIR/inputs" 30 | mv "$TMPFILE" "$SCRIPT_DIR/inputs/$1.in" 31 | -------------------------------------------------------------------------------- /leaderboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxlLind/AdventOfCode2023/7502ebced52fa59cf17016e571f2116f7c7430e9/leaderboard.png -------------------------------------------------------------------------------- /src/bin/01.rs: -------------------------------------------------------------------------------- 1 | const STR_DIGITS: &[&[u8]] = &[b"one", b"two", b"three", b"four", b"five", b"six", b"seven", b"eight", b"nine"]; 2 | 3 | fn digit_sum(w: &[u8], part_two: bool) -> usize { 4 | let mut digits = (0..w.len()).filter_map(|i| match w[i] { 5 | b'0'..=b'9' => Some((w[i] - b'0') as usize), 6 | _ if part_two => STR_DIGITS.iter() 7 | .enumerate() 8 | .find_map(|(di, d)| w[i..].starts_with(d).then_some(di + 1)), 9 | _ => None 10 | }); 11 | let a = digits.next().unwrap(); 12 | let b = digits.last().unwrap_or(a); 13 | a * 10 + b 14 | } 15 | 16 | #[aoc::main(01)] 17 | fn main(input: &str) -> (usize, usize) { 18 | let (mut p1, mut p2) = (0,0); 19 | for l in input.split('\n') { 20 | p1 += digit_sum(l.as_bytes(), false); 21 | p2 += digit_sum(l.as_bytes(), true); 22 | } 23 | (p1, p2) 24 | } 25 | -------------------------------------------------------------------------------- /src/bin/02.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(02)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let games = input.split('\n').map(|l| { 4 | let (id, game) = l.trim_start_matches("Game ").split_once(':').unwrap(); 5 | let colors = game.split([';', ',']).map(|w| { 6 | let (n, color) = w.trim().split_once(' ').unwrap(); 7 | let c = "rgb".bytes().position(|c| c == color.as_bytes()[0]).unwrap(); 8 | (n.parse().unwrap(), c) 9 | }).collect::>(); 10 | (id.parse().unwrap(), colors) 11 | }).collect::>(); 12 | 13 | let p1 = games.iter() 14 | .filter(|(_, actions)| actions.iter().all(|&(n,c)| n <= [12, 13, 14][c])) 15 | .map(|(id,_)| id) 16 | .sum(); 17 | 18 | let p2 = games.iter().map(|(_,actions)| { 19 | let mut a = [0; 3]; 20 | for &(n, c) in actions { 21 | a[c] = a[c].max(n); 22 | } 23 | a[0] * a[1] * a[2] 24 | }).sum(); 25 | 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /src/bin/03.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn find_symbol(lines: &[&[u8]], r: usize, c: usize) -> Option<(usize, usize, char)> { 4 | for (dr, dc) in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] { 5 | let rr = (r as i32 + dr) as usize; 6 | let cc = (c as i32 + dc) as usize; 7 | let Some(&s) = lines.get(rr).and_then(|l| l.get(cc)) else { continue }; 8 | if s != b'.' && !s.is_ascii_digit() { 9 | return Some((cc, rr, s as char)); 10 | } 11 | } 12 | None 13 | } 14 | 15 | #[aoc::main(03)] 16 | fn main(input: &str) -> (usize, usize) { 17 | let lines = input.split('\n').map(str::as_bytes).collect::>(); 18 | let mut symbols = HashMap::<_,Vec<_>>::new(); 19 | for (r, l) in lines.iter().enumerate() { 20 | let mut c = 0; 21 | while c < l.len() { 22 | let (start, mut symbol) = (c, None); 23 | while c < l.len() && l[c].is_ascii_digit() { 24 | if symbol.is_none() { 25 | symbol = find_symbol(&lines, r, c); 26 | } 27 | c += 1; 28 | } 29 | if let Some(symbol) = symbol { 30 | let num = l[start..c].iter().fold(0, |n, c| n * 10 + (c - b'0') as usize); 31 | symbols.entry(symbol).or_default().push(num); 32 | } 33 | c += 1; 34 | } 35 | } 36 | let p2 = symbols.iter() 37 | .filter(|(&(_,_,s),v)| s == '*' && v.len() == 2) 38 | .map(|(_,v)| v[0] * v[1]) 39 | .sum(); 40 | (symbols.values().flatten().sum(), p2) 41 | } 42 | -------------------------------------------------------------------------------- /src/bin/04.rs: -------------------------------------------------------------------------------- 1 | #[aoc::main(04)] 2 | fn main(input: &str) -> (usize, usize) { 3 | let mut p1 = 0; 4 | let mut copies = vec![1; input.split('\n').count()]; 5 | for (i, l) in input.split('\n').enumerate() { 6 | let (_, rest) = l.split_once(": ").unwrap(); 7 | let (wanted, got) = rest.split_once(" | ").unwrap(); 8 | let wanted = wanted.split_whitespace() 9 | .map(|w| w.parse::().unwrap()) 10 | .collect::>(); 11 | let won = got.split_whitespace() 12 | .map(|w| w.parse::().unwrap()) 13 | .filter(|c| wanted.contains(c)) 14 | .count(); 15 | p1 += if won != 0 {1 << (won - 1)} else {0}; 16 | for j in 0..won { 17 | copies[i+j+1] += copies[i]; 18 | } 19 | } 20 | (p1, copies.iter().sum()) 21 | } 22 | -------------------------------------------------------------------------------- /src/bin/05.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn part1(seeds: Vec, layers: &[Vec<(usize, usize, usize)>]) -> usize { 4 | let locations = layers.iter().fold(seeds, |seeds, mappings| 5 | seeds.into_iter().map(|seed| 6 | mappings.iter() 7 | .find(|&&(_, src, range)| (src..src+range).contains(&seed)) 8 | .map(|(dst, src, _)| dst + seed - src) 9 | .unwrap_or(seed) 10 | ).collect() 11 | ); 12 | *locations.iter().min().unwrap() 13 | } 14 | 15 | fn part2(seeds: Vec, layers: &[Vec<(usize, usize, usize)>]) -> usize { 16 | let seeds = seeds.into_iter() 17 | .tuples() 18 | .map(|(a, len)| (a, a + len)) 19 | .collect::>(); 20 | let locations = layers.iter().fold(seeds, |seeds, mappings| 21 | seeds.iter().flat_map(|&(start, end)| { 22 | let mut mapped = Vec::new(); 23 | let mut unmapped = vec![(start, end)]; 24 | for &(dst, src, len) in mappings { 25 | let mut m = Vec::new(); 26 | for (start, end) in unmapped { 27 | let a = (start, end.min(src)); 28 | let b = (start.max(src), (src+len).min(end)); 29 | let c = ((src+len).max(start), end); 30 | if a.0 < a.1 { m.push(a); } 31 | if b.0 < b.1 { mapped.push((b.0-src+dst, b.1-src+dst)); } 32 | if c.0 < c.1 { m.push(c); } 33 | } 34 | unmapped = m; 35 | } 36 | mapped.extend(unmapped); 37 | mapped 38 | }).collect() 39 | ); 40 | locations.iter().map(|r| r.0).min().unwrap() 41 | } 42 | 43 | #[aoc::main(05)] 44 | fn main(input: &str) -> (usize, usize) { 45 | let (seeds, rest) = input.split_once("\n\n").unwrap(); 46 | let seeds = seeds.split_whitespace() 47 | .skip(1) 48 | .map(|s| s.parse().unwrap()) 49 | .collect::>(); 50 | let layers = rest.split("\n\n").map(|s| 51 | s.split('\n').skip(1).map(|l| 52 | l.split_whitespace() 53 | .map(|s| s.parse().unwrap()) 54 | .collect_tuple() 55 | .unwrap() 56 | ).collect::>() 57 | ).collect::>(); 58 | (part1(seeds.clone(), &layers), part2(seeds, &layers)) 59 | } 60 | -------------------------------------------------------------------------------- /src/bin/06.rs: -------------------------------------------------------------------------------- 1 | // d = (t - w) * w => w = (t +- sqrt(t^2 - 4d)) / 2 2 | fn f(t: usize, d: usize) -> usize { 3 | let diff = ((t*t - 4*d) as f64).sqrt(); 4 | let l = (t as f64 - diff) / 2.0; 5 | let h = (t as f64 + diff) / 2.0; 6 | (h.floor() - l.ceil()) as usize + 1 7 | } 8 | 9 | #[aoc::main(06)] 10 | fn main(input: &str) -> (usize, usize) { 11 | let (l1, l2) = input.split_once('\n').unwrap(); 12 | let times = l1.split_whitespace().skip(1).map(|w| w.parse().unwrap()).collect::>(); 13 | let dists = l2.split_whitespace().skip(1).map(|w| w.parse().unwrap()).collect::>(); 14 | let time2 = l1.split_whitespace().skip(1).collect::().parse().unwrap(); 15 | let dist2 = l2.split_whitespace().skip(1).collect::().parse().unwrap(); 16 | let p1 = times.iter().zip(dists).map(|(&t, d)| f(t, d)).product(); 17 | (p1, f(time2, dist2)) 18 | } 19 | -------------------------------------------------------------------------------- /src/bin/07.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn card_idx(c: char, p2: bool) -> usize { 4 | match c { 5 | 'A' => 14, 6 | 'K' => 13, 7 | 'Q' => 12, 8 | 'J' => if p2 {0} else {11}, 9 | 'T' => 10, 10 | '9' => 9, 11 | '8' => 8, 12 | '7' => 7, 13 | '6' => 6, 14 | '5' => 5, 15 | '4' => 4, 16 | '3' => 3, 17 | '2' => 2, 18 | _ => unreachable!() 19 | } 20 | } 21 | 22 | fn hand_strength(cards: &str, p2: bool) -> (usize, usize) { 23 | let card_count = cards.chars().counts(); 24 | let counts = card_count.iter() 25 | .filter(|&(&k,_)| k != 'J' || !p2) 26 | .map(|(_,&v)| v) 27 | .collect::>(); 28 | let jokers = if p2 {*card_count.get(&'J').unwrap_or(&0)} else {0}; 29 | let hand_type = match (*counts.iter().max().unwrap_or(&0), jokers) { 30 | (a,b) if a + b == 5 => 6, 31 | (a,b) if a + b == 4 => 5, 32 | (3,0) => if counts.contains(&2) {4} else {3}, 33 | (2,_) => { 34 | let pairs = counts.iter().filter(|&&v| v == 2).count(); 35 | match (pairs, jokers) { 36 | (2,1) => 4, 37 | (1,1) => 3, 38 | (2,0) => 2, 39 | _ => 1, 40 | } 41 | }, 42 | (1,2) => 3, 43 | (1,1) => 1, 44 | _ => 0, 45 | }; 46 | let idx = cards.chars().fold(0, |acc, c| (acc << 4) + card_idx(c, p2)); 47 | (hand_type, idx) 48 | } 49 | 50 | #[aoc::main(07)] 51 | fn main(input: &str) -> (usize, usize) { 52 | let mut cards = input.split('\n').map(|l| { 53 | let (cards, bid) = l.split_once(' ').unwrap(); 54 | let p1key = hand_strength(cards, false); 55 | let p2key = hand_strength(cards, true); 56 | (bid.parse().unwrap(), p1key, p2key) 57 | }).collect::>(); 58 | cards.sort_unstable_by_key(|&(_,k,_)| k); 59 | let p1 = cards.iter().enumerate().map(|(i, (bid,_,_))| (i + 1) * bid).sum(); 60 | cards.sort_unstable_by_key(|&(_,_,k)| k); 61 | let p2 = cards.iter().enumerate().map(|(i, (bid,_,_))| (i + 1) * bid).sum(); 62 | (p1, p2) 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/08.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | 3 | fn gcd(a: usize, b: usize) -> usize { 4 | match ((a, b), (a & 1, b & 1)) { 5 | _ if a == b => a, 6 | ((_, 0), _) => a, 7 | ((0, _), _) => b, 8 | (_, (0, 1) | (1, 0)) => gcd(a >> 1, b), 9 | (_, (0, 0)) => gcd(a >> 1, b >> 1) << 1, 10 | (_, (1, 1)) => { 11 | let (a, b) = (a.min(b), a.max(b)); 12 | gcd((b - a) >> 1, a) 13 | } 14 | _ => unreachable!(), 15 | } 16 | } 17 | 18 | fn steps(path: &[u8], graph: &HashMap<&[u8],(&[u8],&[u8])>, start: &[u8], goal: &[u8]) -> usize { 19 | let (mut n, mut i) = (start, 1); 20 | for &d in path.iter().cycle() { 21 | n = if d == b'L' {graph[n].0} else {graph[n].1}; 22 | if n.ends_with(goal) { 23 | break; 24 | } 25 | i += 1; 26 | } 27 | i 28 | } 29 | 30 | #[aoc::main(08)] 31 | fn main(input: &str) -> (usize, usize) { 32 | let (path, rest) = input.split_once("\n\n").unwrap(); 33 | let graph = rest.split('\n').map(|l| { 34 | let l = l.as_bytes(); 35 | (&l[0..3], (&l[7..10], &l[12..15])) 36 | }).collect::>(); 37 | let p2 = graph.keys() 38 | .filter(|k| k.ends_with(b"A")) 39 | .map(|node| steps(path.as_bytes(), &graph, node, b"Z")) 40 | .fold(1, |ans, x| (x*ans) / gcd(x,ans)); 41 | (steps(path.as_bytes(), &graph, b"AAA", b"ZZZ"), p2) 42 | } 43 | -------------------------------------------------------------------------------- /src/bin/09.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | #[aoc::main(09)] 4 | fn main(input: &str) -> (isize, isize) { 5 | let (mut p1, mut p2) = (0,0); 6 | for l in input.split('\n') { 7 | let xs = l.split_whitespace() 8 | .map(|w| w.parse::().unwrap()) 9 | .collect::>(); 10 | let mut v = vec![xs]; 11 | while v[v.len()-1].iter().any(|&x| x != 0) { 12 | let xs = v[v.len()-1].iter() 13 | .tuple_windows() 14 | .map(|(a,b)| b - a) 15 | .collect(); 16 | v.push(xs); 17 | } 18 | let mut b = 0; 19 | for xs in v.iter().rev() { 20 | p1 += xs[xs.len()-1]; 21 | b = xs[0] - b; 22 | } 23 | p2 += b; 24 | } 25 | (p1, p2) 26 | } 27 | -------------------------------------------------------------------------------- /src/bin/10.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | 3 | fn find_loop(graph: &[Vec<[bool; 4]>], start: (usize, usize)) -> Option> { 4 | let (mut r, mut c) = start; 5 | let mut d = graph[r][c].iter().position(|&d| d).unwrap(); 6 | let mut seen = HashSet::new(); 7 | loop { 8 | if !seen.insert((r,c)) { 9 | break Some(seen); 10 | } 11 | let came_from = match d { 12 | 0 => {r -= 1; 2}, 13 | 1 => {c += 1; 3}, 14 | 2 => {r += 1; 0}, 15 | 3 => {c -= 1; 1}, 16 | _ => unreachable!(), 17 | }; 18 | if !graph[r][c][came_from] { 19 | break None; 20 | } 21 | d = (0..4).find(|&i| i != came_from && graph[r][c][i]).unwrap(); 22 | } 23 | } 24 | 25 | fn connections(tile: u8) -> [bool; 4] { 26 | match tile { 27 | // [ up, right, down, left] 28 | b'|' => [ true, false, true, false], 29 | b'-' => [false, true, false, true], 30 | b'L' => [ true, true, false, false], 31 | b'J' => [ true, false, false, true], 32 | b'7' => [false, false, true, true], 33 | b'F' => [false, true, true, false], 34 | _ => [false, false, false, false], 35 | } 36 | } 37 | 38 | #[aoc::main(10)] 39 | fn main(input: &str) -> (usize, usize) { 40 | let mut start = (0,0); 41 | let mut graph = input.split('\n').enumerate().map(|(r, line)| 42 | line.bytes().enumerate().map(|(c, tile)| { 43 | if tile == b'S' { 44 | start = (r,c); 45 | } 46 | connections(tile) 47 | }).collect::>() 48 | ).collect::>(); 49 | let pipe_loop = "J|-L7F".bytes().find_map(|start_tile| { 50 | graph[start.0][start.1] = connections(start_tile); 51 | find_loop(&graph, start) 52 | }).unwrap(); 53 | let mut p2 = 0; 54 | for r in 0..graph.len() { 55 | let mut inside = false; 56 | for c in 0..graph[0].len() { 57 | if !pipe_loop.contains(&(r,c)) { 58 | p2 += inside as usize; 59 | } else if graph[r][c][0] { 60 | inside = !inside; 61 | } 62 | } 63 | } 64 | (pipe_loop.len() / 2, p2) 65 | } 66 | -------------------------------------------------------------------------------- /src/bin/11.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | fn solve(universe: &Vec<&[u8]>, mut galaxies: Vec<(usize, usize)>, size: usize) -> usize { 4 | let (rows, cols) = (universe.len(), universe[0].len()); 5 | let empty_rows = (0..rows).filter(|&r| universe[r].iter().all(|&c| c == b'.')); 6 | let empty_cols = (0..cols).filter(|&c| (0..rows).all(|r| universe[r][c] == b'.')); 7 | for r in empty_rows.rev() { 8 | for g in &mut galaxies { 9 | if g.0 > r { g.0 += size - 1 } 10 | } 11 | } 12 | for c in empty_cols.rev() { 13 | for g in &mut galaxies { 14 | if g.1 > c { g.1 += size - 1 } 15 | } 16 | } 17 | galaxies.iter() 18 | .tuple_combinations() 19 | .map(|(&(r1,c1), &(r2,c2))| r1.abs_diff(r2) + c1.abs_diff(c2)) 20 | .sum() 21 | } 22 | 23 | #[aoc::main(11)] 24 | fn main(input: &str) -> (usize, usize) { 25 | let universe = input.split('\n').map(str::as_bytes).collect::>(); 26 | let galaxies = (0..universe.len()) 27 | .cartesian_product(0..universe[0].len()) 28 | .filter(|&(r,c)| universe[r][c] == b'#') 29 | .collect::>(); 30 | let p1 = solve(&universe, galaxies.clone(), 2); 31 | let p2 = solve(&universe, galaxies, 1000000); 32 | (p1, p2) 33 | } 34 | -------------------------------------------------------------------------------- /src/bin/12.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn possible_ways(cache: &mut HashMap<(usize, usize, usize), usize>, s: &[u8], within: Option, remaining: &[usize]) -> usize { 5 | if s.is_empty() { 6 | return match (within, remaining.len()) { 7 | (None, 0) => 1, 8 | (Some(x), 1) if x == remaining[0] => 1, 9 | _ => 0 10 | }; 11 | } 12 | if within.is_some() && remaining.is_empty() { 13 | return 0; 14 | } 15 | 16 | let key = (s.len(), within.unwrap_or(0), remaining.len()); 17 | if let Some(&x) = cache.get(&key) { 18 | return x; 19 | } 20 | 21 | let ways = match (s[0], within) { 22 | (b'.', Some(x)) if x != remaining[0] => 0, 23 | (b'.', Some(_)) => possible_ways(cache, &s[1..], None, &remaining[1..]), 24 | (b'.', None) => possible_ways(cache, &s[1..], None, remaining), 25 | (b'#', Some(_)) => possible_ways(cache, &s[1..], within.map(|x| x+1), remaining), 26 | (b'#', None) => possible_ways(cache, &s[1..], Some(1), remaining), 27 | (b'?', Some(x)) => { 28 | let mut ans = possible_ways(cache, &s[1..], within.map(|x| x+1), remaining); 29 | if x == remaining[0] { 30 | ans += possible_ways(cache, &s[1..], None, &remaining[1..]) 31 | } 32 | ans 33 | } 34 | (b'?', None) => 35 | possible_ways(cache, &s[1..], Some(1), remaining) + 36 | possible_ways(cache, &s[1..], None, remaining), 37 | _ => unreachable!(), 38 | }; 39 | cache.insert(key, ways); 40 | ways 41 | } 42 | 43 | #[aoc::main(12)] 44 | fn main(input: &str) -> (usize, usize) { 45 | let mut cache = HashMap::new(); 46 | let (mut p1, mut p2) = (0,0); 47 | for l in input.split('\n') { 48 | let (vents, rest) = l.split_once(' ').unwrap(); 49 | let nums = rest.split(',').map(|w| w.parse::().unwrap()).collect::>(); 50 | cache.clear(); 51 | p1 += possible_ways(&mut cache, vents.as_bytes(), None, &nums); 52 | 53 | let new_vents = (0..5).map(|_| vents).join("?"); 54 | let new_nums = (0..5).flat_map(|_| &nums).copied().collect::>(); 55 | cache.clear(); 56 | p2 += possible_ways(&mut cache, new_vents.as_bytes(), None, &new_nums); 57 | } 58 | (p1,p2) 59 | } 60 | -------------------------------------------------------------------------------- /src/bin/13.rs: -------------------------------------------------------------------------------- 1 | fn find_col(grid: &[&[u8]], limit: usize) -> Option { 2 | (0..grid[0].len()-1).find(|&c| { 3 | let incorrect = (0..=c.min(grid[0].len() - c - 2)).map(|dc| { 4 | let a = c - dc; 5 | let b = c + 1 + dc; 6 | (0..grid.len()).filter(|&r| grid[r][a] != grid[r][b]).count() 7 | }).sum::(); 8 | incorrect == limit 9 | }) 10 | } 11 | 12 | fn find_row(grid: &[&[u8]], limit: usize) -> Option { 13 | (0..grid.len()-1).find(|&r| { 14 | let incorrect = (0..=r.min(grid.len() - r - 2)).map(|dr| { 15 | let a = r - dr; 16 | let b = r + 1 + dr; 17 | (0..grid[0].len()).filter(|&c| grid[a][c] != grid[b][c]).count() 18 | }).sum::(); 19 | incorrect == limit 20 | }) 21 | } 22 | 23 | fn solve(grids: &[Vec<&[u8]>], limit: usize) -> usize { 24 | grids.iter().map(|grid| 25 | find_row(grid, limit).map(|r| (r + 1) * 100) 26 | .or_else(|| find_col(grid, limit).map(|c| c + 1)) 27 | .unwrap() 28 | ).sum() 29 | } 30 | 31 | #[aoc::main(13)] 32 | fn main(input: &str) -> (usize, usize) { 33 | let grids = input.split("\n\n").map(|s| 34 | s.split('\n').map(|l| l.as_bytes()).collect::>() 35 | ).collect::>(); 36 | (solve(&grids, 0), solve(&grids, 1)) 37 | } 38 | -------------------------------------------------------------------------------- /src/bin/14.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn roll_north(map: &mut Vec>) { 5 | for r in 0..map.len() - 1 { 6 | for c in 0..map[0].len() { 7 | if map[r+1][c] != b'O' { 8 | continue; 9 | } 10 | for rr in (0..=r).rev() { 11 | if map[rr][c] != b'.' { 12 | break; 13 | } 14 | map[rr][c] = b'O'; 15 | map[rr+1][c] = b'.'; 16 | } 17 | } 18 | } 19 | } 20 | 21 | fn rotate(map: &mut Vec>, tmpmap: &mut Vec>) { 22 | for (r,c) in (0..map.len()).cartesian_product(0..map[0].len()) { 23 | tmpmap[c][map.len() - 1 - r] = map[r][c]; 24 | } 25 | std::mem::swap(map, tmpmap); 26 | } 27 | 28 | fn total_load(map: &Vec>) -> usize { 29 | (0..map.len()) 30 | .map(|r| (map.len() - r) * map[r].iter().filter(|&&t| t == b'O').count()) 31 | .sum() 32 | } 33 | 34 | #[aoc::main(14)] 35 | fn main(input: &str) -> (usize, usize) { 36 | let mut map = input.split('\n').map(|l| l.as_bytes().to_vec()).collect::>(); 37 | let p1 = { 38 | let mut map = map.clone(); 39 | roll_north(&mut map); 40 | total_load(&map) 41 | }; 42 | 43 | let mut tmpmap = vec![vec![0; map.len()]; map[0].len()]; 44 | let mut seen = HashMap::new(); 45 | for i in 1..1000000000 { 46 | for _ in 0..4 { 47 | roll_north(&mut map); 48 | rotate(&mut map, &mut tmpmap); 49 | } 50 | if let Some(seen_at) = seen.insert(map.clone(), i) { 51 | if (1000000000 - i) % (i - seen_at) == 0 { 52 | break; 53 | } 54 | } 55 | } 56 | (p1, total_load(&map)) 57 | } 58 | -------------------------------------------------------------------------------- /src/bin/15.rs: -------------------------------------------------------------------------------- 1 | fn hash(s: &str) -> usize { 2 | s.bytes().fold(0, |a, c| (a + c) * 17) as usize 3 | } 4 | 5 | #[aoc::main(15)] 6 | fn main(input: &str) -> (usize, usize) { 7 | let mut map = vec![Vec::new(); 256]; 8 | let mut p1 = 0; 9 | for w in input.split(',') { 10 | p1 += hash(w); 11 | let label_end = w.bytes().position(|c| c == b'-' || c == b'=').unwrap(); 12 | let label = &w[..label_end]; 13 | let bucket = &mut map[hash(label)]; 14 | match (w.as_bytes()[label_end], bucket.iter().position(|&(l,_)| l == label)) { 15 | (b'=', Some(i)) => bucket[i] = (label, w[label_end+1..].parse::().unwrap()), 16 | (b'=', None) => bucket.push((label, w[label_end+1..].parse::().unwrap())), 17 | (b'-', Some(i)) => { bucket.remove(i); }, 18 | (b'-', None) => {}, 19 | _ => unreachable!(), 20 | } 21 | } 22 | let p2 = (0..map.len()) 23 | .flat_map(|b| (0..map[b].len()).map(move |i| (b, i))) 24 | .map(|(b, i)| (b + 1) * (i + 1) * map[b][i].1) 25 | .sum(); 26 | (p1, p2) 27 | } 28 | -------------------------------------------------------------------------------- /src/bin/16.rs: -------------------------------------------------------------------------------- 1 | fn step(r: usize, c: usize, d: usize) -> (usize, usize, usize) { 2 | let (dr, dc) = [(-1,0),(0,1),(1,0),(0,-1)][d]; 3 | ((r as isize + dr) as _, (c as isize + dc) as _, d) 4 | } 5 | 6 | fn energized_tiles(grid: &[&[u8]], start: (usize,usize,usize)) -> usize { 7 | let mut seen = vec![vec![[false; 4]; grid[0].len()]; grid.len()]; 8 | let (mut beams, mut new_beams) = (vec![start], vec![]); 9 | while !beams.is_empty() { 10 | new_beams.clear(); 11 | for &(r,c,d) in &beams { 12 | if r >= grid.len() || c >= grid[0].len() || seen[r][c][d] { 13 | continue; 14 | } 15 | seen[r][c][d] = true; 16 | match (grid[r][c], d) { 17 | (b'|', 1|3) => new_beams.extend([step(r,c,0), step(r,c,2)]), 18 | (b'-', 0|2) => new_beams.extend([step(r,c,1), step(r,c,3)]), 19 | (b'/', _) => new_beams.push(step(r,c,[1,0,3,2][d])), 20 | (b'\\', _) => new_beams.push(step(r,c,[3,2,1,0][d])), 21 | _ => new_beams.push(step(r,c,d)), 22 | } 23 | } 24 | (beams, new_beams) = (new_beams, beams); 25 | } 26 | seen.iter().flatten().filter(|x| x.iter().any(|&b| b)).count() 27 | } 28 | 29 | #[aoc::main(16)] 30 | fn main(input: &str) -> (usize, usize) { 31 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 32 | let p2 = (0..grid.len()).flat_map(|r| [(r,0,1), (r,grid[0].len()-1,3)]) 33 | .chain((0..grid[0].len()).flat_map(|c| [(0,c,2), (grid.len()-1,c,0)])) 34 | .map(|start| energized_tiles(&grid, start)) 35 | .max() 36 | .unwrap(); 37 | (energized_tiles(&grid, (0,0,1)), p2) 38 | } 39 | -------------------------------------------------------------------------------- /src/bin/17.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | use hashbrown::HashMap; 3 | 4 | fn dijkstra(grid: &[&[u8]], minstep: isize, maxstep: isize) -> i64 { 5 | let mut dists = HashMap::new(); 6 | let mut q = BinaryHeap::from_iter([(0, (0,0,(0,0)))]); 7 | while let Some((cost, (r, c, d))) = q.pop() { 8 | if (r,c) == (grid.len() - 1, grid[0].len() - 1) { 9 | return -cost; 10 | } 11 | if dists.get(&(r, c, d)).is_some_and(|&c| -cost > c) { 12 | continue; 13 | } 14 | for (dr, dc) in [(-1,0), (1,0), (0,-1), (0,1)] { 15 | if d == (dr, dc) || d == (-dr, -dc) { 16 | continue; 17 | } 18 | let mut next_cost = -cost; 19 | for dist in 1..=maxstep { 20 | let rr = (r as isize + dr * dist) as usize; 21 | let cc = (c as isize + dc * dist) as usize; 22 | if rr >= grid.len() || cc >= grid[0].len() { 23 | break; 24 | } 25 | next_cost += (grid[rr][cc] - b'0') as i64; 26 | if dist < minstep { 27 | continue; 28 | } 29 | let key = (rr, cc, (dr, dc)); 30 | if next_cost < *dists.get(&key).unwrap_or(&i64::MAX) { 31 | dists.insert(key, next_cost); 32 | q.push((-next_cost, key)); 33 | } 34 | } 35 | } 36 | } 37 | unreachable!() 38 | } 39 | 40 | #[aoc::main(17)] 41 | fn main(input: &str) -> (i64, i64) { 42 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 43 | (dijkstra(&grid, 1, 3), dijkstra(&grid, 4, 10)) 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/18.rs: -------------------------------------------------------------------------------- 1 | fn calc_area(instructions: impl Iterator) -> isize { 2 | let (mut r, mut c, mut a) = (0,0,0); 3 | for (d, n) in instructions { 4 | let (rr, cc) = (r,c); 5 | match d { 6 | b'U' => r -= n, 7 | b'R' => c += n, 8 | b'D' => r += n, 9 | b'L' => c -= n, 10 | _ => unreachable!(), 11 | } 12 | a += (c + cc) * (r - rr) + n; 13 | } 14 | a / 2 + 1 15 | } 16 | 17 | #[aoc::main(18)] 18 | fn main(input: &str) -> (isize, isize) { 19 | let p1 = input.split('\n').map(|l| { 20 | let (n, _) = l[2..].split_once(' ').unwrap(); 21 | (l.as_bytes()[0], n.parse().unwrap()) 22 | }); 23 | let p2 = input.split('\n').map(|l| { 24 | let (_, color) = l.split_once('#').unwrap(); 25 | let d = b"RDLU"[(color.as_bytes()[color.len()-2] - b'0') as usize]; 26 | (d, isize::from_str_radix(&color[0..color.len()-2], 16).unwrap()) 27 | }); 28 | (calc_area(p1), calc_area(p2)) 29 | } 30 | -------------------------------------------------------------------------------- /src/bin/19.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | type WorkFlows<'a> = HashMap<&'a str, (Vec<(char, bool, usize, &'a str)>, &'a str)>; 5 | 6 | fn filter_p1(workflows: &WorkFlows<'_>, vals: [usize; 4]) -> bool { 7 | let mut curr = "in"; 8 | while curr != "A" && curr != "R" { 9 | let workflow = &workflows[curr]; 10 | curr = workflow.0.iter() 11 | .find(|&&(p, lt, n, _)| { 12 | let i = "xmas".chars().position(|c| c == p).unwrap(); 13 | if lt {vals[i] < n} else {vals[i] > n} 14 | }) 15 | .map(|&(_, _, _, label)| label) 16 | .unwrap_or(workflow.1); 17 | } 18 | curr == "A" 19 | } 20 | 21 | fn count_accepted(workflows: &WorkFlows<'_>, curr: &str, mut ranges: [Vec; 4]) -> usize { 22 | if curr == "A" { 23 | return ranges.iter().map(|v| v.len()).product(); 24 | } 25 | if curr == "R" { 26 | return 0; 27 | } 28 | let mut ans = 0; 29 | let workflow = &workflows[curr]; 30 | for &(p, lt, n, label) in &workflow.0 { 31 | let i = "xmas".chars().position(|c| c == p).unwrap(); 32 | let mut newranges = ranges.clone(); 33 | (newranges[i], ranges[i]) = ranges[i].iter().partition(|&&val| if lt {val < n} else {val > n}); 34 | ans += count_accepted(workflows, label, newranges); 35 | } 36 | ans += count_accepted(workflows, workflow.1, ranges); 37 | ans 38 | } 39 | 40 | #[aoc::main(19)] 41 | fn main(input: &str) -> (usize, usize) { 42 | let (workflows, parts) = input.split_once("\n\n").unwrap(); 43 | let workflows = workflows.split('\n').map(|l| { 44 | let (name, rest) = l.split_once('{').unwrap(); 45 | let (rest, label) = rest[0..rest.len()-1].split_at(rest.rfind(',').unwrap()); 46 | let rules = rest.split(',').map(|rule| { 47 | let (rest, label) = rule.split_once(':').unwrap(); 48 | let lt = rest.contains('<'); 49 | let (name, n) = rest.split_once(if lt {'<'} else {'>'}).unwrap(); 50 | (name.as_bytes()[0] as char, lt, n.parse::().unwrap(), label) 51 | }).collect::>(); 52 | (name, (rules, &label[1..])) 53 | }).collect::>(); 54 | 55 | let p1 = parts.split('\n') 56 | .map(|l| 57 | l.split(|c: char| !c.is_ascii_digit()) 58 | .filter(|l| !l.is_empty()) 59 | .map(|w| w.parse::().unwrap()) 60 | .collect_tuple() 61 | .unwrap() 62 | ) 63 | .filter(|&(x,m,a,s)| filter_p1(&workflows, [x, m, a, s])) 64 | .map(|(x,m,a,s)| x + m + a + s) 65 | .sum(); 66 | let p2 = count_accepted(&workflows, "in", std::array::from_fn(|_| (1..=4000).collect::>())); 67 | (p1, p2) 68 | } 69 | -------------------------------------------------------------------------------- /src/bin/20.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use hashbrown::HashMap; 3 | 4 | enum Node<'a> { 5 | FlipFlop(bool), 6 | Conjunction(HashMap<&'a str, bool>), 7 | Broadcaster, 8 | } 9 | 10 | #[aoc::main(20)] 11 | fn main(input: &str) -> (usize, usize) { 12 | let mut g = HashMap::new(); 13 | let mut state = HashMap::new(); 14 | for l in input.split('\n') { 15 | let (src, connections) = l.split_once(" -> ").unwrap(); 16 | let (node, state_type) = match src.as_bytes()[0] { 17 | b'%' => (&src[1..], Node::FlipFlop(false)), 18 | b'&' => (&src[1..], Node::Conjunction(HashMap::new())), 19 | b'b' => (src, Node::Broadcaster), 20 | _ => unreachable!(), 21 | }; 22 | g.insert(node, connections.split(", ").collect::>()); 23 | state.insert(node, state_type); 24 | } 25 | 26 | let mut rx_conjunction = ""; 27 | for (&node, connections) in &g { 28 | for &n in connections { 29 | match state.get_mut(n) { 30 | Some(Node::Conjunction(m)) => { m.insert(node, false); }, 31 | Some(_) => {}, 32 | None => rx_conjunction = node, 33 | } 34 | } 35 | } 36 | let mut cycles = { 37 | let Node::Conjunction(m) = &state[rx_conjunction] else { panic!() }; 38 | m.keys().map(|&node| (node, None)).collect::>() 39 | }; 40 | 41 | let mut p1 = [0,0]; 42 | let mut q = VecDeque::new(); 43 | 'outer: for t in 1.. { 44 | q.push_back(("broadcaster", "button", false)); 45 | while let Some((node, prev, high)) = q.pop_front() { 46 | if t <= 1000 { 47 | p1[high as usize] += 1; 48 | } 49 | if high && node == rx_conjunction { 50 | let v = cycles.get_mut(prev).unwrap(); 51 | if v.is_none() { 52 | *v = Some(t); 53 | if cycles.values().all(|o| o.is_some()) { 54 | break 'outer; 55 | } 56 | } 57 | } 58 | let pulse = match state.get_mut(node) { 59 | Some(Node::FlipFlop(_)) if high => continue, 60 | Some(Node::FlipFlop(on)) => { 61 | *on = !*on; 62 | *on 63 | }, 64 | Some(Node::Conjunction(m)) => { 65 | m.insert(prev, high); 66 | m.values().any(|&b| !b) 67 | } 68 | Some(Node::Broadcaster) => false, 69 | None => continue, 70 | }; 71 | q.extend(g[node].iter().map(|&n| (n, node, pulse))); 72 | } 73 | } 74 | (p1[0] * p1[1], cycles.values().map(|o| o.unwrap()).product()) 75 | } 76 | -------------------------------------------------------------------------------- /src/bin/21.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashSet; 2 | use itertools::Itertools; 3 | 4 | fn bfs(grid: &[&[u8]], start: (isize, isize), steps: usize) -> usize { 5 | let mut pos = HashSet::from_iter([start]); 6 | let mut new_pos = HashSet::new(); 7 | for _ in 0..steps { 8 | new_pos.clear(); 9 | for &(r,c) in &pos { 10 | for (dr, dc) in [(-1,0),(1,0),(0,-1),(0,1)] { 11 | let (rr,cc) = (r + dr, c + dc); 12 | let tile = grid[rr as usize % grid.len()][cc as usize % grid.len()]; 13 | if tile != b'#' { 14 | new_pos.insert((rr,cc)); 15 | } 16 | } 17 | } 18 | (pos, new_pos) = (new_pos, pos); 19 | } 20 | pos.len() 21 | } 22 | 23 | fn find_polynomial(grid: &[&[u8]], start: (isize, isize), n: usize) -> usize { 24 | let n1 = bfs(grid, start, n % grid.len()); 25 | let n2 = bfs(grid, start, n % grid.len() + grid.len()); 26 | let n3 = bfs(grid, start, n % grid.len() + grid.len()*2); 27 | let n = n / grid.len(); 28 | let [a, b, c] = [n1, n2-n1, n3-n2]; 29 | a + b*n + (n * (n-1)/2) * (c-b) 30 | } 31 | 32 | #[aoc::main(21)] 33 | fn main(input: &str) -> (usize, usize) { 34 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 35 | let start = (0..grid.len()) 36 | .cartesian_product(0..grid[0].len()) 37 | .find(|&(r,c)| grid[r][c] == b'S') 38 | .map(|(r,c)| (r as isize, c as isize)) 39 | .unwrap(); 40 | let p1 = bfs(&grid, start, 64); 41 | let p2 = find_polynomial(&grid, start, 26501365); 42 | (p1, p2) 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/22.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::{HashMap, HashSet}; 2 | use itertools::Itertools; 3 | 4 | fn disintegrate_all(adjacent: &[(HashSet, HashSet)], falling: &mut HashSet, i: usize) { 5 | falling.insert(i); 6 | for &above in &adjacent[i].0 { 7 | if adjacent[above].1.iter().all(|x| falling.contains(x)) { 8 | disintegrate_all(adjacent, falling, above); 9 | } 10 | } 11 | } 12 | 13 | #[aoc::main(22)] 14 | fn main(input: &str) -> (usize, usize) { 15 | let mut bricks = input.split('\n').map(|l| 16 | l.split(|c: char| !c.is_ascii_digit()) 17 | .map(|w| w.parse::().unwrap()) 18 | .collect_tuple() 19 | .unwrap() 20 | ).collect::>(); 21 | bricks.sort_by_key(|&(_,_,z1,_,_,_)| z1); 22 | 23 | let mut adjacent = vec![(HashSet::new(), HashSet::new()); bricks.len()]; 24 | let mut space = HashMap::new(); 25 | for i in 0..bricks.len() { 26 | let (x1, y1, mut z1, x2, y2, mut z2) = bricks[i]; 27 | while z1 > 1 && (x1..=x2).cartesian_product(y1..=y2).all(|(x,y)| !space.contains_key(&(x,y,z1-1))) { 28 | z2 -= 1; 29 | z1 -= 1; 30 | } 31 | for (x,y) in (x1..=x2).cartesian_product(y1..=y2) { 32 | for z in z1..=z2 { 33 | space.insert((x,y,z), i); 34 | } 35 | if let Some(&j) = space.get(&(x,y,z1-1)) { 36 | adjacent[j].0.insert(i); 37 | adjacent[i].1.insert(j); 38 | } 39 | } 40 | bricks[i] = (x1, y1, z1, x2, y2, z2); 41 | } 42 | 43 | let mut falling = HashSet::new(); 44 | let (mut p1, mut p2) = (0, 0); 45 | for b in 0..bricks.len() { 46 | falling.clear(); 47 | disintegrate_all(&adjacent, &mut falling, b); 48 | p1 += (falling.len() == 1) as usize; 49 | p2 += falling.len() - 1; 50 | } 51 | (p1, p2) 52 | } 53 | -------------------------------------------------------------------------------- /src/bin/23.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::HashMap; 2 | use itertools::Itertools; 3 | 4 | fn dfs(graph: &[Vec<(usize,usize)>], seen: &mut [bool], goal: usize, curr: usize) -> Option { 5 | if curr == goal { 6 | return Some(0); 7 | } 8 | let mut max_dist = None; 9 | for &(next, d) in &graph[curr] { 10 | if !seen[next] { 11 | seen[next] = true; 12 | if let Some(dist) = dfs(graph, seen, goal, next) { 13 | max_dist = Some(max_dist.unwrap_or(0).max(d+dist)) 14 | } 15 | seen[next] = false; 16 | } 17 | } 18 | max_dist 19 | } 20 | 21 | fn solve(grid: &[&[u8]], part2: bool) -> usize { 22 | let mut graph = HashMap::<_,Vec<_>>::new(); 23 | for (r,c) in (0..grid.len()).cartesian_product(0..grid[0].len()) { 24 | const NEIGHBORS: &[(isize,isize)] = &[(-1,0),(0,1),(1,0),(0,-1)]; 25 | let neighbors = match grid[r][c] { 26 | b'#' => continue, 27 | _ if part2 => NEIGHBORS, 28 | b'.' => NEIGHBORS, 29 | b'^' => &NEIGHBORS[0..][..1], 30 | b'>' => &NEIGHBORS[1..][..1], 31 | b'v' => &NEIGHBORS[2..][..1], 32 | b'<' => &NEIGHBORS[3..][..1], 33 | _ => unreachable!(), 34 | }; 35 | let e = graph.entry((r,c)).or_default(); 36 | for (dr, dc) in neighbors { 37 | let rr = (r as isize + dr) as usize; 38 | let cc = (c as isize + dc) as usize; 39 | if grid.get(rr).and_then(|row| row.get(cc)).is_some_and(|&t| t != b'#') { 40 | e.push((rr,cc,1)); 41 | } 42 | } 43 | } 44 | 45 | // edge contraction (i.e contract maze corridors) 46 | let corridors = graph.iter() 47 | .filter(|(_,n)| n.len() == 2) 48 | .map(|(&node,_)| node) 49 | .collect::>(); 50 | for (r,c) in corridors { 51 | let neighbors = graph.remove(&(r,c)).unwrap(); 52 | let (r1,c1,d1) = neighbors[0]; 53 | let (r2,c2,d2) = neighbors[1]; 54 | let n1 = graph.get_mut(&(r1,c1)).unwrap(); 55 | if let Some(i) = n1.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) { 56 | n1[i] = (r2,c2,d1+d2); 57 | } 58 | let n2 = graph.get_mut(&(r2,c2)).unwrap(); 59 | if let Some(i) = n2.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) { 60 | n2[i] = (r1,c1,d1+d2); 61 | } 62 | } 63 | 64 | // convert: (r,c) hashmap -> index vector 65 | let indexes = graph.keys().enumerate().map(|(i,pos)| (pos,i)).collect::>(); 66 | let mut idx_graph = vec![Vec::new(); graph.len()]; 67 | for (pos, neighbors) in &graph { 68 | idx_graph[indexes[pos]] = neighbors.iter().map(|&(r,c,d)| (indexes[&(r,c)],d)).collect(); 69 | } 70 | 71 | let goal = indexes[&(grid.len()-1, grid[0].len()-2)]; 72 | dfs(&idx_graph, &mut vec![false; idx_graph.len()], goal, indexes[&(0,1)]).unwrap() 73 | } 74 | 75 | #[aoc::main(23)] 76 | fn main(input: &str) -> (usize, usize) { 77 | let grid = input.split('\n').map(str::as_bytes).collect::>(); 78 | (solve(&grid, false), solve(&grid, true)) 79 | } 80 | -------------------------------------------------------------------------------- /src/bin/24.rs: -------------------------------------------------------------------------------- 1 | use z3::ast::{Ast, Int, Real}; 2 | use itertools::Itertools; 3 | 4 | fn part1(hailstones: &[(f64,f64,f64,f64,f64,f64)]) -> usize { 5 | hailstones.iter() 6 | .tuple_combinations() 7 | .filter(|(&(x1,y1,_,dx1,dy1,_), &(x2,y2,_,dx2,dy2,_))| { 8 | let m1 = dy1 / dx1; 9 | let m2 = dy2 / dx2; 10 | if (m2 - m1).abs() <= f64::EPSILON { 11 | return false; 12 | } 13 | let x = (m1 * x1 - m2*x2 + y2 - y1) / (m1 - m2); 14 | let y = (m1*m2*(x2-x1) + m2*y1 - m1*y2) / (m2 - m1); 15 | if dx1.signum() != (x-x1).signum() || dx2.signum() != (x-x2).signum() { 16 | return false; 17 | } 18 | [x,y].iter().all(|v| (200000000000000.0..=400000000000000.0).contains(v)) 19 | }) 20 | .count() 21 | } 22 | 23 | fn part2(hailstones: &[(f64,f64,f64,f64,f64,f64)]) -> usize { 24 | let ctx = z3::Context::new(&z3::Config::new()); 25 | let s = z3::Solver::new(&ctx); 26 | let [fx,fy,fz,fdx,fdy,fdz] = ["fx","fy","fz","fdx","fdy","fdz"].map(|v| Real::new_const(&ctx, v)); 27 | let zero = Int::from_i64(&ctx, 0).to_real(); 28 | for (i, &(x,y,z,dx,dy,dz)) in hailstones[..3].iter().enumerate() { 29 | let [x,y,z,dx,dy,dz] = [x,y,z,dx,dy,dz].map(|v| Int::from_i64(&ctx, v as _).to_real()); 30 | let t = Real::new_const(&ctx, format!("t{i}")); 31 | s.assert(&t.ge(&zero)); 32 | s.assert(&((&x + &dx * &t)._eq(&(&fx + &fdx * &t)))); 33 | s.assert(&((&y + &dy * &t)._eq(&(&fy + &fdy * &t)))); 34 | s.assert(&((&z + &dz * &t)._eq(&(&fz + &fdz * &t)))); 35 | } 36 | assert_eq!(s.check(), z3::SatResult::Sat); 37 | let res = s.get_model().unwrap().eval(&(&fx + &fy + &fz), true).unwrap(); 38 | res.to_string().strip_suffix(".0").unwrap().parse().unwrap() 39 | } 40 | 41 | #[aoc::main(24)] 42 | fn main(input: &str) -> (usize, usize) { 43 | let hailstones = input.split('\n').map(|l| 44 | l.split(['@', ',']) 45 | .map(|w| w.trim().parse::().unwrap()) 46 | .collect_tuple() 47 | .unwrap() 48 | ).collect::>(); 49 | (part1(&hailstones), part2(&hailstones)) 50 | } 51 | -------------------------------------------------------------------------------- /src/bin/25.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use hashbrown::{HashMap, HashSet}; 3 | 4 | fn mincut_edmond_karp(g: &HashMap<&str, HashSet<&str>>, s: &str, t: &str) -> Option { 5 | let mut flow = HashMap::new(); 6 | let mut f = 0; 7 | while f <= 3 { 8 | let mut pred = HashMap::new(); 9 | let mut queue = VecDeque::from_iter([s]); 10 | let mut seen_vertices = 0; 11 | while let Some(cur) = queue.pop_front() { 12 | if pred.contains_key(t) { 13 | break; 14 | } 15 | for &next in &g[cur] { 16 | if next != s && !pred.contains_key(next) && *flow.get(&(cur, next)).unwrap_or(&0) < 1 { 17 | pred.insert(next, cur); 18 | queue.push_back(next); 19 | } 20 | } 21 | seen_vertices += 1; 22 | } 23 | if !pred.contains_key(t) { 24 | if seen_vertices == g.len() { 25 | return None; 26 | } 27 | return Some(seen_vertices * (g.len() - seen_vertices)); 28 | } 29 | 30 | let mut df = i64::MAX; 31 | let mut cur = t; 32 | while let Some(&prev) = pred.get(cur) { 33 | df = df.min(1 - *flow.get(&(prev, cur)).unwrap_or(&0)); 34 | cur = prev; 35 | } 36 | let mut cur = t; 37 | while let Some(&prev) = pred.get(cur) { 38 | *flow.entry((prev, cur)).or_default() += df; 39 | *flow.entry((cur, prev)).or_default() -= df; 40 | cur = prev; 41 | } 42 | f += df; 43 | } 44 | None 45 | } 46 | 47 | #[aoc::main(25)] 48 | fn main(input: &str) -> (usize, char) { 49 | let mut graph = HashMap::<_, HashSet<_>>::new(); 50 | for l in input.split('\n') { 51 | let (a, rest) = l.split_once(": ").unwrap(); 52 | for b in rest.split(' ') { 53 | graph.entry(a).or_default().insert(b); 54 | graph.entry(b).or_default().insert(a); 55 | } 56 | } 57 | let &start = graph.keys().next().unwrap(); 58 | (graph.keys().find_map(|k| mincut_edmond_karp(&graph, start, k)).unwrap(), '🎄') 59 | } 60 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, Ident, LitInt, ItemFn}; 3 | 4 | #[proc_macro_attribute] 5 | pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { 6 | let day = parse_macro_input!(args as LitInt); 7 | let input_path = format!("../../inputs/{}.in", day.token()); 8 | 9 | let mut aoc_solution = parse_macro_input!(input as ItemFn); 10 | aoc_solution.sig.ident = Ident::new("aoc_solution", aoc_solution.sig.ident.span()); 11 | 12 | let tokens = quote::quote! { 13 | const INPUT: &str = include_str!(#input_path); 14 | #aoc_solution 15 | fn main() { 16 | let now = ::std::time::Instant::now(); 17 | let (p1, p2) = aoc_solution(INPUT.trim_end()); 18 | let elapsed = now.elapsed(); 19 | println!("Part one: {}", p1); 20 | println!("Part two: {}", p2); 21 | if elapsed.as_millis() > 0 { 22 | println!("Time: {}ms", elapsed.as_millis()); 23 | } else { 24 | println!("Time: {}μs", elapsed.as_micros()); 25 | } 26 | } 27 | }; 28 | TokenStream::from(tokens) 29 | } 30 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command, error::Error}; 2 | use itertools::Itertools; 3 | 4 | fn extract_microseconds(output: &str) -> Result> { 5 | let out = output.lines().last().unwrap(); 6 | let time = if out.ends_with("ms") { 7 | out["Time: ".len()..out.len()-2].parse::()? * 1000 8 | } else { 9 | out["Time: ".len()..out.len()-3].parse::()? 10 | }; 11 | Ok(time) 12 | } 13 | 14 | fn main() -> Result<(), Box> { 15 | let days = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/"))? 16 | .filter_map(|p| p.ok()?.path().file_stem()?.to_str().map(str::to_string)) 17 | .filter(|day| day.chars().all(|c| c.is_ascii_digit())) 18 | .sorted() 19 | .collect::>(); 20 | let mut total_time = 0; 21 | for day in &days { 22 | let cmd = Command::new("cargo").args(["run", "--release", "--bin", day]).output()?; 23 | if !cmd.status.success() { 24 | println!("{}", String::from_utf8(cmd.stderr)?); 25 | return Err(format!("Failed to compile day {day}!").into()); 26 | } 27 | let output = String::from_utf8(cmd.stdout)?; 28 | println!("Day {}:\n{}", day, output); 29 | total_time += extract_microseconds(&output)?; 30 | } 31 | println!("Total time: {}ms", total_time / 1000); 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/template.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[aoc::main($DAY)] 4 | fn main(input: &str) -> (usize, usize) { 5 | let xs = input.split('\n').map(|l| { 6 | l.split(',').collect::>() 7 | }).collect::>(); 8 | (0, 0) 9 | } 10 | --------------------------------------------------------------------------------