├── 2021 ├── Cargo.toml ├── src │ └── bin │ │ ├── day1 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day10 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day11 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day12 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day13 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day14 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day15 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day16 │ │ └── main.rs │ │ ├── day17 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day18 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day19 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day2 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day20 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day21 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day22 │ │ ├── main.rs │ │ ├── sample_a.txt │ │ └── sample_b.txt │ │ ├── day23 │ │ ├── main.rs │ │ ├── manual_solution_a.txt │ │ └── sample.txt │ │ ├── day24 │ │ ├── main.rs │ │ └── monad_analysis.csv │ │ ├── day25 │ │ ├── final_visualization.txt │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day3 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day4 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day5 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day6 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day7 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day8 │ │ ├── main.rs │ │ └── sample.txt │ │ ├── day9 │ │ ├── main.rs │ │ └── sample.txt │ │ └── template │ │ ├── main.rs │ │ └── sample.txt └── timings.csv ├── 2022 ├── Cargo.toml ├── src │ ├── bin │ │ ├── day1 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day10 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day11 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day12 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day13 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day14 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day15 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day16 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day17 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day18 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day19 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day2 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day20 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day21 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day22 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day23 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day24 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day25 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day3 │ │ │ ├── main.py │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day4 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day5 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day6 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day7 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day8 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ ├── day9 │ │ │ ├── main.rs │ │ │ └── sample.txt │ │ └── template │ │ │ ├── main.rs │ │ │ └── sample.txt │ └── lib.rs ├── timings.csv └── timings.md ├── 2023 ├── Cargo.toml ├── python │ ├── day1 │ │ └── main.py │ ├── day12 │ │ └── main.py │ ├── day13 │ │ └── main.py │ ├── day14 │ │ └── main.py │ ├── day16 │ │ └── main.py │ ├── day2 │ │ └── main.py │ ├── day21 │ │ └── main.py │ ├── day24 │ │ └── main.py │ ├── day25 │ │ └── main.py │ ├── day3 │ │ └── main.py │ ├── day4 │ │ └── main.py │ ├── day5 │ │ └── main.py │ └── template │ │ └── main.py ├── requirements.txt └── src │ ├── bin │ ├── day1 │ │ ├── main.rs │ │ ├── sample_a.txt │ │ └── sample_b.txt │ ├── day10 │ │ ├── main.rs │ │ ├── sample.txt │ │ └── sample_b.txt │ ├── day11 │ │ ├── main.rs │ │ └── sample.txt │ ├── day12 │ │ ├── main.rs │ │ └── sample.txt │ ├── day13 │ │ ├── main.rs │ │ └── sample.txt │ ├── day15 │ │ ├── main.rs │ │ └── sample.txt │ ├── day17 │ │ ├── main.rs │ │ ├── sample_a.txt │ │ └── sample_b.txt │ ├── day18 │ │ ├── main.rs │ │ └── sample.txt │ ├── day19 │ │ ├── main.rs │ │ └── sample.txt │ ├── day2 │ │ ├── main.rs │ │ └── sample.txt │ ├── day20 │ │ ├── main.rs │ │ └── sample.txt │ ├── day22 │ │ ├── main.rs │ │ └── sample.txt │ ├── day23 │ │ ├── main.rs │ │ └── sample.txt │ ├── day3 │ │ ├── main.rs │ │ └── sample.txt │ ├── day4 │ │ ├── main.rs │ │ └── sample.txt │ ├── day5 │ │ ├── main.rs │ │ └── sample.txt │ ├── day6 │ │ ├── main.rs │ │ └── sample.txt │ ├── day7 │ │ ├── main.rs │ │ └── sample.txt │ ├── day8 │ │ ├── main.rs │ │ ├── sample_a.txt │ │ └── sample_b.txt │ ├── day9 │ │ ├── main.rs │ │ └── sample.txt │ └── template │ │ ├── main.rs │ │ └── sample.txt │ └── lib.rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── rustfmt.toml ├── start-day-python.sh └── start-day-rust.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | session 3 | data.txt 4 | .python-version 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /2021/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advent-2021" 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 | arrayvec = "0.7.2" 10 | derive-new = "0.5.9" 11 | glam = "0.20.1" 12 | itertools = "0.10.1" 13 | lazy_static = "1.4.0" 14 | ndarray = "0.15.4" 15 | num = "0.4.0" 16 | regex = "1.5.4" 17 | -------------------------------------------------------------------------------- /2021/src/bin/day1/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | const DATA: &str = include_str!("data.txt"); 4 | 5 | fn main() { 6 | println!("part a: {}", a(DATA)); 7 | println!("part b: {}", b(DATA)); 8 | } 9 | 10 | fn a(data: &str) -> usize { 11 | data.lines() 12 | .map(|line| line.parse::().unwrap()) 13 | .tuple_windows() 14 | .filter(|(a, b)| b > a) 15 | .count() 16 | } 17 | 18 | fn b(data: &str) -> usize { 19 | data.lines() 20 | .map(|line| line.parse::().unwrap()) 21 | .tuple_windows() 22 | .filter(|(a, _, _, b)| b > a) 23 | .count() 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 30 | 31 | #[test] 32 | fn test_a() { 33 | assert_eq!(a(SAMPLE_DATA), 7); 34 | } 35 | 36 | #[test] 37 | fn test_b() { 38 | assert_eq!(b(SAMPLE_DATA), 5); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /2021/src/bin/day1/sample.txt: -------------------------------------------------------------------------------- 1 | 199 2 | 200 3 | 208 4 | 210 5 | 200 6 | 207 7 | 240 8 | 269 9 | 260 10 | 263 11 | -------------------------------------------------------------------------------- /2021/src/bin/day10/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use itertools::Itertools; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | lazy_static::lazy_static! { 13 | static ref PAIRS: HashMap = HashMap::from([ 14 | ('(', ')'), 15 | ('[', ']'), 16 | ('{', '}'), 17 | ('<', '>'), 18 | ]); 19 | } 20 | 21 | fn is_corrupt(stack: &mut Vec, c: char) -> bool { 22 | if PAIRS.contains_key(&c) { 23 | stack.push(c); 24 | return false; 25 | } 26 | if let Some(last) = stack.pop() { 27 | if PAIRS[&last] == c { 28 | return false; 29 | } 30 | } 31 | true 32 | } 33 | 34 | fn part_a(data: &'static str) -> usize { 35 | let char_to_score = HashMap::from([(')', 3), (']', 57), ('}', 1197), ('>', 25137)]); 36 | data.lines() 37 | .filter_map(|line| { 38 | let mut stack = Vec::new(); 39 | line.chars().find(|&c| is_corrupt(&mut stack, c)) 40 | }) 41 | .map(|illegal_char| char_to_score[&illegal_char]) 42 | .sum() 43 | } 44 | 45 | fn part_b(data: &'static str) -> usize { 46 | let char_to_score = HashMap::from([('(', 1), ('[', 2), ('{', 3), ('<', 4)]); 47 | let mut scores = data 48 | .lines() 49 | .filter_map(|line| { 50 | let mut stack = Vec::new(); 51 | for c in line.chars() { 52 | if is_corrupt(&mut stack, c) { 53 | return None; 54 | } 55 | } 56 | Some(stack) 57 | }) 58 | .map(|stack| { 59 | stack 60 | .into_iter() 61 | .rev() 62 | .fold(0, |score, char| score * 5 + char_to_score[&char]) 63 | }) 64 | .collect_vec(); 65 | scores.sort_unstable(); 66 | scores[scores.len() / 2] 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 73 | 74 | #[test] 75 | fn test_a() { 76 | assert_eq!(part_a(SAMPLE_DATA), 26397); 77 | } 78 | 79 | #[test] 80 | fn test_b() { 81 | assert_eq!(part_b(SAMPLE_DATA), 288957); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /2021/src/bin/day10/sample.txt: -------------------------------------------------------------------------------- 1 | [({(<(())[]>[[{[]{<()<>> 2 | [(()[<>])]({[<{<<[]>>( 3 | {([(<{}[<>[]}>{[]{[(<()> 4 | (((({<>}<{<{<>}{[]{[]{} 5 | [[<[([]))<([[{}[[()]]] 6 | [{[{({}]{}}([{[{{{}}([] 7 | {<[[]]>}<{[{[{[]{()[[[] 8 | [<(<(<(<{}))><([]([]() 9 | <{([([[(<>()){}]>(<<{{ 10 | <{([{{}}[<[[[<>{}]]]>[]] -------------------------------------------------------------------------------- /2021/src/bin/day11/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, iter::repeat_with}; 2 | 3 | use itertools::Itertools; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | fn parse(data: &'static str) -> HashMap<(i32, i32), u32> { 13 | data.lines() 14 | .enumerate() 15 | .flat_map(|(row, line)| { 16 | line.chars() 17 | .map(|c| c.to_digit(10).unwrap()) 18 | .enumerate() 19 | .map(move |(col, energy)| ((row as i32, col as i32), energy)) 20 | }) 21 | .collect() 22 | } 23 | 24 | fn flash_if_gt_9(grid: &mut HashMap<(i32, i32), u32>, (y, x): (i32, i32)) { 25 | match grid.get_mut(&(y, x)) { 26 | Some(energy) if *energy > 9 => *energy = 0, 27 | _ => return, 28 | }; 29 | (-1..=1) 30 | .cartesian_product(-1..=1) 31 | .map(|(dy, dx)| (y + dy, x + dx)) 32 | .for_each(|adj| { 33 | if let Some(energy) = grid.get_mut(&adj) { 34 | if *energy > 0 { 35 | *energy += 1; 36 | flash_if_gt_9(grid, adj) 37 | } 38 | } 39 | }) 40 | } 41 | 42 | fn step(grid: &mut HashMap<(i32, i32), u32>) -> usize { 43 | grid.values_mut().for_each(|e| *e += 1); 44 | for p in (0..10).cartesian_product(0..10) { 45 | flash_if_gt_9(grid, p); 46 | } 47 | grid.values().filter(|&&e| e == 0).count() 48 | } 49 | 50 | fn part_a(data: &'static str) -> usize { 51 | let mut grid = parse(data); 52 | repeat_with(|| step(&mut grid)).take(100).sum() 53 | } 54 | 55 | fn part_b(data: &'static str) -> usize { 56 | let mut grid = parse(data); 57 | repeat_with(|| step(&mut grid)) 58 | .position(|flashes| flashes == 100) 59 | .unwrap() 60 | + 1 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 67 | 68 | #[test] 69 | fn test_a() { 70 | assert_eq!(part_a(SAMPLE_DATA), 1656); 71 | } 72 | 73 | #[test] 74 | fn test_b() { 75 | assert_eq!(part_b(SAMPLE_DATA), 195); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /2021/src/bin/day11/sample.txt: -------------------------------------------------------------------------------- 1 | 5483143223 2 | 2745854711 3 | 5264556173 4 | 6141336146 5 | 6357385478 6 | 4167524645 7 | 2176841721 8 | 6882881134 9 | 4846848554 10 | 5283751526 11 | -------------------------------------------------------------------------------- /2021/src/bin/day12/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, iter}; 2 | 3 | use itertools::Itertools; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | const MAX_CAVES: usize = 11; 7 | 8 | fn main() { 9 | println!("part a: {}", part_a(DATA)); 10 | println!("part b: {}", part_b(DATA)); 11 | } 12 | 13 | struct Graph { 14 | start: usize, 15 | end: usize, 16 | is_small: [bool; MAX_CAVES], 17 | connected_to: [Vec; MAX_CAVES], 18 | } 19 | 20 | fn parse(data: &'static str) -> Graph { 21 | let mut is_small = [false; MAX_CAVES]; 22 | let mut connected_to: [Vec; MAX_CAVES] = Default::default(); 23 | let str_graph = data 24 | .lines() 25 | .flat_map(|line| { 26 | let (a, b) = line.split('-').collect_tuple().unwrap(); 27 | [(a, b), (b, a)] 28 | }) 29 | .filter(|&(a, b)| a != "end" && b != "start") 30 | .into_group_map(); 31 | let mut name_to_id = HashMap::new(); 32 | for (id, &name) in str_graph.keys().chain(iter::once(&"end")).enumerate() { 33 | is_small[id] = name.chars().next().unwrap().is_ascii_lowercase(); 34 | name_to_id.insert(name, id); 35 | } 36 | for (from, to) in str_graph { 37 | connected_to[name_to_id[from]] = to.into_iter().map(|name| name_to_id[name]).collect() 38 | } 39 | Graph { 40 | start: name_to_id["start"], 41 | end: name_to_id["end"], 42 | is_small, 43 | connected_to, 44 | } 45 | } 46 | 47 | fn count_paths( 48 | graph: &Graph, 49 | cur_position: usize, 50 | mut visited: [bool; MAX_CAVES], 51 | second_visit_allowed: bool, 52 | ) -> usize { 53 | if cur_position == graph.end { 54 | return 1; 55 | } 56 | visited[cur_position] = graph.is_small[cur_position]; 57 | graph.connected_to[cur_position] 58 | .iter() 59 | .filter(|&&next_position| second_visit_allowed || !visited[next_position]) 60 | .map(|&next_position| { 61 | count_paths( 62 | graph, 63 | next_position, 64 | visited, 65 | second_visit_allowed && !visited[next_position], 66 | ) 67 | }) 68 | .sum() 69 | } 70 | 71 | fn part_a(data: &'static str) -> usize { 72 | let graph = parse(data); 73 | count_paths(&graph, graph.start, Default::default(), false) 74 | } 75 | 76 | fn part_b(data: &'static str) -> usize { 77 | let graph = parse(data); 78 | count_paths(&graph, graph.start, Default::default(), true) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 85 | 86 | #[test] 87 | fn test_a() { 88 | assert_eq!(part_a(SAMPLE_DATA), 226); 89 | } 90 | 91 | #[test] 92 | fn test_b() { 93 | assert_eq!(part_b(SAMPLE_DATA), 3509); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /2021/src/bin/day12/sample.txt: -------------------------------------------------------------------------------- 1 | fs-end 2 | he-DX 3 | fs-he 4 | start-DX 5 | pj-DX 6 | end-zg 7 | zg-sl 8 | zg-pj 9 | pj-he 10 | RW-he 11 | fs-DX 12 | pj-RW 13 | zg-RW 14 | start-pj 15 | he-WI 16 | zg-he 17 | pj-fs 18 | start-RW 19 | -------------------------------------------------------------------------------- /2021/src/bin/day13/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | const DATA: &str = include_str!("data.txt"); 4 | 5 | fn main() { 6 | println!("part a: {}", part_a(DATA)); 7 | println!("{}", part_b(DATA)); 8 | } 9 | 10 | enum Fold { 11 | X(i64), 12 | Y(i64), 13 | } 14 | 15 | type Point = (usize, usize); 16 | 17 | fn parse(data: &'static str) -> (HashSet, Vec) { 18 | let mut lines = data.lines(); 19 | let points = lines 20 | .by_ref() 21 | .take_while(|line| !line.is_empty()) 22 | .map(|line| { 23 | let (x, y) = line.split_once(',').unwrap(); 24 | (x.parse().unwrap(), y.parse().unwrap()) 25 | }) 26 | .collect(); 27 | let folds = lines 28 | .map(|line| { 29 | let (axis, value) = line.split_once('=').unwrap(); 30 | let value = value.parse().unwrap(); 31 | match axis { 32 | "fold along x" => Fold::X(value), 33 | "fold along y" => Fold::Y(value), 34 | _ => panic!(), 35 | } 36 | }) 37 | .collect(); 38 | (points, folds) 39 | } 40 | 41 | fn part_a(data: &'static str) -> usize { 42 | let (points, folds) = parse(data); 43 | apply_folds(points, &folds[0..=0]).len() 44 | } 45 | 46 | fn apply_folds(points: HashSet, folds: &[Fold]) -> HashSet { 47 | points 48 | .into_iter() 49 | .map(|p| { 50 | folds.iter().fold(p, |(x, y), fold| match *fold { 51 | Fold::X(v) => ((v - (v - x as i64).abs()) as usize, y), 52 | Fold::Y(v) => (x, (v - (v - y as i64).abs()) as usize), 53 | }) 54 | }) 55 | .collect() 56 | } 57 | 58 | fn part_b(data: &'static str) -> String { 59 | let (mut points, folds) = parse(data); 60 | points = apply_folds(points, &folds); 61 | let (max_x, max_y) = points 62 | .iter() 63 | .fold((0, 0), |(x1, y1), &(x2, y2)| (x1.max(x2), y1.max(y2))); 64 | let mut grid = vec![vec!['.'; max_x + 1]; max_y + 1]; 65 | grid.iter_mut().for_each(|line| line.push('\n')); 66 | points.into_iter().for_each(|(x, y)| grid[y][x] = '█'); 67 | grid.into_iter().flatten().collect() 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 74 | 75 | #[test] 76 | fn test_a() { 77 | assert_eq!(part_a(SAMPLE_DATA), 17); 78 | } 79 | 80 | #[test] 81 | fn test_b() { 82 | assert_eq!( 83 | part_b(SAMPLE_DATA), 84 | "█████\n\ 85 | █...█\n\ 86 | █...█\n\ 87 | █...█\n\ 88 | █████" 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /2021/src/bin/day13/sample.txt: -------------------------------------------------------------------------------- 1 | 6,10 2 | 0,14 3 | 9,10 4 | 0,3 5 | 10,4 6 | 4,11 7 | 6,0 8 | 6,12 9 | 4,1 10 | 0,13 11 | 10,12 12 | 3,4 13 | 3,0 14 | 8,4 15 | 1,10 16 | 2,14 17 | 8,10 18 | 9,0 19 | 20 | fold along y=7 21 | fold along x=5 22 | -------------------------------------------------------------------------------- /2021/src/bin/day14/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(array_windows)] 2 | use std::{collections::HashMap, iter}; 3 | 4 | use itertools::{iterate, Itertools}; 5 | 6 | const DATA: &str = include_str!("data.txt"); 7 | 8 | fn main() { 9 | println!("part a: {}", part_a(DATA)); 10 | println!("part b: {}", part_b(DATA)); 11 | } 12 | 13 | fn solution(data: &'static str, num_steps: usize) -> usize { 14 | let lines = data.lines().map(|line| line.as_bytes()).collect_vec(); 15 | let rules: HashMap<[u8; 2], u8> = lines[2..] 16 | .iter() 17 | .map(|&line| match *line { 18 | [a, b, .., c] => ([a, b], c), 19 | _ => panic!(), 20 | }) 21 | .collect(); 22 | let pair_counts = iterate(lines[0].array_windows().copied().counts(), |prev| { 23 | prev.iter() 24 | .flat_map(|(&[a, c], &count)| { 25 | let b = rules[&[a, c]]; 26 | [([a, b], count), ([b, c], count)] 27 | }) 28 | .into_grouping_map() 29 | .sum() 30 | }) 31 | .nth(num_steps) 32 | .unwrap(); 33 | let elem_counts = pair_counts 34 | .into_iter() 35 | .map(|([c0, _], count)| (c0, count)) 36 | .chain(iter::once((*lines[0].last().unwrap(), 1))) 37 | .into_grouping_map() 38 | .sum(); 39 | let (min, max) = elem_counts.into_values().minmax().into_option().unwrap(); 40 | max - min 41 | } 42 | 43 | fn part_a(data: &'static str) -> usize { 44 | solution(data, 10) 45 | } 46 | 47 | fn part_b(data: &'static str) -> usize { 48 | solution(data, 40) 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 55 | 56 | #[test] 57 | fn test_a() { 58 | assert_eq!(part_a(SAMPLE_DATA), 1588); 59 | } 60 | 61 | #[test] 62 | fn test_b() { 63 | assert_eq!(part_b(SAMPLE_DATA), 2188189693529); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /2021/src/bin/day14/sample.txt: -------------------------------------------------------------------------------- 1 | NNCB 2 | 3 | CH -> B 4 | HH -> N 5 | CB -> H 6 | NH -> C 7 | HB -> C 8 | HC -> B 9 | HN -> C 10 | NN -> C 11 | BH -> H 12 | NC -> B 13 | NB -> B 14 | BN -> B 15 | BB -> N 16 | BC -> B 17 | CC -> N 18 | CN -> C 19 | -------------------------------------------------------------------------------- /2021/src/bin/day15/main.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Reverse, collections::BinaryHeap}; 2 | 3 | use ndarray::{Array, Array2, Axis}; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | fn parse(data: &'static str) -> Array2 { 13 | let lines: Vec<_> = data.lines().collect(); 14 | Array::from_shape_vec( 15 | [lines.len(), lines[0].len()], 16 | data.chars().filter_map(|c| c.to_digit(10)).collect(), 17 | ) 18 | .unwrap() 19 | } 20 | 21 | fn part_a(data: &'static str) -> u32 { 22 | let grid = parse(data); 23 | best_total_risk(&grid) 24 | } 25 | 26 | fn adjacent([x, y]: [usize; 2]) -> impl Iterator { 27 | [ 28 | Some([x, y + 1]), 29 | Some([x + 1, y]), 30 | x.checked_sub(1).map(|x| [x, y]), 31 | y.checked_sub(1).map(|y| [x, y]), 32 | ] 33 | .into_iter() 34 | .flatten() 35 | } 36 | 37 | fn best_total_risk(grid: &Array2) -> u32 { 38 | let mut best_known = Array::from_elem(grid.shape(), u32::MAX); 39 | let mut queue = BinaryHeap::from([(Reverse(0), [0, 0])]); 40 | while let Some((Reverse(total_risk), idx)) = queue.pop() { 41 | let best_known_risk = best_known.get_mut(idx).unwrap(); 42 | if total_risk < *best_known_risk { 43 | *best_known_risk = total_risk; 44 | for adj in adjacent(idx) { 45 | if let Some(risk) = grid.get(adj) { 46 | queue.push((Reverse(total_risk + risk), adj)); 47 | } 48 | } 49 | } 50 | } 51 | *best_known.last().unwrap() 52 | } 53 | 54 | fn cycle(risk: u32) -> u32 { 55 | (risk - 1) % 9 + 1 56 | } 57 | 58 | fn part_b(data: &'static str) -> u32 { 59 | let grid = parse(data); 60 | let mut five_grids = grid.clone(); 61 | for i in 1..=4 { 62 | five_grids 63 | .append(Axis(0), grid.mapv(|r| cycle(r + i)).view()) 64 | .unwrap() 65 | } 66 | let mut twent_five_grids = five_grids.clone(); 67 | for i in 1..=4 { 68 | twent_five_grids 69 | .append(Axis(1), five_grids.mapv(|r| cycle(r + i)).view()) 70 | .unwrap() 71 | } 72 | best_total_risk(&twent_five_grids) 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 79 | 80 | #[test] 81 | fn test_a() { 82 | assert_eq!(part_a(SAMPLE_DATA), 40); 83 | } 84 | 85 | #[test] 86 | fn test_b() { 87 | assert_eq!(part_b(SAMPLE_DATA), 315); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /2021/src/bin/day15/sample.txt: -------------------------------------------------------------------------------- 1 | 1163751742 2 | 1381373672 3 | 2136511328 4 | 3694931569 5 | 7463417111 6 | 1319128137 7 | 1359912421 8 | 3125421639 9 | 1293138521 10 | 2311944581 11 | -------------------------------------------------------------------------------- /2021/src/bin/day16/main.rs: -------------------------------------------------------------------------------- 1 | use std::vec::IntoIter; 2 | 3 | use itertools::Itertools; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | fn parse(data: &'static str) -> Vec { 13 | data.trim() 14 | .chars() 15 | .flat_map(|c| { 16 | let n = c.to_digit(16).unwrap(); 17 | (0..4).rev().map(move |i| (n & (1 << i)) != 0) 18 | }) 19 | .collect() 20 | } 21 | 22 | fn bits_to_number(bits: &[bool]) -> usize { 23 | bits.iter() 24 | .rev() 25 | .enumerate() 26 | .map(|(i, &b)| (b as usize) << i) 27 | .sum() 28 | } 29 | 30 | fn bits_iter_to_number(bits: impl Iterator) -> usize { 31 | bits_to_number(&bits.collect_vec()) 32 | } 33 | 34 | struct ParseOutcome { 35 | version_sum: usize, 36 | value: usize, 37 | } 38 | 39 | fn parse_bits(bits: &mut IntoIter) -> ParseOutcome { 40 | let mut version_sum = bits_iter_to_number(bits.take(3)); 41 | let type_id = bits_iter_to_number(bits.take(3)); 42 | 43 | if type_id == 4 { 44 | let mut literal_bin = Vec::new(); 45 | let mut keep_reading = true; 46 | while keep_reading { 47 | keep_reading = bits.next().unwrap(); 48 | literal_bin.extend(bits.take(4)) 49 | } 50 | let value = bits_to_number(&literal_bin); 51 | return ParseOutcome { version_sum, value }; 52 | } 53 | 54 | let len_type_is_subpackets = bits.next().unwrap(); 55 | let (num_subpackets, num_bits) = if len_type_is_subpackets { 56 | (bits_iter_to_number(bits.take(11)), usize::MAX) 57 | } else { 58 | (usize::MAX, bits_iter_to_number(bits.take(15))) 59 | }; 60 | 61 | let bits_left = bits.len(); 62 | let mut sub_values = Vec::new(); 63 | while (bits_left - bits.len()) < num_bits && sub_values.len() < num_subpackets { 64 | let parsed = parse_bits(bits); 65 | version_sum += parsed.version_sum; 66 | sub_values.push(parsed.value); 67 | } 68 | 69 | let value = match type_id { 70 | 0 => sub_values.into_iter().sum(), 71 | 1 => sub_values.into_iter().product(), 72 | 2 => sub_values.into_iter().min().unwrap(), 73 | 3 => sub_values.into_iter().max().unwrap(), 74 | 5 => (sub_values[0] > sub_values[1]) as usize, 75 | 6 => (sub_values[0] < sub_values[1]) as usize, 76 | 7 => (sub_values[0] == sub_values[1]) as usize, 77 | _ => panic!("{type_id} is not a valid type id"), 78 | }; 79 | 80 | ParseOutcome { version_sum, value } 81 | } 82 | 83 | fn part_a(data: &'static str) -> usize { 84 | let bits = parse(data); 85 | parse_bits(bits.into_iter().by_ref()).version_sum 86 | } 87 | 88 | fn part_b(data: &'static str) -> usize { 89 | let bits = parse(data); 90 | parse_bits(bits.into_iter().by_ref()).value 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | #[test] 97 | fn test_a() { 98 | assert_eq!(part_a("8A004A801A8002F478"), 16); 99 | assert_eq!(part_a("620080001611562C8802118E34"), 12); 100 | assert_eq!(part_a("C0015000016115A2E0802F182340"), 23); 101 | assert_eq!(part_a("A0016C880162017C3686B18A3D4780"), 31); 102 | } 103 | 104 | #[test] 105 | fn test_b() { 106 | assert_eq!(part_b("C200B40A82"), 3); 107 | assert_eq!(part_b("04005AC33890"), 54); 108 | assert_eq!(part_b("880086C3E88112"), 7); 109 | assert_eq!(part_b("CE00C43D881120"), 9); 110 | assert_eq!(part_b("D8005AC2A8F0"), 1); 111 | assert_eq!(part_b("F600BC2D8F"), 0); 112 | assert_eq!(part_b("9C005AC2F8F0"), 0); 113 | assert_eq!(part_b("9C0141080250320F1802104A08"), 1); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /2021/src/bin/day17/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::{iterate, Itertools}; 2 | use regex::Regex; 3 | 4 | const DATA: &str = include_str!("data.txt"); 5 | 6 | fn main() { 7 | println!("part a: {}", part_a(DATA)); 8 | println!("part b: {}", part_b(DATA)); 9 | } 10 | 11 | fn parse(data: &'static str) -> [isize; 4] { 12 | let pat = Regex::new(r"target area: x=(-?\d+)\.\.(-?\d+), y=(-?\d+)\.\.(-?\d+)").unwrap(); 13 | let caps = pat.captures(data).unwrap(); 14 | [&caps[1], &caps[2], &caps[3], &caps[4]].map(|s| s.parse().unwrap()) 15 | } 16 | 17 | fn part_a(data: &'static str) -> usize { 18 | let [_min_x, _max_x, min_y, _max_y] = parse(data); 19 | let vel_y = -1 - min_y; 20 | (vel_y * (1 + vel_y) / 2) as usize 21 | } 22 | 23 | fn part_b(data: &'static str) -> usize { 24 | let [min_x, max_x, min_y, max_y] = parse(data); 25 | let possible_vels_x = 1..=max_x; 26 | let possible_vels_y = min_y..-min_y; 27 | (possible_vels_x) 28 | .cartesian_product(possible_vels_y) 29 | .filter(|&(vel_x, vel_y)| { 30 | iterate((0, 0, vel_x, vel_y), |&(x, y, vel_x, vel_y)| { 31 | (x + vel_x, y + vel_y, vel_x - vel_x.signum(), vel_y - 1) 32 | }) 33 | .take_while(|&(x, y, _, _)| x <= max_x && y >= min_y) 34 | .any(|(x, y, _, _)| min_x <= x && x <= max_x && min_y <= y && y <= max_y) 35 | }) 36 | .count() 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 43 | 44 | #[test] 45 | fn test_a() { 46 | assert_eq!(part_a(SAMPLE_DATA), 45); 47 | } 48 | 49 | #[test] 50 | fn test_b() { 51 | assert_eq!(part_b(SAMPLE_DATA), 112); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /2021/src/bin/day17/sample.txt: -------------------------------------------------------------------------------- 1 | target area: x=20..30, y=-10..-5 2 | -------------------------------------------------------------------------------- /2021/src/bin/day18/sample.txt: -------------------------------------------------------------------------------- 1 | [[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]] 2 | [[[5,[2,8]],4],[5,[[9,9],0]]] 3 | [6,[[[6,2],[5,6]],[[7,6],[4,7]]]] 4 | [[[6,[0,7]],[0,9]],[4,[9,[9,0]]]] 5 | [[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]] 6 | [[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]] 7 | [[[[5,4],[7,7]],8],[[8,3],8]] 8 | [[9,3],[[9,9],[6,[4,9]]]] 9 | [[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]] 10 | [[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]] 11 | -------------------------------------------------------------------------------- /2021/src/bin/day19/sample.txt: -------------------------------------------------------------------------------- 1 | --- scanner 0 --- 2 | 404,-588,-901 3 | 528,-643,409 4 | -838,591,734 5 | 390,-675,-793 6 | -537,-823,-458 7 | -485,-357,347 8 | -345,-311,381 9 | -661,-816,-575 10 | -876,649,763 11 | -618,-824,-621 12 | 553,345,-567 13 | 474,580,667 14 | -447,-329,318 15 | -584,868,-557 16 | 544,-627,-890 17 | 564,392,-477 18 | 455,729,728 19 | -892,524,684 20 | -689,845,-530 21 | 423,-701,434 22 | 7,-33,-71 23 | 630,319,-379 24 | 443,580,662 25 | -789,900,-551 26 | 459,-707,401 27 | 28 | --- scanner 1 --- 29 | 686,422,578 30 | 605,423,415 31 | 515,917,-361 32 | -336,658,858 33 | 95,138,22 34 | -476,619,847 35 | -340,-569,-846 36 | 567,-361,727 37 | -460,603,-452 38 | 669,-402,600 39 | 729,430,532 40 | -500,-761,534 41 | -322,571,750 42 | -466,-666,-811 43 | -429,-592,574 44 | -355,545,-477 45 | 703,-491,-529 46 | -328,-685,520 47 | 413,935,-424 48 | -391,539,-444 49 | 586,-435,557 50 | -364,-763,-893 51 | 807,-499,-711 52 | 755,-354,-619 53 | 553,889,-390 54 | 55 | --- scanner 2 --- 56 | 649,640,665 57 | 682,-795,504 58 | -784,533,-524 59 | -644,584,-595 60 | -588,-843,648 61 | -30,6,44 62 | -674,560,763 63 | 500,723,-460 64 | 609,671,-379 65 | -555,-800,653 66 | -675,-892,-343 67 | 697,-426,-610 68 | 578,704,681 69 | 493,664,-388 70 | -671,-858,530 71 | -667,343,800 72 | 571,-461,-707 73 | -138,-166,112 74 | -889,563,-600 75 | 646,-828,498 76 | 640,759,510 77 | -630,509,768 78 | -681,-892,-333 79 | 673,-379,-804 80 | -742,-814,-386 81 | 577,-820,562 82 | 83 | --- scanner 3 --- 84 | -589,542,597 85 | 605,-692,669 86 | -500,565,-823 87 | -660,373,557 88 | -458,-679,-417 89 | -488,449,543 90 | -626,468,-788 91 | 338,-750,-386 92 | 528,-832,-391 93 | 562,-778,733 94 | -938,-730,414 95 | 543,643,-506 96 | -524,371,-870 97 | 407,773,750 98 | -104,29,83 99 | 378,-903,-323 100 | -778,-728,485 101 | 426,699,580 102 | -438,-605,-362 103 | -469,-447,-387 104 | 509,732,623 105 | 647,635,-688 106 | -868,-804,481 107 | 614,-800,639 108 | 595,780,-596 109 | 110 | --- scanner 4 --- 111 | 727,592,562 112 | -293,-554,779 113 | 441,611,-461 114 | -714,465,-776 115 | -743,427,-804 116 | -660,-479,-426 117 | 832,-632,460 118 | 927,-485,-438 119 | 408,393,-506 120 | 466,436,-512 121 | 110,16,151 122 | -258,-428,682 123 | -393,719,612 124 | -211,-452,876 125 | 808,-476,-593 126 | -575,615,604 127 | -485,667,467 128 | -680,325,-822 129 | -627,-443,-432 130 | 872,-547,-609 131 | 833,512,582 132 | 807,604,487 133 | 839,-516,451 134 | 891,-625,532 135 | -652,-548,-490 136 | 30,-46,-14 137 | -------------------------------------------------------------------------------- /2021/src/bin/day2/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use Command::{Down, Forward, Up}; 3 | 4 | const DATA: &str = include_str!("data.txt"); 5 | 6 | fn main() { 7 | println!("part a: {}", part_a(DATA)); 8 | println!("part b: {}", part_b(DATA)); 9 | } 10 | 11 | enum Command { 12 | Forward(i32), 13 | Down(i32), 14 | Up(i32), 15 | } 16 | 17 | impl From<&str> for Command { 18 | fn from(s: &str) -> Self { 19 | let (direction, amount) = s.split_whitespace().collect_tuple().unwrap(); 20 | let n = amount.parse().unwrap(); 21 | match direction { 22 | "forward" => Forward(n), 23 | "down" => Down(n), 24 | "up" => Up(n), 25 | _ => panic!(), 26 | } 27 | } 28 | } 29 | 30 | fn part_a(data: &str) -> i32 { 31 | let mut dist = 0; 32 | let mut depth = 0; 33 | for command in data.lines().map(Command::from) { 34 | match command { 35 | Forward(n) => dist += n, 36 | Down(n) => depth += n, 37 | Up(n) => depth -= n, 38 | } 39 | } 40 | dist * depth 41 | } 42 | 43 | fn part_b(data: &str) -> i32 { 44 | let mut dist = 0; 45 | let mut depth = 0; 46 | let mut aim = 0; 47 | for command in data.lines().map(Command::from) { 48 | match command { 49 | Forward(n) => { 50 | dist += n; 51 | depth += aim * n 52 | } 53 | Down(n) => aim += n, 54 | Up(n) => aim -= n, 55 | } 56 | } 57 | dist * depth 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 64 | 65 | #[test] 66 | fn test_a() { 67 | assert_eq!(part_a(SAMPLE_DATA), 150); 68 | } 69 | 70 | #[test] 71 | fn test_b() { 72 | assert_eq!(part_b(SAMPLE_DATA), 900); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /2021/src/bin/day2/sample.txt: -------------------------------------------------------------------------------- 1 | forward 5 2 | down 5 3 | forward 8 4 | up 3 5 | down 8 6 | forward 2 7 | -------------------------------------------------------------------------------- /2021/src/bin/day20/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::reversed_empty_ranges)] 2 | use derive_new::new; 3 | use ndarray::{s, Array, Array2, Zip}; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | #[derive(new)] 13 | struct Image { 14 | data: Array2, 15 | default: bool, 16 | } 17 | 18 | fn parse(data: &'static str) -> (Vec, Image) { 19 | let lines: Vec<_> = data.lines().collect(); 20 | let image_enhancement = lines[0].chars().map(|c| c == '#').collect(); 21 | let (rows, cols) = (lines[2..].len(), lines[2].len()); 22 | let default = false; 23 | let mut data = Array2::from_elem([rows + 4, cols + 4], default); 24 | let data_vec = lines[2..] 25 | .iter() 26 | .flat_map(|line| line.chars().map(|c| c == '#')) 27 | .collect(); 28 | data.slice_mut(s![2..-2, 2..-2]) 29 | .assign(&Array::from_shape_vec([rows, cols], data_vec).unwrap()); 30 | (image_enhancement, Image::new(data, default)) 31 | } 32 | 33 | fn enhance_image(image_enhancement: &[bool], image: &Image) -> Image { 34 | let shape = image.data.shape(); 35 | let default = image_enhancement[if image.default { 0b111111111 } else { 0 }]; 36 | let mut data = Array2::from_elem([shape[0] + 2, shape[1] + 2], default); 37 | Zip::from(image.data.windows([3, 3])).map_assign_into( 38 | data.slice_mut(s![2..-2, 2..-2]), 39 | |window| { 40 | let idx = (window[[0, 0]] as usize * 0b100000000) 41 | | (window[[0, 1]] as usize * 0b010000000) 42 | | (window[[0, 2]] as usize * 0b001000000) 43 | | (window[[1, 0]] as usize * 0b000100000) 44 | | (window[[1, 1]] as usize * 0b000010000) 45 | | (window[[1, 2]] as usize * 0b000001000) 46 | | (window[[2, 0]] as usize * 0b000000100) 47 | | (window[[2, 1]] as usize * 0b000000010) 48 | | (window[[2, 2]] as usize); 49 | image_enhancement[idx] 50 | }, 51 | ); 52 | Image::new(data, default) 53 | } 54 | 55 | fn solve(data: &'static str, iterations: usize) -> usize { 56 | let (image_enhancement, mut image) = parse(data); 57 | for _ in 0..iterations { 58 | image = enhance_image(&image_enhancement, &image) 59 | } 60 | image.data.map(|&b| b as usize).sum() 61 | } 62 | 63 | fn part_a(data: &'static str) -> usize { 64 | solve(data, 2) 65 | } 66 | 67 | fn part_b(data: &'static str) -> usize { 68 | solve(data, 50) 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 75 | 76 | #[test] 77 | fn test_a() { 78 | assert_eq!(part_a(SAMPLE_DATA), 35); 79 | } 80 | 81 | #[test] 82 | fn test_b() { 83 | assert_eq!(part_b(SAMPLE_DATA), 3351); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /2021/src/bin/day20/sample.txt: -------------------------------------------------------------------------------- 1 | ..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..# 2 | 3 | #..#. 4 | #.... 5 | ##..# 6 | ..#.. 7 | ..### 8 | -------------------------------------------------------------------------------- /2021/src/bin/day21/main.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | use num::Integer; 3 | 4 | const DATA: &str = include_str!("data.txt"); 5 | 6 | fn main() { 7 | println!("part a: {}", part_a(DATA)); 8 | println!("part b: {}", part_b(DATA)); 9 | } 10 | 11 | fn parse(data: &'static str) -> [usize; 2] { 12 | let mut positions = data 13 | .lines() 14 | .map(|line| line.chars().last().unwrap().to_digit(10).unwrap() as usize); 15 | [positions.next().unwrap(), positions.next().unwrap()] 16 | } 17 | 18 | fn cycle(val: T, quot: T) -> T { 19 | (val - T::one()) % quot + T::one() 20 | } 21 | 22 | fn part_a(data: &'static str) -> usize { 23 | let mut positions = parse(data); 24 | let mut scores = [0, 0]; 25 | for turn in 0.. { 26 | let player_idx = turn % 2; 27 | let roll_sum = (1..=3).map(|i| cycle(turn * 3 + i, 100)).sum::(); 28 | positions[player_idx] = cycle(positions[player_idx] + roll_sum, 10); 29 | scores[player_idx] += positions[player_idx]; 30 | if scores[player_idx] >= 1000 { 31 | return scores[(player_idx + 1) % 2] * (turn + 1) * 3; 32 | } 33 | } 34 | unreachable!() 35 | } 36 | 37 | #[derive(new, Clone, Copy)] 38 | struct PlayerState { 39 | position: u8, 40 | score: u8, 41 | num_universes: usize, 42 | } 43 | 44 | struct QuantumIterate { 45 | state: Vec, 46 | } 47 | 48 | impl From for QuantumIterate { 49 | fn from(pos: usize) -> Self { 50 | Self { 51 | state: vec![PlayerState::new(pos as u8, 0, 1)], 52 | } 53 | } 54 | } 55 | 56 | impl Iterator for QuantumIterate { 57 | type Item = [usize; 2]; 58 | 59 | fn next(&mut self) -> Option { 60 | if self.state.is_empty() { 61 | return None; 62 | } 63 | let (won, didnt_win) = self 64 | .state 65 | .iter() 66 | .flat_map(|&ps| { 67 | [(3, 1), (4, 3), (5, 6), (6, 7), (7, 6), (8, 3), (9, 1)].map( 68 | |(roll_sum, num_universes)| { 69 | let pos = cycle(ps.position + roll_sum, 10); 70 | PlayerState::new(pos, ps.score + pos, ps.num_universes * num_universes) 71 | }, 72 | ) 73 | }) 74 | .partition(|player_state| player_state.score >= 21); 75 | self.state = didnt_win; 76 | Some([&won, &self.state].map(|pss| pss.iter().map(|ps| ps.num_universes).sum())) 77 | } 78 | } 79 | 80 | fn part_b(data: &'static str) -> usize { 81 | let player_positions = parse(data); 82 | let player_win_lose_per_turn: [Vec<[usize; 2]>; 2] = player_positions 83 | .map(QuantumIterate::from) 84 | .map(|it| it.collect()); 85 | let [p1, p2] = player_win_lose_per_turn; 86 | let total_wins = [(&p1[1..], &p2[..]), (&p2[..], &p1[..])].map(|(winner, loser)| { 87 | winner 88 | .iter() 89 | .zip(loser.iter()) 90 | .map(|([wins, _], [_, losses])| wins * losses) 91 | .sum::() 92 | }); 93 | total_wins[0].max(total_wins[1]) 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 100 | 101 | #[test] 102 | fn test_a() { 103 | assert_eq!(part_a(SAMPLE_DATA), 739785); 104 | } 105 | 106 | #[test] 107 | fn test_b() { 108 | assert_eq!(part_b(SAMPLE_DATA), 444_356_092_776_315); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /2021/src/bin/day21/sample.txt: -------------------------------------------------------------------------------- 1 | Player 1 starting position: 4 2 | Player 2 starting position: 8 3 | -------------------------------------------------------------------------------- /2021/src/bin/day22/sample_a.txt: -------------------------------------------------------------------------------- 1 | on x=-20..26,y=-36..17,z=-47..7 2 | on x=-20..33,y=-21..23,z=-26..28 3 | on x=-22..28,y=-29..23,z=-38..16 4 | on x=-46..7,y=-6..46,z=-50..-1 5 | on x=-49..1,y=-3..46,z=-24..28 6 | on x=2..47,y=-22..22,z=-23..27 7 | on x=-27..23,y=-28..26,z=-21..29 8 | on x=-39..5,y=-6..47,z=-3..44 9 | on x=-30..21,y=-8..43,z=-13..34 10 | on x=-22..26,y=-27..20,z=-29..19 11 | off x=-48..-32,y=26..41,z=-47..-37 12 | on x=-12..35,y=6..50,z=-50..-2 13 | off x=-48..-32,y=-32..-16,z=-15..-5 14 | on x=-18..26,y=-33..15,z=-7..46 15 | off x=-40..-22,y=-38..-28,z=23..41 16 | on x=-16..35,y=-41..10,z=-47..6 17 | off x=-32..-23,y=11..30,z=-14..3 18 | on x=-49..-5,y=-3..45,z=-29..18 19 | off x=18..30,y=-20..-8,z=-3..13 20 | on x=-41..9,y=-7..43,z=-33..15 21 | on x=-54112..-39298,y=-85059..-49293,z=-27449..7877 22 | on x=967..23432,y=45373..81175,z=27513..53682 23 | -------------------------------------------------------------------------------- /2021/src/bin/day22/sample_b.txt: -------------------------------------------------------------------------------- 1 | on x=-5..47,y=-31..22,z=-19..33 2 | on x=-44..5,y=-27..21,z=-14..35 3 | on x=-49..-1,y=-11..42,z=-10..38 4 | on x=-20..34,y=-40..6,z=-44..1 5 | off x=26..39,y=40..50,z=-2..11 6 | on x=-41..5,y=-41..6,z=-36..8 7 | off x=-43..-33,y=-45..-28,z=7..25 8 | on x=-33..15,y=-32..19,z=-34..11 9 | off x=35..47,y=-46..-34,z=-11..5 10 | on x=-14..36,y=-6..44,z=-16..29 11 | on x=-57795..-6158,y=29564..72030,z=20435..90618 12 | on x=36731..105352,y=-21140..28532,z=16094..90401 13 | on x=30999..107136,y=-53464..15513,z=8553..71215 14 | on x=13528..83982,y=-99403..-27377,z=-24141..23996 15 | on x=-72682..-12347,y=18159..111354,z=7391..80950 16 | on x=-1060..80757,y=-65301..-20884,z=-103788..-16709 17 | on x=-83015..-9461,y=-72160..-8347,z=-81239..-26856 18 | on x=-52752..22273,y=-49450..9096,z=54442..119054 19 | on x=-29982..40483,y=-108474..-28371,z=-24328..38471 20 | on x=-4958..62750,y=40422..118853,z=-7672..65583 21 | on x=55694..108686,y=-43367..46958,z=-26781..48729 22 | on x=-98497..-18186,y=-63569..3412,z=1232..88485 23 | on x=-726..56291,y=-62629..13224,z=18033..85226 24 | on x=-110886..-34664,y=-81338..-8658,z=8914..63723 25 | on x=-55829..24974,y=-16897..54165,z=-121762..-28058 26 | on x=-65152..-11147,y=22489..91432,z=-58782..1780 27 | on x=-120100..-32970,y=-46592..27473,z=-11695..61039 28 | on x=-18631..37533,y=-124565..-50804,z=-35667..28308 29 | on x=-57817..18248,y=49321..117703,z=5745..55881 30 | on x=14781..98692,y=-1341..70827,z=15753..70151 31 | on x=-34419..55919,y=-19626..40991,z=39015..114138 32 | on x=-60785..11593,y=-56135..2999,z=-95368..-26915 33 | on x=-32178..58085,y=17647..101866,z=-91405..-8878 34 | on x=-53655..12091,y=50097..105568,z=-75335..-4862 35 | on x=-111166..-40997,y=-71714..2688,z=5609..50954 36 | on x=-16602..70118,y=-98693..-44401,z=5197..76897 37 | on x=16383..101554,y=4615..83635,z=-44907..18747 38 | off x=-95822..-15171,y=-19987..48940,z=10804..104439 39 | on x=-89813..-14614,y=16069..88491,z=-3297..45228 40 | on x=41075..99376,y=-20427..49978,z=-52012..13762 41 | on x=-21330..50085,y=-17944..62733,z=-112280..-30197 42 | on x=-16478..35915,y=36008..118594,z=-7885..47086 43 | off x=-98156..-27851,y=-49952..43171,z=-99005..-8456 44 | off x=2032..69770,y=-71013..4824,z=7471..94418 45 | on x=43670..120875,y=-42068..12382,z=-24787..38892 46 | off x=37514..111226,y=-45862..25743,z=-16714..54663 47 | off x=25699..97951,y=-30668..59918,z=-15349..69697 48 | off x=-44271..17935,y=-9516..60759,z=49131..112598 49 | on x=-61695..-5813,y=40978..94975,z=8655..80240 50 | off x=-101086..-9439,y=-7088..67543,z=33935..83858 51 | off x=18020..114017,y=-48931..32606,z=21474..89843 52 | off x=-77139..10506,y=-89994..-18797,z=-80..59318 53 | off x=8476..79288,y=-75520..11602,z=-96624..-24783 54 | on x=-47488..-1262,y=24338..100707,z=16292..72967 55 | off x=-84341..13987,y=2429..92914,z=-90671..-1318 56 | off x=-37810..49457,y=-71013..-7894,z=-105357..-13188 57 | off x=-27365..46395,y=31009..98017,z=15428..76570 58 | off x=-70369..-16548,y=22648..78696,z=-1892..86821 59 | on x=-53470..21291,y=-120233..-33476,z=-44150..38147 60 | off x=-93533..-4276,y=-16170..68771,z=-104985..-24507 61 | -------------------------------------------------------------------------------- /2021/src/bin/day23/manual_solution_a.txt: -------------------------------------------------------------------------------- 1 | ############# 2 | #...........# 3 | ###D#C#A#B### 4 | #B#C#D#A# 5 | ######### 6 | 7 | 6 8 | ############# 9 | #.A.........# 10 | ###D#C#.#B### 11 | #B#C#D#A# 12 | ######### 13 | 14 | 66 15 | ############# 16 | #.A.B.......# 17 | ###D#C#.#.### 18 | #B#C#D#A# 19 | ######### 20 | 21 | 69 22 | ############# 23 | #.A.B.....A.# 24 | ###D#C#.#.### 25 | #B#C#D#.# 26 | ######### 27 | 28 | 6069 29 | ############# 30 | #.A.B.....A.# 31 | ###D#C#.#.### 32 | #B#C#.#D# 33 | ######### 34 | 35 | 6569 36 | ############# 37 | #.A.B.....A.# 38 | ###D#.#.#.### 39 | #B#C#C#D# 40 | ######### 41 | 42 | 43 | 7069 44 | ############# 45 | #.A.B.....A.# 46 | ###D#.#C#.### 47 | #B#.#C#D# 48 | ######### 49 | 50 | 7099 51 | ############# 52 | #.A.......A.# 53 | ###D#.#C#.### 54 | #B#B#C#D# 55 | ######### 56 | 57 | 15099 58 | ############# 59 | #.A.......A.# 60 | ###.#.#C#D### 61 | #B#B#C#D# 62 | ######### 63 | 64 | 15149 65 | ############# 66 | #.A.......A.# 67 | ###.#B#C#D### 68 | #.#B#C#D# 69 | ######### 70 | 71 | 15152 72 | ############# 73 | #.........A.# 74 | ###.#B#C#D### 75 | #A#B#C#D# 76 | ######### 77 | 78 | 15160 79 | ############# 80 | #...........# 81 | ###A#B#C#D### 82 | #A#B#C#D# 83 | ######### 84 | -------------------------------------------------------------------------------- /2021/src/bin/day23/sample.txt: -------------------------------------------------------------------------------- 1 | ############# 2 | #...........# 3 | ###B#C#B#D### 4 | #D#C#B#A# 5 | #D#B#A#C# 6 | #A#D#C#A# 7 | ######### 8 | -------------------------------------------------------------------------------- /2021/src/bin/day24/main.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | 3 | const DATA: &str = include_str!("data.txt"); 4 | 5 | fn main() { 6 | println!("part a: {}", part_a(DATA)); 7 | println!("part b: {}", part_b(DATA)); 8 | } 9 | 10 | #[derive(Debug, Clone, Copy)] 11 | struct StepParams { 12 | do_cmp: bool, 13 | cmp_now: i8, 14 | cmp_later: i8, 15 | } 16 | 17 | #[allow(clippy::needless_range_loop)] 18 | fn parse(data: &'static str) -> [StepParams; 14] { 19 | let lines: Vec<_> = data.lines().collect(); 20 | let mut params = [None; 14]; 21 | let get_param = |line_id: usize| lines[line_id].rsplit_once(' ').unwrap().1.parse().unwrap(); 22 | for i in 0..14 { 23 | params[i] = Some(StepParams { 24 | do_cmp: get_param(i * 18 + 4) == 26, 25 | cmp_now: get_param(i * 18 + 5), 26 | cmp_later: get_param(i * 18 + 15), 27 | }) 28 | } 29 | params.map(Option::unwrap) 30 | } 31 | 32 | #[derive(Debug, new, Clone, Copy)] 33 | struct Rule { 34 | cmp_to: usize, 35 | val: i8, 36 | } 37 | 38 | fn get_rules(params: [StepParams; 14]) -> [Rule; 14] { 39 | let mut cmp_stack = Vec::new(); 40 | let mut rules = [None; 14]; 41 | for (i, step_params) in params.iter().enumerate() { 42 | if step_params.do_cmp { 43 | let Rule { cmp_to, val } = cmp_stack.pop().unwrap(); 44 | rules[i] = Some(Rule::new(cmp_to, val + step_params.cmp_now)); 45 | rules[cmp_to] = Some(Rule::new(i, -val - step_params.cmp_now)); 46 | } else { 47 | cmp_stack.push(Rule::new(i, step_params.cmp_later)) 48 | } 49 | } 50 | rules.map(Option::unwrap) 51 | } 52 | 53 | fn part_a(data: &'static str) -> String { 54 | let params = parse(data); 55 | let rules = get_rules(params); 56 | rules.map(|r| 9.min(9 + r.val).to_string()).concat() 57 | } 58 | 59 | fn part_b(data: &'static str) -> String { 60 | let params = parse(data); 61 | let rules = get_rules(params); 62 | rules.map(|r| 1.max(1 + r.val).to_string()).concat() 63 | } 64 | -------------------------------------------------------------------------------- /2021/src/bin/day24/monad_analysis.csv: -------------------------------------------------------------------------------- 1 | inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w |inp w 2 | mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 |mul x 0 3 | add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z |add x z 4 | mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 |mod x 26 5 | ! div z 1 |div z 1 |div z 1 |div z 26 |div z 1 |div z 1 |div z 1 |div z 26 |div z 1 |div z 26 |div z 26 |div z 26 |div z 26 |div z 26 6 | ! add x 10 |add x 12 |add x 15 |add x -9 |add x 15 |add x 10 |add x 14 |add x -5 |add x 14 |add x -7 |add x -12 |add x -10 |add x -1 |add x -11 7 | eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w |eql x w 8 | eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 |eql x 0 9 | mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 10 | add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 |add y 25 11 | mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x 12 | add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 |add y 1 13 | mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y |mul z y 14 | mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 |mul y 0 15 | add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w |add y w 16 | ! add y 15 |add y 8 |add y 2 |add y 6 |add y 13 |add y 4 |add y 1 |add y 9 |add y 5 |add y 13 |add y 9 |add y 6 |add y 2 |add y 2 17 | mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x |mul y x 18 | add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y |add z y 19 | 20 | in pseudo code: 21 | 22 | total = 0 23 | for (a, b, c) in parsed { 24 | w = inp(); 25 | rem = total % 26; 26 | total /= a; (a is 1 | 26) 27 | if rem + b != w { 28 | total = total * 26 + w + c 29 | } 30 | } 31 | total == 0 32 | 33 | z is kind of like a stack of integers between 1 and 25 34 | parsed = [ 35 | (1, 10, 15) // z = [15 + n[0]] 36 | (1, 12, 8) // z = [15 + n[0], 8 + n[1]] 37 | (1, 15, 2) // z = [15 + n[0], 8 + n[1], 2 + n[2]] 38 | (26, -9, 6) // z = [15 + n[0], 8 + n[1]] 39 | (1, 15, 13) // z = [15 + n[0], 8 + n[1], 13 + n[4]] 40 | (1, 10, 4) // z = [15 + n[0], 8 + n[1], 13 + n[4], 4 + n[5]] 41 | (1, 14, 1) // z = [15 + n[0], 8 + n[1], 13 + n[4], 4 + n[5], 1 + n[6]] 42 | (26, -5, 9) // z = [15 + n[0], 8 + n[1], 13 + n[4], 4 + n[5]] 43 | (1, 14, 5) // z = [15 + n[0], 8 + n[1], 13 + n[4], 4 + n[5], 5 + n[8]] 44 | (26, -7, 13) // z = [15 + n[0], 8 + n[1], 13 + n[4], 4 + n[5]] 45 | (26, -12, 9) // z = [15 + n[0], 8 + n[1], 13 + n[4]] 46 | (26, -10, 6) // z = [15 + n[0], 8 + n[1]] 47 | (26, -1, 2) // z = [15 + n[0]] 48 | (26, -11, 2) // z = [] 49 | ] 50 | 51 | for a number n to be valid: 52 | n[3] == n[2] - 7 53 | n[7] == n[6] - 4 54 | n[9] == n[8] - 2 55 | n[10] == n[5] - 8 56 | n[11] == n[4] + 3 57 | n[12] == n[1] + 7 58 | n[13] == n[0] + 4 59 | 60 | which means the largest possible number is: 61 | 52926995971999 62 | 63 | and the smallest possible number is: 64 | 11811951311485 65 | -------------------------------------------------------------------------------- /2021/src/bin/day25/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use ndarray::{Array, Array2}; 3 | use SeaCucumber::*; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | } 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 12 | enum SeaCucumber { 13 | East, 14 | South, 15 | } 16 | 17 | fn parse(data: &'static str) -> Array2> { 18 | let lines: Vec<_> = data.lines().collect(); 19 | Array::from_shape_vec( 20 | [lines.len(), lines[0].len()], 21 | data.chars() 22 | .filter_map(|c| match c { 23 | '>' => Some(Some(East)), 24 | 'v' => Some(Some(South)), 25 | '.' => Some(None), 26 | _ => None, 27 | }) 28 | .collect(), 29 | ) 30 | .unwrap() 31 | } 32 | 33 | // For debugging 34 | #[allow(dead_code)] 35 | fn print_grid(grid: &Array2>) { 36 | let s: String = grid 37 | .outer_iter() 38 | .flat_map(|row| { 39 | row.iter() 40 | .map(|c| match c { 41 | Some(East) => '>', 42 | Some(South) => 'v', 43 | None => '.', 44 | }) 45 | .chain(Some('\n')) 46 | .collect_vec() 47 | }) 48 | .collect(); 49 | println!("{s}") 50 | } 51 | 52 | fn step_direction(grid: &mut Array2>, direction: SeaCucumber) -> bool { 53 | let lanes = match direction { 54 | East => grid.rows_mut(), 55 | South => grid.columns_mut(), 56 | }; 57 | let mut moved = false; 58 | let mut indexes_to_move = Vec::with_capacity(140); 59 | lanes.into_iter().for_each(|mut lane| { 60 | indexes_to_move.clear(); 61 | indexes_to_move.extend( 62 | lane.windows(2) 63 | .into_iter() 64 | .positions(|pair| [pair[0], pair[1]] == [Some(direction), None]), 65 | ); 66 | if [*lane.last().unwrap(), lane[0]] == [Some(direction), None] { 67 | moved = true; 68 | *lane.last_mut().unwrap() = None; 69 | lane[0] = Some(direction); 70 | } 71 | if !indexes_to_move.is_empty() { 72 | moved = true 73 | } 74 | for &i in &indexes_to_move { 75 | lane[i] = None; 76 | lane[i + 1] = Some(direction); 77 | } 78 | }); 79 | moved 80 | } 81 | 82 | fn step_both_directions(grid: &mut Array2>) -> bool { 83 | let mut moved = step_direction(grid, East); 84 | moved |= step_direction(grid, South); 85 | moved 86 | } 87 | 88 | fn part_a(data: &'static str) -> usize { 89 | let mut grid = parse(data); 90 | for i in 1.. { 91 | if !step_both_directions(&mut grid) { 92 | return i; 93 | } 94 | } 95 | unreachable!() 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 102 | 103 | #[test] 104 | fn test_a() { 105 | assert_eq!(part_a(SAMPLE_DATA), 58); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /2021/src/bin/day25/sample.txt: -------------------------------------------------------------------------------- 1 | v...>>.vv> 2 | .vv>>.vv.. 3 | >>.>v>...v 4 | >>v>>.>.v. 5 | v>v.vv.v.. 6 | >.>>..v... 7 | .vv..>.>v. 8 | v.v..>>v.v 9 | ....v..v.> 10 | -------------------------------------------------------------------------------- /2021/src/bin/day3/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | const DATA: &str = include_str!("data.txt"); 4 | 5 | fn main() { 6 | println!("part a: {}", part_a(DATA)); 7 | println!("part b: {}", part_b(DATA)); 8 | } 9 | 10 | fn parse(data: &'static str) -> (Vec, usize) { 11 | ( 12 | data.lines() 13 | .map(|line| u16::from_str_radix(line, 2).unwrap()) 14 | .collect(), 15 | data.chars().take_while(|&c| c != '\n').count(), 16 | ) 17 | } 18 | 19 | fn part_a(data: &'static str) -> usize { 20 | let (data, num_bits) = parse(data); 21 | let gamma = (0..num_bits) 22 | .map(|bit_pos| 1 << bit_pos) 23 | .filter(|mask| data.iter().filter(|&num| num & mask != 0).count() > data.len() / 2) 24 | .fold(0, |gamma, mask| gamma | mask) as usize; 25 | let interesting_bits = (1 << num_bits) - 1; 26 | gamma * (!gamma & interesting_bits) 27 | } 28 | 29 | fn part_b(data: &'static str) -> usize { 30 | let (data, num_bits) = parse(data); 31 | rating(&data, num_bits, true) * rating(&data, num_bits, false) 32 | } 33 | 34 | fn rating(data: &[u16], num_bits: usize, bit_criteria: bool) -> usize { 35 | let mut remaining = data.iter().copied().collect_vec(); 36 | for bit_pos in (0..num_bits).rev() { 37 | let mask = 1 << bit_pos; 38 | let mut groups = remaining.into_iter().into_group_map_by(|num| num & mask); 39 | remaining = match bit_criteria == (groups[&mask].len() >= groups[&0].len()) { 40 | true => groups.remove(&mask).unwrap(), 41 | false => groups.remove(&0).unwrap(), 42 | }; 43 | if remaining.len() == 1 { 44 | return remaining[0] as usize; 45 | } 46 | } 47 | panic!() 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 54 | 55 | #[test] 56 | fn test_a() { 57 | assert_eq!(part_a(SAMPLE_DATA), 198); 58 | } 59 | 60 | #[test] 61 | fn test_b() { 62 | assert_eq!(part_b(SAMPLE_DATA), 230); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /2021/src/bin/day3/sample.txt: -------------------------------------------------------------------------------- 1 | 00100 2 | 11110 3 | 10110 4 | 10111 5 | 10101 6 | 01111 7 | 00111 8 | 11100 9 | 10000 10 | 11001 11 | 00010 12 | 01010 13 | -------------------------------------------------------------------------------- /2021/src/bin/day4/main.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use ndarray::Array2; 3 | 4 | const DATA: &str = include_str!("data.txt"); 5 | fn main() { 6 | let (mut boards, draws) = parse(DATA); 7 | let win_turns = all_win_turns(&mut boards, &draws); 8 | println!("part a: {}", part_a_calc(&win_turns, &boards, &draws)); 9 | println!("part b: {}", part_b_calc(&win_turns, &boards, &draws)); 10 | } 11 | 12 | type Board = Array2<(u8, bool)>; 13 | 14 | fn parse(data: &'static str) -> (Vec, Vec) { 15 | let mut split = data.trim().split("\n\n"); 16 | let draws: Vec = split 17 | .next() 18 | .unwrap() 19 | .split(',') 20 | .map(|n| n.parse().unwrap()) 21 | .collect(); 22 | let boards = split 23 | .map(|board_str| { 24 | let board_vec = board_str 25 | .split_whitespace() 26 | .map(|n| (n.parse().unwrap(), false)) 27 | .collect(); 28 | Array2::from_shape_vec([5, 5], board_vec).unwrap() 29 | }) 30 | .collect(); 31 | (boards, draws) 32 | } 33 | 34 | fn mark_board(board: &mut Board, draw: u8) -> Option<(usize, usize)> { 35 | board 36 | .indexed_iter_mut() 37 | .find(|&(_idx, &mut (num, _mark))| num == draw) 38 | .map(|(idx, (_num, mark))| { 39 | *mark = true; 40 | idx 41 | }) 42 | } 43 | 44 | fn board_win_turn(board: &mut Board, draws: &[u8]) -> usize { 45 | draws 46 | .iter() 47 | .position(|&draw| { 48 | if let Some((row, col)) = mark_board(board, draw) { 49 | return board.row(row).iter().all(|&(_num, mark)| mark) 50 | || board.column(col).iter().all(|&(_num, mark)| mark); 51 | } 52 | false 53 | }) 54 | .unwrap() 55 | } 56 | 57 | fn all_win_turns(boards: &mut [Board], draws: &[u8]) -> Vec { 58 | boards 59 | .iter_mut() 60 | .map(|b| board_win_turn(b, draws)) 61 | .collect() 62 | } 63 | 64 | fn score(board: &Board, winning_draw: u8) -> usize { 65 | board 66 | .iter() 67 | .filter(|&&(_num, mark)| !mark) 68 | .map(|&(num, _mark)| num as usize) 69 | .sum::() 70 | * winning_draw as usize 71 | } 72 | 73 | fn part_a_calc(win_turns: &[usize], boards: &[Board], draws: &[u8]) -> usize { 74 | let best_board_id = win_turns.iter().position_min().unwrap(); 75 | score(&boards[best_board_id], draws[win_turns[best_board_id]]) 76 | } 77 | 78 | fn part_b_calc(win_turns: &[usize], boards: &[Board], draws: &[u8]) -> usize { 79 | let worst_board_id = win_turns.iter().position_max().unwrap(); 80 | score(&boards[worst_board_id], draws[win_turns[worst_board_id]]) 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 87 | 88 | #[test] 89 | fn test_a() { 90 | let (mut boards, draws) = parse(SAMPLE_DATA); 91 | let win_turns = all_win_turns(&mut boards, &draws); 92 | assert_eq!(part_a_calc(&win_turns, &boards, &draws), 4512); 93 | } 94 | 95 | #[test] 96 | fn test_b() { 97 | let (mut boards, draws) = parse(SAMPLE_DATA); 98 | let win_turns = all_win_turns(&mut boards, &draws); 99 | assert_eq!(part_b_calc(&win_turns, &boards, &draws), 1924); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /2021/src/bin/day4/sample.txt: -------------------------------------------------------------------------------- 1 | 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 2 | 3 | 22 13 17 11 0 4 | 8 2 23 4 24 5 | 21 9 14 16 7 6 | 6 10 3 18 5 7 | 1 12 20 15 19 8 | 9 | 3 15 0 2 22 10 | 9 18 13 17 5 11 | 19 8 7 25 23 12 | 20 11 10 24 4 13 | 14 21 16 12 6 14 | 15 | 14 21 17 24 4 16 | 10 16 15 9 19 17 | 18 8 23 26 20 18 | 22 11 13 6 5 19 | 2 0 12 3 7 20 | -------------------------------------------------------------------------------- /2021/src/bin/day5/main.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | use itertools::Itertools; 4 | use ndarray::{s, Array2}; 5 | 6 | const DATA: &str = include_str!("data.txt"); 7 | 8 | fn main() { 9 | println!("part a: {}", part_a(DATA)); 10 | println!("part b: {}", part_b(DATA)); 11 | } 12 | 13 | fn parse(data: &'static str) -> impl Iterator { 14 | data.lines().map(|line| { 15 | line.split(" -> ") 16 | .flat_map(|point| point.split(',')) 17 | .map(|coord| coord.parse().unwrap()) 18 | .collect_tuple() 19 | .unwrap() 20 | }) 21 | } 22 | 23 | fn range(c1: usize, c2: usize) -> RangeInclusive { 24 | c1.min(c2)..=c1.max(c2) 25 | } 26 | 27 | fn solution(data: &'static str, calc_diagonal: bool) -> usize { 28 | let mut grid = Array2::zeros([1000, 1000]); 29 | for (x1, y1, x2, y2) in parse(data) { 30 | if x1 == x2 { 31 | let mut points = grid.slice_mut(s![x1, range(y1, y2)]); 32 | points += 1; 33 | } else if y1 == y2 { 34 | let mut points = grid.slice_mut(s![range(x1, x2), y1]); 35 | points += 1; 36 | } else if calc_diagonal { 37 | let step_x = if x1 < x2 { 1 } else { -1 }; 38 | let step_y = if y1 < y2 { 1 } else { -1 }; 39 | let mut slice = grid.slice_mut(s![ 40 | range(x1, x2);step_x, 41 | range(y1, y2);step_y 42 | ]); 43 | let mut points = slice.diag_mut(); 44 | points += 1; 45 | } 46 | } 47 | grid.fold(0, |acc, &cur| acc + (cur >= 2) as usize) 48 | } 49 | 50 | fn part_a(data: &'static str) -> usize { 51 | solution(data, false) 52 | } 53 | 54 | fn part_b(data: &'static str) -> usize { 55 | solution(data, true) 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::*; 61 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 62 | 63 | #[test] 64 | fn test_a() { 65 | assert_eq!(part_a(SAMPLE_DATA), 5); 66 | } 67 | 68 | #[test] 69 | fn test_b() { 70 | assert_eq!(part_b(SAMPLE_DATA), 12); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /2021/src/bin/day5/sample.txt: -------------------------------------------------------------------------------- 1 | 0,9 -> 5,9 2 | 8,0 -> 0,8 3 | 9,4 -> 3,4 4 | 2,2 -> 2,1 5 | 7,0 -> 7,4 6 | 6,4 -> 2,0 7 | 0,9 -> 2,9 8 | 3,4 -> 1,4 9 | 0,0 -> 8,8 10 | 5,5 -> 8,2 -------------------------------------------------------------------------------- /2021/src/bin/day6/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use itertools::{iterate, Itertools}; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | fn parse(data: &'static str) -> HashMap { 13 | data.trim().split(',').map(|v| v.parse().unwrap()).counts() 14 | } 15 | 16 | fn next_state(age_counts: &HashMap) -> HashMap { 17 | let mut age_counts: HashMap<_, _> = age_counts 18 | .iter() 19 | .map(|(&age, &count)| (age - 1, count)) 20 | .collect(); 21 | if let Some(births) = age_counts.remove(&-1) { 22 | *age_counts.entry(6).or_insert(0) += births; 23 | age_counts.insert(8, births); 24 | } 25 | age_counts 26 | } 27 | 28 | fn num_fish(data: &'static str, generations: usize) -> usize { 29 | iterate(parse(data), next_state) 30 | .nth(generations) 31 | .unwrap() 32 | .values() 33 | .sum() 34 | } 35 | 36 | fn part_a(data: &'static str) -> usize { 37 | num_fish(data, 80) 38 | } 39 | 40 | fn part_b(data: &'static str) -> usize { 41 | num_fish(data, 256) 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 48 | 49 | #[test] 50 | fn test_a() { 51 | assert_eq!(part_a(SAMPLE_DATA), 5934); 52 | } 53 | 54 | #[test] 55 | fn test_b() { 56 | assert_eq!(part_b(SAMPLE_DATA), 26984457539); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /2021/src/bin/day6/sample.txt: -------------------------------------------------------------------------------- 1 | 3,4,3,1,2 -------------------------------------------------------------------------------- /2021/src/bin/day7/main.rs: -------------------------------------------------------------------------------- 1 | const DATA: &str = include_str!("data.txt"); 2 | 3 | fn main() { 4 | println!("part a: {}", part_a(DATA)); 5 | println!("part b: {}", part_b(DATA)); 6 | } 7 | 8 | fn parse(data: &'static str) -> Vec { 9 | data.trim().split(',').map(|n| n.parse().unwrap()).collect() 10 | } 11 | 12 | fn part_a(data: &'static str) -> i64 { 13 | let mut positions = parse(data); 14 | positions.sort_unstable(); 15 | let align = positions[positions.len() / 2]; 16 | positions.iter().map(|pos| (pos - align).abs()).sum() 17 | } 18 | 19 | fn fuel_needed(steps: i64) -> i64 { 20 | (steps + 1) * steps / 2 21 | } 22 | 23 | fn total_fuel(positions: &[i64], align: i64) -> i64 { 24 | positions 25 | .iter() 26 | .map(|pos| fuel_needed((pos - align).abs())) 27 | .sum() 28 | } 29 | 30 | /// We can't just round the average, because the derivative is 31 | /// align - avg + sum(signum(align - pos)) / 2N 32 | /// As opposed to 33 | /// align - avg. 34 | /// Since the last term can never be more than 0.5, 35 | /// We only need to check the floor and ceiling of avg 36 | fn part_b(data: &'static str) -> i64 { 37 | let positions = parse(data); 38 | let avg = positions.iter().sum::() as f64 / positions.len() as f64; 39 | let floor = avg.floor() as i64; 40 | let ceil = avg.ceil() as i64; 41 | total_fuel(&positions, floor).min(total_fuel(&positions, ceil)) 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 48 | 49 | #[test] 50 | fn test_a() { 51 | assert_eq!(part_a(SAMPLE_DATA), 37); 52 | } 53 | 54 | #[test] 55 | fn test_b() { 56 | assert_eq!(part_b(SAMPLE_DATA), 168); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /2021/src/bin/day7/sample.txt: -------------------------------------------------------------------------------- 1 | 16,1,2,0,4,2,7,1,2,14 -------------------------------------------------------------------------------- /2021/src/bin/day8/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashMap}, 3 | iter::repeat, 4 | }; 5 | 6 | use itertools::Itertools; 7 | 8 | const DATA: &str = include_str!("data.txt"); 9 | 10 | fn main() { 11 | println!("part a: {}", part_a(DATA)); 12 | println!("part b: {}", part_b(DATA)); 13 | } 14 | 15 | #[derive(Default)] 16 | struct FourDigitDisplay { 17 | patterns: Vec<&'static str>, 18 | output: Vec<&'static str>, 19 | } 20 | 21 | impl From<&'static str> for FourDigitDisplay { 22 | fn from(s: &'static str) -> Self { 23 | let (patterns_str, output_str) = s.split(" | ").collect_tuple().unwrap(); 24 | let patterns = patterns_str.split_whitespace().collect(); 25 | let output = output_str.split_whitespace().collect(); 26 | Self { patterns, output } 27 | } 28 | } 29 | 30 | fn part_a(data: &'static str) -> usize { 31 | data.lines() 32 | .map(FourDigitDisplay::from) 33 | .flat_map(|fdd| fdd.output) 34 | .filter(|digit| matches!(digit.len(), 2 | 3 | 4 | 7)) 35 | .count() 36 | } 37 | 38 | const PATTERNS: [&str; 10] = [ 39 | "abcefg", "cf", "acdeg", "acdfg", "bcdf", "abdfg", "abdefg", "acf", "abcdefg", "abcdfg", 40 | ]; 41 | 42 | fn get_decoder() -> impl Fn(&[&'static str]) -> HashMap { 43 | let key_to_char_true = get_key_to_char(&PATTERNS); 44 | move |patterns| { 45 | let key_to_char_mangled = get_key_to_char(patterns); 46 | key_to_char_mangled 47 | .into_iter() 48 | .map(|(key, c)| (c, key_to_char_true[&key])) 49 | .collect() 50 | } 51 | } 52 | 53 | fn get_key_to_char(patterns: &[&'static str]) -> HashMap<(usize, BTreeSet), char> { 54 | let occurences = patterns.iter().flat_map(|pat| pat.chars()).counts(); 55 | let pat_lengths = patterns 56 | .iter() 57 | .flat_map(|pat| pat.chars().zip(repeat(pat.len()))) 58 | .into_group_map(); 59 | ('a'..='g') 60 | .map(|c| { 61 | let lengths = pat_lengths[&c].iter().copied().collect(); 62 | ((occurences[&c], lengths), c) 63 | }) 64 | .collect() 65 | } 66 | 67 | fn part_b(data: &'static str) -> usize { 68 | let decode = get_decoder(); 69 | data.lines() 70 | .map(FourDigitDisplay::from) 71 | .map(|fdd| { 72 | let translation = decode(&fdd.patterns); 73 | fdd.output 74 | .iter() 75 | .map(|&out_pat| { 76 | let pat = out_pat 77 | .chars() 78 | .map(|c| translation[&c]) 79 | .sorted_unstable() 80 | .collect::(); 81 | PATTERNS.iter().find_position(|&&p| p == pat).unwrap().0 82 | }) 83 | .rev() 84 | .enumerate() 85 | .map(|(i, num)| num * 10_usize.pow(i as u32)) 86 | .sum::() 87 | }) 88 | .sum() 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 95 | 96 | #[test] 97 | fn test_a() { 98 | assert_eq!(part_a(SAMPLE_DATA), 26); 99 | } 100 | 101 | #[test] 102 | fn test_b() { 103 | assert_eq!(part_b(SAMPLE_DATA), 61229); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /2021/src/bin/day8/sample.txt: -------------------------------------------------------------------------------- 1 | be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe 2 | edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc 3 | fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg 4 | fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb 5 | aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea 6 | fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb 7 | dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe 8 | bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef 9 | egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb 10 | gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce -------------------------------------------------------------------------------- /2021/src/bin/day9/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use itertools::Itertools; 4 | 5 | const DATA: &str = include_str!("data.txt"); 6 | 7 | fn main() { 8 | println!("part a: {}", part_a(DATA)); 9 | println!("part b: {}", part_b(DATA)); 10 | } 11 | 12 | fn parse(data: &'static str) -> HashMap<(i64, i64), usize> { 13 | data.lines() 14 | .enumerate() 15 | .flat_map(|(row, line)| { 16 | line.chars() 17 | .map(|c| c.to_digit(10).unwrap() as usize) 18 | .enumerate() 19 | .map(move |(col, val)| ((row as i64, col as i64), val)) 20 | }) 21 | .collect() 22 | } 23 | 24 | fn adjacent_points( 25 | (y, x): (i64, i64), 26 | data: &HashMap<(i64, i64), usize>, 27 | ) -> impl Iterator + '_ { 28 | [(-1, 0), (1, 0), (0, -1), (0, 1)] 29 | .map(|(dy, dx)| (y + dy, x + dx)) 30 | .into_iter() 31 | .filter(|p| data.contains_key(p)) 32 | } 33 | 34 | fn get_low_points(data: &HashMap<(i64, i64), usize>) -> impl Iterator + '_ { 35 | data.keys() 36 | .copied() 37 | .filter(|&p| adjacent_points(p, data).all(|adj| data[&adj] > data[&p])) 38 | } 39 | 40 | fn part_a(data: &'static str) -> usize { 41 | let data = parse(data); 42 | get_low_points(&data).map(|p| data[&p] + 1).sum() 43 | } 44 | 45 | fn get_basin_size(p: (i64, i64), data: &HashMap<(i64, i64), usize>) -> usize { 46 | let mut basin = HashSet::new(); 47 | crawl(p, data, &mut basin); 48 | basin.len() 49 | } 50 | 51 | fn crawl(p: (i64, i64), data: &HashMap<(i64, i64), usize>, basin: &mut HashSet<(i64, i64)>) { 52 | if basin.contains(&p) || data[&p] == 9 { 53 | return; 54 | } 55 | basin.insert(p); 56 | adjacent_points(p, data).for_each(|p| crawl(p, data, basin)) 57 | } 58 | 59 | fn part_b(data: &'static str) -> usize { 60 | let data = parse(data); 61 | get_low_points(&data) 62 | .map(|p| get_basin_size(p, &data)) 63 | .sorted_unstable() 64 | .rev() 65 | .take(3) 66 | .product() 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 73 | 74 | #[test] 75 | fn test_a() { 76 | assert_eq!(part_a(SAMPLE_DATA), 15); 77 | } 78 | 79 | #[test] 80 | fn test_b() { 81 | assert_eq!(part_b(SAMPLE_DATA), 1134); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /2021/src/bin/day9/sample.txt: -------------------------------------------------------------------------------- 1 | 2199943210 2 | 3987894921 3 | 9856789892 4 | 8767896789 5 | 9899965678 -------------------------------------------------------------------------------- /2021/src/bin/template/main.rs: -------------------------------------------------------------------------------- 1 | const DATA: &str = include_str!("data.txt"); 2 | 3 | fn main() { 4 | println!("part a: {}", part_a(DATA)); 5 | println!("part b: {}", part_b(DATA)); 6 | } 7 | 8 | fn parse(data: &'static str) { 9 | println!("{data}") 10 | } 11 | 12 | fn part_a(data: &'static str) -> usize { 13 | parse(data); 14 | 0 15 | } 16 | 17 | fn part_b(data: &'static str) -> usize { 18 | parse(data); 19 | 0 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | const SAMPLE_DATA: &str = include_str!("sample.txt"); 26 | 27 | #[test] 28 | fn test_a() { 29 | assert_eq!(part_a(SAMPLE_DATA), 0); 30 | } 31 | 32 | #[test] 33 | fn test_b() { 34 | assert_eq!(part_b(SAMPLE_DATA), 0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /2021/src/bin/template/sample.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazytieguy/advent-of-code/7c6f768cd909eab312b2320ea471afb583d7d167/2021/src/bin/template/sample.txt -------------------------------------------------------------------------------- /2021/timings.csv: -------------------------------------------------------------------------------- 1 | day,time_ms 2 | 01,001 3 | 02,001 4 | 03,001 5 | 04,001 6 | 05,005 7 | 06,001 8 | 07,001 9 | 08,003 10 | 09,008 11 | 10,001 12 | 11,005 13 | 12,008 14 | 13,001 15 | 14,002 16 | 15,099 17 | 16,001 18 | 17,002 19 | 18,012 20 | 19,006 21 | 20,004 22 | 21,002 23 | 22,007 24 | 23,123 25 | 24,001 26 | 25,055 27 | -------------------------------------------------------------------------------- /2022/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advent-2022" 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 | itertools = "0.10.5" 10 | nom = "7.1.1" 11 | nom-supreme = "0.8.0" 12 | -------------------------------------------------------------------------------- /2022/src/bin/day1/main.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Reverse; 2 | 3 | use advent_2022::*; 4 | use itertools::Itertools; 5 | use nom::{ 6 | character::complete::{line_ending, u32}, 7 | multi::{fold_many1, separated_list1}, 8 | }; 9 | use nom_supreme::ParserExt; 10 | 11 | boilerplate!(Day); 12 | 13 | impl BasicSolution for Day { 14 | type Parsed = Vec; 15 | type Answer = u32; 16 | const SAMPLE_ANSWER_A: Self::TestAnswer = 24000; 17 | const SAMPLE_ANSWER_B: Self::TestAnswer = 45000; 18 | 19 | fn parse(data: &str) -> IResult { 20 | separated_list1( 21 | line_ending, 22 | fold_many1( 23 | u32.terminated(line_ending), 24 | || 0, 25 | |total, item| total + item, 26 | ), 27 | )(data) 28 | } 29 | 30 | fn a(data: Self::Parsed) -> Self::Answer { 31 | data.into_iter().max().expect("At least one elf") 32 | } 33 | 34 | fn b(data: Self::Parsed) -> Self::Answer { 35 | data.into_iter() 36 | .sorted_unstable_by_key(|&cals| Reverse(cals)) 37 | .take(3) 38 | .sum() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /2022/src/bin/day1/sample.txt: -------------------------------------------------------------------------------- 1 | 1000 2 | 2000 3 | 3000 4 | 5 | 4000 6 | 7 | 5000 8 | 6000 9 | 10 | 7000 11 | 8000 12 | 9000 13 | 14 | 10000 15 | -------------------------------------------------------------------------------- /2022/src/bin/day10/main.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | 3 | use advent_2022::*; 4 | use itertools::{repeat_n, Itertools}; 5 | use nom::{ 6 | branch::alt, 7 | bytes::complete::tag, 8 | character::complete::{i32, line_ending}, 9 | multi::separated_list1, 10 | Parser, 11 | }; 12 | use nom_supreme::ParserExt; 13 | 14 | boilerplate!(Day); 15 | 16 | impl BasicSolution for Day { 17 | type Parsed = Vec; 18 | type Answer = String; 19 | type TestAnswer = &'static str; 20 | const SAMPLE_ANSWER_A: Self::TestAnswer = "13140"; 21 | const SAMPLE_ANSWER_B: Self::TestAnswer = " 22 | ##..##..##..##..##..##..##..##..##..##.. 23 | ###...###...###...###...###...###...###. 24 | ####....####....####....####....####.... 25 | #####.....#####.....#####.....#####..... 26 | ######......######......######......#### 27 | #######.......#######.......#######....."; 28 | 29 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 30 | separated_list1(line_ending, parse_operation)(data) 31 | } 32 | 33 | fn a(data: Self::Parsed) -> Self::Answer { 34 | iter_register(&data) 35 | .zip(1..) 36 | .filter(|(_, cycle)| [20, 60, 100, 140, 180, 220].contains(cycle)) 37 | .map(|(reg_x, cycle)| reg_x * cycle) 38 | .sum::() 39 | .to_string() 40 | } 41 | 42 | fn b(data: Self::Parsed) -> Self::Answer { 43 | iter_register(&data) 44 | .chunks(40) 45 | .into_iter() 46 | .flat_map(|row| { 47 | iter::once('\n').chain(row.zip(0..).map(|(x, pos)| { 48 | if x.abs_diff(pos) <= 1 { 49 | '#' 50 | } else { 51 | '.' 52 | } 53 | })) 54 | }) 55 | .collect() 56 | } 57 | } 58 | 59 | #[derive(Debug, Clone, Copy)] 60 | enum Operation { 61 | Noop, 62 | Add(i32), 63 | } 64 | 65 | use Operation::*; 66 | 67 | fn parse_operation(input: &str) -> IResult { 68 | alt((tag("noop").value(Noop), tag("addx ").precedes(i32).map(Add)))(input) 69 | } 70 | 71 | fn iter_register(data: &[Operation]) -> impl Iterator + '_ { 72 | data.iter() 73 | .scan(1, |register, op| { 74 | Some(repeat_n( 75 | *register, // dereference before mutating 76 | match op { 77 | Noop => 1, 78 | Add(x) => { 79 | *register += x; 80 | 2 81 | } 82 | }, 83 | )) 84 | }) 85 | .flatten() 86 | } 87 | -------------------------------------------------------------------------------- /2022/src/bin/day10/sample.txt: -------------------------------------------------------------------------------- 1 | addx 15 2 | addx -11 3 | addx 6 4 | addx -3 5 | addx 5 6 | addx -1 7 | addx -8 8 | addx 13 9 | addx 4 10 | noop 11 | addx -1 12 | addx 5 13 | addx -1 14 | addx 5 15 | addx -1 16 | addx 5 17 | addx -1 18 | addx 5 19 | addx -1 20 | addx -35 21 | addx 1 22 | addx 24 23 | addx -19 24 | addx 1 25 | addx 16 26 | addx -11 27 | noop 28 | noop 29 | addx 21 30 | addx -15 31 | noop 32 | noop 33 | addx -3 34 | addx 9 35 | addx 1 36 | addx -3 37 | addx 8 38 | addx 1 39 | addx 5 40 | noop 41 | noop 42 | noop 43 | noop 44 | noop 45 | addx -36 46 | noop 47 | addx 1 48 | addx 7 49 | noop 50 | noop 51 | noop 52 | addx 2 53 | addx 6 54 | noop 55 | noop 56 | noop 57 | noop 58 | noop 59 | addx 1 60 | noop 61 | noop 62 | addx 7 63 | addx 1 64 | noop 65 | addx -13 66 | addx 13 67 | addx 7 68 | noop 69 | addx 1 70 | addx -33 71 | noop 72 | noop 73 | noop 74 | addx 2 75 | noop 76 | noop 77 | noop 78 | addx 8 79 | noop 80 | addx -1 81 | addx 2 82 | addx 1 83 | noop 84 | addx 17 85 | addx -9 86 | addx 1 87 | addx 1 88 | addx -3 89 | addx 11 90 | noop 91 | noop 92 | addx 1 93 | noop 94 | addx 1 95 | noop 96 | noop 97 | addx -13 98 | addx -19 99 | addx 1 100 | addx 3 101 | addx 26 102 | addx -30 103 | addx 12 104 | addx -1 105 | addx 3 106 | addx 1 107 | noop 108 | noop 109 | noop 110 | addx -9 111 | addx 18 112 | addx 1 113 | addx 2 114 | noop 115 | noop 116 | addx 9 117 | noop 118 | noop 119 | noop 120 | addx -1 121 | addx 2 122 | addx -37 123 | addx 1 124 | addx 3 125 | noop 126 | addx 15 127 | addx -21 128 | addx 22 129 | addx -6 130 | addx 1 131 | noop 132 | addx 2 133 | addx 1 134 | noop 135 | addx -10 136 | noop 137 | noop 138 | addx 20 139 | addx 1 140 | addx 2 141 | addx 2 142 | addx -6 143 | addx -11 144 | noop 145 | noop 146 | noop 147 | -------------------------------------------------------------------------------- /2022/src/bin/day11/main.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Reverse; 2 | 3 | use advent_2022::*; 4 | use itertools::Itertools; 5 | use nom::{ 6 | branch::alt, 7 | bytes::complete::tag, 8 | character::complete::{char, line_ending, u64, u8}, 9 | multi::{separated_list0, separated_list1}, 10 | sequence::tuple, 11 | Parser, 12 | }; 13 | use nom_supreme::ParserExt; 14 | 15 | boilerplate!(Day); 16 | 17 | impl BasicSolution for Day { 18 | type Parsed = Vec; 19 | type Answer = u64; 20 | const SAMPLE_ANSWER_A: Self::TestAnswer = 10605; 21 | const SAMPLE_ANSWER_B: Self::TestAnswer = 2713310158; 22 | 23 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 24 | separated_list1(line_ending, monkey)(data) 25 | } 26 | 27 | fn a(mut monkeys: Self::Parsed) -> Self::Answer { 28 | stuff_slinging_simian_shenanigans(&mut monkeys, 20, |n| n / 3) 29 | } 30 | 31 | fn b(mut monkeys: Self::Parsed) -> Self::Answer { 32 | let least_common_denominator: u64 = monkeys.iter().map(|m| m.test).product(); 33 | stuff_slinging_simian_shenanigans(&mut monkeys, 10000, |n| n % least_common_denominator) 34 | } 35 | } 36 | 37 | #[derive(Debug, Clone, Copy)] 38 | enum Op { 39 | Add(u64), 40 | Multiply(u64), 41 | Square, 42 | } 43 | 44 | #[derive(Debug, Clone)] 45 | struct Monkey { 46 | inspected: u64, 47 | items: Vec, 48 | operation: Op, 49 | test: u64, 50 | if_true: usize, 51 | if_false: usize, 52 | } 53 | 54 | fn monkey_number(data: &str) -> IResult { 55 | tag("Monkey ") 56 | .precedes(u8.map(|n| n as usize)) 57 | .terminated(char(':').and(line_ending)) 58 | .parse(data) 59 | } 60 | 61 | fn starting_items(data: &str) -> IResult> { 62 | tag(" Starting items: ") 63 | .precedes(separated_list0(tag(", "), u64)) 64 | .terminated(line_ending) 65 | .parse(data) 66 | } 67 | 68 | fn operation(data: &str) -> IResult { 69 | tag(" Operation: new = old ") 70 | .precedes(alt(( 71 | tag("+ ").precedes(u64).map(Op::Add), 72 | tag("* ").precedes(u64).map(Op::Multiply), 73 | tag("* old").value(Op::Square), 74 | ))) 75 | .terminated(line_ending) 76 | .parse(data) 77 | } 78 | 79 | fn test(data: &str) -> IResult { 80 | tag(" Test: divisible by ") 81 | .precedes(u64) 82 | .terminated(line_ending) 83 | .parse(data) 84 | } 85 | 86 | fn if_true(data: &str) -> IResult { 87 | tag(" If true: throw to monkey ") 88 | .precedes(u8.map(|n| n as usize)) 89 | .terminated(line_ending) 90 | .parse(data) 91 | } 92 | 93 | fn if_false(data: &str) -> IResult { 94 | tag(" If false: throw to monkey ") 95 | .precedes(u8.map(|n| n as usize)) 96 | .terminated(line_ending) 97 | .parse(data) 98 | } 99 | 100 | fn monkey(data: &str) -> IResult { 101 | tuple(( 102 | monkey_number, 103 | starting_items, 104 | operation, 105 | test, 106 | if_true, 107 | if_false, 108 | )) 109 | .map(|(_, items, operation, test, if_true, if_false)| Monkey { 110 | inspected: 0, 111 | items, 112 | operation, 113 | test, 114 | if_true, 115 | if_false, 116 | }) 117 | .parse(data) 118 | } 119 | 120 | fn stuff_slinging_simian_shenanigans( 121 | monkeys: &mut [Monkey], 122 | rounds: usize, 123 | manage_worry_level: impl Fn(u64) -> u64, 124 | ) -> u64 { 125 | for (_, turn) in (0..rounds).cartesian_product(0..monkeys.len()) { 126 | while let Some(item) = monkeys[turn].items.pop() { 127 | monkeys[turn].inspected += 1; 128 | let new = match monkeys[turn].operation { 129 | Op::Add(n) => item + n, 130 | Op::Multiply(n) => item * n, 131 | Op::Square => item * item, 132 | }; 133 | let new = manage_worry_level(new); 134 | let throw_to = if new % monkeys[turn].test == 0 { 135 | monkeys[turn].if_true 136 | } else { 137 | monkeys[turn].if_false 138 | }; 139 | monkeys[throw_to].items.push(new); 140 | } 141 | } 142 | monkey_business(monkeys) 143 | } 144 | 145 | fn monkey_business(monkeys: &[Monkey]) -> u64 { 146 | monkeys 147 | .iter() 148 | .map(|m| m.inspected) 149 | .sorted_by_key(|&n| Reverse(n)) 150 | .take(2) 151 | .product() 152 | } 153 | -------------------------------------------------------------------------------- /2022/src/bin/day11/sample.txt: -------------------------------------------------------------------------------- 1 | Monkey 0: 2 | Starting items: 79, 98 3 | Operation: new = old * 19 4 | Test: divisible by 23 5 | If true: throw to monkey 2 6 | If false: throw to monkey 3 7 | 8 | Monkey 1: 9 | Starting items: 54, 65, 75, 74 10 | Operation: new = old + 6 11 | Test: divisible by 19 12 | If true: throw to monkey 2 13 | If false: throw to monkey 0 14 | 15 | Monkey 2: 16 | Starting items: 79, 60, 97 17 | Operation: new = old * old 18 | Test: divisible by 13 19 | If true: throw to monkey 1 20 | If false: throw to monkey 3 21 | 22 | Monkey 3: 23 | Starting items: 74 24 | Operation: new = old + 3 25 | Test: divisible by 17 26 | If true: throw to monkey 0 27 | If false: throw to monkey 1 28 | -------------------------------------------------------------------------------- /2022/src/bin/day12/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use advent_2022::*; 4 | use itertools::Itertools; 5 | 6 | boilerplate!(Day); 7 | 8 | impl BasicSolution for Day { 9 | type Parsed = Input; 10 | type Answer = u32; 11 | const SAMPLE_ANSWER_A: Self::TestAnswer = 31; 12 | const SAMPLE_ANSWER_B: Self::TestAnswer = 29; 13 | 14 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 15 | let mut height_map = data 16 | .lines() 17 | .map(|row| row.as_bytes().to_vec()) 18 | .collect_vec(); 19 | 20 | let mut find_position_and_assign = |from, to| { 21 | height_map 22 | .iter_mut() 23 | .enumerate() 24 | .find_map(|(x, row)| { 25 | row.iter_mut() 26 | .enumerate() 27 | .find(|(_, &mut height)| height == from) 28 | .map(|(y, height)| { 29 | *height = to; 30 | (x, y) 31 | }) 32 | }) 33 | .expect("The character should be present in the input") 34 | }; 35 | Ok(( 36 | "", 37 | Input { 38 | start: find_position_and_assign(b'S', b'a'), 39 | end: find_position_and_assign(b'E', b'z'), 40 | height_map, 41 | }, 42 | )) 43 | } 44 | 45 | fn a(data: Self::Parsed) -> Self::Answer { 46 | bfs(&data, |&pos| pos == data.start) 47 | } 48 | 49 | fn b(data: Self::Parsed) -> Self::Answer { 50 | bfs(&data, |&(x, y)| data.height_map[x][y] == b'a') 51 | } 52 | } 53 | 54 | type Position = (usize, usize); 55 | type HeightMap = Vec>; 56 | 57 | #[derive(Debug, Clone)] 58 | struct Input { 59 | start: Position, 60 | end: Position, 61 | height_map: HeightMap, 62 | } 63 | 64 | fn neighbors(height_map: &HeightMap, (x, y): Position) -> impl Iterator + '_ { 65 | let checked_add_signed_2d = 66 | move |(dx, dy)| x.checked_add_signed(dx).zip(y.checked_add_signed(dy)); 67 | let is_high_enough = move |&height| height >= height_map[x][y] - 1; 68 | let is_valid_neighbor = move |&(x, y): &Position| { 69 | height_map 70 | .get(x) 71 | .and_then(|row| row.get(y)) 72 | .is_some_and(is_high_enough) 73 | }; 74 | [(-1, 0), (1, 0), (0, -1), (0, 1)] 75 | .into_iter() 76 | .filter_map(checked_add_signed_2d) 77 | .filter(is_valid_neighbor) 78 | } 79 | 80 | fn bfs( 81 | Input { 82 | end, height_map, .. 83 | }: &Input, 84 | success: impl Fn(&Position) -> bool, 85 | ) -> u32 { 86 | let mut seen = vec![vec![false; height_map[0].len()]; height_map.len()]; 87 | let mut queue = VecDeque::from([(*end, 0)]); 88 | while let Some((pos, steps)) = queue.pop_front() { 89 | if success(&pos) { 90 | return steps; 91 | } 92 | if seen[pos.0][pos.1] { 93 | continue; 94 | } 95 | seen[pos.0][pos.1] = true; 96 | for neighbor in neighbors(height_map, pos) { 97 | queue.push_back((neighbor, steps + 1)); 98 | } 99 | } 100 | unreachable!("should always find a path") 101 | } 102 | -------------------------------------------------------------------------------- /2022/src/bin/day12/sample.txt: -------------------------------------------------------------------------------- 1 | Sabqponm 2 | abcryxxl 3 | accszExk 4 | acctuvwj 5 | abdefghi 6 | -------------------------------------------------------------------------------- /2022/src/bin/day13/main.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use advent_2022::*; 4 | use itertools::Itertools; 5 | use nom::{ 6 | branch::alt, 7 | character::complete::{char, line_ending, u8}, 8 | multi::{separated_list0, separated_list1}, 9 | sequence::{delimited, separated_pair}, 10 | Parser, 11 | }; 12 | use nom_supreme::ParserExt; 13 | use Value::*; 14 | 15 | boilerplate!(Day); 16 | 17 | impl BasicSolution for Day { 18 | type Parsed = Vec<(Value, Value)>; 19 | type Answer = usize; 20 | const SAMPLE_ANSWER_A: Self::TestAnswer = 13; 21 | const SAMPLE_ANSWER_B: Self::TestAnswer = 140; 22 | 23 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 24 | separated_list1( 25 | line_ending, 26 | separated_pair(value, line_ending, value).terminated(line_ending), 27 | )(data) 28 | } 29 | 30 | fn a(data: Self::Parsed) -> Self::Answer { 31 | data.into_iter() 32 | .enumerate() 33 | .filter(|(_, (a, b))| b >= a) 34 | .map(|(i, _)| i + 1) 35 | .sum() 36 | } 37 | 38 | fn b(data: Self::Parsed) -> Self::Answer { 39 | let divider_a = List(vec![List(vec![Integer(2)])]); 40 | let divider_b = List(vec![List(vec![Integer(6)])]); 41 | let all_packets = data 42 | .iter() 43 | .flat_map(|(a, b)| [a, b]) 44 | .chain([÷r_a, ÷r_b]) 45 | .sorted_unstable() 46 | .collect_vec(); 47 | all_packets.partition_point(|&v| v <= ÷r_a) 48 | * all_packets.partition_point(|&v| v <= ÷r_b) 49 | } 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | enum Value { 54 | Integer(u8), 55 | List(Vec), 56 | } 57 | 58 | impl Ord for Value { 59 | fn cmp(&self, other: &Self) -> Ordering { 60 | match (self, other) { 61 | (Integer(a), Integer(b)) => a.cmp(b), 62 | (List(a), List(b)) => a.cmp(b), 63 | (Integer(a), list) => List(vec![Integer(*a)]).cmp(list), 64 | (list, Integer(b)) => list.cmp(&List(vec![Integer(*b)])), 65 | } 66 | } 67 | } 68 | 69 | impl PartialEq for Value { 70 | fn eq(&self, other: &Self) -> bool { 71 | matches!(self.cmp(other), Ordering::Equal) 72 | } 73 | } 74 | 75 | impl Eq for Value {} 76 | 77 | impl PartialOrd for Value { 78 | fn partial_cmp(&self, other: &Self) -> Option { 79 | Some(self.cmp(other)) 80 | } 81 | } 82 | 83 | fn value(data: &str) -> IResult { 84 | alt(( 85 | u8.map(Integer), 86 | delimited(char('['), separated_list0(char(','), value), char(']')).map(List), 87 | ))(data) 88 | } 89 | -------------------------------------------------------------------------------- /2022/src/bin/day13/sample.txt: -------------------------------------------------------------------------------- 1 | [1,1,3,1,1] 2 | [1,1,5,1,1] 3 | 4 | [[1],[2,3,4]] 5 | [[1],4] 6 | 7 | [9] 8 | [[8,7,6]] 9 | 10 | [[4,4],4,4] 11 | [[4,4],4,4,4] 12 | 13 | [7,7,7,7] 14 | [7,7,7] 15 | 16 | [] 17 | [3] 18 | 19 | [[[]]] 20 | [[]] 21 | 22 | [1,[2,[3,[4,[5,6,7]]]],8,9] 23 | [1,[2,[3,[4,[5,6,0]]]],8,9] 24 | -------------------------------------------------------------------------------- /2022/src/bin/day14/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use itertools::process_results; 3 | use nom::{ 4 | character::complete::{char, u16, u8}, 5 | sequence::separated_pair, 6 | Parser, 7 | }; 8 | 9 | boilerplate!(Day); 10 | 11 | impl BasicSolution for Day { 12 | type Parsed = (BitGrid, usize); 13 | type Answer = usize; 14 | const SAMPLE_ANSWER_A: Self::TestAnswer = 24; 15 | const SAMPLE_ANSWER_B: Self::TestAnswer = 93; 16 | 17 | fn parse(data: &str) -> IResult { 18 | let mut rocks = BitGrid::new(); 19 | let mut max_y = 0; 20 | for line in data.lines() { 21 | process_results(line.split(" -> ").map(parse_coords), |it| { 22 | it.map(|(_, xy)| xy).reduce(|(x1, y1), (x2, y2)| { 23 | max_y = max_y.max(y1).max(y2); 24 | for x in x1.min(x2)..=x1.max(x2) { 25 | for y in y1.min(y2)..=y1.max(y2) { 26 | rocks.insert(x, y); 27 | } 28 | } 29 | (x2, y2) 30 | }) 31 | })?; 32 | } 33 | Ok(("", (rocks, max_y))) 34 | } 35 | 36 | fn a((rocks, max_y): Self::Parsed) -> Self::Answer { 37 | solve::(rocks, max_y) 38 | } 39 | 40 | fn b((rocks, max_y): Self::Parsed) -> Self::Answer { 41 | solve::(rocks, max_y) 42 | } 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | struct BitGrid([[u32; 5]; 320]); 47 | 48 | fn solve(mut taken_coords: BitGrid, max_y: usize) -> usize { 49 | let num_rocks = taken_coords.len(); 50 | drop_sand::(&mut taken_coords, max_y + 2, 500, 0); 51 | taken_coords.len() - num_rocks 52 | } 53 | 54 | fn parse_coords(data: &str) -> IResult<(usize, usize)> { 55 | separated_pair(u16.map(usize::from), char(','), u8.map(usize::from))(data) 56 | } 57 | 58 | fn drop_sand( 59 | taken_coords: &mut BitGrid, 60 | floor: usize, 61 | x: usize, 62 | y: usize, 63 | ) -> bool { 64 | if y == floor { 65 | return true; 66 | } 67 | if taken_coords.contains(x, y) { 68 | return false; 69 | } 70 | for x in [x, x - 1, x + 1] { 71 | if drop_sand::(taken_coords, floor, x, y + 1) && !SOLID_FLOOR { 72 | return true; 73 | } 74 | } 75 | taken_coords.insert(x, y); 76 | false 77 | } 78 | 79 | impl BitGrid { 80 | fn new() -> Self { 81 | Self([[0; 5]; 320]) 82 | } 83 | 84 | fn len(&self) -> usize { 85 | self.0 86 | .into_iter() 87 | .flatten() 88 | .map(|x| x.count_ones() as usize) 89 | .sum() 90 | } 91 | 92 | fn insert(&mut self, x: usize, y: usize) { 93 | self.0[x + 160 - 500][y / 32] |= 1 << (y % 32); 94 | } 95 | 96 | fn contains(&self, x: usize, y: usize) -> bool { 97 | self.0[x + 160 - 500][y / 32] & (1 << (y % 32)) != 0 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /2022/src/bin/day14/sample.txt: -------------------------------------------------------------------------------- 1 | 498,4 -> 498,6 -> 496,6 2 | 503,4 -> 502,4 -> 502,9 -> 494,9 3 | -------------------------------------------------------------------------------- /2022/src/bin/day15/sample.txt: -------------------------------------------------------------------------------- 1 | Sensor at x=2, y=18: closest beacon is at x=-2, y=15 2 | Sensor at x=9, y=16: closest beacon is at x=10, y=16 3 | Sensor at x=13, y=2: closest beacon is at x=15, y=3 4 | Sensor at x=12, y=14: closest beacon is at x=10, y=16 5 | Sensor at x=10, y=20: closest beacon is at x=10, y=16 6 | Sensor at x=14, y=17: closest beacon is at x=10, y=16 7 | Sensor at x=8, y=7: closest beacon is at x=2, y=10 8 | Sensor at x=2, y=0: closest beacon is at x=2, y=10 9 | Sensor at x=0, y=11: closest beacon is at x=2, y=10 10 | Sensor at x=20, y=14: closest beacon is at x=25, y=17 11 | Sensor at x=17, y=20: closest beacon is at x=21, y=22 12 | Sensor at x=16, y=7: closest beacon is at x=15, y=3 13 | Sensor at x=14, y=3: closest beacon is at x=15, y=3 14 | Sensor at x=20, y=1: closest beacon is at x=15, y=3 15 | -------------------------------------------------------------------------------- /2022/src/bin/day16/sample.txt: -------------------------------------------------------------------------------- 1 | Valve AA has flow rate=0; tunnels lead to valves DD, II, BB 2 | Valve BB has flow rate=13; tunnels lead to valves CC, AA 3 | Valve CC has flow rate=2; tunnels lead to valves DD, BB 4 | Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE 5 | Valve EE has flow rate=3; tunnels lead to valves FF, DD 6 | Valve FF has flow rate=0; tunnels lead to valves EE, GG 7 | Valve GG has flow rate=0; tunnels lead to valves FF, HH 8 | Valve HH has flow rate=22; tunnel leads to valve GG 9 | Valve II has flow rate=0; tunnels lead to valves AA, JJ 10 | Valve JJ has flow rate=21; tunnel leads to valve II 11 | -------------------------------------------------------------------------------- /2022/src/bin/day17/sample.txt: -------------------------------------------------------------------------------- 1 | >>><<><>><<<>><>>><<<>>><<<><<<>><>><<>> 2 | -------------------------------------------------------------------------------- /2022/src/bin/day18/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use nom::{ 3 | character::complete::{char, line_ending, u8}, 4 | multi::separated_list1, 5 | sequence::tuple, 6 | Parser, 7 | }; 8 | 9 | boilerplate!(Day); 10 | 11 | const SIZE: usize = 24; 12 | const TEST_SIZE: usize = 9; 13 | 14 | impl Solution for Day { 15 | type Parsed = (Vec, Arr3D); 16 | type ParsedTest = (Vec, Arr3D); 17 | type Answer = usize; 18 | const SAMPLE_ANSWER_A: Self::TestAnswer = 64; 19 | const SAMPLE_ANSWER_B: Self::TestAnswer = 58; 20 | 21 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 22 | parse(data) 23 | } 24 | 25 | fn a(data: Self::Parsed) -> Self::Answer { 26 | part_a(data) 27 | } 28 | 29 | fn b(data: Self::Parsed) -> Self::Answer { 30 | part_b(data) 31 | } 32 | 33 | fn parse_test(data: &'static str) -> IResult<'static, Self::ParsedTest> { 34 | parse(data) 35 | } 36 | 37 | fn a_test(data: Self::ParsedTest) -> Self::Answer { 38 | part_a(data) 39 | } 40 | 41 | fn b_test(data: Self::ParsedTest) -> Self::Answer { 42 | part_b(data) 43 | } 44 | } 45 | 46 | type Tuple = (u8, u8, u8); 47 | type Arr3D = [[[bool; N]; N]; N]; 48 | 49 | fn parse(data: &str) -> IResult<(Vec, Arr3D)> { 50 | separated_list1(line_ending, parse_cube) 51 | .map(|coords| { 52 | let mut matrix = [[[false; N]; N]; N]; 53 | for &(x, y, z) in &coords { 54 | matrix[x as usize][y as usize][z as usize] = true; 55 | } 56 | (coords, matrix) 57 | }) 58 | .parse(data) 59 | } 60 | 61 | fn part_a((coords, matrix): (Vec, Arr3D)) -> usize { 62 | coords 63 | .iter() 64 | .flat_map(|&t| adjacent_coords::(t)) 65 | .filter(|&(x, y, z)| !matrix[x as usize][y as usize][z as usize]) 66 | .count() 67 | } 68 | 69 | fn part_b((_coords, matrix): (Vec, Arr3D)) -> usize { 70 | let mut visited = [[[false; N]; N]; N]; 71 | let mut encountered = 0; 72 | let mut queue = vec![(0, 0, 0)]; 73 | while let Some((x, y, z)) = queue.pop() { 74 | if visited[x as usize][y as usize][z as usize] { 75 | continue; 76 | } 77 | visited[x as usize][y as usize][z as usize] = true; 78 | adjacent_coords::((x, y, z)).for_each(|(x, y, z)| { 79 | if matrix[x as usize][y as usize][z as usize] { 80 | encountered += 1; 81 | } else { 82 | queue.push((x, y, z)) 83 | } 84 | }); 85 | } 86 | encountered 87 | } 88 | 89 | fn parse_cube(data: &str) -> IResult<(u8, u8, u8)> { 90 | tuple((u8, char(','), u8, char(','), u8)) 91 | .map(|(a, _, b, _, c)| (a + 1, b + 1, c + 1)) 92 | .parse(data) 93 | } 94 | 95 | fn adjacent_coords((x, y, z): Tuple) -> impl Iterator { 96 | [ 97 | (-1, 0, 0), 98 | (1, 0, 0), 99 | (0, -1, 0), 100 | (0, 1, 0), 101 | (0, 0, -1), 102 | (0, 0, 1), 103 | ] 104 | .iter() 105 | .filter_map(move |&(dx, dy, dz)| { 106 | x.checked_add_signed(dx) 107 | .zip(y.checked_add_signed(dy)) 108 | .zip(z.checked_add_signed(dz)) 109 | }) 110 | .map(|((x, y), z)| (x, y, z)) 111 | .filter(|&(x, y, z)| (x as usize) < N && (y as usize) < N && (z as usize) < N) 112 | } 113 | -------------------------------------------------------------------------------- /2022/src/bin/day18/sample.txt: -------------------------------------------------------------------------------- 1 | 2,2,2 2 | 1,2,2 3 | 3,2,2 4 | 2,1,2 5 | 2,3,2 6 | 2,2,1 7 | 2,2,3 8 | 2,2,4 9 | 2,2,6 10 | 1,2,5 11 | 3,2,5 12 | 2,1,5 13 | 2,3,5 14 | -------------------------------------------------------------------------------- /2022/src/bin/day19/sample.txt: -------------------------------------------------------------------------------- 1 | Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian. 2 | Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian. 3 | -------------------------------------------------------------------------------- /2022/src/bin/day2/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use nom::{ 3 | branch::alt, 4 | character::complete::{char, line_ending}, 5 | multi::separated_list1, 6 | sequence::separated_pair, 7 | }; 8 | use nom_supreme::ParserExt; 9 | 10 | boilerplate!(Day); 11 | 12 | impl BasicSolution for Day { 13 | type Parsed = Vec<(i8, i8)>; 14 | type Answer = u32; 15 | const SAMPLE_ANSWER_A: Self::TestAnswer = 15; 16 | const SAMPLE_ANSWER_B: Self::TestAnswer = 12; 17 | 18 | fn parse(data: &str) -> IResult { 19 | separated_list1( 20 | line_ending, 21 | separated_pair( 22 | alt((char('A').value(0), char('B').value(1), char('C').value(2))), 23 | char(' '), 24 | alt((char('X').value(0), char('Y').value(1), char('Z').value(2))), 25 | ), 26 | )(data) 27 | } 28 | 29 | fn a(data: Self::Parsed) -> Self::Answer { 30 | data.into_iter() 31 | .map(|(opponent_choice, own_choice)| { 32 | let result_score = match (own_choice - opponent_choice).rem_euclid(3) { 33 | 0 => 3, // tie 34 | 1 => 6, // win 35 | 2 => 0, // lose 36 | _ => unreachable!(), 37 | }; 38 | let choice_score = own_choice as u32 + 1; 39 | choice_score + result_score 40 | }) 41 | .sum() 42 | } 43 | 44 | fn b(data: Self::Parsed) -> Self::Answer { 45 | data.into_iter() 46 | .map(|(opponent_choice, result)| { 47 | let choice_score = (opponent_choice + result - 1).rem_euclid(3) as u32 + 1; 48 | let result_score = result as u32 * 3; 49 | choice_score + result_score 50 | }) 51 | .sum() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /2022/src/bin/day2/sample.txt: -------------------------------------------------------------------------------- 1 | A Y 2 | B X 3 | C Z 4 | -------------------------------------------------------------------------------- /2022/src/bin/day20/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use itertools::{iterate, Itertools}; 3 | use nom::{ 4 | character::complete::{i64, line_ending}, 5 | multi::separated_list1, 6 | }; 7 | 8 | boilerplate!(Day); 9 | 10 | impl Solution for Day { 11 | type Parsed = Vec; 12 | type Answer = i64; 13 | const SAMPLE_ANSWER_A: Self::TestAnswer = 3; 14 | const SAMPLE_ANSWER_B: Self::TestAnswer = 1623178306; 15 | 16 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 17 | separated_list1(line_ending, i64)(data) 18 | } 19 | 20 | fn parse_test(data: &'static str) -> IResult<'static, Self::ParsedTest> { 21 | Self::parse(data) 22 | } 23 | 24 | fn a(data: Self::Parsed) -> Self::Answer { 25 | solve::<25>(data, 1, 1) 26 | } 27 | 28 | fn b(data: Self::Parsed) -> Self::Answer { 29 | solve::<25>(data, 811589153, 10) 30 | } 31 | 32 | fn a_test(data: Self::ParsedTest) -> Self::TestAnswer { 33 | solve::<1>(data, 1, 1) 34 | } 35 | 36 | fn b_test(data: Self::ParsedTest) -> Self::TestAnswer { 37 | solve::<1>(data, 811589153, 10) 38 | } 39 | } 40 | 41 | fn solve(data: Vec, decryption_key: i64, iterations: usize) -> i64 { 42 | let numbers = data.into_iter().map(|x| x * decryption_key).collect_vec(); 43 | let mut prev = (0..numbers.len() as u16).collect_vec(); 44 | let mut next = prev.clone(); 45 | prev.rotate_right(1); 46 | next.rotate_left(NEXT_SIZE % numbers.len()); 47 | for _ in 0..iterations { 48 | for (cur, &n) in numbers.iter().enumerate() { 49 | // remove cur from the list 50 | fix_pairs_backwards(prev[cur], next[cur], &mut prev, &mut next, cur as u16); 51 | 52 | // find the node to insert cur after 53 | let amount_to_move = n.rem_euclid(numbers.len() as i64 - 1) as usize; 54 | let target = find_target::(prev[cur], amount_to_move, &prev, &next); 55 | 56 | // insert cur after target 57 | prev[cur] = target; 58 | fix_pairs_backwards( 59 | cur as u16, 60 | next[target as usize], 61 | &mut prev, 62 | &mut next, 63 | target, 64 | ); 65 | } 66 | } 67 | let zero_index = numbers 68 | .iter() 69 | .position(|&x| x == 0) 70 | .expect("an element with value 0"); 71 | iterate(zero_index as u16, |&cur| { 72 | find_target::(cur, 1000, &prev, &next) 73 | }) 74 | .skip(1) 75 | .take(3) 76 | .map(|i| numbers[i as usize]) 77 | .sum() 78 | } 79 | 80 | fn fix_pairs_backwards(left: u16, right: u16, prev: &mut [u16], next: &mut [u16], stop: u16) { 81 | let (far_prev, immediate_next) = iterate(left, |&i| prev[i as usize]) 82 | .zip(iterate(right, |&i| prev[i as usize])) 83 | .inspect(|&(before, after)| { 84 | next[before as usize] = after; 85 | }) 86 | .find(|&(_, after)| prev[after as usize] == stop) 87 | .unwrap(); 88 | prev[immediate_next as usize] = left; 89 | next[prev[far_prev as usize] as usize] = left; 90 | } 91 | 92 | fn find_target( 93 | from: u16, 94 | amount_to_move: usize, 95 | prev: &[u16], 96 | next: &[u16], 97 | ) -> u16 { 98 | let overshot_target = iterate(from, |&cur| next[cur as usize]) 99 | .nth((NEXT_SIZE + amount_to_move) / NEXT_SIZE) 100 | .unwrap(); 101 | iterate(overshot_target, |&cur| prev[cur as usize]) 102 | .nth(NEXT_SIZE - amount_to_move % NEXT_SIZE) 103 | .unwrap() 104 | } 105 | -------------------------------------------------------------------------------- /2022/src/bin/day20/sample.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | -3 4 | 3 5 | -2 6 | 0 7 | 4 8 | -------------------------------------------------------------------------------- /2022/src/bin/day21/sample.txt: -------------------------------------------------------------------------------- 1 | root: pppw + sjmn 2 | dbpl: 5 3 | cczh: sllz + lgvd 4 | zczc: 2 5 | ptdq: humn - dvpt 6 | dvpt: 3 7 | lfqf: 4 8 | humn: 5 9 | ljgn: 2 10 | sjmn: drzm * dbpl 11 | sllz: 4 12 | pppw: cczh / lfqf 13 | lgvd: ljgn * ptdq 14 | drzm: hmdt - zczc 15 | hmdt: 32 16 | -------------------------------------------------------------------------------- /2022/src/bin/day22/sample.txt: -------------------------------------------------------------------------------- 1 | ...# 2 | .#.. 3 | #... 4 | .... 5 | ...#.......# 6 | ........#... 7 | ..#....#.... 8 | ..........#. 9 | ...#.... 10 | .....#.. 11 | .#...... 12 | ......#. 13 | 14 | 10R5L5R10L4R5L5 15 | -------------------------------------------------------------------------------- /2022/src/bin/day23/sample.txt: -------------------------------------------------------------------------------- 1 | ....#.. 2 | ..###.# 3 | #...#.# 4 | .#...## 5 | #.###.. 6 | ##.#.## 7 | .#..#.. 8 | -------------------------------------------------------------------------------- /2022/src/bin/day24/sample.txt: -------------------------------------------------------------------------------- 1 | #.###### 2 | #>>.<^<# 3 | #.<..<<# 4 | #>v.><># 5 | #<^v^^># 6 | ######.# 7 | -------------------------------------------------------------------------------- /2022/src/bin/day25/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use nom::{ 3 | branch::alt, 4 | character::complete::{char, line_ending}, 5 | multi::fold_many1, 6 | }; 7 | use nom_supreme::ParserExt; 8 | 9 | boilerplate!(Day); 10 | 11 | impl BasicSolution for Day { 12 | type Parsed = i64; 13 | type Answer = String; 14 | type TestAnswer = &'static str; 15 | const SAMPLE_ANSWER_A: Self::TestAnswer = "2=-1=0"; 16 | const SAMPLE_ANSWER_B: Self::TestAnswer = ""; 17 | 18 | fn parse(data: &str) -> IResult { 19 | fold_many1(snafu.terminated(line_ending), || 0, |acc, cur| acc + cur)(data) 20 | } 21 | 22 | fn a(data: Self::Parsed) -> Self::Answer { 23 | to_snafu(data) 24 | } 25 | 26 | fn b(_data: Self::Parsed) -> Self::Answer { 27 | "".into() 28 | } 29 | } 30 | 31 | fn snafu_digit(input: &str) -> IResult { 32 | alt(( 33 | char('2').value(2), 34 | char('1').value(1), 35 | char('0').value(0), 36 | char('-').value(-1), 37 | char('=').value(-2), 38 | ))(input) 39 | } 40 | 41 | fn snafu(input: &str) -> IResult { 42 | fold_many1(snafu_digit, || 0, |acc, cur| acc * 5 + cur)(input) 43 | } 44 | 45 | fn to_snafu(number: i64) -> String { 46 | itertools::unfold(number, |number| { 47 | if *number == 0 { 48 | return None; 49 | } 50 | let digit_value = (((*number % 5) + 2) % 5) - 2; 51 | *number -= digit_value; 52 | *number /= 5; 53 | Some(match digit_value { 54 | -2 => '=', 55 | -1 => '-', 56 | 0 => '0', 57 | 1 => '1', 58 | 2 => '2', 59 | _ => unreachable!(), 60 | }) 61 | }) 62 | .collect::() 63 | .chars() 64 | .rev() 65 | .collect() 66 | } 67 | -------------------------------------------------------------------------------- /2022/src/bin/day25/sample.txt: -------------------------------------------------------------------------------- 1 | 1=-0-2 2 | 12111 3 | 2=0= 4 | 21 5 | 2=01 6 | 111 7 | 20012 8 | 112 9 | 1=-1= 10 | 1-12 11 | 12 12 | 1= 13 | 122 14 | -------------------------------------------------------------------------------- /2022/src/bin/day3/main.py: -------------------------------------------------------------------------------- 1 | import string 2 | from collections.abc import Iterable 3 | from pathlib import Path 4 | 5 | 6 | def priority(item: str) -> int: 7 | return string.ascii_letters.index(item) + 1 8 | 9 | 10 | def find_single_intersection(group: Iterable[str]) -> str: 11 | intersection = set.intersection(*(set(items) for items in group)) 12 | assert len(intersection) == 1, group 13 | return intersection.pop() 14 | 15 | 16 | def part_a(lines: list[str]) -> int: 17 | ans = 0 18 | for line in lines: 19 | left = line[: len(line) // 2] 20 | right = line[len(line) // 2 :] 21 | single_intersection = find_single_intersection([left, right]) 22 | ans += priority(single_intersection) 23 | return ans 24 | 25 | 26 | def part_b(lines: list[str]) -> int: 27 | ans = 0 28 | for i in range(0, len(lines), 3): 29 | group = lines[i : i + 3] 30 | single_intersection = find_single_intersection(group) 31 | ans += priority(single_intersection) 32 | return ans 33 | 34 | 35 | def main(): 36 | data = (Path(__file__).parent / "data.txt").read_text() 37 | lines = data.splitlines() 38 | print(f"part a: {part_a(lines)}") 39 | print(f"part b: {part_b(lines)}") 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /2022/src/bin/day3/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(anonymous_lifetime_in_impl_trait)] 2 | use std::{collections::HashSet, str::Lines}; 3 | 4 | use advent_2022::*; 5 | use itertools::Itertools; 6 | 7 | boilerplate!(Day); 8 | 9 | impl BasicSolution for Day { 10 | type Parsed = Lines<'static>; 11 | type Answer = u32; 12 | const SAMPLE_ANSWER_A: Self::TestAnswer = 157; 13 | const SAMPLE_ANSWER_B: Self::TestAnswer = 70; 14 | 15 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 16 | Ok(("", data.lines())) 17 | } 18 | 19 | fn a(data: Self::Parsed) -> Self::Answer { 20 | data.map(|line| line.split_at(line.len() / 2)) 21 | .map(|(left, right)| intersecting_item([left, right])) 22 | .map(priority) 23 | .sum() 24 | } 25 | 26 | fn b(data: Self::Parsed) -> Self::Answer { 27 | data.chunks(3) 28 | .into_iter() 29 | .map(intersecting_item) 30 | .map(priority) 31 | .sum() 32 | } 33 | } 34 | 35 | fn priority(item: u8) -> u32 { 36 | match item { 37 | b'a'..=b'z' => item - b'a' + 1, 38 | b'A'..=b'Z' => item - b'A' + 27, 39 | _ => panic!("illegal character {item}"), 40 | } 41 | .into() 42 | } 43 | 44 | fn intersecting_item(group: impl IntoIterator) -> u8 { 45 | group 46 | .into_iter() 47 | .map(|items| items.bytes().collect::>()) 48 | .reduce(|a, b| a.intersection(&b).copied().collect()) 49 | .expect("there should be more than one set") 50 | .into_iter() 51 | .exactly_one() 52 | .expect("there should be exactly one item in the intersection") 53 | } 54 | -------------------------------------------------------------------------------- /2022/src/bin/day3/sample.txt: -------------------------------------------------------------------------------- 1 | vJrwpWtwJgWrhcsFMMfFFhFp 2 | jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL 3 | PmmdzqPrVvPwwTWBwg 4 | wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn 5 | ttgJtRGJQctTZtZT 6 | CrZsJsPPZsGzwwsLwLmpwMDw -------------------------------------------------------------------------------- /2022/src/bin/day4/main.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | use advent_2022::*; 4 | use nom::{ 5 | character::complete::{char, line_ending, u32}, 6 | multi::separated_list1, 7 | Parser, 8 | }; 9 | use nom_supreme::ParserExt; 10 | 11 | type RangesPair = [RangeInclusive; 2]; 12 | 13 | boilerplate!(Day); 14 | 15 | impl BasicSolution for Day { 16 | type Parsed = Vec; 17 | type Answer = usize; 18 | const SAMPLE_ANSWER_A: Self::TestAnswer = 2; 19 | const SAMPLE_ANSWER_B: Self::TestAnswer = 4; 20 | 21 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 22 | separated_list1( 23 | line_ending, 24 | u32.separated_array(char('-')) 25 | .map(|[a, b]| a..=b) 26 | .separated_array(char(',')), 27 | )(data) 28 | } 29 | 30 | fn a(data: Self::Parsed) -> Self::Answer { 31 | data.into_iter() 32 | .filter(one_range_contains_the_other) 33 | .count() 34 | } 35 | 36 | fn b(data: Self::Parsed) -> Self::Answer { 37 | data.into_iter().filter(ranges_overlap).count() 38 | } 39 | } 40 | 41 | fn one_range_contains_the_other([a, b]: &RangesPair) -> bool { 42 | a.contains(b.start()) && a.contains(b.end()) || b.contains(a.start()) && b.contains(a.end()) 43 | } 44 | 45 | fn ranges_overlap([a, b]: &RangesPair) -> bool { 46 | a.contains(b.start()) || a.contains(b.end()) 47 | } 48 | -------------------------------------------------------------------------------- /2022/src/bin/day4/sample.txt: -------------------------------------------------------------------------------- 1 | 2-4,6-8 2 | 2-3,4-5 3 | 5-7,7-9 4 | 2-8,3-7 5 | 6-6,4-6 6 | 2-6,4-8 7 | -------------------------------------------------------------------------------- /2022/src/bin/day5/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(get_many_mut)] 2 | use std::collections::VecDeque; 3 | 4 | use advent_2022::*; 5 | use nom::{ 6 | branch::alt, 7 | bytes::complete::tag, 8 | character::{ 9 | complete::{anychar, char, line_ending, u8}, 10 | streaming::not_line_ending, 11 | }, 12 | multi::{many1_count, separated_list1}, 13 | sequence::{delimited, separated_pair, tuple}, 14 | Parser, 15 | }; 16 | use nom_supreme::{multi::parse_separated_terminated, ParserExt}; 17 | 18 | boilerplate!(Day); 19 | 20 | impl BasicSolution for Day { 21 | type Parsed = (Stacks, Vec); 22 | type Answer = String; 23 | type TestAnswer = &'static str; 24 | const SAMPLE_ANSWER_A: Self::TestAnswer = "CMZ"; 25 | const SAMPLE_ANSWER_B: Self::TestAnswer = "MCD"; 26 | 27 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 28 | separated_pair(stacks, line_ending, instructions)(data) 29 | } 30 | 31 | fn a((stacks, instructions): Self::Parsed) -> Self::Answer { 32 | solve::(stacks, &instructions) 33 | } 34 | 35 | fn b((stacks, instructions): Self::Parsed) -> Self::Answer { 36 | solve::(stacks, &instructions) 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone, Copy)] 41 | struct Instruction { 42 | amount: u8, 43 | from: u8, 44 | to: u8, 45 | } 46 | 47 | type Stacks = Vec>; 48 | 49 | fn solve(mut stacks: Stacks, instructions: &[Instruction]) -> String { 50 | for &Instruction { amount, from, to } in instructions { 51 | let (amount, from, to) = (amount as usize, from as usize, to as usize); 52 | let [from, to] = stacks 53 | .get_many_mut([from, to]) 54 | .expect("stacks should exist"); 55 | let at = from.len() - amount; 56 | if REVERSE_ORDER { 57 | to.extend(from.drain(at..).rev()); 58 | } else { 59 | to.extend(from.drain(at..)); 60 | } 61 | } 62 | get_top_crates(stacks) 63 | } 64 | 65 | fn crate_(input: &str) -> IResult> { 66 | let crate_ = delimited( 67 | char('['), 68 | anychar.verify(char::is_ascii_uppercase), 69 | char(']'), 70 | ) 71 | .map(Some); 72 | let not_crate = tag(" ").value(None); 73 | alt((crate_, not_crate))(input) 74 | } 75 | 76 | fn stacks(input: &str) -> IResult { 77 | let mut stacks = vec![]; 78 | let crate_row = parse_separated_terminated( 79 | crate_, 80 | char(' '), 81 | line_ending, 82 | || 0, 83 | |i, opt_crate| { 84 | if i == stacks.len() { 85 | stacks.push(VecDeque::new()); 86 | } 87 | if let Some(c) = opt_crate { 88 | stacks[i].push_front(c); 89 | } 90 | i + 1 91 | }, 92 | ); 93 | let (input, _) = many1_count(crate_row)(input)?; 94 | let (input, _) = not_line_ending.terminated(line_ending).parse(input)?; 95 | Ok((input, stacks)) 96 | } 97 | 98 | fn instruction(input: &str) -> IResult { 99 | let (input, (_, amount, _, from, _, to)) = 100 | tuple((tag("move "), u8, tag(" from "), u8, tag(" to "), u8))(input)?; 101 | let (from, to) = (from - 1, to - 1); 102 | Ok((input, Instruction { amount, from, to })) 103 | } 104 | 105 | fn instructions(input: &str) -> IResult> { 106 | separated_list1(line_ending, instruction)(input) 107 | } 108 | 109 | fn get_top_crates(stacks: Stacks) -> String { 110 | stacks 111 | .into_iter() 112 | .map(|stack| { 113 | *stack 114 | .back() 115 | .expect("each stack should have at least one crate") 116 | }) 117 | .collect() 118 | } 119 | -------------------------------------------------------------------------------- /2022/src/bin/day5/sample.txt: -------------------------------------------------------------------------------- 1 | [D] 2 | [N] [C] 3 | [Z] [M] [P] 4 | 1 2 3 5 | 6 | move 1 from 2 to 1 7 | move 3 from 1 to 3 8 | move 2 from 2 to 1 9 | move 1 from 1 to 2 10 | -------------------------------------------------------------------------------- /2022/src/bin/day6/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use itertools::Itertools; 3 | 4 | boilerplate!(Day); 5 | 6 | impl BasicSolution for Day { 7 | type Parsed = &'static str; 8 | type Answer = usize; 9 | const SAMPLE_ANSWER_A: Self::TestAnswer = 7; 10 | const SAMPLE_ANSWER_B: Self::TestAnswer = 19; 11 | 12 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 13 | Ok(("", data)) 14 | } 15 | 16 | fn a(data: Self::Parsed) -> Self::Answer { 17 | solve::<4>(data) 18 | } 19 | 20 | fn b(data: Self::Parsed) -> Self::Answer { 21 | solve::<14>(data) 22 | } 23 | } 24 | 25 | fn solve(data: &str) -> usize { 26 | data.as_bytes() 27 | .windows(N) 28 | .position(|window| window.iter().all_unique()) 29 | .expect("There should be an all unique sequence") 30 | + N 31 | } 32 | -------------------------------------------------------------------------------- /2022/src/bin/day6/sample.txt: -------------------------------------------------------------------------------- 1 | mjqjpqmgbljsphdztnvjfqwrcgsmlb 2 | -------------------------------------------------------------------------------- /2022/src/bin/day7/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use advent_2022::*; 4 | use nom::{ 5 | branch::alt, 6 | bytes::complete::tag, 7 | character::complete::{line_ending, not_line_ending, u32}, 8 | multi::{fold_many0, fold_many1}, 9 | Parser, 10 | }; 11 | use nom_supreme::ParserExt; 12 | 13 | boilerplate!(Day); 14 | 15 | const TOTAL_DISK_SPACE: u32 = 70000000; 16 | const NEEDED_DISK_SPACE: u32 = 30000000; 17 | 18 | impl BasicSolution for Day { 19 | type Parsed = HashMap, u32>; 20 | type Answer = u32; 21 | const SAMPLE_ANSWER_A: Self::TestAnswer = 95437; 22 | const SAMPLE_ANSWER_B: Self::TestAnswer = 24933642; 23 | 24 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 25 | let mut current_dir = vec![]; 26 | fold_many1( 27 | parse_command, 28 | HashMap::new, 29 | move |mut directory_sizes, command| { 30 | compute_command(command, &mut current_dir, &mut directory_sizes); 31 | directory_sizes 32 | }, 33 | )(data) 34 | } 35 | 36 | fn a(data: Self::Parsed) -> Self::Answer { 37 | data.into_values().filter(|&size| size < 100000).sum() 38 | } 39 | 40 | fn b(data: Self::Parsed) -> Self::Answer { 41 | let root_directory_size = data[&vec![]]; 42 | let need_to_free = 43 | (root_directory_size + NEEDED_DISK_SPACE).saturating_sub(TOTAL_DISK_SPACE); 44 | data.into_values() 45 | .filter(|&size| size >= need_to_free) 46 | .min() 47 | .expect("At least one directory should be larger than the missing space") 48 | } 49 | } 50 | 51 | #[derive(Debug, Clone, Copy)] 52 | enum CDTarget<'a> { 53 | Root, 54 | Parent, 55 | Child(&'a str), 56 | } 57 | 58 | #[derive(Debug, Clone, Copy)] 59 | enum Command<'a> { 60 | LS(u32), 61 | CD(CDTarget<'a>), 62 | } 63 | 64 | use CDTarget::*; 65 | use Command::*; 66 | 67 | fn compute_command<'a>( 68 | command: Command<'a>, 69 | current_dir: &mut Vec<&'a str>, 70 | directory_sizes: &mut HashMap, u32>, 71 | ) { 72 | match command { 73 | LS(current_dir_size) => { 74 | for i in 0..=current_dir.len() { 75 | let path = ¤t_dir[..i]; 76 | if let Some(size) = directory_sizes.get_mut(path) { 77 | *size += current_dir_size; 78 | } else { 79 | directory_sizes.insert(path.to_vec(), current_dir_size); 80 | } 81 | } 82 | } 83 | CD(target) => match target { 84 | Root => current_dir.clear(), 85 | Parent => { 86 | current_dir.pop(); 87 | } 88 | Child(name) => current_dir.push(name), 89 | }, 90 | } 91 | } 92 | 93 | fn parse_cd(data: &str) -> IResult { 94 | tag("cd ") 95 | .precedes(alt(( 96 | tag("/").value(Root), 97 | tag("..").value(Parent), 98 | not_line_ending.map(Child), 99 | ))) 100 | .map(CD) 101 | .terminated(line_ending.opt()) 102 | .parse(data) 103 | } 104 | 105 | fn parse_ls_output_line(data: &str) -> IResult { 106 | u32.or(tag("dir").value(0)) 107 | .terminated(not_line_ending.and(line_ending.opt())) 108 | .parse(data) 109 | } 110 | 111 | fn parse_ls(data: &str) -> IResult { 112 | tag("ls\n") 113 | .precedes(fold_many0(parse_ls_output_line, || 0, |acc, cur| acc + cur)) 114 | .map(LS) 115 | .parse(data) 116 | } 117 | 118 | fn parse_command(data: &str) -> IResult { 119 | tag("$ ").precedes(alt((parse_cd, parse_ls))).parse(data) 120 | } 121 | -------------------------------------------------------------------------------- /2022/src/bin/day7/sample.txt: -------------------------------------------------------------------------------- 1 | $ cd / 2 | $ ls 3 | dir a 4 | 14848514 b.txt 5 | 8504156 c.dat 6 | dir d 7 | $ cd a 8 | $ ls 9 | dir e 10 | 29116 f 11 | 2557 g 12 | 62596 h.lst 13 | $ cd e 14 | $ ls 15 | 584 i 16 | $ cd .. 17 | $ cd .. 18 | $ cd d 19 | $ ls 20 | 4060174 j 21 | 8033020 d.log 22 | 5626152 d.ext 23 | 7214296 k 24 | -------------------------------------------------------------------------------- /2022/src/bin/day8/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | 3 | boilerplate!(Day); 4 | 5 | impl BasicSolution for Day { 6 | type Parsed = Vec<&'static [u8]>; 7 | type Answer = usize; 8 | const SAMPLE_ANSWER_A: Self::TestAnswer = 21; 9 | const SAMPLE_ANSWER_B: Self::TestAnswer = 8; 10 | 11 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 12 | Ok(("", data.lines().map(|line| line.as_bytes()).collect())) 13 | } 14 | 15 | fn a(data: Self::Parsed) -> Self::Answer { 16 | iter_tree_directions(&data) 17 | .map(|(tree, mut directions)| { 18 | let tree_is_higher = |other_tree| tree > other_tree; 19 | directions.0.all(tree_is_higher) 20 | || directions.1.all(tree_is_higher) 21 | || directions.2.all(tree_is_higher) 22 | || directions.3.all(tree_is_higher) 23 | }) 24 | .filter(|&visible| visible) 25 | .count() 26 | } 27 | 28 | fn b(data: Self::Parsed) -> Self::Answer { 29 | iter_tree_directions(&data) 30 | .map(|(tree, directions)| { 31 | count_trees_visible(tree, directions.0) 32 | * count_trees_visible(tree, directions.1) 33 | * count_trees_visible(tree, directions.2) 34 | * count_trees_visible(tree, directions.3) 35 | }) 36 | .max() 37 | .expect("At least one tree") 38 | } 39 | } 40 | 41 | fn iter_tree_directions<'a>( 42 | data: &'a [&'a [u8]], 43 | ) -> impl Iterator< 44 | Item = ( 45 | u8, 46 | ( 47 | impl ExactSizeIterator + 'a, 48 | impl ExactSizeIterator + 'a, 49 | impl ExactSizeIterator + 'a, 50 | impl ExactSizeIterator + 'a, 51 | ), 52 | ), 53 | > + 'a { 54 | data.iter().enumerate().flat_map(move |(i, &row)| { 55 | row.iter().enumerate().map(move |(j, &tree)| { 56 | ( 57 | tree, 58 | ( 59 | // to the right 60 | row[j + 1..].iter().copied(), 61 | // to the left 62 | row[..j].iter().rev().copied(), 63 | // to the bottom 64 | data[i + 1..].iter().map(move |r| r[j]), 65 | // to the top 66 | data[..i].iter().rev().map(move |r| r[j]), 67 | ), 68 | ) 69 | }) 70 | }) 71 | } 72 | 73 | fn count_trees_visible(tree: u8, mut trees: impl ExactSizeIterator) -> usize { 74 | let num_trees = trees.len(); 75 | trees 76 | .position(|other_tree| other_tree >= tree) 77 | .map_or(num_trees, |pos| pos + 1) 78 | } 79 | -------------------------------------------------------------------------------- /2022/src/bin/day8/sample.txt: -------------------------------------------------------------------------------- 1 | 30373 2 | 25512 3 | 65332 4 | 33549 5 | 35390 -------------------------------------------------------------------------------- /2022/src/bin/day9/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | use itertools::{repeat_n, Itertools}; 3 | use nom::{ 4 | branch::alt, 5 | character::complete::{char, line_ending, u8}, 6 | multi::separated_list1, 7 | sequence::separated_pair, 8 | }; 9 | use nom_supreme::ParserExt; 10 | 11 | boilerplate!(Day); 12 | 13 | impl BasicSolution for Day { 14 | type Parsed = Vec<(Direction, u8)>; 15 | type Answer = usize; 16 | const SAMPLE_ANSWER_A: Self::TestAnswer = 13; 17 | const SAMPLE_ANSWER_B: Self::TestAnswer = 1; 18 | 19 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 20 | separated_list1(line_ending, movement)(data) 21 | } 22 | 23 | fn a(data: Self::Parsed) -> Self::Answer { 24 | solve::<2>(&data) 25 | } 26 | 27 | fn b(data: Self::Parsed) -> Self::Answer { 28 | solve::<10>(&data) 29 | } 30 | } 31 | 32 | #[derive(Debug, Clone, Copy)] 33 | enum Direction { 34 | Right, 35 | Up, 36 | Left, 37 | Down, 38 | } 39 | use Direction::*; 40 | 41 | fn movement(input: &str) -> IResult<(Direction, u8)> { 42 | separated_pair( 43 | alt(( 44 | char('R').value(Right), 45 | char('U').value(Up), 46 | char('L').value(Left), 47 | char('D').value(Down), 48 | )), 49 | char(' '), 50 | u8, 51 | )(input) 52 | } 53 | 54 | type Point = (i16, i16); 55 | 56 | fn solve(data: &[(Direction, u8)]) -> usize { 57 | let mut points = [(0, 0); N]; 58 | data.iter() 59 | .flat_map(|&(direction, amount)| repeat_n(direction, amount as usize)) 60 | .map(|direction| { 61 | reposition_head(direction, &mut points[0]); 62 | (0..N - 1).for_each(|i| follow_knot(points[i], &mut points[i + 1])); 63 | points[N - 1] 64 | }) 65 | .unique() 66 | .count() 67 | } 68 | 69 | fn follow_knot(leader: Point, follower: &mut Point) { 70 | let diff = (leader.0 - follower.0, leader.1 - follower.1); 71 | if diff.0.abs() > 1 || diff.1.abs() > 1 { 72 | follower.0 += diff.0.signum(); 73 | follower.1 += diff.1.signum(); 74 | } 75 | } 76 | 77 | fn reposition_head(direction: Direction, head: &mut Point) { 78 | match direction { 79 | Right => head.0 += 1, 80 | Up => head.1 += 1, 81 | Left => head.0 -= 1, 82 | Down => head.1 -= 1, 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | #[test] 88 | fn extra_test_b() -> OutResult { 89 | const DATA: &str = "R 5 90 | U 8 91 | L 8 92 | D 3 93 | R 17 94 | D 10 95 | L 25 96 | U 20 97 | "; 98 | let parsed = Day::final_parse(DATA)?; 99 | assert_eq!(::b(parsed), 36); 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /2022/src/bin/day9/sample.txt: -------------------------------------------------------------------------------- 1 | R 4 2 | U 4 3 | L 3 4 | D 1 5 | R 4 6 | D 1 7 | L 5 8 | R 2 9 | -------------------------------------------------------------------------------- /2022/src/bin/template/main.rs: -------------------------------------------------------------------------------- 1 | use advent_2022::*; 2 | 3 | boilerplate!(Day); 4 | 5 | impl BasicSolution for Day { 6 | type Parsed = &'static str; 7 | type Answer = u32; 8 | const SAMPLE_ANSWER_A: Self::TestAnswer = 0; 9 | const SAMPLE_ANSWER_B: Self::TestAnswer = 0; 10 | 11 | fn parse(data: &'static str) -> IResult<'static, Self::Parsed> { 12 | Ok(("", data)) 13 | } 14 | 15 | fn a(data: Self::Parsed) -> Self::Answer { 16 | todo!("{data}") 17 | } 18 | 19 | fn b(_: Self::Parsed) -> Self::Answer { 20 | 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /2022/src/bin/template/sample.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazytieguy/advent-of-code/7c6f768cd909eab312b2320ea471afb583d7d167/2022/src/bin/template/sample.txt -------------------------------------------------------------------------------- /2022/timings.csv: -------------------------------------------------------------------------------- 1 | problem,mean,stddev,median 2 | 01-a,0.55,0.051,0.54 3 | 01-b,0.55,0.045,0.54 4 | 02-a,0.61,0.069,0.6 5 | 02-b,0.6,0.063,0.59 6 | 03-a,0.77,0.075,0.75 7 | 03-b,0.78,0.223,0.75 8 | 04-a,0.6,0.057,0.59 9 | 04-b,0.6,0.049,0.6 10 | 05-a,0.58,0.073,0.56 11 | 05-b,0.57,0.064,0.55 12 | 06-a,0.65,0.051,0.64 13 | 06-b,0.9,0.061,0.89 14 | 07-a,0.65,0.063,0.64 15 | 07-b,0.68,0.068,0.66 16 | 08-a,0.81,0.087,0.8 17 | 08-b,0.82,0.08,0.81 18 | 09-a,0.97,0.068,0.97 19 | 09-b,1.17,0.208,1.14 20 | 10-a,0.52,0.042,0.51 21 | 10-b,0.52,0.045,0.52 22 | 11-a,0.52,0.054,0.52 23 | 11-b,4.78,0.39,4.75 24 | 12-a,0.77,0.148,0.76 25 | 12-b,0.74,0.088,0.72 26 | 13-a,1.03,0.073,1.02 27 | 13-b,1.19,0.185,1.15 28 | 14-a,0.61,0.112,0.6 29 | 14-b,1.01,0.139,0.98 30 | 15-a,0.54,0.046,0.53 31 | 15-b,0.55,0.062,0.54 32 | 16-a,1.29,0.154,1.25 33 | 16-b,1.56,0.177,1.53 34 | 17-a,1.23,0.14,1.2 35 | 17-b,1.53,0.16,1.51 36 | 18-a,0.76,0.1,0.74 37 | 18-b,1.24,0.127,1.2 38 | 19-a,1.3,0.12,1.27 39 | 19-b,0.82,0.116,0.82 40 | 20-a,1.87,0.164,1.86 41 | 20-b,12.16,0.626,12.02 42 | 21-a,0.9,0.073,0.89 43 | 21-b,1.05,0.097,1.04 44 | 22-a,1.11,0.12,1.09 45 | 22-b,0.91,0.074,0.91 46 | 23-a,0.69,0.05,0.68 47 | 23-b,4.75,0.355,4.72 48 | 24-a,0.71,0.133,0.7 49 | 24-b,0.84,0.11,0.83 50 | 25-a,0.61,0.094,0.59 51 | -------------------------------------------------------------------------------- /2022/timings.md: -------------------------------------------------------------------------------- 1 | # 2022 Timings 2 | 3 | The [timings](timings.csv) file contains measurements made by [hyperfine](https://github.com/sharkdp/hyperfine). The units are milliseconds. 4 | -------------------------------------------------------------------------------- /2023/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advent-2023" 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 | arrayvec = "0.7.4" 11 | fxhash = "0.2.1" 12 | itertools = "0.12.0" 13 | num = "0.4.1" 14 | winnow = "0.5.19" 15 | -------------------------------------------------------------------------------- /2023/python/day1/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | SAMPLE_INPUT = """1abc2 4 | pqr3stu8vwx 5 | a1b2c3d4e5f 6 | treb7uchet 7 | """ 8 | SAMPLE_INPUT_B = """two1nine 9 | eightwothree 10 | abcone2threexyz 11 | xtwone3four 12 | 4nineeightseven2 13 | zoneight234 14 | 7pqrstsixteen 15 | """ 16 | INPUT = Path("data.txt").read_text() 17 | 18 | 19 | def part_a(input: str): 20 | return sum(extract_number(line) for line in input.splitlines()) 21 | 22 | 23 | def part_b(input: str): 24 | return sum(extract_number(fix_line(line)) for line in input.splitlines()) 25 | 26 | 27 | def fix_line(line: str): 28 | return ( 29 | line.replace("one", "o1e") 30 | .replace("two", "t2o") 31 | .replace("three", "t3e") 32 | .replace("four", "f4r") 33 | .replace("five", "f5e") 34 | .replace("six", "s6x") 35 | .replace("seven", "s7n") 36 | .replace("eight", "e8t") 37 | .replace("nine", "n9e") 38 | ) 39 | 40 | 41 | def extract_number(line: str): 42 | first = "" 43 | last = "" 44 | for char in line: 45 | if char.isdigit(): 46 | last = char 47 | if not first: 48 | first = char 49 | return int(first + last) 50 | 51 | 52 | def test_part_a(capsys): 53 | assert part_a(SAMPLE_INPUT) == 142 54 | with capsys.disabled(): 55 | print(f"Part A: {part_a(INPUT)}") 56 | 57 | 58 | def test_part_b(capsys): 59 | assert part_b(SAMPLE_INPUT_B) == 281 60 | with capsys.disabled(): 61 | print(f"Part B: {part_b(INPUT)}") 62 | -------------------------------------------------------------------------------- /2023/python/day12/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | SAMPLE_INPUT = """???.### 1,1,3 4 | .??..??...?##. 1,1,3 5 | ?#?#?#?#?#?#?#? 1,3,1,6 6 | ????.#...#... 4,1,1 7 | ????.######..#####. 1,6,5 8 | ?###???????? 3,2,1 9 | """ 10 | SAMPLE_INPUT_B = SAMPLE_INPUT 11 | INPUT = Path("data.txt").read_text() 12 | 13 | 14 | def part_a(input: str): 15 | total = 0 16 | for line in input.splitlines(): 17 | springs, group_sizes = line.split() 18 | group_sizes = [int(n) for n in group_sizes.split(",")] 19 | total += count_arrangements(springs, group_sizes) 20 | return total 21 | 22 | 23 | def part_b(input: str): 24 | total = 0 25 | for line in input.splitlines(): 26 | springs, group_sizes = line.split() 27 | group_sizes = [int(n) for n in group_sizes.split(",")] 28 | springs = "?".join(springs for _ in range(5)) 29 | group_sizes = group_sizes * 5 30 | total += count_arrangements(springs, group_sizes) 31 | return total 32 | 33 | 34 | def count_arrangements( 35 | springs: str, group_sizes: list[int], cache: list[list[int | None]] | None = None 36 | ) -> int: 37 | if cache is None: 38 | # e for end, to cover an edge case 39 | springs += "e" 40 | cache = [[None for _ in range(len(group_sizes))] for _ in range(len(springs))] 41 | 42 | if not group_sizes: 43 | if "#" in springs: 44 | # too many ?s were replaced with # 45 | return 0 46 | # all remaining ?s are . 47 | return 1 48 | 49 | if len(springs) < sum(group_sizes) + len(group_sizes): 50 | # not enough space for remaining numbers 51 | return 0 52 | 53 | if (cached := cache[len(springs) - 1][len(group_sizes) - 1]) is not None: 54 | return cached 55 | 56 | arangements = 0 57 | if springs[0] in ".?": 58 | arangements += count_arrangements(springs[1:], group_sizes, cache) 59 | 60 | next_group_size = group_sizes[0] 61 | if "." not in springs[:next_group_size] and springs[next_group_size] != "#": 62 | skip = next_group_size + 1 63 | arangements += count_arrangements(springs[skip:], group_sizes[1:], cache) 64 | 65 | cache[len(springs) - 1][len(group_sizes) - 1] = arangements 66 | return arangements 67 | 68 | 69 | def test_count_arrangements(): 70 | assert count_arrangements("???.###", [1, 1, 3]) == 1 71 | assert count_arrangements(".??..??...?##.", [1, 1, 3]) == 4 72 | assert count_arrangements("?#?#?#?#?#?#?#?", [1, 3, 1, 6]) == 1 73 | assert count_arrangements("????.#...#...", [4, 1, 1]) == 1 74 | assert count_arrangements("????.######..#####.", [1, 6, 5]) == 4 75 | assert count_arrangements("?###????????", [3, 2, 1]) == 10 76 | 77 | 78 | def test_part_a(capsys): 79 | assert part_a(SAMPLE_INPUT) == 21 80 | with capsys.disabled(): 81 | print(f"Part A: {part_a(INPUT)}") 82 | 83 | 84 | def test_part_b(capsys): 85 | assert part_b(SAMPLE_INPUT_B) == 525152 86 | with capsys.disabled(): 87 | print(f"Part B: {part_b(INPUT)}") 88 | -------------------------------------------------------------------------------- /2023/python/day13/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | SAMPLE_INPUT = """#.##..##. 4 | ..#.##.#. 5 | ##......# 6 | ##......# 7 | ..#.##.#. 8 | ..##..##. 9 | #.#.##.#. 10 | 11 | #...##..# 12 | #....#..# 13 | ..##..### 14 | #####.##. 15 | #####.##. 16 | ..##..### 17 | #....#..# 18 | """ 19 | SAMPLE_INPUT_B = SAMPLE_INPUT 20 | INPUT = Path("data.txt").read_text() 21 | 22 | 23 | def str_mismatches(a: str, b: str) -> int: 24 | return sum(ac != bc for ac, bc in zip(a, b)) 25 | 26 | 27 | def find_reflection_index(note: list[str], allowed_mismatches: int = 0) -> int | None: 28 | for i in range(1, len(note)): 29 | mirrored_row_pairs = zip(note[i:], reversed(note[:i])) 30 | mismatches = sum(str_mismatches(a, b) for a, b in mirrored_row_pairs) 31 | if mismatches == allowed_mismatches: 32 | return i 33 | return None 34 | 35 | 36 | def score(note: list[str], allowed_mismatches: int = 0) -> int: 37 | if mirror_row := find_reflection_index(note, allowed_mismatches): 38 | return mirror_row * 100 39 | transposed = ["".join(col) for col in zip(*note)] 40 | if mirror_column := find_reflection_index(transposed, allowed_mismatches): 41 | return mirror_column 42 | raise ValueError("No reflection found:\n" + "\n".join(note)) 43 | 44 | 45 | def parse_input(input: str): 46 | return (note.splitlines() for note in input.split("\n\n")) 47 | 48 | 49 | def part_a(input: str): 50 | return sum(score(note) for note in parse_input(input)) 51 | 52 | 53 | def part_b(input: str): 54 | return sum(score(note, 1) for note in parse_input(input)) 55 | 56 | 57 | def test_part_a(capsys): 58 | assert part_a(SAMPLE_INPUT) == 405 59 | with capsys.disabled(): 60 | print(f"Part A: {part_a(INPUT)}") 61 | 62 | 63 | def test_part_b(capsys): 64 | assert part_b(SAMPLE_INPUT_B) == 400 65 | with capsys.disabled(): 66 | print(f"Part B: {part_b(INPUT)}") 67 | -------------------------------------------------------------------------------- /2023/python/day14/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Iterable 3 | 4 | SAMPLE_INPUT = """\ 5 | O....#.... 6 | O.OO#....# 7 | .....##... 8 | OO.#O....O 9 | .O.....O#. 10 | O.#..O.#.# 11 | ..O..#O..O 12 | .......O.. 13 | #....###.. 14 | #OO..#.... 15 | """ 16 | SAMPLE_INPUT_B = SAMPLE_INPUT 17 | INPUT = Path("data.txt").read_text() 18 | 19 | 20 | def part_a(input: str): 21 | grid = input.splitlines() 22 | tilted_north = list(zip(*map(roll_start, zip(*grid)))) 23 | return count_north_beam_load(tilted_north) 24 | 25 | 26 | def part_b(input: str): 27 | grid = input.splitlines() 28 | billionth_grid = find_billionth_grid(grid) 29 | return count_north_beam_load(billionth_grid) 30 | 31 | 32 | def find_billionth_grid(initial_grid: list[str]) -> list[str]: 33 | grid_history = [initial_grid] 34 | for num_cycles in range(1, 1000): 35 | next_grid = spin_cycle(grid_history[-1]) 36 | for i, grid in enumerate(grid_history): 37 | if grid == next_grid: 38 | start = i 39 | length = num_cycles - start 40 | offset = (1_000_000_000 - start) % length 41 | return grid_history[start + offset] 42 | grid_history.append(next_grid) 43 | raise RuntimeError("No match found") 44 | 45 | 46 | def spin_cycle(grid: list[str]) -> list[str]: 47 | tilted_north = zip(*map(roll_start, zip(*grid))) 48 | tilted_west = map(roll_start, tilted_north) 49 | tilted_south = reversed( 50 | list(zip(*(roll_start(reversed(row)) for row in zip(*tilted_west)))) 51 | ) 52 | tilted_east = ["".join(reversed(roll_start(reversed(row)))) for row in tilted_south] 53 | return tilted_east 54 | 55 | 56 | def roll_start(row: Iterable[str]) -> str: 57 | new_row = "" 58 | for i, c in enumerate(row): 59 | if c == "O": 60 | new_row += "O" 61 | if c == "#": 62 | new_row += "." * (i - len(new_row)) 63 | new_row += "#" 64 | new_row += "." * (i + 1 - len(new_row)) # type: ignore 65 | return new_row 66 | 67 | 68 | def test_roll_start(): 69 | assert roll_start(".O#..O.#.#") == "O.#O...#.#" 70 | 71 | 72 | def count_north_beam_load(grid: list[str]): 73 | return sum(map(count_column_load, zip(*grid))) 74 | 75 | 76 | def count_column_load(column: str): 77 | return sum(i + 1 for i, c in enumerate(reversed(column)) if c == "O") 78 | 79 | 80 | def test_part_a(capsys): 81 | assert part_a(SAMPLE_INPUT) == 136 82 | with capsys.disabled(): 83 | print(f"Part A: {part_a(INPUT)}") 84 | 85 | 86 | def test_part_b(capsys): 87 | assert part_b(SAMPLE_INPUT_B) == 64 88 | with capsys.disabled(): 89 | print(f"Part B: {part_b(INPUT)}") 90 | -------------------------------------------------------------------------------- /2023/python/day16/main.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from pathlib import Path 3 | 4 | SAMPLE_INPUT = r""".|...\.... 5 | |.-.\..... 6 | .....|-... 7 | ........|. 8 | .......... 9 | .........\ 10 | ..../.\\.. 11 | .-.-/..|.. 12 | .|....-|.\ 13 | ..//.|.... 14 | """ 15 | SAMPLE_INPUT_B = SAMPLE_INPUT 16 | INPUT = Path("data.txt").read_text() 17 | 18 | Queue = collections.deque[tuple[tuple[int, int], tuple[int, int]]] 19 | 20 | 21 | def part_a(input: str, start=((0, 0), (0, 1))): 22 | grid = input.splitlines() 23 | seen = set() 24 | queue: Queue = collections.deque([start]) 25 | while queue: 26 | beam = queue.popleft() 27 | if beam in seen: 28 | continue 29 | (row, col), (drow, dcol) = beam 30 | if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]): 31 | continue 32 | seen.add(beam) 33 | match (drow, dcol): 34 | case (0, 1): 35 | match grid[row][col]: 36 | case "|": 37 | queue.append(((row + 1, col), (1, 0))) 38 | queue.append(((row - 1, col), (-1, 0))) 39 | case "/": 40 | queue.append(((row - 1, col), (-1, 0))) 41 | case "\\": 42 | queue.append(((row + 1, col), (1, 0))) 43 | case _: 44 | queue.append(((row, col + 1), (0, 1))) 45 | case (0, -1): 46 | match grid[row][col]: 47 | case "|": 48 | queue.append(((row + 1, col), (1, 0))) 49 | queue.append(((row - 1, col), (-1, 0))) 50 | case "/": 51 | queue.append(((row + 1, col), (1, 0))) 52 | case "\\": 53 | queue.append(((row - 1, col), (-1, 0))) 54 | case _: 55 | queue.append(((row, col - 1), (0, -1))) 56 | case (1, 0): 57 | match grid[row][col]: 58 | case "-": 59 | queue.append(((row, col + 1), (0, 1))) 60 | queue.append(((row, col - 1), (0, -1))) 61 | case "/": 62 | queue.append(((row, col - 1), (0, -1))) 63 | case "\\": 64 | queue.append(((row, col + 1), (0, 1))) 65 | case _: 66 | queue.append(((row + 1, col), (1, 0))) 67 | case (-1, 0): 68 | match grid[row][col]: 69 | case "-": 70 | queue.append(((row, col + 1), (0, 1))) 71 | queue.append(((row, col - 1), (0, -1))) 72 | case "/": 73 | queue.append(((row, col + 1), (0, 1))) 74 | case "\\": 75 | queue.append(((row, col - 1), (0, -1))) 76 | case _: 77 | queue.append(((row - 1, col), (-1, 0))) 78 | return len({coords for coords, _ in seen}) 79 | 80 | 81 | def part_b(input: str): 82 | grid = input.splitlines() 83 | rows = len(grid) 84 | cols = len(grid[0]) 85 | max_energized = 0 86 | for row in range(rows): 87 | max_energized = max(max_energized, part_a(input, ((row, 0), (0, 1)))) 88 | max_energized = max(max_energized, part_a(input, ((row, cols - 1), (0, -1)))) 89 | for col in range(cols): 90 | max_energized = max(max_energized, part_a(input, ((0, col), (1, 0)))) 91 | max_energized = max(max_energized, part_a(input, ((rows - 1, col), (-1, 0)))) 92 | return max_energized 93 | 94 | 95 | def test_part_a(capsys): 96 | assert part_a(SAMPLE_INPUT) == 46 97 | with capsys.disabled(): 98 | print(f"Part A: {part_a(INPUT)}") 99 | 100 | 101 | def test_part_b(capsys): 102 | assert part_b(SAMPLE_INPUT_B) == 51 103 | with capsys.disabled(): 104 | print(f"Part B: {part_b(INPUT)}") 105 | -------------------------------------------------------------------------------- /2023/python/day2/main.py: -------------------------------------------------------------------------------- 1 | import re 2 | from dataclasses import dataclass 3 | from pathlib import Path 4 | 5 | SAMPLE_INPUT = """Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 6 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 7 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 8 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 9 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green 10 | """ 11 | INPUT = Path("data.txt").read_text() 12 | 13 | 14 | @dataclass 15 | class Game: 16 | red: int 17 | green: int 18 | blue: int 19 | 20 | 21 | def part_a(input: str): 22 | games = [game(line) for line in input.splitlines()] 23 | return sum( 24 | i + 1 25 | for i, game in enumerate(games) 26 | if game.red <= 12 and game.green <= 13 and game.blue <= 14 27 | ) 28 | 29 | 30 | def part_b(input: str): 31 | games = [game(line) for line in input.splitlines()] 32 | return sum(game.red * game.green * game.blue for game in games) 33 | 34 | 35 | RED = re.compile(r"(\d+) red") 36 | GREEN = re.compile(r"(\d+) green") 37 | BLUE = re.compile(r"(\d+) blue") 38 | 39 | 40 | def game(line: str): 41 | red = max(int(match.group(1)) for match in RED.finditer(line)) 42 | green = max(int(match.group(1)) for match in GREEN.finditer(line)) 43 | blue = max(int(match.group(1)) for match in BLUE.finditer(line)) 44 | 45 | return Game( 46 | red=red, 47 | green=green, 48 | blue=blue, 49 | ) 50 | 51 | 52 | def test_part_a(capsys): 53 | assert part_a(SAMPLE_INPUT) == 8 54 | with capsys.disabled(): 55 | print(f"Part A: {part_a(INPUT)}") 56 | 57 | 58 | def test_part_b(capsys): 59 | assert part_b(SAMPLE_INPUT) == 2286 60 | with capsys.disabled(): 61 | print(f"Part B: {part_b(INPUT)}") 62 | -------------------------------------------------------------------------------- /2023/python/day21/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | SAMPLE_INPUT = """\ 4 | ........... 5 | .....###.#. 6 | .###.##..#. 7 | ..#.#...#.. 8 | ....#.#.... 9 | .##..S####. 10 | .##..#...#. 11 | .......##.. 12 | .##.#.####. 13 | .##..##.##. 14 | ........... 15 | """ 16 | SAMPLE_INPUT_B = SAMPLE_INPUT 17 | INPUT = Path("data.txt").read_text() 18 | 19 | 20 | def find_start(grid: list[str]) -> tuple[int, int]: 21 | for row, line in enumerate(grid): 22 | for col, char in enumerate(line): 23 | if char == "S": 24 | return (row, col) 25 | raise ValueError("No start found") 26 | 27 | 28 | def part_a(input: str, steps: int, start: tuple[int, int] | None = None): 29 | grid = input.splitlines() 30 | if start is None: 31 | start = find_start(grid) 32 | 33 | even_reachable = {start} 34 | odd_reachable = set() 35 | frontier = even_reachable 36 | 37 | def coords_invalid(row, col): 38 | return ( 39 | row < 0 40 | or row >= len(grid) 41 | or col < 0 42 | or col >= len(grid[0]) 43 | or grid[row][col] == "#" 44 | ) 45 | 46 | while steps > 0: 47 | steps -= 2 48 | to_add_even = [] 49 | to_add_odd = [] 50 | for row, col in frontier: 51 | for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]: 52 | first_step_row = row + dr 53 | first_step_col = col + dc 54 | if coords_invalid(first_step_row, first_step_col): 55 | continue 56 | to_add_odd.append((first_step_row, first_step_col)) 57 | for dr2, dc2 in [(0, 1), (0, -1), (1, 0), (-1, 0)]: 58 | second_step_row = first_step_row + dr2 59 | second_step_col = first_step_col + dc2 60 | if coords_invalid(second_step_row, second_step_col): 61 | continue 62 | to_add_even.append((second_step_row, second_step_col)) 63 | frontier = set(to_add_even) - even_reachable 64 | even_reachable.update(to_add_even) 65 | odd_reachable.update(to_add_odd) 66 | 67 | return len(even_reachable) if steps == 0 else len(odd_reachable) 68 | 69 | 70 | def part_b(input: str): 71 | steps = 26501365 72 | n = steps // 131 # number of full grid repetitions in each direction 73 | count_full_odd = 1 + 2 * n + n**2 74 | count_full_even = n**2 75 | count_negative_odd_corners = n + 1 76 | count_positive_even_corners = n 77 | full_odd = part_a(input, 263) # a large odd number 78 | full_even = part_a(input, 262) # a large even number 79 | half_odd = part_a(input, 65) 80 | even_corners = [ 81 | part_a(input, 64, start) 82 | for start in [ 83 | (0, 0), 84 | (0, 130), 85 | (130, 0), 86 | (130, 130), 87 | ] 88 | ] 89 | return ( 90 | count_full_odd * full_odd 91 | + count_full_even * full_even 92 | - count_negative_odd_corners * (full_odd - half_odd) 93 | + sum(count_positive_even_corners * corner for corner in even_corners) 94 | ) 95 | 96 | 97 | def test_part_a(capsys): 98 | assert part_a(SAMPLE_INPUT, 6) == 16 99 | with capsys.disabled(): 100 | print(f"Part A: {part_a(INPUT, 64)}") 101 | 102 | 103 | def test_part_b(capsys): 104 | with capsys.disabled(): 105 | print(f"Part B: {part_b(INPUT)}") 106 | -------------------------------------------------------------------------------- /2023/python/day24/main.py: -------------------------------------------------------------------------------- 1 | import itertools # noqa: F401 2 | from dataclasses import dataclass # noqa: F401 3 | from pathlib import Path 4 | 5 | import pytest 6 | import sympy 7 | 8 | SAMPLE_INPUT = """19, 13, 30 @ -2, 1, -2 9 | 18, 19, 22 @ -1, -1, -2 10 | 20, 25, 34 @ -2, -2, -4 11 | 12, 31, 28 @ -1, -2, -1 12 | 20, 19, 15 @ 1, -5, -3 13 | """ 14 | SAMPLE_INPUT_B = SAMPLE_INPUT 15 | INPUT = Path("data.txt").read_text() 16 | 17 | 18 | @dataclass 19 | class HailStone: 20 | initial_position: tuple[int, int, int] 21 | velocity: tuple[int, int, int] 22 | 23 | def intersection_xy(self, other: "HailStone") -> tuple[float, float] | None: 24 | x1, y1, _ = self.initial_position 25 | x2, y2, _ = other.initial_position 26 | vx1, vy1, _ = self.velocity 27 | vx2, vy2, _ = other.velocity 28 | try: 29 | t2 = (y2 * vx1 - y1 * vx1 + x1 * vy1 - x2 * vy1) / (vx2 * vy1 - vy2 * vx1) 30 | except ZeroDivisionError: 31 | return None 32 | t1 = (x2 + t2 * vx2 - x1) / vx1 33 | if t1 < 0 or t2 < 0: 34 | return None 35 | x = x1 + t1 * vx1 36 | y = y1 + t1 * vy1 37 | return x, y 38 | 39 | 40 | def find_stone_position(hailstones: list[HailStone]) -> tuple[int, int, int]: 41 | h1, h2, h3 = hailstones[:3] 42 | x1, y1, z1 = h1.initial_position 43 | x2, y2, z2 = h2.initial_position 44 | x3, y3, z3 = h3.initial_position 45 | v1x, v1y, v1z = h1.velocity 46 | v2x, v2y, v2z = h2.velocity 47 | v3x, v3y, v3z = h3.velocity 48 | x, y, z, vx, vy, vz = sympy.symbols("x y z vx vy vz", integer=True) 49 | t1, t2, t3 = sympy.symbols("t1 t2 t3", real=True) 50 | (solution,) = sympy.solve( 51 | [ 52 | x + t1 * vx - x1 + t1 * v1x, 53 | y + t1 * vy - y1 + t1 * v1y, 54 | z + t1 * vz - z1 + t1 * v1z, 55 | x + t2 * vx - x2 + t2 * v2x, 56 | y + t2 * vy - y2 + t2 * v2y, 57 | z + t2 * vz - z2 + t2 * v2z, 58 | x + t3 * vx - x3 + t3 * v3x, 59 | y + t3 * vy - y3 + t3 * v3y, 60 | z + t3 * vz - z3 + t3 * v3z, 61 | ], 62 | ) 63 | return solution[x], solution[y], solution[z] 64 | 65 | 66 | def test_intersection_xy(): 67 | h1 = HailStone((19, 13, 30), (-2, 1, -2)) 68 | h2 = HailStone((18, 19, 22), (-1, -1, -2)) 69 | assert h1.intersection_xy(h2) == pytest.approx((14.333, 15.333), 0.001) 70 | h4 = HailStone((20, 19, 15), (1, -5, -3)) 71 | assert h1.intersection_xy(h4) is None 72 | h3 = HailStone((20, 25, 34), (-2, -2, -4)) 73 | assert h2.intersection_xy(h3) is None 74 | 75 | 76 | def parse_hailstone(line: str) -> HailStone: 77 | positions, velocities = line.split(" @ ") 78 | positions = tuple(map(int, positions.split(","))) 79 | velocities = tuple(map(int, velocities.split(","))) 80 | return HailStone(positions, velocities) # type: ignore 81 | 82 | 83 | def part_a(input: str, min_coord: int, max_coord: int): 84 | hailstones = [parse_hailstone(line) for line in input.splitlines()] 85 | total = 0 86 | for h1, h2 in itertools.combinations(hailstones, 2): 87 | if (intersection := h1.intersection_xy(h2)) is not None: 88 | x, y = intersection 89 | if min_coord <= x <= max_coord and min_coord <= y <= max_coord: 90 | total += 1 91 | return total 92 | 93 | 94 | def part_b(input: str): 95 | hailstones = [parse_hailstone(line) for line in input.splitlines()] 96 | x, y, z = find_stone_position(hailstones) 97 | return x + y + z 98 | 99 | 100 | def test_part_a(capsys): 101 | assert part_a(SAMPLE_INPUT, 7, 27) == 2 102 | with capsys.disabled(): 103 | print(f"Part A: {part_a(INPUT, 200000000000000, 400000000000000)}") 104 | 105 | 106 | def test_part_b(capsys): 107 | assert part_b(SAMPLE_INPUT_B) == 47 108 | with capsys.disabled(): 109 | print(f"Part B: {part_b(INPUT)}") 110 | -------------------------------------------------------------------------------- /2023/python/day25/main.py: -------------------------------------------------------------------------------- 1 | import collections # noqa: F401 2 | import copy 3 | import random 4 | from pathlib import Path 5 | 6 | SAMPLE_INPUT = """jqt: rhn xhk nvd 7 | rsh: frs pzl lsr 8 | xhk: hfx 9 | cmg: qnr nvd lhk bvb 10 | rhn: xhk bvb hfx 11 | bvb: xhk hfx 12 | pzl: lsr hfx nvd 13 | qnr: nvd 14 | ntq: jqt hfx bvb xhk 15 | nvd: lhk 16 | lsr: lhk 17 | rzs: qnr cmg lsr rsh 18 | frs: qnr lhk lsr 19 | """ 20 | INPUT = Path("data.txt").read_text() 21 | 22 | 23 | def connected_components(graph: dict[str, set[str]]) -> set[int]: 24 | seen = set() 25 | components = set() 26 | for node in graph: 27 | if node in seen: 28 | continue 29 | component_size = 0 30 | stack = [node] 31 | while stack: 32 | node = stack.pop() 33 | if node in seen: 34 | continue 35 | component_size += 1 36 | seen.add(node) 37 | stack.extend(graph[node]) 38 | components.add(component_size) 39 | return components 40 | 41 | 42 | def part_a(input: str): 43 | graph = collections.defaultdict(set) 44 | for line in input.splitlines(): 45 | (source, destinations) = line.split(": ") 46 | for destination in destinations.split(): 47 | graph[source].add(destination) 48 | graph[destination].add(source) 49 | graph = dict(graph) 50 | while True: 51 | # Remove a random edge for each node, 52 | # After enough tries we'll probably hit the correct 3 edges. 53 | # The rest of the graph is connected enough 54 | # that we don't need to worry about the missing edges. 55 | seen = set() 56 | test_graph = copy.deepcopy(graph) 57 | for node, adjacent in graph.items(): 58 | viable_remove_choices = [a for a in adjacent if a not in seen] 59 | if node in seen or not viable_remove_choices: 60 | continue 61 | remove = random.choice(viable_remove_choices) 62 | test_graph[node].remove(remove) 63 | test_graph[remove].remove(node) 64 | seen.add(node) 65 | seen.add(remove) 66 | components = connected_components(test_graph) 67 | if len(components) == 2: 68 | first, second = components 69 | return first * second 70 | 71 | 72 | def test_part_a(capsys): 73 | with capsys.disabled(): 74 | assert part_a(SAMPLE_INPUT) == 54 75 | print(f"Part A: {part_a(INPUT)}") 76 | -------------------------------------------------------------------------------- /2023/python/day3/main.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import defaultdict 3 | from dataclasses import dataclass 4 | from itertools import chain 5 | from pathlib import Path 6 | 7 | SAMPLE_INPUT = """467..114.. 8 | ...*...... 9 | ..35..633. 10 | ......#... 11 | 617*...... 12 | .....+.58. 13 | ..592..... 14 | ......755. 15 | ...$.*.... 16 | .664.598.. 17 | """ 18 | SAMPLE_INPUT_B = SAMPLE_INPUT 19 | INPUT = Path("data.txt").read_text() 20 | 21 | NUMBER = re.compile(r"\d+") 22 | 23 | 24 | @dataclass 25 | class Number: 26 | value: int 27 | row: int 28 | start_column: int 29 | end_column: int 30 | 31 | def adjacent_coords(self): 32 | return ( 33 | (row, column) 34 | for row in range(self.row - 1, self.row + 2) 35 | for column in range(self.start_column - 1, self.end_column + 1) 36 | ) 37 | 38 | 39 | def part_a(input: str): 40 | grid = input.splitlines() 41 | numbers = parse_numbers(grid) 42 | return sum( 43 | number.value 44 | for number in numbers 45 | if any( 46 | ((c := get_2d(grid, row, column)) and c not in ".0123456789") 47 | for row, column in number.adjacent_coords() 48 | ) 49 | ) 50 | 51 | 52 | def part_b(input: str): 53 | grid = input.splitlines() 54 | numbers = parse_numbers(grid) 55 | possible_gears = defaultdict(list) 56 | for number in numbers: 57 | for row, column in number.adjacent_coords(): 58 | if get_2d(grid, row, column) == "*": 59 | possible_gears[(row, column)].append(number.value) 60 | return sum(vals[0] * vals[1] for vals in possible_gears.values() if len(vals) == 2) 61 | 62 | 63 | def parse_numbers(grid: list[str]): 64 | return chain.from_iterable( 65 | find_numbers(line_num, line) for line_num, line in enumerate(grid) 66 | ) 67 | 68 | 69 | def find_numbers(line_num: int, line: str): 70 | return [ 71 | Number( 72 | value=int(match.group(0)), 73 | row=line_num, 74 | start_column=match.start(), 75 | end_column=match.end(), 76 | ) 77 | for match in NUMBER.finditer(line) 78 | ] 79 | 80 | 81 | def get_2d(grid: list[str], row: int, column: int): 82 | if row < 0 or row >= len(grid): 83 | return None 84 | if column < 0 or column >= len(grid[row]): 85 | return None 86 | return grid[row][column] 87 | 88 | 89 | def test_part_a(capsys): 90 | with capsys.disabled(): 91 | assert part_a(SAMPLE_INPUT) == 4361 92 | print(f"Part A: {part_a(INPUT)}") 93 | 94 | 95 | def test_part_b(capsys): 96 | assert part_b(SAMPLE_INPUT_B) == 467835 97 | with capsys.disabled(): 98 | print(f"Part B: {part_b(INPUT)}") 99 | -------------------------------------------------------------------------------- /2023/python/day4/main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | SAMPLE_INPUT = """Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 4 | Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 5 | Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 6 | Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 7 | Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 8 | Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 9 | """ 10 | SAMPLE_INPUT_B = SAMPLE_INPUT 11 | INPUT = Path("data.txt").read_text() 12 | 13 | 14 | def part_a(input: str): 15 | return sum(card_score(*parse_card(card)) for card in input.splitlines()) 16 | 17 | 18 | def part_b(input: str): 19 | card_scores = [card_wins(*parse_card(card)) for card in input.splitlines()] 20 | card_copies = [1] * len(card_scores) 21 | for i, score in enumerate(card_scores): 22 | for j in range(i + 1, score + i + 1): 23 | try: 24 | card_copies[j] += card_copies[i] 25 | except IndexError: 26 | break 27 | return sum(card_copies) 28 | 29 | 30 | def parse_card(card: str) -> tuple[set[str], set[str]]: 31 | _, numbers = card.split(":") 32 | winning, owned = numbers.split("|") 33 | winning = set(winning.split()) 34 | owned = set(owned.split()) 35 | return winning, owned 36 | 37 | 38 | def card_score(winning: set[str], owned: set[str]) -> int: 39 | wins = card_wins(winning, owned) 40 | return 2**wins // 2 41 | 42 | 43 | def card_wins(winning: set[str], owned: set[str]) -> int: 44 | return len(winning & owned) 45 | 46 | 47 | def test_part_a(capsys): 48 | assert part_a(SAMPLE_INPUT) == 13 49 | with capsys.disabled(): 50 | print(f"Part A: {part_a(INPUT)}") 51 | 52 | 53 | def test_part_b(capsys): 54 | assert part_b(SAMPLE_INPUT_B) == 30 55 | with capsys.disabled(): 56 | print(f"Part B: {part_b(INPUT)}") 57 | -------------------------------------------------------------------------------- /2023/python/day5/main.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from pathlib import Path 3 | from typing import Callable, Generic, TypeVar 4 | 5 | import toolz 6 | 7 | SAMPLE_INPUT = """seeds: 79 14 55 13 8 | 9 | seed-to-soil map: 10 | 50 98 2 11 | 52 50 48 12 | 13 | soil-to-fertilizer map: 14 | 0 15 37 15 | 37 52 2 16 | 39 0 15 17 | 18 | fertilizer-to-water map: 19 | 49 53 8 20 | 0 11 42 21 | 42 0 7 22 | 57 7 4 23 | 24 | water-to-light map: 25 | 88 18 7 26 | 18 25 70 27 | 28 | light-to-temperature map: 29 | 45 77 23 30 | 81 45 19 31 | 68 64 13 32 | 33 | temperature-to-humidity map: 34 | 0 69 1 35 | 1 0 69 36 | 37 | humidity-to-location map: 38 | 60 56 37 39 | 56 93 4 40 | """ 41 | SAMPLE_INPUT_B = SAMPLE_INPUT 42 | INPUT = Path("data.txt").read_text() 43 | 44 | 45 | @dataclass 46 | class Mapping: 47 | from_range: range 48 | dest: int 49 | 50 | 51 | T = TypeVar("T") 52 | 53 | 54 | @dataclass 55 | class MappingResult(Generic[T]): 56 | mapped: T | None = None 57 | not_mapped: list[T] = field(default_factory=list) 58 | 59 | 60 | def part_a(input: str): 61 | seeds, all_mappings = parse_input(input) 62 | locations = map_all(seeds, all_mappings, map_single) 63 | return min(locations) 64 | 65 | 66 | def part_b(input: str): 67 | seeds, all_mappings = parse_input(input) 68 | seed_ranges = [range(a, a + b) for a, b in toolz.itertoolz.partition(2, seeds)] 69 | location_ranges = map_all(seed_ranges, all_mappings, map_range) 70 | return min(r.start for r in location_ranges) 71 | 72 | 73 | def map_all( 74 | objects: list[T], 75 | all_mappings: list[list[Mapping]], 76 | mapping_function: Callable[[Mapping, T], MappingResult[T]], 77 | ) -> list[T]: 78 | for mappings in all_mappings: 79 | next_objects = [] 80 | for mapping in mappings: 81 | leftovers = [] 82 | for o in objects: 83 | mapping_result = mapping_function(mapping, o) 84 | if mapping_result.mapped is not None: 85 | next_objects.append(mapping_result.mapped) 86 | for not_mapped in mapping_result.not_mapped: 87 | leftovers.append(not_mapped) 88 | objects = leftovers 89 | objects = next_objects + objects 90 | return objects 91 | 92 | 93 | def map_single(mapping: Mapping, input: int) -> MappingResult[int]: 94 | if input in mapping.from_range: 95 | return MappingResult(mapped=input + mapping.dest - mapping.from_range.start) 96 | return MappingResult(not_mapped=[input]) 97 | 98 | 99 | def map_range(mapping: Mapping, input: range) -> MappingResult[range]: 100 | before = range(input.start, min(input.stop, mapping.from_range.start)) 101 | 102 | mapped = range( 103 | max(input.start, mapping.from_range.start) 104 | + mapping.dest 105 | - mapping.from_range.start, 106 | min(input.stop, mapping.from_range.stop) 107 | + mapping.dest 108 | - mapping.from_range.start, 109 | ) 110 | 111 | after = range(max(input.start, mapping.from_range.stop), input.stop) 112 | 113 | return MappingResult( 114 | mapped=mapped if not_empty(mapped) else None, 115 | not_mapped=list(filter(not_empty, [before, after])), 116 | ) 117 | 118 | 119 | def not_empty(r: range) -> bool: 120 | return r.stop > r.start 121 | 122 | 123 | def parse_input(input: str) -> tuple[list[int], list[list[Mapping]]]: 124 | seeds, *all_mappings = input.split("\n\n") 125 | seeds = [int(seed) for seed in seeds.split()[1:]] 126 | all_mappings = [ 127 | [parse_mapping(mapping) for mapping in mappings.splitlines()[1:]] 128 | for mappings in all_mappings 129 | ] 130 | return seeds, all_mappings 131 | 132 | 133 | def parse_mapping(line: str) -> Mapping: 134 | dest, start, length = line.split() 135 | dest = int(dest) 136 | start = int(start) 137 | length = int(length) 138 | return Mapping(from_range=range(start, start + length), dest=dest) 139 | 140 | 141 | def test_part_a(capsys): 142 | assert part_a(SAMPLE_INPUT) == 35 143 | with capsys.disabled(): 144 | print(f"Part A: {part_a(INPUT)}") 145 | 146 | 147 | def test_part_b(capsys): 148 | assert part_b(SAMPLE_INPUT_B) == 46 149 | with capsys.disabled(): 150 | print(f"Part B: {part_b(INPUT)}") 151 | -------------------------------------------------------------------------------- /2023/python/template/main.py: -------------------------------------------------------------------------------- 1 | import collections # noqa: F401 2 | import itertools # noqa: F401 3 | import math # noqa: F401 4 | import re # noqa: F401 5 | from dataclasses import dataclass # noqa: F401 6 | from pathlib import Path 7 | 8 | import toolz # noqa: F401 9 | 10 | SAMPLE_INPUT = """ 11 | """ 12 | SAMPLE_INPUT_B = SAMPLE_INPUT 13 | INPUT = Path("data.txt").read_text() 14 | 15 | 16 | def part_a(input: str): 17 | return 0 18 | 19 | 20 | def part_b(input: str): 21 | return 0 22 | 23 | 24 | def test_part_a(capsys): 25 | assert part_a(SAMPLE_INPUT) == 0 26 | with capsys.disabled(): 27 | print(f"Part A: {part_a(INPUT)}") 28 | 29 | 30 | def test_part_b(capsys): 31 | assert part_b(SAMPLE_INPUT_B) == 0 32 | with capsys.disabled(): 33 | print(f"Part B: {part_b(INPUT)}") 34 | -------------------------------------------------------------------------------- /2023/requirements.txt: -------------------------------------------------------------------------------- 1 | sympy 2 | toolz 3 | pytest 4 | pytest-watch 5 | -------------------------------------------------------------------------------- /2023/src/bin/day1/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::borrow::Cow; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use anyhow::anyhow; 6 | 7 | struct Day; 8 | 9 | impl BasicSolution for Day { 10 | const INPUT: &'static str = include_str!("data.txt"); 11 | const SAMPLE_INPUT: &'static str = include_str!("sample_a.txt"); 12 | const SAMPLE_INPUT_B: &'static str = include_str!("sample_b.txt"); 13 | 14 | type Shared = &'static str; 15 | type Answer = u32; 16 | 17 | const SAMPLE_ANSWER_A: Self::TestAnswer = 142; 18 | const SAMPLE_ANSWER_B: Self::TestAnswer = 281; 19 | 20 | fn shared(input: &'static str) -> anyhow::Result { 21 | Ok(input) 22 | } 23 | 24 | fn part_a(document: Cow) -> anyhow::Result { 25 | solve(&document, &[]) 26 | } 27 | 28 | fn part_b(document: Self::Shared) -> anyhow::Result { 29 | solve( 30 | document, 31 | &[ 32 | ("one", 1), 33 | ("two", 2), 34 | ("three", 3), 35 | ("four", 4), 36 | ("five", 5), 37 | ("six", 6), 38 | ("seven", 7), 39 | ("eight", 8), 40 | ("nine", 9), 41 | ], 42 | ) 43 | } 44 | } 45 | 46 | fn solve(document: &str, spelled_out_vals: &[(&str, u32)]) -> anyhow::Result { 47 | let calibration_value = |line: &str| { 48 | let err = || anyhow!("Couldn't find a digit in line '{line}'"); 49 | 50 | let digit_at_i = |i| { 51 | let literal = line[i..=i].parse().ok(); 52 | let match_spelled = |&(digit, val)| line[i..].starts_with(digit).then_some(val); 53 | literal.or_else(|| spelled_out_vals.iter().find_map(match_spelled)) 54 | }; 55 | 56 | let first = (0..line.len()).find_map(digit_at_i).ok_or_else(err)?; 57 | let last = (0..line.len()).rev().find_map(digit_at_i).ok_or_else(err)?; 58 | 59 | Ok(first * 10 + last) 60 | }; 61 | itertools::process_results(document.lines().map(calibration_value), |it| it.sum()) 62 | } 63 | 64 | fn main() -> anyhow::Result<()> { 65 | Day::main() 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::*; 71 | 72 | #[test] 73 | fn a() -> anyhow::Result<()> { 74 | Day::test_part_a() 75 | } 76 | 77 | #[test] 78 | fn b() -> anyhow::Result<()> { 79 | Day::test_part_b() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /2023/src/bin/day1/sample_a.txt: -------------------------------------------------------------------------------- 1 | 1abc2 2 | pqr3stu8vwx 3 | a1b2c3d4e5f 4 | treb7uchet 5 | -------------------------------------------------------------------------------- /2023/src/bin/day1/sample_b.txt: -------------------------------------------------------------------------------- 1 | two1nine 2 | eightwothree 3 | abcone2threexyz 4 | xtwone3four 5 | 4nineeightseven2 6 | zoneight234 7 | 7pqrstsixteen 8 | -------------------------------------------------------------------------------- /2023/src/bin/day10/sample.txt: -------------------------------------------------------------------------------- 1 | 7-F7- 2 | .FJ|7 3 | SJLL7 4 | |F--J 5 | LJ.LJ 6 | -------------------------------------------------------------------------------- /2023/src/bin/day10/sample_b.txt: -------------------------------------------------------------------------------- 1 | FF7FSF7F7F7F7F7F---7 2 | L|LJ||||||||||||F--J 3 | FL-7LJLJ||||||LJL-77 4 | F--JF--7||LJLJ7F7FJ- 5 | L---JF-JLJ.||-FJLJJ7 6 | |F|F-JF---7F7-L7L|7| 7 | |FFJF7L7F-JF7|JL---7 8 | 7-L-JL7||F7|L7F-7F7| 9 | L.L7LFJ|||||FJL7||LJ 10 | L7JLJL-JLJLJL--JLJ.L 11 | -------------------------------------------------------------------------------- /2023/src/bin/day11/main.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use advent_2023::{BasicSolution, Solution}; 4 | use winnow::{ 5 | combinator::{alt, repeat}, 6 | Parser, 7 | }; 8 | 9 | struct Day; 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 12 | enum Pixel { 13 | Galaxy, 14 | Space, 15 | } 16 | 17 | impl BasicSolution for Day { 18 | const INPUT: &'static str = include_str!("data.txt"); 19 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 20 | 21 | type Shared = Vec>; 22 | type Answer = usize; 23 | 24 | const SAMPLE_ANSWER_A: Self::TestAnswer = 374; 25 | const SAMPLE_ANSWER_B: Self::TestAnswer = 82000210; 26 | 27 | fn shared(input: &'static str) -> anyhow::Result { 28 | input 29 | .lines() 30 | .map(|line| repeat(1.., pixel).parse(line).map_err(anyhow::Error::msg)) 31 | .collect() 32 | } 33 | 34 | fn part_a(image: Cow) -> anyhow::Result { 35 | Ok(solve::<2>(&image)) 36 | } 37 | 38 | fn part_b(image: Self::Shared) -> anyhow::Result { 39 | Ok(solve::<1_000_000>(&image)) 40 | } 41 | } 42 | 43 | fn solve(image: &[Vec]) -> usize { 44 | let total_galaxies = count_galaxies(image.iter().flatten().copied()); 45 | let galaxies_in_each_row = image.iter().map(|row| count_galaxies(row.iter().copied())); 46 | let total_row_traversals = 47 | count_traversals_simultaneously::(galaxies_in_each_row, total_galaxies); 48 | 49 | let galaxies_in_each_column = 50 | (0..image[0].len()).map(|col| count_galaxies(image.iter().map(|row| row[col]))); 51 | let total_column_traversals = 52 | count_traversals_simultaneously::(galaxies_in_each_column, total_galaxies); 53 | 54 | total_row_traversals + total_column_traversals 55 | } 56 | 57 | fn count_traversals_simultaneously( 58 | galaxies_in_each_line: impl Iterator, 59 | total_galaxies: usize, 60 | ) -> usize { 61 | galaxies_in_each_line 62 | .fold( 63 | (0, total_galaxies, 0), 64 | |(galaxies_behind, galaxies_ahead, traversed), galaxies_in_line| { 65 | let coefficient = if galaxies_in_line == 0 { EXPANSION } else { 1 }; 66 | ( 67 | galaxies_behind + galaxies_in_line, 68 | galaxies_ahead - galaxies_in_line, 69 | traversed + galaxies_behind * galaxies_ahead * coefficient, 70 | ) 71 | }, 72 | ) 73 | .2 74 | } 75 | 76 | fn count_galaxies(pixels: impl Iterator) -> usize { 77 | pixels.filter(|&p| p == Pixel::Galaxy).count() 78 | } 79 | 80 | fn pixel(input: &mut &'static str) -> winnow::PResult { 81 | alt(('#'.value(Pixel::Galaxy), '.'.value(Pixel::Space))).parse_next(input) 82 | } 83 | 84 | fn main() -> anyhow::Result<()> { 85 | Day::main() 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | 92 | #[test] 93 | fn a() -> anyhow::Result<()> { 94 | Day::test_part_a() 95 | } 96 | 97 | #[test] 98 | fn b() -> anyhow::Result<()> { 99 | Day::test_part_b() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /2023/src/bin/day11/sample.txt: -------------------------------------------------------------------------------- 1 | ...#...... 2 | .......#.. 3 | #......... 4 | .......... 5 | ......#... 6 | .#........ 7 | .........# 8 | .......... 9 | .......#.. 10 | #...#..... 11 | -------------------------------------------------------------------------------- /2023/src/bin/day12/sample.txt: -------------------------------------------------------------------------------- 1 | ???.### 1,1,3 2 | .??..??...?##. 1,1,3 3 | ?#?#?#?#?#?#?#? 1,3,1,6 4 | ????.#...#... 4,1,1 5 | ????.######..#####. 1,6,5 6 | ?###???????? 3,2,1 7 | -------------------------------------------------------------------------------- /2023/src/bin/day13/main.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use advent_2023::{BasicSolution, Solution}; 4 | use anyhow::anyhow; 5 | 6 | struct Day; 7 | 8 | impl BasicSolution for Day { 9 | const INPUT: &'static str = include_str!("data.txt"); 10 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 11 | 12 | type Shared = Vec>; 13 | type Answer = usize; 14 | 15 | const SAMPLE_ANSWER_A: Self::TestAnswer = 405; 16 | const SAMPLE_ANSWER_B: Self::TestAnswer = 400; 17 | 18 | fn shared(input: &'static str) -> anyhow::Result { 19 | Ok(input 20 | .split("\n\n") 21 | .map(|note| note.lines().map(str::as_bytes).collect()) 22 | .collect()) 23 | } 24 | 25 | fn part_a(notes: Cow) -> anyhow::Result { 26 | notes.iter().map(|note| score(note, 0)).sum() 27 | } 28 | 29 | fn part_b(notes: Self::Shared) -> anyhow::Result { 30 | notes.iter().map(|note| score(note, 1)).sum() 31 | } 32 | } 33 | 34 | fn score(note: &[&[u8]], allowed_mismatches: usize) -> anyhow::Result { 35 | if let Some(mirror_row) = find_reflection_index( 36 | note.iter().map(|row| row.iter().copied()), 37 | allowed_mismatches, 38 | ) { 39 | Ok(mirror_row * 100) 40 | } else if let Some(mirror_column) = find_reflection_index( 41 | (0..note[0].len()).map(|i| note.iter().map(move |row| row[i])), 42 | allowed_mismatches, 43 | ) { 44 | Ok(mirror_column) 45 | } else { 46 | Err(anyhow!("No mirror found for note\n{note:?}")) 47 | } 48 | } 49 | 50 | fn find_reflection_index + Clone>( 51 | note: impl DoubleEndedIterator + ExactSizeIterator + Clone, 52 | allowed_mismatches: usize, 53 | ) -> Option { 54 | (1..note.len()).find(|&i| { 55 | note.clone() 56 | .take(i) 57 | .rev() 58 | .zip(note.clone().skip(i)) 59 | .map(|(a, b)| a.zip(b).filter(|(a, b)| a != b).count()) 60 | .sum::() 61 | == allowed_mismatches 62 | }) 63 | } 64 | 65 | fn main() -> anyhow::Result<()> { 66 | Day::main() 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | 73 | #[test] 74 | fn a() -> anyhow::Result<()> { 75 | Day::test_part_a() 76 | } 77 | 78 | #[test] 79 | fn b() -> anyhow::Result<()> { 80 | Day::test_part_b() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /2023/src/bin/day13/sample.txt: -------------------------------------------------------------------------------- 1 | #.##..##. 2 | ..#.##.#. 3 | ##......# 4 | ##......# 5 | ..#.##.#. 6 | ..##..##. 7 | #.#.##.#. 8 | 9 | #...##..# 10 | #....#..# 11 | ..##..### 12 | #####.##. 13 | #####.##. 14 | ..##..### 15 | #....#..# 16 | -------------------------------------------------------------------------------- /2023/src/bin/day15/main.rs: -------------------------------------------------------------------------------- 1 | use std::{array, borrow::Cow}; 2 | 3 | use advent_2023::{BasicSolution, Solution}; 4 | use winnow::{ 5 | ascii::{alpha1, dec_uint}, 6 | combinator::{alt, preceded}, 7 | Parser, 8 | }; 9 | 10 | struct Day; 11 | 12 | impl BasicSolution for Day { 13 | const INPUT: &'static str = include_str!("data.txt"); 14 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 15 | 16 | type Shared = Vec<&'static str>; 17 | type Answer = usize; 18 | 19 | const SAMPLE_ANSWER_A: Self::TestAnswer = 1320; 20 | const SAMPLE_ANSWER_B: Self::TestAnswer = 145; 21 | 22 | fn shared(input: &'static str) -> anyhow::Result { 23 | Ok(input.trim_end().split(',').collect()) 24 | } 25 | 26 | fn part_a(steps: Cow) -> anyhow::Result { 27 | Ok(steps.iter().copied().map(hash).sum()) 28 | } 29 | 30 | fn part_b(steps: Self::Shared) -> anyhow::Result { 31 | let mut hash_map: [Vec<(&str, u8)>; 256] = array::from_fn(|_| Vec::new()); 32 | for step in steps { 33 | let (label, operation) = (alpha1, operation) 34 | .parse(step) 35 | .map_err(anyhow::Error::msg)?; 36 | let bucket = hash(label); 37 | match operation { 38 | Operation::Remove => { 39 | hash_map[bucket].retain(|(l, _)| *l != label); 40 | } 41 | Operation::Insert(focal_length) => { 42 | if let Some((_, current)) = 43 | hash_map[bucket].iter_mut().find(|(l, _)| *l == label) 44 | { 45 | *current = focal_length; 46 | } else { 47 | hash_map[bucket].push((label, focal_length)); 48 | } 49 | } 50 | } 51 | } 52 | Ok(hash_map 53 | .into_iter() 54 | .zip(1..) 55 | .flat_map(|(bucket, box_number)| { 56 | bucket 57 | .into_iter() 58 | .zip(1..) 59 | .map(move |((_, focal_length), slot_number)| { 60 | box_number * slot_number * focal_length as usize 61 | }) 62 | }) 63 | .sum()) 64 | } 65 | } 66 | 67 | fn hash(input: &str) -> usize { 68 | input 69 | .bytes() 70 | .fold(0, |hash, b| ((hash + b as usize) * 17) % 256) 71 | } 72 | 73 | #[derive(Debug, Clone, Copy)] 74 | enum Operation { 75 | Remove, 76 | Insert(u8), 77 | } 78 | 79 | fn operation(input: &mut &'static str) -> winnow::PResult { 80 | alt(( 81 | '-'.value(Operation::Remove), 82 | preceded('=', dec_uint).map(Operation::Insert), 83 | )) 84 | .parse_next(input) 85 | } 86 | 87 | fn main() -> anyhow::Result<()> { 88 | Day::main() 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | 95 | #[test] 96 | fn a() -> anyhow::Result<()> { 97 | Day::test_part_a() 98 | } 99 | 100 | #[test] 101 | fn b() -> anyhow::Result<()> { 102 | Day::test_part_b() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /2023/src/bin/day15/sample.txt: -------------------------------------------------------------------------------- 1 | rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 2 | -------------------------------------------------------------------------------- /2023/src/bin/day17/sample_a.txt: -------------------------------------------------------------------------------- 1 | 2413432311323 2 | 3215453535623 3 | 3255245654254 4 | 3446585845452 5 | 4546657867536 6 | 1438598798454 7 | 4457876987766 8 | 3637877979653 9 | 4654967986887 10 | 4564679986453 11 | 1224686865563 12 | 2546548887735 13 | 4322674655533 14 | -------------------------------------------------------------------------------- /2023/src/bin/day17/sample_b.txt: -------------------------------------------------------------------------------- 1 | 111111111111 2 | 999999999991 3 | 999999999991 4 | 999999999991 5 | 999999999991 6 | -------------------------------------------------------------------------------- /2023/src/bin/day18/main.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use advent_2023::{BasicSolution, Solution}; 4 | use anyhow::bail; 5 | use itertools::Itertools; 6 | use winnow::{ 7 | ascii::{dec_uint, hex_uint}, 8 | combinator::alt, 9 | seq, Parser, 10 | }; 11 | 12 | struct Day; 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 | enum Direction { 16 | Right, 17 | Down, 18 | Left, 19 | Up, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 23 | struct Edge { 24 | direction: Direction, 25 | length: u64, 26 | color: u64, 27 | } 28 | 29 | impl BasicSolution for Day { 30 | const INPUT: &'static str = include_str!("data.txt"); 31 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 32 | 33 | type Shared = Vec; 34 | type Answer = u64; 35 | 36 | const SAMPLE_ANSWER_A: Self::TestAnswer = 62; 37 | const SAMPLE_ANSWER_B: Self::TestAnswer = 952408144115; 38 | 39 | fn shared(input: &'static str) -> anyhow::Result { 40 | input 41 | .lines() 42 | .map(|line| edge.parse(line).map_err(anyhow::Error::msg)) 43 | .collect() 44 | } 45 | 46 | fn part_a(edges: Cow) -> anyhow::Result { 47 | calc_lagoon_area(&edges) 48 | } 49 | 50 | fn part_b(edges: Self::Shared) -> anyhow::Result { 51 | let edges = edges 52 | .into_iter() 53 | .map(Edge::fix) 54 | .collect::>>()?; 55 | calc_lagoon_area(&edges) 56 | } 57 | } 58 | 59 | fn calc_lagoon_area(edges: &[Edge]) -> Result { 60 | let mut trench = vec![(0, 0)]; 61 | let mut current = (0, 0); 62 | for edge in edges.iter() { 63 | let length = edge.length as i64; 64 | let (x, y) = current; 65 | let (dx, dy) = match edge.direction { 66 | Direction::Up => (length, 0), 67 | Direction::Down => (-length, 0), 68 | Direction::Left => (0, -length), 69 | Direction::Right => (0, length), 70 | }; 71 | current = (x + dx, y + dy); 72 | trench.push(current); 73 | } 74 | Ok(1 + shoelace_formula(&trench) + edges.iter().map(|e| e.length).sum::() / 2) 75 | } 76 | 77 | impl Edge { 78 | fn fix(self) -> anyhow::Result { 79 | let direction = match self.color & 0b1111 { 80 | 0 => Direction::Right, 81 | 1 => Direction::Down, 82 | 2 => Direction::Left, 83 | 3 => Direction::Up, 84 | last_digit => bail!("Invalid direction: {last_digit}"), 85 | }; 86 | Ok(Edge { 87 | direction, 88 | length: self.color >> 4, 89 | color: self.color, 90 | }) 91 | } 92 | } 93 | 94 | fn shoelace_formula(coords: &[(i64, i64)]) -> u64 { 95 | coords 96 | .iter() 97 | .copied() 98 | .chain([coords[0]]) 99 | .tuple_windows() 100 | .map(|((y1, x1), (y2, x2))| x1 * y2 - x2 * y1) 101 | .sum::() 102 | .unsigned_abs() 103 | / 2 104 | } 105 | 106 | fn edge(input: &mut &'static str) -> winnow::PResult { 107 | seq! {Edge { 108 | direction: alt(('R'.value(Direction::Right), 'L'.value(Direction::Left), 'U'.value(Direction::Up), 'D'.value(Direction::Down))), 109 | _: ' ', 110 | length: dec_uint, 111 | _: " (#", 112 | color: hex_uint, 113 | _: ')', 114 | }}.parse_next(input) 115 | } 116 | 117 | fn main() -> anyhow::Result<()> { 118 | Day::main() 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::*; 124 | 125 | #[test] 126 | fn a() -> anyhow::Result<()> { 127 | Day::test_part_a() 128 | } 129 | 130 | #[test] 131 | fn b() -> anyhow::Result<()> { 132 | Day::test_part_b() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /2023/src/bin/day18/sample.txt: -------------------------------------------------------------------------------- 1 | R 6 (#70c710) 2 | D 5 (#0dc571) 3 | L 2 (#5713f0) 4 | D 2 (#d2c081) 5 | R 2 (#59c680) 6 | D 2 (#411b91) 7 | L 5 (#8ceee2) 8 | U 2 (#caa173) 9 | L 1 (#1b58a2) 10 | U 2 (#caa171) 11 | R 2 (#7807d2) 12 | U 3 (#a77fa3) 13 | L 2 (#015232) 14 | U 2 (#7a21e3) 15 | -------------------------------------------------------------------------------- /2023/src/bin/day19/sample.txt: -------------------------------------------------------------------------------- 1 | px{a<2006:qkq,m>2090:A,rfg} 2 | pv{a>1716:R,A} 3 | lnx{m>1548:A,A} 4 | rfg{s<537:gd,x>2440:R,A} 5 | qs{s>3448:A,lnx} 6 | qkq{x<1416:A,crn} 7 | crn{x>2662:A,R} 8 | in{s<1351:px,qqz} 9 | qqz{s>2770:qs,m<1801:hdj,R} 10 | gd{a>3333:R,R} 11 | hdj{m>838:A,pv} 12 | 13 | {x=787,m=2655,a=1222,s=2876} 14 | {x=1679,m=44,a=2067,s=496} 15 | {x=2036,m=264,a=79,s=2244} 16 | {x=2461,m=1339,a=466,s=291} 17 | {x=2127,m=1623,a=2188,s=1013} 18 | -------------------------------------------------------------------------------- /2023/src/bin/day2/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::borrow::Cow; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use winnow::{ 6 | ascii::dec_uint, 7 | combinator::{alt, fold_repeat, opt, preceded}, 8 | seq, Parser, 9 | }; 10 | 11 | struct Day; 12 | 13 | #[derive(Debug, Clone)] 14 | struct Game { 15 | id: u8, 16 | revealed: [u8; 3], 17 | } 18 | 19 | const RED: usize = 0; 20 | const GREEN: usize = 1; 21 | const BLUE: usize = 2; 22 | 23 | impl BasicSolution for Day { 24 | const INPUT: &'static str = include_str!("data.txt"); 25 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 26 | 27 | type Shared = Vec; 28 | type Answer = u32; 29 | 30 | const SAMPLE_ANSWER_A: Self::TestAnswer = 8; 31 | const SAMPLE_ANSWER_B: Self::TestAnswer = 2286; 32 | 33 | fn shared(input: &'static str) -> anyhow::Result { 34 | input 35 | .lines() 36 | .map(|line| game.parse(line).map_err(anyhow::Error::msg)) 37 | .collect() 38 | } 39 | 40 | fn part_a(games: Cow) -> anyhow::Result { 41 | Ok(games 42 | .iter() 43 | .filter(|game| { 44 | let [red, green, blue] = game.revealed; 45 | red <= 12 && green <= 13 && blue <= 14 46 | }) 47 | .map(|game| u32::from(game.id)) 48 | .sum()) 49 | } 50 | 51 | fn part_b(games: Self::Shared) -> anyhow::Result { 52 | Ok(games 53 | .into_iter() 54 | .map(|game| { 55 | let [red, green, blue] = game.revealed; 56 | u32::from(red) * u32::from(green) * u32::from(blue) 57 | }) 58 | .sum()) 59 | } 60 | } 61 | 62 | fn game(input: &mut &str) -> winnow::PResult { 63 | seq! {Game{ 64 | _: "Game ", 65 | id: dec_uint, 66 | _: ": ", 67 | revealed: revealed, 68 | }} 69 | .parse_next(input) 70 | } 71 | 72 | fn revealed(input: &mut &str) -> winnow::PResult<[u8; 3]> { 73 | fold_repeat( 74 | 1.., 75 | preceded(opt(alt((", ", "; "))), color_count), 76 | || [0; 3], 77 | |mut acc, (n, color)| { 78 | acc[color] = acc[color].max(n); 79 | acc 80 | }, 81 | ) 82 | .parse_next(input) 83 | } 84 | 85 | fn color_count(input: &mut &str) -> winnow::PResult<(u8, usize)> { 86 | seq! {( 87 | dec_uint, 88 | _: ' ', 89 | alt(("red".value(RED), "green".value(GREEN), "blue".value(BLUE))), 90 | )} 91 | .parse_next(input) 92 | } 93 | 94 | fn main() -> anyhow::Result<()> { 95 | Day::main() 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | #[test] 103 | fn a() -> anyhow::Result<()> { 104 | Day::test_part_a() 105 | } 106 | 107 | #[test] 108 | fn b() -> anyhow::Result<()> { 109 | Day::test_part_b() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /2023/src/bin/day2/sample.txt: -------------------------------------------------------------------------------- 1 | Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 2 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 3 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 4 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 5 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green 6 | -------------------------------------------------------------------------------- /2023/src/bin/day20/sample.txt: -------------------------------------------------------------------------------- 1 | broadcaster -> a 2 | %a -> inv, con 3 | &inv -> b 4 | %b -> con 5 | &con -> output 6 | -------------------------------------------------------------------------------- /2023/src/bin/day22/sample.txt: -------------------------------------------------------------------------------- 1 | 1,0,1~1,2,1 2 | 0,0,2~2,0,2 3 | 0,2,3~2,2,3 4 | 0,0,4~0,2,4 5 | 2,0,5~2,2,5 6 | 0,1,6~2,1,6 7 | 1,1,8~1,1,9 8 | -------------------------------------------------------------------------------- /2023/src/bin/day23/sample.txt: -------------------------------------------------------------------------------- 1 | #.##################### 2 | #.......#########...### 3 | #######.#########.#.### 4 | ###.....#.>.>.###.#.### 5 | ###v#####.#v#.###.#.### 6 | ###.>...#.#.#.....#...# 7 | ###v###.#.#.#########.# 8 | ###...#.#.#.......#...# 9 | #####.#.#.#######.#.### 10 | #.....#.#.#.......#...# 11 | #.#####.#.#.#########v# 12 | #.#...#...#...###...>.# 13 | #.#.#v#######v###.###v# 14 | #...#.>.#...>.>.#.###.# 15 | #####v#.#.###v#.#.###.# 16 | #.....#...#...#.#.#...# 17 | #.#########.###.#.#.### 18 | #...###...#...#...#.### 19 | ###.###.#.###v#####v### 20 | #...#...#.#.>.>.#.>.### 21 | #.###.###.#.###.#.#v### 22 | #.....###...###...#...# 23 | #####################.# 24 | -------------------------------------------------------------------------------- /2023/src/bin/day3/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::{borrow::Cow, collections::HashMap, ops::Range}; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use itertools::Itertools; 6 | use winnow::{ 7 | ascii::dec_uint, 8 | combinator::{iterator, preceded}, 9 | stream::AsChar, 10 | token::take_till, 11 | Located, Parser, 12 | }; 13 | 14 | struct Day; 15 | 16 | #[derive(Debug, Clone)] 17 | struct Schematic<'a> { 18 | raw: Vec<&'a [u8]>, 19 | numbers: Vec, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | struct Number { 24 | row: usize, 25 | columns: Range, 26 | value: u32, 27 | } 28 | 29 | impl BasicSolution for Day { 30 | const INPUT: &'static str = include_str!("data.txt"); 31 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 32 | 33 | type Shared = Schematic<'static>; 34 | type Answer = u32; 35 | 36 | const SAMPLE_ANSWER_A: Self::TestAnswer = 4361; 37 | const SAMPLE_ANSWER_B: Self::TestAnswer = 467_835; 38 | 39 | fn shared(input: &'static str) -> anyhow::Result { 40 | let mut numbers = Vec::new(); 41 | for (row, line) in input.lines().enumerate() { 42 | let mut iter_nums = 43 | iterator(Located::new(line), preceded(not_dec, dec_uint.with_span())); 44 | numbers.extend(iter_nums.map(|(value, columns)| Number { 45 | row, 46 | columns, 47 | value, 48 | })); 49 | // Purely for error checking 50 | let (rest, ()) = iter_nums.finish().map_err(anyhow::Error::msg)?; 51 | not_dec.parse(rest).map_err(anyhow::Error::msg)?; 52 | } 53 | let raw = input.lines().map(str::as_bytes).collect_vec(); 54 | Ok(Schematic { raw, numbers }) 55 | } 56 | 57 | fn part_a(schematic: Cow) -> anyhow::Result { 58 | Ok(schematic 59 | .numbers 60 | .iter() 61 | .filter(|number| { 62 | number.adjacent_coords().any(|coords| { 63 | get_2d(&schematic.raw, coords).is_some_and(|c| !c.is_ascii_digit() && c != b'.') 64 | }) 65 | }) 66 | .map(|number| number.value) 67 | .sum()) 68 | } 69 | 70 | fn part_b(Schematic { numbers, raw }: Self::Shared) -> anyhow::Result { 71 | let mut potential_gears: HashMap<(usize, usize), Vec> = HashMap::new(); 72 | for num in numbers { 73 | num.adjacent_coords() 74 | .filter(|&coords| get_2d(&raw, coords) == Some(b'*')) 75 | .for_each(|coords| { 76 | potential_gears.entry(coords).or_default().push(num.value); 77 | }); 78 | } 79 | Ok(potential_gears 80 | .into_values() 81 | .filter(|values| values.len() == 2) 82 | .map(|values| values.into_iter().product::()) 83 | .sum()) 84 | } 85 | } 86 | 87 | fn not_dec(input: &mut Located<&'static str>) -> winnow::PResult<&'static str> { 88 | take_till(0.., AsChar::is_dec_digit).parse_next(input) 89 | } 90 | 91 | impl Number { 92 | fn adjacent_coords(&self) -> impl Iterator + '_ { 93 | let rows_to_check = self.row.saturating_sub(1)..=self.row + 1; 94 | let columns_to_check = self.columns.start.saturating_sub(1)..=self.columns.end; 95 | rows_to_check.cartesian_product(columns_to_check) 96 | } 97 | } 98 | 99 | fn get_2d(grid: &[&[u8]], (row, col): (usize, usize)) -> Option { 100 | grid.get(row).and_then(|line| line.get(col)).copied() 101 | } 102 | 103 | fn main() -> anyhow::Result<()> { 104 | Day::main() 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::*; 110 | 111 | #[test] 112 | fn a() -> anyhow::Result<()> { 113 | Day::test_part_a() 114 | } 115 | 116 | #[test] 117 | fn b() -> anyhow::Result<()> { 118 | Day::test_part_b() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /2023/src/bin/day3/sample.txt: -------------------------------------------------------------------------------- 1 | 467..114.. 2 | ...*...... 3 | ..35..633. 4 | ......#... 5 | 617*...... 6 | .....+.58. 7 | ..592..... 8 | ......755. 9 | ...$.*.... 10 | .664.598.. 11 | -------------------------------------------------------------------------------- /2023/src/bin/day4/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::borrow::Cow; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use itertools::Itertools; 6 | use winnow::{combinator::rest, token::take_until0, Parser}; 7 | 8 | struct Day; 9 | 10 | #[derive(Debug, Clone)] 11 | struct Card { 12 | matches: usize, 13 | } 14 | 15 | impl BasicSolution for Day { 16 | const INPUT: &'static str = include_str!("data.txt"); 17 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 18 | 19 | type Shared = Vec; 20 | type Answer = usize; 21 | 22 | const SAMPLE_ANSWER_A: Self::TestAnswer = 13; 23 | const SAMPLE_ANSWER_B: Self::TestAnswer = 30; 24 | 25 | fn shared(input: &'static str) -> anyhow::Result { 26 | input 27 | .lines() 28 | .map(|line| card.parse(line).map_err(anyhow::Error::msg)) 29 | .collect() 30 | } 31 | 32 | fn part_a(cards: Cow) -> anyhow::Result { 33 | cards 34 | .iter() 35 | .map(|card| u32::try_from(card.matches).map(|matches| 2usize.pow(matches) / 2)) 36 | .sum::>() 37 | .map_err(anyhow::Error::from) 38 | } 39 | 40 | fn part_b(cards: Self::Shared) -> anyhow::Result { 41 | let mut card_copies = vec![1; cards.len()]; 42 | cards.iter().enumerate().for_each(|(i, card)| { 43 | let copies_of_cur = card_copies[i]; 44 | card_copies[i + 1..] 45 | .iter_mut() 46 | .take(card.matches) 47 | .for_each(|c| *c += copies_of_cur); 48 | }); 49 | Ok(card_copies.into_iter().sum()) 50 | } 51 | } 52 | 53 | fn card(input: &mut &str) -> winnow::PResult { 54 | let (_, _, winning_numbers, _, numbers_i_own) = 55 | (take_until0(":"), ':', take_until0("|"), '|', rest).parse_next(input)?; 56 | let matches = winning_numbers 57 | .split_ascii_whitespace() 58 | .filter(|n| numbers_i_own.split_ascii_whitespace().contains(n)) 59 | .count(); 60 | Ok(Card { matches }) 61 | } 62 | 63 | fn main() -> anyhow::Result<()> { 64 | Day::main() 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn a() -> anyhow::Result<()> { 73 | Day::test_part_a() 74 | } 75 | 76 | #[test] 77 | fn b() -> anyhow::Result<()> { 78 | Day::test_part_b() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /2023/src/bin/day4/sample.txt: -------------------------------------------------------------------------------- 1 | Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 2 | Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 3 | Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 4 | Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 5 | Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 6 | Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 7 | -------------------------------------------------------------------------------- /2023/src/bin/day5/sample.txt: -------------------------------------------------------------------------------- 1 | seeds: 79 14 55 13 2 | 3 | seed-to-soil map: 4 | 50 98 2 5 | 52 50 48 6 | 7 | soil-to-fertilizer map: 8 | 0 15 37 9 | 37 52 2 10 | 39 0 15 11 | 12 | fertilizer-to-water map: 13 | 49 53 8 14 | 0 11 42 15 | 42 0 7 16 | 57 7 4 17 | 18 | water-to-light map: 19 | 88 18 7 20 | 18 25 70 21 | 22 | light-to-temperature map: 23 | 45 77 23 24 | 81 45 19 25 | 68 64 13 26 | 27 | temperature-to-humidity map: 28 | 0 69 1 29 | 1 0 69 30 | 31 | humidity-to-location map: 32 | 60 56 37 33 | 56 93 4 34 | -------------------------------------------------------------------------------- /2023/src/bin/day6/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![feature(float_next_up_down)] 3 | use std::borrow::Cow; 4 | 5 | use advent_2023::{BasicSolution, Solution}; 6 | use anyhow::bail; 7 | use winnow::{ 8 | ascii::{dec_uint, space1}, 9 | combinator::{opt, separated}, 10 | seq, Parser, 11 | }; 12 | 13 | struct Day; 14 | 15 | #[derive(Debug, Clone)] 16 | struct Records { 17 | times: Vec, 18 | distances: Vec, 19 | } 20 | 21 | impl BasicSolution for Day { 22 | const INPUT: &'static str = include_str!("data.txt"); 23 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 24 | 25 | type Shared = Records; 26 | type Answer = u64; 27 | 28 | const SAMPLE_ANSWER_A: Self::TestAnswer = 288; 29 | const SAMPLE_ANSWER_B: Self::TestAnswer = 71503; 30 | 31 | fn shared(input: &'static str) -> anyhow::Result { 32 | records.parse(input).map_err(anyhow::Error::msg) 33 | } 34 | 35 | fn part_a(records: Cow) -> anyhow::Result { 36 | if records.times.len() != records.distances.len() { 37 | bail!( 38 | "times and distances have different lenghts. 39 | times={:?} 40 | distances={:?}", 41 | records.times, 42 | records.distances 43 | ); 44 | } 45 | records 46 | .times 47 | .iter() 48 | .zip(&records.distances) 49 | .map(|(&time, &distance)| possible_ways_to_win(time, distance)) 50 | .product::>() 51 | .ok_or_else(|| anyhow::anyhow!("A cast failed")) 52 | } 53 | 54 | fn part_b(Records { times, distances }: Self::Shared) -> anyhow::Result { 55 | let time = join_numbers(×)?; 56 | let distance = join_numbers(&distances)?; 57 | possible_ways_to_win(time, distance).ok_or_else(|| anyhow::anyhow!("A cast failed")) 58 | } 59 | } 60 | 61 | fn possible_ways_to_win(time: u64, record_distance: u64) -> Option { 62 | // distance = (time - hold_time) * hold_time 63 | // distance = time * hold_time - hold_time^2 64 | // hold_time^2 - time * hold_time + distance = 0 65 | // hold_time = (time +- sqrt(time^2 - 4 * distance)) / 2 66 | 67 | let time: f64 = num::cast(time)?; 68 | let record_distance: f64 = num::cast(record_distance)?; 69 | let root_term = time.powi(2) - 4. * record_distance; 70 | let smallest_hold_time_to_match_record = (time - root_term.sqrt()) / 2.; 71 | let largest_hold_time_to_match_record = (time + root_term.sqrt()) / 2.; 72 | let smallest_int_hold_time_to_beet_record: u64 = 73 | num::cast(smallest_hold_time_to_match_record.next_up().ceil())?; 74 | let largest_int_hold_time_to_beet_record: u64 = 75 | num::cast(largest_hold_time_to_match_record.next_down().floor())?; 76 | 77 | Some(largest_int_hold_time_to_beet_record - smallest_int_hold_time_to_beet_record + 1) 78 | } 79 | 80 | fn join_numbers(distances: &[u64]) -> Result { 81 | distances 82 | .iter() 83 | .map(ToString::to_string) 84 | .collect::() 85 | .parse() 86 | } 87 | 88 | fn records(input: &mut &'static str) -> winnow::PResult { 89 | seq! {Records{ 90 | _: ("Time:", space1), 91 | times: numbers, 92 | _: ("\nDistance:", space1), 93 | distances: numbers, 94 | _: opt("\n"), 95 | }} 96 | .parse_next(input) 97 | } 98 | 99 | fn numbers(input: &mut &'static str) -> winnow::PResult> { 100 | separated(1.., dec_uint::<_, u64, _>, space1).parse_next(input) 101 | } 102 | 103 | fn main() -> anyhow::Result<()> { 104 | Day::main() 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::*; 110 | 111 | #[test] 112 | fn a() -> anyhow::Result<()> { 113 | Day::test_part_a() 114 | } 115 | 116 | #[test] 117 | fn b() -> anyhow::Result<()> { 118 | Day::test_part_b() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /2023/src/bin/day6/sample.txt: -------------------------------------------------------------------------------- 1 | Time: 7 15 30 2 | Distance: 9 40 200 3 | -------------------------------------------------------------------------------- /2023/src/bin/day7/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::{borrow::Cow, cmp::Reverse}; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use itertools::Itertools; 6 | use winnow::{ascii::dec_uint, seq, token::any, Parser}; 7 | 8 | struct Day; 9 | 10 | #[derive(Debug, Clone)] 11 | struct Bid { 12 | hand: [u8; 5], 13 | amount: u16, 14 | } 15 | 16 | // The two top counts define the hand type 17 | type HandType = [u8; 2]; 18 | 19 | impl BasicSolution for Day { 20 | const INPUT: &'static str = include_str!("data.txt"); 21 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 22 | 23 | type Shared = Vec; 24 | type Answer = u32; 25 | 26 | const SAMPLE_ANSWER_A: Self::TestAnswer = 6440; 27 | const SAMPLE_ANSWER_B: Self::TestAnswer = 5905; 28 | 29 | fn shared(input: &'static str) -> anyhow::Result { 30 | input 31 | .lines() 32 | .map(|line| bid.parse(line).map_err(anyhow::Error::msg)) 33 | .collect() 34 | } 35 | 36 | fn part_a(bids: Cow) -> anyhow::Result { 37 | Ok(sum_sortable_bids(bids.iter().map(|bid| { 38 | (hand_type_part_a(bid.hand), bid.hand, bid.amount) 39 | }))) 40 | } 41 | 42 | fn part_b(bids: Self::Shared) -> anyhow::Result { 43 | Ok(sum_sortable_bids(bids.into_iter().map(|mut bid| { 44 | jacks_to_jokers(&mut bid.hand); 45 | (hand_type_part_b(bid.hand), bid.hand, bid.amount) 46 | }))) 47 | } 48 | } 49 | 50 | fn sum_sortable_bids(bid_amounts: impl Iterator) -> u32 { 51 | bid_amounts 52 | .sorted_unstable() 53 | .zip(1..) 54 | .map(|((_, _, amount), i)| i * u32::from(amount)) 55 | .sum() 56 | } 57 | 58 | fn hand_type_part_a(hand: [u8; 5]) -> HandType { 59 | let mut counts = card_counts(hand); 60 | counts.sort_unstable_by_key(|&c| Reverse(c)); 61 | let [first, second, ..] = counts; 62 | [first, second] 63 | } 64 | 65 | fn hand_type_part_b(hand: [u8; 5]) -> HandType { 66 | let mut counts = card_counts(hand); 67 | counts[1..].sort_unstable_by_key(|&c| Reverse(c)); 68 | let [jokers, first, second, ..] = counts; 69 | [jokers + first, second] 70 | } 71 | 72 | fn card_counts(hand: [u8; 5]) -> [u8; 13] { 73 | hand.iter().fold([0; 13], |mut counts, &card| { 74 | counts[card as usize] += 1; 75 | counts 76 | }) 77 | } 78 | 79 | fn jacks_to_jokers(hand: &mut [u8; 5]) { 80 | for card in hand.iter_mut() { 81 | match card { 82 | 0..=8 => *card += 1, 83 | 9 => *card = 0, 84 | _ => {} 85 | } 86 | } 87 | } 88 | 89 | fn bid(input: &mut &'static str) -> winnow::PResult { 90 | seq! {Bid { 91 | hand: (card, card, card, card, card).map(From::from), 92 | _: ' ', 93 | amount: dec_uint, 94 | }} 95 | .parse_next(input) 96 | } 97 | 98 | fn card(input: &mut &'static str) -> winnow::PResult { 99 | any.verify_map(|c| match c { 100 | '2'..='9' => Some(c as u8 - b'2'), 101 | 'T' => Some(8), 102 | 'J' => Some(9), 103 | 'Q' => Some(10), 104 | 'K' => Some(11), 105 | 'A' => Some(12), 106 | _ => None, 107 | }) 108 | .parse_next(input) 109 | } 110 | 111 | fn main() -> anyhow::Result<()> { 112 | Day::main() 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | 119 | #[test] 120 | fn a() -> anyhow::Result<()> { 121 | Day::test_part_a() 122 | } 123 | 124 | #[test] 125 | fn b() -> anyhow::Result<()> { 126 | Day::test_part_b() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /2023/src/bin/day7/sample.txt: -------------------------------------------------------------------------------- 1 | 32T3K 765 2 | T55J5 684 3 | KK677 28 4 | KTJJT 220 5 | QQQJA 483 6 | -------------------------------------------------------------------------------- /2023/src/bin/day8/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::{borrow::Cow, collections::HashMap}; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use anyhow::anyhow; 6 | use itertools::process_results; 7 | use num::Integer; 8 | use winnow::{ 9 | ascii::alphanumeric1, 10 | combinator::{alt, opt, repeat, separated}, 11 | seq, Parser, 12 | }; 13 | 14 | struct Day; 15 | 16 | #[derive(Debug, Clone)] 17 | enum Direction { 18 | Left, 19 | Right, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | struct Maps<'a> { 24 | network: HashMap<&'a str, (&'a str, &'a str)>, 25 | instructions: Vec, 26 | } 27 | 28 | impl BasicSolution for Day { 29 | const INPUT: &'static str = include_str!("data.txt"); 30 | const SAMPLE_INPUT: &'static str = include_str!("sample_a.txt"); 31 | const SAMPLE_INPUT_B: &'static str = include_str!("sample_b.txt"); 32 | 33 | type Shared = Maps<'static>; 34 | type Answer = usize; 35 | 36 | const SAMPLE_ANSWER_A: Self::TestAnswer = 2; 37 | const SAMPLE_ANSWER_B: Self::TestAnswer = 6; 38 | 39 | fn shared(input: &'static str) -> anyhow::Result { 40 | maps.parse(input).map_err(anyhow::Error::msg) 41 | } 42 | 43 | fn part_a(maps: Cow) -> anyhow::Result { 44 | maps.count_steps("AAA", |node| node == "ZZZ") 45 | } 46 | 47 | fn part_b(maps: Self::Shared) -> anyhow::Result { 48 | process_results( 49 | maps.network 50 | .keys() 51 | .filter(|k| k.ends_with('A')) 52 | .map(|start| maps.count_steps(start, |node| node.ends_with('Z'))), 53 | |it| { 54 | it.reduce(|a, b| a.lcm(&b)) 55 | .ok_or_else(|| anyhow!("Less than two starting nodes")) 56 | }, 57 | )? 58 | } 59 | } 60 | 61 | impl Maps<'_> { 62 | fn count_steps(&self, start: &str, target: impl Fn(&str) -> bool) -> anyhow::Result { 63 | let mut current = start; 64 | self.instructions 65 | .iter() 66 | .cycle() 67 | .take(1_000_000) 68 | .position(|direction| { 69 | let (left, right) = &self.network[current]; 70 | current = match direction { 71 | Direction::Left => left, 72 | Direction::Right => right, 73 | }; 74 | target(current) 75 | }) 76 | .map(|c| c + 1) 77 | .ok_or_else(|| anyhow!("Cycle starting at {start} not found after 1_000_000 steps")) 78 | } 79 | } 80 | 81 | fn maps<'a>(input: &mut &'a str) -> winnow::PResult> { 82 | seq! {Maps { 83 | instructions: instructions, 84 | _: "\n\n", 85 | network: network, 86 | _: opt('\n'), 87 | }} 88 | .parse_next(input) 89 | } 90 | 91 | fn network<'a>(input: &mut &'a str) -> winnow::PResult> { 92 | separated(1.., node_targets, '\n').parse_next(input) 93 | } 94 | 95 | fn node_targets<'a>(input: &mut &'a str) -> winnow::PResult<(&'a str, (&'a str, &'a str))> { 96 | seq! {( 97 | alphanumeric1, 98 | _: " = ", 99 | seq!{(_: '(', alphanumeric1, _: ", ", alphanumeric1, _: ')')}, 100 | )} 101 | .parse_next(input) 102 | } 103 | 104 | fn instructions(input: &mut &str) -> winnow::PResult> { 105 | repeat(1.., direction).parse_next(input) 106 | } 107 | 108 | fn direction(input: &mut &str) -> winnow::PResult { 109 | alt(('L'.value(Direction::Left), 'R'.value(Direction::Right))).parse_next(input) 110 | } 111 | 112 | fn main() -> anyhow::Result<()> { 113 | Day::main() 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | 120 | #[test] 121 | fn a() -> anyhow::Result<()> { 122 | Day::test_part_a() 123 | } 124 | 125 | #[test] 126 | fn b() -> anyhow::Result<()> { 127 | Day::test_part_b() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /2023/src/bin/day8/sample_a.txt: -------------------------------------------------------------------------------- 1 | RL 2 | 3 | AAA = (BBB, CCC) 4 | BBB = (DDD, EEE) 5 | CCC = (ZZZ, GGG) 6 | DDD = (DDD, DDD) 7 | EEE = (EEE, EEE) 8 | GGG = (GGG, GGG) 9 | ZZZ = (ZZZ, ZZZ) 10 | -------------------------------------------------------------------------------- /2023/src/bin/day8/sample_b.txt: -------------------------------------------------------------------------------- 1 | LR 2 | 3 | 11A = (11B, XXX) 4 | 11B = (XXX, 11Z) 5 | 11Z = (11B, XXX) 6 | 22A = (22B, XXX) 7 | 22B = (22C, 22C) 8 | 22C = (22Z, 22Z) 9 | 22Z = (22B, 22B) 10 | XXX = (XXX, XXX) 11 | -------------------------------------------------------------------------------- /2023/src/bin/day9/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use std::borrow::Cow; 3 | 4 | use advent_2023::{BasicSolution, Solution}; 5 | use anyhow::anyhow; 6 | use itertools::{iterate, Itertools}; 7 | use winnow::{ascii::dec_int, combinator::separated, Parser}; 8 | 9 | struct Day; 10 | 11 | impl BasicSolution for Day { 12 | const INPUT: &'static str = include_str!("data.txt"); 13 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 14 | 15 | type Shared = Vec<(i32, i32)>; 16 | type Answer = i32; 17 | 18 | const SAMPLE_ANSWER_A: Self::TestAnswer = 114; 19 | const SAMPLE_ANSWER_B: Self::TestAnswer = 2; 20 | 21 | fn shared(input: &'static str) -> anyhow::Result { 22 | input 23 | .lines() 24 | .map(|line| { 25 | history 26 | .parse(line) 27 | .map_err(anyhow::Error::msg) 28 | .and_then(extrapolate) 29 | }) 30 | .collect() 31 | } 32 | 33 | fn part_a(extrapolations: Cow) -> anyhow::Result { 34 | Ok(extrapolations.iter().map(|(_, back)| back).sum()) 35 | } 36 | 37 | fn part_b(extrapolations: Self::Shared) -> anyhow::Result { 38 | Ok(extrapolations.iter().map(|(front, _)| front).sum()) 39 | } 40 | } 41 | 42 | fn extrapolate(history: Vec) -> anyhow::Result<(i32, i32)> { 43 | let diff_sequence = iterate(history, |line| { 44 | line.iter() 45 | .copied() 46 | .tuple_windows() 47 | .map(|(a, b)| b - a) 48 | .collect() 49 | }) 50 | .take_while(|line| line.iter().all_equal_value() != Ok(&0)) 51 | .collect_vec(); 52 | diff_sequence 53 | .iter() 54 | .rev() 55 | .try_fold((0, 0), |(first_bellow, last_bellow), line| { 56 | Some((line.first()? - first_bellow, line.last()? + last_bellow)) 57 | }) 58 | .ok_or_else(|| anyhow!("Extrapolation failed. Diff sequence: {diff_sequence:?}")) 59 | } 60 | 61 | fn history(data: &mut &'static str) -> winnow::PResult> { 62 | separated(1.., dec_int::<_, i32, _>, ' ').parse_next(data) 63 | } 64 | 65 | fn main() -> anyhow::Result<()> { 66 | Day::main() 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | 73 | #[test] 74 | fn a() -> anyhow::Result<()> { 75 | Day::test_part_a() 76 | } 77 | 78 | #[test] 79 | fn b() -> anyhow::Result<()> { 80 | Day::test_part_b() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /2023/src/bin/day9/sample.txt: -------------------------------------------------------------------------------- 1 | 0 3 6 9 12 15 2 | 1 3 6 10 15 21 3 | 10 13 16 21 30 45 4 | -------------------------------------------------------------------------------- /2023/src/bin/template/main.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use advent_2023::{BasicSolution, Solution}; 4 | use winnow::{combinator::rest, Parser}; 5 | 6 | struct Day; 7 | 8 | impl BasicSolution for Day { 9 | const INPUT: &'static str = include_str!("data.txt"); 10 | const SAMPLE_INPUT: &'static str = include_str!("sample.txt"); 11 | 12 | type Shared = Vec<&'static str>; 13 | type Answer = u32; 14 | 15 | const SAMPLE_ANSWER_A: Self::TestAnswer = 0; 16 | const SAMPLE_ANSWER_B: Self::TestAnswer = 0; 17 | 18 | fn shared(input: &'static str) -> anyhow::Result { 19 | input 20 | .lines() 21 | .map(|line| line_parser.parse(line).map_err(anyhow::Error::msg)) 22 | .collect() 23 | } 24 | 25 | fn part_a(shared: Cow) -> anyhow::Result { 26 | todo!("{shared:?}") 27 | } 28 | 29 | fn part_b(_: Self::Shared) -> anyhow::Result { 30 | Ok(0) 31 | } 32 | } 33 | 34 | fn line_parser(input: &mut &'static str) -> winnow::PResult<&'static str> { 35 | rest.parse_next(input) 36 | } 37 | 38 | fn main() -> anyhow::Result<()> { 39 | Day::main() 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn a() -> anyhow::Result<()> { 48 | Day::test_part_a() 49 | } 50 | 51 | #[test] 52 | fn b() -> anyhow::Result<()> { 53 | Day::test_part_b() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /2023/src/bin/template/sample.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crazytieguy/advent-of-code/7c6f768cd909eab312b2320ea471afb583d7d167/2023/src/bin/template/sample.txt -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["2021", "2022", "2023"] 4 | 5 | [profile.release] 6 | strip = true 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Advent of Code](https://adventofcode.com/) 2 | 3 | This is one of my favorite settings for honing my programming skills. In addition to enjoying the challenge and never looking up the answer, each year I had a specific focus: 4 | * In 2021 I focused on learning Rust. 5 | * In 2022 I focused on optimizing my solutions until they are faster than any other solution I know of (see [reddit](https://www.reddit.com/r/adventofcode/)). 6 | * In 2023 I focused on solving the problems quickly and having very good error handling. 7 | 8 | Some solutions I'm especially proud of: 9 | * [2022 day 16](https://github.com/Crazytieguy/advent-of-code/blob/master/2022/src/bin/day16/main.rs), which forced me to find and implement an algorithm that would be efficient enough (branch and bound). It took me 10 hours and was the closest I got to giving up on a problem. 10 | * [2022 day 20](https://github.com/Crazytieguy/advent-of-code/blob/master/2022/src/bin/day20/main.rs), for which I invented a linked list where each node points to some generic number of steps after it and 1 step behind it. 11 | * [2022 day 23](https://github.com/Crazytieguy/advent-of-code/blob/master/2022/src/bin/day23/main.rs), where I learned to work with SIMD, and came up with some ideas that later went into my work on lending iterators. 12 | * [2023 day 25](https://github.com/Crazytieguy/advent-of-code/blob/master/2023/python/day25/main.py), where I didn't know the classical way of solving it (max-flow/min-cut), and invented a different algorithm that was tailored to this problem and turned out to have the same complexity. 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Crate" 3 | -------------------------------------------------------------------------------- /start-day-python.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p $1/python/day$2 4 | cp -n $1/python/template/* $1/python/day$2/ 5 | curl https://adventofcode.com/$1/day/$2/input --cookie "session=$(cat session)" > $1/python/day$2/data.txt 6 | cd $1/python/day$2 && ptw -q -- main.py 7 | -------------------------------------------------------------------------------- /start-day-rust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p $1/src/bin/day$2 4 | cp -n $1/src/bin/template/* $1/src/bin/day$2/ 5 | curl https://adventofcode.com/$1/day/$2/input --cookie "session=$(cat session)" > $1/src/bin/day$2/data.txt 6 | cargo watch -C $1 -x "test --bin day$2 -- --nocapture" 7 | --------------------------------------------------------------------------------