├── Cargo.toml ├── src ├── lib.rs ├── day01.rs ├── day06.rs ├── day07.rs ├── day10.rs ├── day14.rs ├── day09.rs ├── day03.rs ├── day17.rs ├── day02.rs ├── day11.rs ├── day05.rs ├── utils.rs ├── day04.rs ├── day15.rs ├── day12.rs ├── day16.rs ├── day13.rs └── day08.rs ├── .gitignore ├── README.md └── LICENSE /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Belén Albeza "] 3 | name = "aoc-2021" 4 | version = "0.1.0" 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | aoc-runner = "0.3.0" 11 | aoc-runner-derive = "0.3.0" 12 | itertools = "0.10.3" 13 | lazy_static = "1.4.0" 14 | regex = "1.5.4" 15 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc_lib; 2 | 3 | mod utils; 4 | 5 | pub mod day01; 6 | pub mod day02; 7 | pub mod day03; 8 | pub mod day04; 9 | pub mod day05; 10 | pub mod day06; 11 | pub mod day07; 12 | pub mod day08; 13 | pub mod day09; 14 | pub mod day10; 15 | pub mod day11; 16 | pub mod day12; 17 | pub mod day13; 18 | pub mod day14; 19 | pub mod day15; 20 | pub mod day16; 21 | pub mod day17; 22 | 23 | aoc_lib! { year = 2021 } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | # 15 | # already existing elements were commented out 16 | 17 | /target 18 | #Cargo.lock 19 | 20 | /input/ 21 | -------------------------------------------------------------------------------- /src/day01.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | #[aoc_generator(day1)] 5 | pub fn parse_input(input: &str) -> Vec { 6 | input.lines().map(|x| x.parse().unwrap()).collect() 7 | } 8 | 9 | #[aoc(day1, part1)] 10 | pub fn solve_part1(measurements: &[u64]) -> u64 { 11 | measurements.windows(2).fold(0, |total, window| { 12 | total + if window[1] > window[0] { 1 } else { 0 } 13 | }) 14 | } 15 | 16 | #[aoc(day1, part2)] 17 | pub fn solve_part2(measurements: &[u64]) -> u64 { 18 | let triplets: Vec<&[u64]> = measurements.windows(3).collect(); 19 | triplets.windows(2).fold(0, |total, window| { 20 | let prev_sum: u64 = window[0].iter().sum(); 21 | let curr_sum: u64 = window[1].iter().sum(); 22 | total + if curr_sum > prev_sum { 1 } else { 0 } 23 | }) 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn test_day1_part1() { 32 | let input: Vec = vec![199, 200, 208, 210, 200, 207, 240, 269, 260, 263]; 33 | assert_eq!(solve_part1(&input), 7); 34 | } 35 | 36 | #[test] 37 | fn test_day1_part2() { 38 | let input: Vec = vec![199, 200, 208, 210, 200, 207, 240, 269, 260, 263]; 39 | assert_eq!(solve_part2(&input), 5); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/day06.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc_generator; 2 | use aoc_runner_derive::aoc; 3 | 4 | use std::collections::HashMap; 5 | 6 | #[aoc_generator(day6)] 7 | pub fn parse_input(input: &str) -> Vec { 8 | input.split(',').map(|x| x.parse::().unwrap()).collect() 9 | } 10 | 11 | fn simulate_fish(fish: i64, n: i64, cache: &mut HashMap<(i64, i64), u64>) -> u64 { 12 | // termination condition for recursivity 13 | if n < 0 { return 0; } 14 | 15 | // try to retrieve a pre-cached result 16 | if let Some(cached_res) = cache.get(&(fish, n)) { 17 | return cached_res.to_owned(); 18 | } 19 | 20 | // compute reproduction over n days 21 | let mut fish_count = 1; 22 | for i in (fish..n+fish).step_by(7) { 23 | fish_count += simulate_fish(8, n-i-1, cache); 24 | } 25 | 26 | // save the result in the cache 27 | cache.insert((fish, n), fish_count); 28 | fish_count 29 | } 30 | 31 | fn simulation(input: &[u64], n: u64) -> u64 { 32 | let mut population_count: u64 = 0; 33 | let mut cache = HashMap::new(); 34 | 35 | for fish in input.to_vec().into_iter() { 36 | population_count += simulate_fish(fish as i64, n as i64, &mut cache); 37 | } 38 | 39 | population_count 40 | } 41 | 42 | #[aoc(day6, part1)] 43 | pub fn solve_part1(input: &[u64]) -> u64 { 44 | simulation(input, 80) 45 | } 46 | 47 | #[aoc(day6, part2)] 48 | pub fn solve_part2(input: &[u64]) -> u64 { 49 | simulation(input, 256) 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | 56 | #[test] 57 | fn test_day6_part1() { 58 | let input = vec![3,4,3,1,2]; 59 | assert_eq!(solve_part1(&input), 5934); 60 | } 61 | 62 | #[test] 63 | fn test_day6_part2() { 64 | let input = vec![3,4,3,1,2]; 65 | assert_eq!(solve_part2(&input), 26984457539); 66 | } 67 | } -------------------------------------------------------------------------------- /src/day07.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | #[aoc_generator(day7)] 5 | pub fn parse_input(input: &str) -> Vec { 6 | input 7 | .split(',') 8 | .map(|x| x.parse::().unwrap()) 9 | .collect() 10 | } 11 | 12 | fn distance(a: u64, b: u64) -> u64 { 13 | (a as i64 - b as i64).abs() as u64 14 | } 15 | 16 | fn compounded_distance(a: u64, b: u64) -> u64 { 17 | (0..=distance(a, b)).sum() 18 | } 19 | 20 | fn solver(crabs: &[u64], distance_fn: fn(u64, u64) -> u64) -> u64 { 21 | let min_x = crabs.iter().min().copied().unwrap() as u64; 22 | let max_x = crabs.iter().max().copied().unwrap() as u64; 23 | 24 | (min_x..=max_x) 25 | .map(|x| { 26 | crabs 27 | .iter() 28 | .fold(0, |total, crab| total + distance_fn(*crab, x)) 29 | }) 30 | .min() 31 | .unwrap() 32 | } 33 | 34 | #[aoc(day7, part1)] 35 | pub fn solve_part1(crabs: &[u64]) -> u64 { 36 | solver(crabs, distance) 37 | } 38 | 39 | #[aoc(day7, part2)] 40 | pub fn solve_part2(crabs: &[u64]) -> u64 { 41 | solver(crabs, compounded_distance) 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | #[test] 49 | fn test_day7_part1() { 50 | let input: Vec = vec![16, 1, 2, 0, 4, 2, 7, 1, 2, 14]; 51 | assert_eq!(solve_part1(&input), 37); 52 | } 53 | 54 | #[test] 55 | fn test_day7_compunded_distance() { 56 | assert_eq!(compounded_distance(5, 5), 0); 57 | assert_eq!(compounded_distance(16, 5), 66); 58 | assert_eq!(compounded_distance(1, 5), 10); 59 | assert_eq!(compounded_distance(5, 0), 15); 60 | } 61 | 62 | #[test] 63 | fn test_day7_part2() { 64 | let input: Vec = vec![16, 1, 2, 0, 4, 2, 7, 1, 2, 14]; 65 | assert_eq!(solve_part2(&input), 168); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/day10.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | #[aoc_generator(day10)] 5 | pub fn parse_input(input: &str) -> Vec { 6 | input.lines().map(|x| x.to_string()).collect() 7 | } 8 | 9 | fn is_open_token(token: char) -> bool { 10 | ['(', '[', '{', '<'].contains(&token) 11 | } 12 | 13 | fn get_expected_token_for(token: char) -> char { 14 | match token { 15 | '(' => ')', 16 | '[' => ']', 17 | '{' => '}', 18 | '<' => '>', 19 | _ => ' ', 20 | } 21 | } 22 | 23 | fn parse_line(input: &str) -> Result, char> { 24 | let mut buffer: Vec = vec![]; 25 | for c in input.chars() { 26 | if is_open_token(c) { 27 | buffer.push(get_expected_token_for(c)); 28 | continue; 29 | } 30 | 31 | let expected = buffer.pop().unwrap(); 32 | if c != expected { 33 | return Err(c); 34 | } 35 | } 36 | Ok(buffer) 37 | } 38 | 39 | #[aoc(day10, part1)] 40 | pub fn solve_part1(input: &[String]) -> u64 { 41 | input 42 | .iter() 43 | .map(|x| match parse_line(x) { 44 | Ok(_) => 0, 45 | Err(')') => 3, 46 | Err(']') => 57, 47 | Err('}') => 1197, 48 | Err('>') => 25137, 49 | _ => 0, 50 | }) 51 | .sum() 52 | } 53 | 54 | #[aoc(day10, part2)] 55 | pub fn solve_part2(input: &[String]) -> u64 { 56 | let buffers = input.iter().filter_map(|x| { 57 | let result = parse_line(x); 58 | match result { 59 | Ok(buffer) => Some(buffer.iter().rev().collect::()), 60 | _ => None, 61 | } 62 | }); 63 | 64 | let mut scores: Vec = buffers 65 | .into_iter() 66 | .map(|x| { 67 | x.chars() 68 | .map(|y| match y { 69 | ')' => 1, 70 | ']' => 2, 71 | '}' => 3, 72 | '>' => 4, 73 | _ => 0, 74 | }) 75 | .fold(0, |total, c| total * 5 + c) 76 | }) 77 | .collect(); 78 | 79 | scores.sort_unstable(); 80 | *scores.get(scores.len() / 2).unwrap() 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | 87 | const RAW_INPUT: &str = r#"[({(<(())[]>[[{[]{<()<>> 88 | [(()[<>])]({[<{<<[]>>( 89 | {([(<{}[<>[]}>{[]{[(<()> 90 | (((({<>}<{<{<>}{[]{[]{} 91 | [[<[([]))<([[{}[[()]]] 92 | [{[{({}]{}}([{[{{{}}([] 93 | {<[[]]>}<{[{[{[]{()[[[] 94 | [<(<(<(<{}))><([]([]() 95 | <{([([[(<>()){}]>(<<{{ 96 | <{([{{}}[<[[[<>{}]]]>[]]"#; 97 | 98 | #[test] 99 | pub fn test_day10_solve_part1() { 100 | assert_eq!(solve_part1(&parse_input(RAW_INPUT)), 26397); 101 | } 102 | 103 | #[test] 104 | pub fn test_day10_solve_part2() { 105 | assert_eq!(solve_part2(&parse_input(RAW_INPUT)), 288957); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/day14.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | 3 | use std::collections::HashMap; 4 | 5 | pub fn parse_input(input: &str) -> (String, HashMap) { 6 | let mut sections = input.split("\n\n"); 7 | let starter = sections.next().unwrap().to_owned(); 8 | let rules: HashMap = sections 9 | .next() 10 | .unwrap() 11 | .lines() 12 | .map(|raw| { 13 | let mut chunks = raw.split(" -> "); 14 | let key = chunks.next().unwrap().to_owned(); 15 | let new_char = chunks.next().unwrap().to_owned(); 16 | (key, new_char) 17 | }) 18 | .collect(); 19 | 20 | (starter, rules) 21 | } 22 | 23 | fn polymerize(polymer: &str, rules: &HashMap, steps: usize) -> String { 24 | let mut polymer = polymer.to_string(); 25 | 26 | for _ in 0..steps { 27 | let mut buffer = polymer.chars().next().unwrap().to_string(); 28 | 29 | for window in polymer.as_bytes().windows(2) { 30 | let pair = String::from_utf8(window.to_vec()).unwrap(); 31 | if let Some(insert) = rules.get(&pair) { 32 | buffer.push_str(insert); 33 | } 34 | buffer.push(pair.chars().nth(1).unwrap()); 35 | } 36 | 37 | polymer = buffer.clone(); 38 | } 39 | 40 | polymer.to_string() 41 | } 42 | 43 | fn count_chars(input: &str) -> HashMap { 44 | let mut freqs = HashMap::new(); 45 | for c in input.chars() { 46 | *freqs.entry(c).or_insert(0) += 1; 47 | } 48 | freqs 49 | } 50 | 51 | #[aoc(day14, part1)] 52 | pub fn solve_part1(input: &str) -> u64 { 53 | let (template, rules) = parse_input(input); 54 | let polymer = polymerize(&template, &rules, 10); 55 | 56 | let freqs = count_chars(&polymer); 57 | let (_, max_count) = freqs.iter().max_by(|(_, a), (_, b)| a.cmp(b)).unwrap(); 58 | let (_, min_count) = freqs.iter().min_by(|(_, a), (_, b)| a.cmp(b)).unwrap(); 59 | 60 | max_count - min_count 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | #[test] 68 | fn test_day14_parse_input() { 69 | let input = "NNCB\n\nCH -> B\nHH -> N"; 70 | assert_eq!( 71 | parse_input(input), 72 | ( 73 | "NNCB".to_string(), 74 | HashMap::::from([ 75 | ("CH".to_string(), "B".to_string()), 76 | ("HH".to_string(), "N".to_string()) 77 | ]) 78 | ) 79 | ); 80 | } 81 | 82 | const INPUT: &str = r#"NNCB 83 | 84 | CH -> B 85 | HH -> N 86 | CB -> H 87 | NH -> C 88 | HB -> C 89 | HC -> B 90 | HN -> C 91 | NN -> C 92 | BH -> H 93 | NC -> B 94 | NB -> B 95 | BN -> B 96 | BB -> N 97 | BC -> B 98 | CC -> N 99 | CN -> C"#; 100 | 101 | #[test] 102 | fn test_day14_solve_part1() { 103 | assert_eq!(solve_part1(INPUT), 1588); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/day09.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | 3 | use crate::utils::Grid; 4 | 5 | type Cell = (u64, (usize, usize)); 6 | 7 | pub fn parse_input(input: &str) -> Vec> { 8 | input 9 | .lines() 10 | .map(|row| { 11 | row.chars() 12 | .map(|x| x.to_string().parse::().unwrap()) 13 | .collect() 14 | }) 15 | .collect() 16 | } 17 | 18 | fn build_map(raw_input: &str) -> Grid { 19 | let input = parse_input(raw_input); 20 | let width = input.first().unwrap().len(); 21 | let cells: Vec = input.into_iter().flatten().collect(); 22 | Grid::new(&cells, width) 23 | } 24 | 25 | fn get_low_points(map: &Grid) -> Vec { 26 | let mut low_points = vec![]; 27 | 28 | for x in 0..(map.size().0 as i32) { 29 | for y in 0..(map.size().1 as i32) { 30 | let cell = map.cell_at(x, y).unwrap(); 31 | if map 32 | .neighbors_at(x, y) 33 | .iter() 34 | .all(|(neighbor, _)| cell < *neighbor) 35 | { 36 | low_points.push((cell, (x as usize, y as usize))); 37 | } 38 | } 39 | } 40 | 41 | low_points 42 | } 43 | 44 | #[aoc(day9, part1)] 45 | pub fn solve_part1(input: &str) -> u64 { 46 | let map = build_map(input); 47 | let low_points = get_low_points(&map); 48 | 49 | low_points.into_iter().map(|(x, _)| x + 1).sum() 50 | } 51 | 52 | fn basin_at(map: &Grid, coords: (usize, usize), marked: &mut Grid) -> Vec { 53 | let (x, y) = (coords.0 as i32, coords.1 as i32); 54 | let height = map.cell_at(x, y).unwrap(); 55 | let is_marked = marked.cell_at(x, y).unwrap(); 56 | 57 | if height >= 9 || is_marked { 58 | return vec![]; 59 | } 60 | 61 | marked.set_at(coords.0, coords.1, true); 62 | 63 | let neighbors: Vec<(u64, (usize, usize))> = map 64 | .neighbors_at(x, y) 65 | .into_iter() 66 | .filter(|(n, _)| *n > height) 67 | .map(|(_, point)| basin_at(map, point, marked)) 68 | .flatten() 69 | .collect(); 70 | 71 | vec![vec![(height, coords)], neighbors].concat() 72 | } 73 | 74 | #[aoc(day9, part2)] 75 | pub fn solve_part2(input: &str) -> u64 { 76 | let map = build_map(input); 77 | let low_points = get_low_points(&map); 78 | 79 | // There's a basin at each low point. Collect their size. 80 | let mut marked = Grid::::new(&vec![false; map.size().0 * map.size().1], map.size().0); 81 | let mut basins: Vec = low_points 82 | .into_iter() 83 | .map(|(_, coords)| basin_at(&map, coords, &mut marked)) 84 | .map(|b| b.len()) 85 | .collect(); 86 | 87 | basins.sort_unstable(); 88 | 89 | basins[basins.len() - 3..].iter().product::() as u64 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | 96 | const INPUT: &str = r#"2199943210 97 | 3987894921 98 | 9856789892 99 | 8767896789 100 | 9899965678 101 | "#; 102 | 103 | #[test] 104 | fn test_day9_solve_part1() { 105 | assert_eq!(solve_part1(INPUT), 15); 106 | } 107 | 108 | #[test] 109 | fn test_day9_solve_part2() { 110 | assert_eq!(solve_part2(INPUT), 1134); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/day03.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | #[aoc_generator(day3)] 5 | pub fn parse_input(input: &str) -> Vec { 6 | input.lines().map(|x| x.to_string()).collect() 7 | } 8 | 9 | fn build_report(input: &[String]) -> (u32, Vec) { 10 | let n_bits = input.get(0).unwrap().len() as u32; 11 | let report: Vec = input.iter().map(|x| u32::from_str_radix(x, 2).unwrap()).collect(); 12 | (n_bits, report) 13 | } 14 | 15 | #[aoc(day3, part1)] 16 | pub fn solve_part1(input: &[String]) -> u32 { 17 | let (n_bits, report) = build_report(input); 18 | let mut gamma_rate = 0; 19 | 20 | for i in 0..n_bits { 21 | let mask = 2_u32.pow(i); 22 | let ones = report.iter().filter(|x| *x & mask == mask).count(); 23 | if ones > report.len() / 2 { 24 | gamma_rate += mask 25 | } 26 | } 27 | 28 | let mask = 2_u32.pow(n_bits) - 1; // so for 5 bits we would get this mask 0b11111 29 | let epsilon_rate = !gamma_rate & mask ; 30 | 31 | gamma_rate * epsilon_rate 32 | } 33 | 34 | #[derive(Debug, PartialEq, Clone, Copy)] 35 | enum BitCriteria { 36 | MostCommon, 37 | LeastCommon, 38 | } 39 | 40 | fn filter_report(bit_index: u32, list: &[u32], criteria: BitCriteria) -> Vec { 41 | let mask = 2_u32.pow(bit_index); 42 | let ones: Vec = list.to_vec().into_iter().filter(|x| x & mask == mask).collect(); 43 | let zeros: Vec = list.to_vec().into_iter().filter(|x| x & mask != mask).collect(); 44 | 45 | match criteria { 46 | BitCriteria::MostCommon => if ones.len() >= zeros.len() { ones } else { zeros }, 47 | BitCriteria::LeastCommon => if zeros.len() <= ones.len() { zeros } else { ones }, 48 | } 49 | } 50 | 51 | fn get_rating(n_bits: u32, list: &[u32], criteria: BitCriteria) -> u32 { 52 | let mut candidates = list.to_vec(); 53 | for i in (0..n_bits).rev() { 54 | candidates = filter_report(i, &candidates, criteria); 55 | if candidates.len() == 1 { break; } 56 | } 57 | 58 | *candidates.get(0).unwrap() 59 | } 60 | 61 | #[aoc(day3, part2)] 62 | pub fn solve_part2(input: &[String]) -> u32 { 63 | let (n_bits, report) = build_report(input); 64 | 65 | let oxygen_rating = get_rating(n_bits, &report, BitCriteria::MostCommon); 66 | let co2_rating = get_rating(n_bits, &report, BitCriteria::LeastCommon); 67 | 68 | oxygen_rating * co2_rating 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | 75 | #[test] 76 | fn test_day3_part1() { 77 | let input: Vec = vec![ 78 | "00100", 79 | "11110", 80 | "10110", 81 | "10111", 82 | "10101", 83 | "01111", 84 | "00111", 85 | "11100", 86 | "10000", 87 | "11001", 88 | "00010", 89 | "01010", 90 | ].iter().map(|x| x.to_string()).collect(); 91 | assert_eq!(solve_part1(&input), 198) 92 | } 93 | 94 | #[test] 95 | fn test_day3_part2() { 96 | let input: Vec = vec![ 97 | "00100", 98 | "11110", 99 | "10110", 100 | "10111", 101 | "10101", 102 | "01111", 103 | "00111", 104 | "11100", 105 | "10000", 106 | "11001", 107 | "00010", 108 | "01010", 109 | ].iter().map(|x| x.to_string()).collect(); 110 | assert_eq!(solve_part2(&input), 230); 111 | } 112 | } -------------------------------------------------------------------------------- /src/day17.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | use lazy_static::lazy_static; 4 | use regex::Regex; 5 | 6 | use std::cmp::Ordering; 7 | 8 | type Point = (i64, i64); 9 | type Area = (Point, Point); 10 | 11 | #[aoc_generator(day17)] 12 | pub fn parse_input(input: &str) -> Area { 13 | // example input -> `target area: x=20..30, y=-10..-5` 14 | lazy_static! { 15 | static ref RE: Regex = Regex::new(r"-?\d+").unwrap(); 16 | } 17 | 18 | let numbers: Vec = RE 19 | .find_iter(input) 20 | .map(|x| x.as_str().parse::().unwrap()) 21 | .collect(); 22 | ((numbers[0], numbers[1]), (numbers[2], numbers[3])) 23 | } 24 | 25 | #[derive(Debug, Clone, Copy, PartialEq)] 26 | struct Probe { 27 | velocity: Point, 28 | position: Point, 29 | } 30 | 31 | impl Iterator for Probe { 32 | type Item = Point; 33 | 34 | fn next(&mut self) -> Option { 35 | self.position.0 += self.velocity.0; 36 | self.position.1 += self.velocity.1; 37 | 38 | self.velocity.0 += match self.velocity.0.cmp(&0) { 39 | Ordering::Greater => -1, 40 | Ordering::Less => 1, 41 | Ordering::Equal => 0, 42 | }; 43 | self.velocity.1 += -1; 44 | 45 | Some(self.position) 46 | } 47 | } 48 | 49 | fn is_inside_bbox(point: Point, area: Area) -> bool { 50 | let (range_x, range_y) = area; 51 | let (x, y) = point; 52 | x >= range_x.0 && x <= range_x.1 && y >= range_y.0 && y <= range_y.1 53 | } 54 | 55 | fn shoot(velocity: Point, area: Area) -> Option { 56 | let mut probe = Probe { 57 | velocity, 58 | position: (0, 0), 59 | }; 60 | let mut max_y = probe.position.1; 61 | loop { 62 | let position = probe.next().unwrap(); 63 | max_y = std::cmp::max(max_y, position.1); 64 | if is_inside_bbox(position, area) { 65 | return Some(max_y); 66 | } 67 | if probe.velocity.1 < 0 && position.1 < area.1 .0 { 68 | return None; 69 | } 70 | if probe.velocity.0 == 0 && (probe.position.0 < area.0 .0 || probe.position.0 > area.0 .1) { 71 | return None; 72 | } 73 | } 74 | } 75 | 76 | #[aoc(day17, part1)] 77 | pub fn solve_part1(area: &Area) -> i64 { 78 | let mut shots: Vec = vec![]; 79 | for x in 0..=area.0 .1 { 80 | for y in area.1 .0..500 { 81 | if let Some(y) = shoot((x, y), *area) { 82 | shots.push(y); 83 | } 84 | } 85 | } 86 | 87 | *shots.iter().max().unwrap() 88 | } 89 | 90 | #[aoc(day17, part2)] 91 | pub fn solve_part2(area: &Area) -> usize { 92 | let mut shots: Vec = vec![]; 93 | 94 | for x in 0..=area.0 .1 { 95 | for y in area.1 .0..500 { 96 | if shoot((x, y), *area).is_some() { 97 | shots.push((x, y)); 98 | } 99 | } 100 | } 101 | 102 | shots.len() as usize 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::*; 108 | 109 | #[test] 110 | fn test_day17_parse_input() { 111 | assert_eq!( 112 | parse_input("target area: x=20..30, y=-10..-5"), 113 | ((20, 30), (-10, -5)) 114 | ); 115 | } 116 | 117 | #[test] 118 | fn test_day17_solve_part1() { 119 | let area = ((20, 30), (-10, -5)); 120 | assert_eq!(solve_part1(&area), 45); 121 | } 122 | 123 | #[test] 124 | fn test_day17_solve_part2() { 125 | let area = ((20, 30), (-10, -5)); 126 | assert_eq!(solve_part2(&area), 112); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/day02.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | use std::convert::From; 5 | 6 | #[derive(Debug, PartialEq, Clone, Copy)] 7 | pub enum Command { 8 | Forward(i64), 9 | Up(i64), 10 | Down(i64), 11 | } 12 | 13 | impl From<&str> for Command { 14 | fn from(raw: &str) -> Self { 15 | let tokens: Vec<&str> = raw.split(' ').collect(); 16 | let cmd = tokens[0]; 17 | let value = tokens[1].parse::().unwrap(); 18 | 19 | match cmd { 20 | "forward" => Self::Forward(value), 21 | "up" => Self::Up(value), 22 | "down" => Self::Down(value), 23 | _ => panic!("Unrecognized command: {}", raw), 24 | } 25 | } 26 | } 27 | 28 | pub trait Submarine { 29 | fn exec(&mut self, cmd: &Command); 30 | fn run(&mut self, input: &[Command]) { 31 | for cmd in input { 32 | self.exec(cmd); 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct SubmarineV1 { 39 | x: i64, 40 | y: i64, 41 | } 42 | 43 | impl Submarine for SubmarineV1 { 44 | fn exec(&mut self, cmd: &Command) { 45 | match cmd { 46 | Command::Forward(delta) => self.x += delta, 47 | Command::Up(delta) => self.y -= delta, 48 | Command::Down(delta) => self.y += delta, 49 | } 50 | } 51 | } 52 | 53 | #[derive(Debug)] 54 | pub struct SubmarineV2 { 55 | x: i64, 56 | y: i64, 57 | aim: i64, 58 | } 59 | 60 | impl Submarine for SubmarineV2 { 61 | fn exec(&mut self, cmd: &Command) { 62 | match cmd { 63 | Command::Up(delta) => self.aim -= delta, 64 | Command::Down(delta) => self.aim += delta, 65 | Command::Forward(delta) => { 66 | self.x += delta; 67 | self.y += self.aim * delta; 68 | } 69 | } 70 | } 71 | } 72 | 73 | #[aoc_generator(day2)] 74 | pub fn parse_input(input: &str) -> Vec { 75 | input.lines().map(Command::from).collect() 76 | } 77 | 78 | #[aoc(day2, part1)] 79 | pub fn solve_part1(input: &[Command]) -> i64 { 80 | let mut submarine = SubmarineV1 { x: 0, y: 0 }; 81 | submarine.run(input); 82 | 83 | submarine.x * submarine.y 84 | } 85 | 86 | #[aoc(day2, part2)] 87 | pub fn solve_part2(input: &[Command]) -> i64 { 88 | let mut submarine = SubmarineV2 { x: 0, y: 0, aim: 0 }; 89 | submarine.run(input); 90 | 91 | submarine.x * submarine.y 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | 98 | #[test] 99 | pub fn test_day2_parse_input() { 100 | let input = String::from("forward 5\ndown 5\nforward 8\nup 3\ndown 8\nforward 2"); 101 | let parsed = parse_input(&input); 102 | assert_eq!(parsed.len(), 6); 103 | assert_eq!( 104 | parsed, 105 | vec![ 106 | Command::Forward(5), 107 | Command::Down(5), 108 | Command::Forward(8), 109 | Command::Up(3), 110 | Command::Down(8), 111 | Command::Forward(2), 112 | ] 113 | ); 114 | } 115 | 116 | #[test] 117 | pub fn test_day2_part1() { 118 | let commands = vec![ 119 | Command::Forward(5), 120 | Command::Down(5), 121 | Command::Forward(8), 122 | Command::Up(3), 123 | Command::Down(8), 124 | Command::Forward(2), 125 | ]; 126 | 127 | assert_eq!(solve_part1(&commands), 150); 128 | } 129 | 130 | #[test] 131 | pub fn test_day2_part2() { 132 | let commands = vec![ 133 | Command::Forward(5), 134 | Command::Down(5), 135 | Command::Forward(8), 136 | Command::Up(3), 137 | Command::Down(8), 138 | Command::Forward(2), 139 | ]; 140 | 141 | assert_eq!(solve_part2(&commands), 900); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/day11.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | 3 | use crate::utils::Grid; 4 | 5 | type Octopus = u64; 6 | 7 | fn parse_input(input: &str) -> Grid { 8 | let octopuses: Vec> = input 9 | .lines() 10 | .map(|row| { 11 | row.chars() 12 | .map(|x| x.to_string().parse::().unwrap()) 13 | .collect() 14 | }) 15 | .collect(); 16 | let width = octopuses.get(0).unwrap().len(); 17 | let cells: Vec = octopuses.into_iter().flatten().collect(); 18 | Grid::new(&cells, width) 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | struct OctopusSim { 23 | pub map: Grid, 24 | } 25 | 26 | impl OctopusSim { 27 | pub fn new(map: Grid) -> Self { 28 | Self { map } 29 | } 30 | 31 | pub fn run(&mut self, steps: usize) -> u64 { 32 | let mut flashes = 0; 33 | for _ in 0..steps { 34 | flashes += self.tick() 35 | } 36 | flashes 37 | } 38 | 39 | fn tick(&mut self) -> u64 { 40 | // 1. Increase the level energy of all octopuses by 1 41 | for octopus in self.map.cells.iter_mut() { 42 | *octopus += 1; 43 | } 44 | // 2. Flash octopuses with energy level > 9 45 | let mut flashes: Vec<(usize, usize)> = vec![]; 46 | loop { 47 | let did_flash = self.tick_map(&mut flashes); 48 | if !did_flash { 49 | break; 50 | } 51 | } 52 | 53 | flashes.len() as u64 54 | } 55 | 56 | fn flash_at(&mut self, x: usize, y: usize, flashed: &[(usize, usize)]) { 57 | let neighbors = self.map.neighbors8_at(x as i32, y as i32); 58 | 59 | for (octopus, point) in neighbors 60 | .iter() 61 | .filter(|(_, point)| !flashed.contains(point)) 62 | { 63 | self.map.set_at(point.0, point.1, octopus + 1); 64 | } 65 | } 66 | 67 | fn tick_map(&mut self, flashes: &mut Vec<(usize, usize)>) -> bool { 68 | let mut did_flash = false; 69 | for x in 0..self.map.size().0 { 70 | for y in 0..self.map.size().1 { 71 | if self.tick_cell(x, y, flashes) { 72 | did_flash = true; 73 | } 74 | } 75 | } 76 | 77 | did_flash 78 | } 79 | 80 | fn tick_cell(&mut self, x: usize, y: usize, flashes: &mut Vec<(usize, usize)>) -> bool { 81 | if self.map.cell_at(x as i32, y as i32).unwrap() <= 9 { 82 | return false; 83 | } 84 | 85 | self.map.set_at(x, y, 0); 86 | 87 | if flashes.contains(&(x, y)) { 88 | false 89 | } else { 90 | self.flash_at(x, y, flashes); 91 | flashes.push((x, y)); 92 | true 93 | } 94 | } 95 | } 96 | 97 | #[aoc(day11, part1)] 98 | pub fn solve_part1(input: &str) -> u64 { 99 | let map = parse_input(input); 100 | let mut sim = OctopusSim::new(map); 101 | 102 | sim.run(100) 103 | } 104 | 105 | #[aoc(day11, part2)] 106 | pub fn solve_part2(input: &str) -> u64 { 107 | let map = parse_input(input); 108 | let mut sim = OctopusSim::new(map); 109 | 110 | let mut steps = 0; 111 | let octopus_count = sim.map.size().0 * sim.map.size().1; 112 | 113 | loop { 114 | steps += 1; 115 | let flashes = sim.run(1); 116 | if flashes >= octopus_count as u64 { 117 | break; 118 | } 119 | } 120 | 121 | steps 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | const RAW_INPUT: &str = r#"5483143223 129 | 2745854711 130 | 5264556173 131 | 6141336146 132 | 6357385478 133 | 4167524645 134 | 2176841721 135 | 6882881134 136 | 4846848554 137 | 5283751526"#; 138 | 139 | #[test] 140 | fn test_day11_solve_part1() { 141 | assert_eq!(solve_part1(RAW_INPUT), 1656); 142 | } 143 | 144 | #[test] 145 | fn test_day11_solve_part2() { 146 | assert_eq!(solve_part2(RAW_INPUT), 195); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/day05.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | use std::collections::HashMap; 4 | use std::convert::From; 5 | 6 | pub type Point = (i64, i64); 7 | 8 | #[derive(Debug, PartialEq, Clone, Copy)] 9 | pub struct Segment { 10 | pub start: Point, 11 | pub end: Point, 12 | } 13 | 14 | impl From<&str> for Segment { 15 | fn from(raw: &str) -> Self { 16 | let points: Vec = raw 17 | .split(" -> ") 18 | .map(|x| x.split(',')) 19 | .flatten() 20 | .map(|x| x.parse::().unwrap()) 21 | .collect(); 22 | 23 | Self { 24 | start: (points[0], points[1]), 25 | end: (points[2], points[3]), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Debug, PartialEq, Clone, Copy)] 31 | pub enum Dir { 32 | Horizontal, 33 | Vertical, 34 | Diagonal, 35 | } 36 | 37 | impl Segment { 38 | pub fn direction(&self) -> Dir { 39 | if self.start.0 == self.end.0 { 40 | Dir::Vertical 41 | } else if self.start.1 == self.end.1 { 42 | Dir::Horizontal 43 | } else { 44 | Dir::Diagonal 45 | } 46 | } 47 | 48 | pub fn points(&self) -> Vec { 49 | // Rust ranges do not support a negative step, so h_range and v_range 50 | // are a dirty way to bypass this. 51 | let h_range: Vec = if self.start.0 < self.end.0 { 52 | (self.start.0..=self.end.0).collect() 53 | } else { 54 | (self.end.0..=self.start.0).rev().collect() 55 | }; 56 | 57 | let v_range: Vec = if self.start.1 < self.end.1 { 58 | (self.start.1..=self.end.1).collect() 59 | } else { 60 | (self.end.1..=self.start.1).rev().collect() 61 | }; 62 | 63 | match self.direction() { 64 | Dir::Vertical => v_range.into_iter().map(|y| (self.start.0, y)).collect(), 65 | Dir::Horizontal => h_range.into_iter().map(|x| (x, self.start.1)).collect(), 66 | Dir::Diagonal => h_range.into_iter().zip(v_range.into_iter()).collect(), 67 | } 68 | } 69 | } 70 | 71 | #[aoc_generator(day5)] 72 | pub fn parse_input(input: &str) -> Vec { 73 | input.lines().map(Segment::from).collect() 74 | } 75 | 76 | fn solve(vents: &[Segment]) -> u64 { 77 | let mut ocean_map = HashMap::new(); 78 | 79 | for p in vents.iter().map(|x| x.points()).flatten() { 80 | *ocean_map.entry(p).or_insert(0) += 1; 81 | } 82 | 83 | ocean_map 84 | .values() 85 | .fold(0, |total, x| total + if *x > 1 { 1 } else { 0 }) 86 | } 87 | 88 | #[aoc(day5, part1)] 89 | pub fn solve_part1(input: &[Segment]) -> u64 { 90 | let vents = input 91 | .to_vec() 92 | .into_iter() 93 | .filter(|x| x.direction() != Dir::Diagonal) 94 | .collect::>(); 95 | solve(&vents) 96 | } 97 | 98 | #[aoc(day5, part2)] 99 | pub fn solve_part2(input: &[Segment]) -> u64 { 100 | solve(input) 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | 107 | #[test] 108 | fn test_day5_parse_segment() { 109 | assert_eq!( 110 | Segment::from("1,2 -> 3,14"), 111 | Segment { 112 | start: (1, 2), 113 | end: (3, 14) 114 | } 115 | ); 116 | } 117 | 118 | #[test] 119 | fn test_day5_segment_points() { 120 | assert_eq!( 121 | Segment::from("0,0 -> 2,2").points(), 122 | vec![(0, 0), (1, 1), (2, 2)] 123 | ); 124 | assert_eq!( 125 | Segment::from("2,2 -> 0,0").points(), 126 | vec![(2, 2), (1, 1), (0, 0)] 127 | ); 128 | assert_eq!( 129 | Segment::from("0,0 -> 2,0").points(), 130 | vec![(0, 0), (1, 0), (2, 0)] 131 | ); 132 | assert_eq!( 133 | Segment::from("2,0 -> 0,0").points(), 134 | vec![(2, 0), (1, 0), (0, 0)] 135 | ); 136 | assert_eq!( 137 | Segment::from("0,0 -> 0,2").points(), 138 | vec![(0, 0), (0, 1), (0, 2)] 139 | ); 140 | } 141 | 142 | const INPUT: &str = r#"0,9 -> 5,9 143 | 8,0 -> 0,8 144 | 9,4 -> 3,4 145 | 2,2 -> 2,1 146 | 7,0 -> 7,4 147 | 6,4 -> 2,0 148 | 0,9 -> 2,9 149 | 3,4 -> 1,4 150 | 0,0 -> 8,8 151 | 5,5 -> 8,2"#; 152 | 153 | #[test] 154 | fn test_day5_part1() { 155 | let input = parse_input(INPUT); 156 | assert_eq!(solve_part1(&input), 5); 157 | } 158 | 159 | #[test] 160 | fn test_day5_part2() { 161 | let input = parse_input(INPUT); 162 | assert_eq!(solve_part2(&input), 12); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub struct Grid { 5 | pub cells: Vec, 6 | width: usize, 7 | height: usize, 8 | } 9 | 10 | impl Grid { 11 | pub fn new(cells: &[T], width: usize) -> Self { 12 | let len = cells.len(); 13 | let cells = cells.to_owned(); 14 | Self { 15 | cells, 16 | width, 17 | height: len / width, 18 | } 19 | } 20 | 21 | pub fn size(&self) -> (usize, usize) { 22 | (self.width, self.height) 23 | } 24 | 25 | pub fn cell_at(&self, x: i32, y: i32) -> Option { 26 | if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 { 27 | return None; 28 | } 29 | 30 | let i = self.index_for(x as usize, y as usize); 31 | Some(self.cells[i as usize].clone()) 32 | } 33 | 34 | pub fn full_cell(&self, x: usize, y: usize) -> (T, (usize, usize)) { 35 | let i = self.index_for(x, y); 36 | (self.cells[i].clone(), (x, y)) 37 | } 38 | 39 | pub fn set_at(&mut self, x: usize, y: usize, value: T) { 40 | if x >= self.width || y >= self.height { 41 | panic!("Trying to set unexisting coordinates: ({}, {})", x, y); 42 | } 43 | 44 | let i = self.index_for(x, y); 45 | self.cells[i] = value; 46 | } 47 | 48 | pub fn neighbors_at(&self, x: i32, y: i32) -> Vec<(T, (usize, usize))> { 49 | [(x, y - 1), (x + 1, y), (x, y + 1), (x - 1, y)] 50 | .iter() 51 | .filter_map(|point| { 52 | self.cell_at(point.0, point.1) 53 | .map(|cell| (cell, (point.0 as usize, point.1 as usize))) 54 | }) 55 | .collect() 56 | } 57 | 58 | pub fn neighbors8_at(&self, x: i32, y: i32) -> Vec<(T, (usize, usize))> { 59 | [ 60 | (x, y - 1), 61 | (x + 1, y - 1), 62 | (x + 1, y), 63 | (x + 1, y + 1), 64 | (x, y + 1), 65 | (x - 1, y + 1), 66 | (x - 1, y), 67 | (x - 1, y - 1), 68 | ] 69 | .iter() 70 | .filter_map(|point| { 71 | self.cell_at(point.0, point.1) 72 | .map(|cell| (cell, (point.0 as usize, point.1 as usize))) 73 | }) 74 | .collect() 75 | } 76 | 77 | fn index_for(&self, x: usize, y: usize) -> usize { 78 | y * self.width + x 79 | } 80 | } 81 | 82 | impl fmt::Display for Grid { 83 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 84 | let mut buffer = "".to_string(); 85 | for y in 0..self.size().1 { 86 | for x in 0..self.size().0 { 87 | buffer.push_str(&format!("{:?}", self.cell_at(x as i32, y as i32).unwrap())); 88 | } 89 | buffer.push('\n'); 90 | } 91 | writeln!(f, "{}", buffer) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | 99 | #[test] 100 | fn test_grid_constructor() { 101 | let input = vec![1, 2, 3, 4, 5, 6]; 102 | assert_eq!( 103 | Grid::::new(&input, 2), 104 | Grid { 105 | cells: vec![1, 2, 3, 4, 5, 6], 106 | width: 2, 107 | height: 3 108 | } 109 | ); 110 | } 111 | 112 | #[test] 113 | fn test_grid_size() { 114 | let input = vec![1, 2, 3, 4, 5, 6]; 115 | let grid = Grid::::new(&input, 2); 116 | 117 | assert_eq!(grid.size(), (2, 3)) 118 | } 119 | 120 | #[test] 121 | fn test_grid_cell_at() { 122 | let input = vec![1, 2, 3, 4, 5, 6]; 123 | let grid = Grid::::new(&input, 2); 124 | 125 | assert_eq!(grid.cell_at(-1, 0), None); 126 | assert_eq!(grid.cell_at(1, -1), None); 127 | assert_eq!(grid.cell_at(0, 3), None); 128 | assert_eq!(grid.cell_at(2, 0), None); 129 | assert_eq!(grid.cell_at(0, 0), Some(1)); 130 | assert_eq!(grid.cell_at(1, 1), Some(4)); 131 | assert_eq!(grid.cell_at(1, 2), Some(6)); 132 | } 133 | 134 | #[test] 135 | fn test_grid_neighbors_at() { 136 | let input = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; 137 | let grid = Grid::::new(&input, 3); 138 | assert_eq!(grid.neighbors_at(0, 0), vec![(2, (1, 0)), (4, (0, 1))]); 139 | assert_eq!( 140 | grid.neighbors_at(1, 0), 141 | vec![(3, (2, 0)), (5, (1, 1)), (1, (0, 0))] 142 | ); 143 | assert_eq!( 144 | grid.neighbors_at(1, 1), 145 | vec![(2, (1, 0)), (6, (2, 1)), (8, (1, 2)), (4, (0, 1))] 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/day04.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | 3 | use std::convert::From; 4 | 5 | const CARD_SIZE: usize = 5; 6 | 7 | #[derive(Debug, PartialEq, Clone, Copy)] 8 | pub enum Number { 9 | Unmarked(u64), 10 | Marked(u64), 11 | } 12 | 13 | impl Number { 14 | pub fn is_marked(&self) -> bool { 15 | match self { 16 | Self::Unmarked(_) => false, 17 | Self::Marked(_) => true, 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, PartialEq, Clone, Copy)] 23 | pub struct Card { 24 | numbers: [Number; CARD_SIZE * CARD_SIZE], 25 | } 26 | 27 | impl From<&str> for Card { 28 | fn from(raw: &str) -> Self { 29 | let mut card = Card { 30 | numbers: [Number::Unmarked(0); CARD_SIZE * CARD_SIZE], 31 | }; 32 | let raw_numbers = raw.split_whitespace().map(|x| x.parse::().unwrap()); 33 | 34 | for (i, x) in raw_numbers.into_iter().enumerate() { 35 | card.numbers[i] = Number::Unmarked(x); 36 | } 37 | 38 | card 39 | } 40 | } 41 | 42 | impl Card { 43 | fn is_bingo_at_range(&self, range: std::ops::Range, step: usize) -> bool { 44 | for i in range.step_by(step) { 45 | if !self.numbers[i].is_marked() { 46 | return false; 47 | } 48 | } 49 | true 50 | } 51 | 52 | fn is_bingo(&self, i: usize) -> bool { 53 | let col = i % CARD_SIZE; 54 | let row = i / CARD_SIZE; 55 | 56 | let row_range = (CARD_SIZE * row)..(CARD_SIZE * (row + 1)); 57 | let col_range = col..(CARD_SIZE * CARD_SIZE); 58 | 59 | self.is_bingo_at_range(row_range, 1) || self.is_bingo_at_range(col_range, CARD_SIZE) 60 | } 61 | 62 | fn score(&self) -> u64 { 63 | self.numbers 64 | .iter() 65 | .map(|x| match x { 66 | Number::Unmarked(value) => *value, 67 | _ => 0, 68 | }) 69 | .sum() 70 | } 71 | 72 | pub fn mark(&mut self, value: u64) -> Option { 73 | let position = self 74 | .numbers 75 | .iter() 76 | .position(|x| *x == Number::Unmarked(value)); 77 | if let Some(i) = position { 78 | self.numbers[i] = Number::Marked(value); 79 | if self.is_bingo(i) { 80 | return Some(self.score() * value); 81 | } 82 | } 83 | 84 | None 85 | } 86 | } 87 | 88 | fn parse_input(input: &str) -> (Vec, Vec) { 89 | let mut chunks = input.split("\n\n"); 90 | // the first chunk contains the numbers to be drawn 91 | let draws: Vec = chunks 92 | .next() 93 | .unwrap() 94 | .split(',') 95 | .map(|x| x.parse::().unwrap()) 96 | .collect(); 97 | // following chunks contain the bingo cards 98 | let cards: Vec = chunks.map(Card::from).collect(); 99 | 100 | (draws, cards) 101 | } 102 | 103 | #[aoc(day4, part1)] 104 | pub fn solve_part1(input: &str) -> u64 { 105 | let (draws, mut cards) = parse_input(input); 106 | 107 | for draw in draws.into_iter() { 108 | for card in cards.iter_mut() { 109 | if let Some(score) = card.mark(draw) { 110 | return score; 111 | }; 112 | } 113 | } 114 | 115 | 0 116 | } 117 | 118 | #[aoc(day4, part2)] 119 | pub fn solve_part2(input: &str) -> u64 { 120 | let (draws, mut cards) = parse_input(input); 121 | 122 | let mut latest_winner_score: u64 = 0; 123 | let mut to_ignore = Vec::::new(); 124 | 125 | for draw in draws.into_iter() { 126 | for (i, card) in cards.iter_mut().enumerate() { 127 | if to_ignore.contains(&i) { 128 | continue; 129 | } 130 | if let Some(score) = card.mark(draw) { 131 | latest_winner_score = score; 132 | to_ignore.push(i) 133 | }; 134 | } 135 | } 136 | 137 | latest_winner_score 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use super::*; 143 | 144 | const INPUT: &str = r#"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 145 | 146 | 22 13 17 11 0 147 | 8 2 23 4 24 148 | 21 9 14 16 7 149 | 6 10 3 18 5 150 | 1 12 20 15 19 151 | 152 | 3 15 0 2 22 153 | 9 18 13 17 5 154 | 19 8 7 25 23 155 | 20 11 10 24 4 156 | 14 21 16 12 6 157 | 158 | 14 21 17 24 4 159 | 10 16 15 9 19 160 | 18 8 23 26 20 161 | 22 11 13 6 5 162 | 2 0 12 3 7 163 | "#; 164 | 165 | #[test] 166 | fn test_day4_part1() { 167 | assert_eq!(solve_part1(INPUT), 4512); 168 | } 169 | 170 | #[test] 171 | fn test_day4_part2() { 172 | assert_eq!(solve_part2(INPUT), 1924); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/day15.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::Grid; 2 | use aoc_runner_derive::aoc; 3 | 4 | use std::cmp::Ordering; 5 | use std::collections::{BinaryHeap, HashMap}; 6 | use std::convert::From; 7 | 8 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 9 | struct Tile { 10 | cost: usize, 11 | position: (usize, usize), 12 | } 13 | 14 | impl From<(usize, Point)> for Tile { 15 | fn from(item: (usize, Point)) -> Self { 16 | Self { 17 | cost: item.0, 18 | position: item.1, 19 | } 20 | } 21 | } 22 | 23 | impl Ord for Tile { 24 | fn cmp(&self, other: &Self) -> Ordering { 25 | other 26 | .cost 27 | .cmp(&self.cost) 28 | .then_with(|| self.position.cmp(&other.position)) 29 | } 30 | } 31 | 32 | impl PartialOrd for Tile { 33 | fn partial_cmp(&self, other: &Self) -> Option { 34 | Some(self.cmp(other)) 35 | } 36 | } 37 | 38 | type Point = (usize, usize); 39 | 40 | fn heuristic(a: Point, b: Point) -> usize { 41 | ((a.0 as i64 - b.0 as i64).abs() + (a.0 as i64 - b.0 as i64).abs()) as usize 42 | } 43 | 44 | fn a_star(grid: &Grid, start: Point, end: Point) -> Vec { 45 | let mut frontier = BinaryHeap::new(); // priority queue 46 | let goal = Tile::from(grid.full_cell(end.0, end.1)); 47 | frontier.push(Tile::from(grid.full_cell(start.0, start.1))); 48 | 49 | let mut came_from: HashMap> = HashMap::new(); 50 | let mut cost_so_far: HashMap = HashMap::new(); 51 | 52 | came_from.insert(start, None); 53 | cost_so_far.insert(start, 0); 54 | 55 | while !frontier.is_empty() { 56 | let current = frontier.pop().unwrap(); 57 | 58 | if current == goal { 59 | break; 60 | } 61 | 62 | for (neighbor_cost, neighbor_position) in 63 | grid.neighbors_at(current.position.0 as i32, current.position.1 as i32) 64 | { 65 | let new_cost = cost_so_far[¤t.position] + neighbor_cost; 66 | if cost_so_far.get(&neighbor_position).is_none() 67 | || new_cost < cost_so_far[&neighbor_position] 68 | { 69 | cost_so_far.insert(neighbor_position, new_cost); 70 | let priority = new_cost + heuristic(end, neighbor_position); 71 | frontier.push(Tile::from((priority, neighbor_position))); 72 | came_from.insert(neighbor_position, Some(current.position)); 73 | } 74 | } 75 | } 76 | 77 | let mut path: Vec = vec![Tile::from(grid.full_cell(end.0, end.1))]; 78 | let mut current_cell = end; 79 | while let Some(cell) = came_from.get(¤t_cell).unwrap() { 80 | path.push(Tile::from(grid.full_cell(cell.0, cell.1))); 81 | current_cell = *cell; 82 | } 83 | 84 | path.into_iter().rev().collect() 85 | } 86 | 87 | fn parse_input(input: &str) -> Grid { 88 | let tiles: Vec> = input 89 | .lines() 90 | .map(|line| { 91 | line.chars() 92 | .map(|c| c.to_string().parse::().unwrap()) 93 | .collect() 94 | }) 95 | .collect(); 96 | 97 | let width = tiles.get(0).unwrap().len(); 98 | let cells: Vec = tiles.into_iter().flatten().collect(); 99 | 100 | Grid::::new(&cells, width) 101 | } 102 | 103 | #[aoc(day15, part1)] 104 | pub fn solve_part1(input: &str) -> u64 { 105 | let map = parse_input(input); 106 | 107 | let path = a_star(&map, (0, 0), (map.size().0 - 1, map.size().1 - 1)); 108 | 109 | path.into_iter() 110 | .filter(|tile| tile.position != (0, 0)) // exclude starting point 111 | .map(|tile| tile.cost) 112 | .sum::() as u64 113 | } 114 | 115 | #[aoc(day15, part2)] 116 | pub fn solve_part2(input: &str) -> u64 { 117 | const MULTI: usize = 5; 118 | let mini_map = parse_input(input); 119 | let mini_width = mini_map.size().0; 120 | let mini_height = mini_map.size().1; 121 | let full_width = mini_width * MULTI; 122 | let full_height = mini_height * MULTI; 123 | 124 | let mut map = Grid::::new(&vec![0; full_width * full_height], full_width); 125 | for y in 0..mini_height { 126 | for x in 0..mini_width { 127 | for i in 0..MULTI { 128 | for j in 0..MULTI { 129 | let offset = i + j; 130 | let mut risk = mini_map.cell_at(x as i32, y as i32).unwrap() + offset; 131 | if risk > 9 { 132 | risk -= 9 133 | } 134 | map.set_at(x + mini_width * j, y + i * mini_height, risk); 135 | } 136 | } 137 | } 138 | } 139 | 140 | let path = a_star(&map, (0, 0), (map.size().0 - 1, map.size().1 - 1)); 141 | 142 | path.into_iter() 143 | .filter(|tile| tile.position != (0, 0)) // exclude starting point 144 | .map(|tile| tile.cost) 145 | .sum::() as u64 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | 152 | const INPUT: &str = r#"1163751742 153 | 1381373672 154 | 2136511328 155 | 3694931569 156 | 7463417111 157 | 1319128137 158 | 1359912421 159 | 3125421639 160 | 1293138521 161 | 2311944581 162 | "#; 163 | 164 | #[test] 165 | fn test_day15_solve_part1() { 166 | assert_eq!(solve_part1(INPUT), 40); 167 | } 168 | 169 | #[test] 170 | fn test_day15_solve_part2() { 171 | assert_eq!(solve_part2(INPUT), 315); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/day12.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | use std::collections::HashMap; 5 | 6 | type Edge = (String, String); 7 | 8 | #[derive(Debug, PartialEq, Clone)] 9 | enum CaveSize { 10 | Small, 11 | Big, 12 | } 13 | 14 | #[derive(Debug)] 15 | struct CaveSystem { 16 | edges: HashMap>, 17 | } 18 | 19 | impl CaveSystem { 20 | pub fn new(input: &[Edge]) -> Self { 21 | let mut edges = HashMap::new(); 22 | 23 | for edge in input { 24 | // connections are bidirectional, so we create two 25 | // entries per input edge 26 | edges 27 | .entry(edge.0.to_owned()) 28 | .or_insert_with(Vec::new) 29 | .push(edge.1.to_owned()); 30 | edges 31 | .entry(edge.1.to_owned()) 32 | .or_insert_with(Vec::new) 33 | .push(edge.0.to_owned()); 34 | } 35 | 36 | Self { edges } 37 | } 38 | 39 | pub fn search_paths_v1(&self, start: &str, end: &str, partial: &[String]) -> Vec> { 40 | // add current node into the partial route 41 | let new_partial: Vec = vec![partial.to_owned(), vec![start.to_owned()]] 42 | .into_iter() 43 | .flatten() 44 | .collect(); 45 | 46 | // terminal condition -> arrived at destination 47 | if start == end { 48 | return vec![new_partial]; 49 | } 50 | 51 | let candidates = self.edges[start].iter().filter(|cave| { 52 | Self::cave_size_for(cave) == CaveSize::Big || !new_partial.contains(cave) 53 | }); 54 | 55 | candidates 56 | .map(|cave| self.search_paths_v1(cave, end, &new_partial)) 57 | .flatten() 58 | .collect() 59 | } 60 | 61 | pub fn search_paths_v2( 62 | &self, 63 | start: &str, 64 | end: &str, 65 | partial: &[String], 66 | visited: &str, 67 | ) -> Vec> { 68 | // add current node into the partial route 69 | let new_partial: Vec = vec![partial.to_owned(), vec![start.to_owned()]] 70 | .into_iter() 71 | .flatten() 72 | .collect(); 73 | 74 | // terminal condition -> arrived at destination 75 | if start == end { 76 | return vec![new_partial]; 77 | } 78 | 79 | let candidates = self.edges[start].iter().filter(|cave| { 80 | if *cave == "start" { 81 | return false; 82 | } 83 | if Self::cave_size_for(cave) == CaveSize::Big { 84 | return true; 85 | } 86 | 87 | let n_visits = new_partial.iter().filter(|x| x == cave).count(); 88 | 89 | if visited == *cave { 90 | n_visits < 2 91 | } else { 92 | n_visits == 0 93 | } 94 | }); 95 | 96 | let mut paths: Vec> = candidates 97 | .map(|cave| { 98 | if visited.is_empty() && Self::cave_size_for(cave) == CaveSize::Small { 99 | [ 100 | self.search_paths_v2(cave, end, &new_partial, cave), 101 | self.search_paths_v2(cave, end, &new_partial, ""), 102 | ] 103 | .concat() 104 | } else { 105 | [self.search_paths_v2(cave, end, &new_partial, visited)].concat() 106 | } 107 | }) 108 | .flatten() 109 | .collect(); 110 | 111 | // remove duplicates 112 | paths.sort(); 113 | paths.dedup(); 114 | 115 | paths 116 | } 117 | 118 | fn cave_size_for(name: &str) -> CaveSize { 119 | if name.to_uppercase() == name { 120 | CaveSize::Big 121 | } else { 122 | CaveSize::Small 123 | } 124 | } 125 | } 126 | 127 | #[aoc_generator(day12)] 128 | pub fn parse_input(input: &str) -> Vec { 129 | input 130 | .lines() 131 | .map(|edge| { 132 | let mut chunks = edge.split('-'); 133 | ( 134 | chunks.next().unwrap().to_string(), 135 | chunks.next().unwrap().to_string(), 136 | ) 137 | }) 138 | .collect() 139 | } 140 | 141 | #[aoc(day12, part1)] 142 | pub fn solve_part1(input: &[Edge]) -> u64 { 143 | let caves = CaveSystem::new(input); 144 | let paths = caves.search_paths_v1("start", "end", &[]); 145 | 146 | paths.len() as u64 147 | } 148 | 149 | #[aoc(day12, part2)] 150 | pub fn solve_part2(input: &[Edge]) -> u64 { 151 | let caves = CaveSystem::new(input); 152 | let paths = caves.search_paths_v2("start", "end", &[], ""); 153 | 154 | paths.len() as u64 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::*; 160 | 161 | const INPUT: [&str; 3] = [ 162 | "start-A\nstart-b\nA-c\nA-b\nb-d\nA-end\nb-end", 163 | "dc-end\nHN-start\nstart-kj\ndc-start\ndc-HN\nLN-dc\nHN-end\nkj-sa\nkj-HN\nkj-dc", 164 | "fs-end\nhe-DX\nfs-he\nstart-DX\npj-DX\nend-zg\nzg-sl\nzg-pj\npj-he\nRW-he\nfs-DX\npj-RW\nzg-RW\nstart-pj\nhe-WI\nzg-he\npj-fs\nstart-RW", 165 | ]; 166 | 167 | #[test] 168 | fn test_day12_solve_part1() { 169 | assert_eq!(solve_part1(&parse_input(INPUT[0])), 10); 170 | assert_eq!(solve_part1(&parse_input(INPUT[1])), 19); 171 | assert_eq!(solve_part1(&parse_input(INPUT[2])), 226); 172 | } 173 | 174 | #[test] 175 | fn test_day12_solve_part2() { 176 | assert_eq!(solve_part2(&parse_input(INPUT[0])), 36); 177 | assert_eq!(solve_part2(&parse_input(INPUT[1])), 103); 178 | assert_eq!(solve_part2(&parse_input(INPUT[2])), 3509); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/day16.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | 4 | use std::convert::From; 5 | 6 | #[aoc_generator(day16)] 7 | pub fn parse_input(input: &str) -> String { 8 | input 9 | .chars() 10 | .map(|x| { 11 | let raw = u64::from_str_radix(&x.to_string(), 16).unwrap(); // parse hex digit 12 | let bits = format!("{:04b}", raw); // convert it to a binary string 13 | bits 14 | }) 15 | .collect() 16 | } 17 | 18 | #[derive(Debug, Clone, Copy, PartialEq)] 19 | enum PacketType { 20 | Literal, 21 | Operator(u8), 22 | } 23 | 24 | impl From for PacketType { 25 | fn from(x: u8) -> Self { 26 | match x { 27 | 4 => Self::Literal, 28 | _ => Self::Operator(x), 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, PartialEq, Clone)] 34 | enum Length { 35 | Bits(usize), // how many bits subpackets take 36 | Count(usize), // how many subpackets 37 | } 38 | 39 | #[derive(Debug, PartialEq, Clone)] 40 | struct Packet { 41 | version: u8, 42 | type_id: PacketType, 43 | subpackets: Vec, 44 | value: Option, // bold assumption, but… 45 | length_id: Option, 46 | } 47 | 48 | impl From<&str> for Packet { 49 | fn from(raw: &str) -> Self { 50 | let version = u8::from_str_radix(&raw[0..3], 2).unwrap(); 51 | let type_id = PacketType::from(u8::from_str_radix(&raw[3..6], 2).unwrap()); 52 | let length_id = match type_id { 53 | PacketType::Literal => None, 54 | PacketType::Operator(_) => Some(match raw.chars().nth(6) { 55 | Some('0') => Length::Bits(usize::from_str_radix(&raw[7..7 + 15], 2).unwrap()), 56 | Some('1') => Length::Count(usize::from_str_radix(&raw[7..7 + 11], 2).unwrap()), 57 | _ => panic!("Unrecognized length id"), 58 | }), 59 | }; 60 | 61 | Packet { 62 | version, 63 | type_id, 64 | subpackets: vec![], 65 | value: None, 66 | length_id, 67 | } 68 | } 69 | } 70 | 71 | fn parse_message(message: &str) -> Vec { 72 | const MIN_LENGTH: usize = 6 + 1 + 4; // minimum length of packet is a literal with 4 bits 73 | let mut packets = Vec::::new(); 74 | let mut offset = 0; 75 | 76 | while offset < message.len() - MIN_LENGTH - 1 { 77 | let packet_type = 78 | PacketType::from(u8::from_str_radix(&message[offset + 3..offset + 6], 2).unwrap()); 79 | let packet_end = match packet_type { 80 | PacketType::Literal => { 81 | let mut i = offset + 6; 82 | while message.chars().nth(i) == Some('1') { 83 | i += 5; 84 | } 85 | i += 5; 86 | i 87 | } 88 | PacketType::Operator(_) => match message.chars().nth(offset + 6) { 89 | Some('0') => offset + 7 + 15, 90 | Some('1') => offset + 7 + 11, 91 | _ => panic!("Unrecognized length type"), 92 | }, 93 | }; 94 | 95 | let packet = Packet::from(&message[offset..packet_end]); 96 | packets.push(packet); 97 | offset = packet_end as usize 98 | } 99 | 100 | packets 101 | } 102 | 103 | #[aoc(day16, part1)] 104 | pub fn solve_part1(message: &str) -> u64 { 105 | parse_message(message) 106 | .iter() 107 | .map(|packet| packet.version as u64) 108 | .sum::() 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::*; 114 | 115 | #[test] 116 | fn test_parse_literal() { 117 | assert_eq!( 118 | Packet::from("110100101111111000101000"), 119 | Packet { 120 | version: 6, 121 | type_id: PacketType::Literal, 122 | subpackets: vec![], 123 | value: None, 124 | length_id: None, 125 | } 126 | ); 127 | } 128 | 129 | #[test] 130 | fn test_parse_operator_length_bits() { 131 | assert_eq!( 132 | Packet::from("00111000000000000110111101000101001010010001001000000000"), 133 | Packet { 134 | version: 1, 135 | type_id: PacketType::Operator(6), 136 | length_id: Some(Length::Bits(27)), 137 | subpackets: vec![ 138 | // Packet::from("11010001010"), 139 | // Packet::from("0101001000100100"), 140 | ], 141 | value: None, 142 | } 143 | ); 144 | } 145 | 146 | #[test] 147 | fn test_parse_operator_length_count() { 148 | assert_eq!( 149 | Packet::from("11101110000000001101010000001100100000100011000001100000"), 150 | Packet { 151 | version: 7, 152 | type_id: PacketType::Operator(3), 153 | length_id: Some(Length::Count(3)), 154 | subpackets: vec![ 155 | // Packet::from("01010000001"), 156 | // Packet::from("10010000010"), 157 | // Packet::from("00110000011"), 158 | ], 159 | value: None, 160 | } 161 | ) 162 | } 163 | 164 | #[test] 165 | fn test_day16_parse_input() { 166 | assert_eq!(parse_input("F"), "1111"); 167 | assert_eq!(parse_input("2"), "0010"); 168 | assert_eq!(parse_input("A8"), "10101000"); 169 | } 170 | 171 | #[test] 172 | fn test_day16_solve_part1() { 173 | assert_eq!(solve_part1(&parse_input("8A004A801A8002F478")), 16); 174 | assert_eq!(solve_part1(&parse_input("620080001611562C8802118E34")), 12); 175 | assert_eq!( 176 | solve_part1(&parse_input("C0015000016115A2E0802F182340")), 177 | 23 178 | ); 179 | assert_eq!( 180 | solve_part1(&parse_input("A0016C880162017C3686B18A3D4780")), 181 | 31 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/day13.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | use lazy_static::lazy_static; 4 | use regex::Regex; 5 | use std::fmt; 6 | 7 | use crate::utils::Grid; 8 | 9 | #[derive(Debug, PartialEq, Clone, Copy)] 10 | pub enum Fold { 11 | Up(usize), 12 | Left(usize), 13 | } 14 | 15 | type Tile = bool; 16 | 17 | #[derive(Debug, PartialEq, Clone)] 18 | pub struct Manual { 19 | grid: Grid, 20 | instructions: Vec, 21 | } 22 | 23 | impl Manual { 24 | pub fn fold(&mut self, fold: Fold) { 25 | let old_width = self.grid.size().0; 26 | let old_height = self.grid.size().1; 27 | let new_width = match fold { 28 | Fold::Up(_) => old_width, 29 | Fold::Left(x) => old_width - x - 1, 30 | }; 31 | let new_height = match fold { 32 | Fold::Up(y) => old_height - y - 1, 33 | Fold::Left(_) => old_height, 34 | }; 35 | 36 | let tiles: Vec = (0..(new_height * new_width)) 37 | .map(|i| { 38 | let x = (i % new_width) as i32; 39 | let y = (i / new_width) as i32; 40 | 41 | let (folding_x, folding_y) = match fold { 42 | Fold::Up(_) => (x, old_height as i32 - y - 1), 43 | Fold::Left(_) => (old_width as i32 - x - 1, y), 44 | }; 45 | 46 | let top = self.grid.cell_at(x, y).unwrap_or(false); 47 | let bottom = self.grid.cell_at(folding_x, folding_y).unwrap_or(false); 48 | 49 | top || bottom 50 | }) 51 | .collect(); 52 | 53 | self.grid = Grid::::new(&tiles, new_width); 54 | } 55 | } 56 | 57 | impl fmt::Display for Manual { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | let mut buffer = "".to_string(); 60 | for y in 0..self.grid.size().1 { 61 | for x in 0..self.grid.size().0 { 62 | let tile = self.grid.cell_at(x as i32, y as i32).unwrap(); 63 | buffer.push(match tile { 64 | true => '#', 65 | false => '.', 66 | }); 67 | } 68 | if y < self.grid.size().1 - 1 { 69 | buffer.push('\n') 70 | } 71 | } 72 | writeln!(f, "{}", buffer) 73 | } 74 | } 75 | 76 | #[aoc_generator(day13)] 77 | pub fn parse_input(input: &str) -> Manual { 78 | lazy_static! { 79 | static ref PARSER: Regex = 80 | Regex::new(r"^fold along (?P\w{1})=(?P\d+)$").unwrap(); 81 | } 82 | 83 | let mut sections_split = input.split("\n\n"); 84 | 85 | let dots: Vec<(usize, usize)> = sections_split 86 | .next() 87 | .unwrap() 88 | .lines() 89 | .map(|raw| { 90 | let mut dot_split = raw.split(','); 91 | ( 92 | dot_split.next().unwrap().parse::().unwrap(), 93 | dot_split.next().unwrap().parse::().unwrap(), 94 | ) 95 | }) 96 | .collect(); 97 | 98 | let instructions = sections_split 99 | .next() 100 | .unwrap() 101 | .lines() 102 | .map(|raw| { 103 | let captured = PARSER.captures(raw).unwrap(); 104 | let axis = captured.name("axis").unwrap().as_str(); 105 | let position = captured 106 | .name("position") 107 | .unwrap() 108 | .as_str() 109 | .parse::() 110 | .unwrap(); 111 | match axis { 112 | "x" => Fold::Left(position), 113 | "y" => Fold::Up(position), 114 | _ => panic!("Unrecognized axis"), 115 | } 116 | }) 117 | .collect(); 118 | 119 | let sheet_width = dots.iter().map(|(x, _)| x).max().unwrap() + 1; 120 | let sheet_height = dots.iter().map(|(_, y)| y).max().unwrap() + 1; 121 | let tiles: Vec = vec![false; sheet_width * sheet_height]; 122 | 123 | let mut grid = Grid::::new(&tiles, sheet_width); 124 | for (x, y) in dots { 125 | grid.set_at(x, y, true); 126 | } 127 | 128 | Manual { grid, instructions } 129 | } 130 | 131 | #[aoc(day13, part1)] 132 | pub fn solve_part1(manual: &Manual) -> u64 { 133 | let mut manual = manual.to_owned(); 134 | let fold = *manual.instructions.get(0).unwrap(); 135 | 136 | manual.fold(fold); 137 | 138 | manual.grid.cells.into_iter().filter(|x| *x).count() as u64 139 | } 140 | 141 | #[aoc(day13, part2)] 142 | pub fn solve_part2(manual: &Manual) -> String { 143 | let mut manual = manual.to_owned(); 144 | let instructions = manual.instructions.to_owned(); 145 | 146 | for fold in instructions.into_iter() { 147 | manual.fold(fold); 148 | } 149 | 150 | format!("\n{}", manual) // line break for readability 151 | } 152 | 153 | #[cfg(test)] 154 | mod tests { 155 | use super::*; 156 | 157 | #[test] 158 | fn test_day13_parse_input() { 159 | let input = "1,0\n0,1\n1,2\n2,2\n\nfold along y=1\nfold along x=1"; 160 | 161 | assert_eq!( 162 | parse_input(input), 163 | Manual { 164 | grid: Grid::::new( 165 | &[false, true, false, true, false, false, false, true, true], 166 | 3 167 | ), 168 | instructions: vec![Fold::Up(1), Fold::Left(1)] 169 | } 170 | ); 171 | } 172 | 173 | #[test] 174 | fn test_fold_up() { 175 | let mut manual = Manual { 176 | grid: Grid::::new( 177 | &[false, true, false, true, false, false, false, true, true], 178 | 3, 179 | ), 180 | instructions: vec![], 181 | }; 182 | 183 | manual.fold(Fold::Up(1)); 184 | 185 | assert_eq!(manual.grid.size(), (3, 1)); 186 | assert_eq!(manual.grid.cells, vec![false, true, true]); 187 | } 188 | 189 | #[test] 190 | fn test_fold_left() { 191 | let mut manual = Manual { 192 | grid: Grid::::new( 193 | &[false, true, false, true, false, false, false, true, true], 194 | 3, 195 | ), 196 | instructions: vec![], 197 | }; 198 | 199 | manual.fold(Fold::Left(1)); 200 | 201 | assert_eq!(manual.grid.size(), (1, 3)); 202 | assert_eq!(manual.grid.cells, vec![false, true, true]); 203 | } 204 | 205 | const RAW_INPUT: &str = r#"6,10 206 | 0,14 207 | 9,10 208 | 0,3 209 | 10,4 210 | 4,11 211 | 6,0 212 | 6,12 213 | 4,1 214 | 0,13 215 | 10,12 216 | 3,4 217 | 3,0 218 | 8,4 219 | 1,10 220 | 2,14 221 | 8,10 222 | 9,0 223 | 224 | fold along y=7 225 | fold along x=5"#; 226 | 227 | #[test] 228 | fn test_day13_solve_part1() { 229 | let input = parse_input(RAW_INPUT); 230 | assert_eq!(solve_part1(&input), 17); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/day08.rs: -------------------------------------------------------------------------------- 1 | use aoc_runner_derive::aoc; 2 | use aoc_runner_derive::aoc_generator; 3 | use itertools::Itertools; 4 | 5 | use std::collections::HashMap; 6 | 7 | type SegmentsDisplay = (Vec, Vec); 8 | 9 | #[aoc_generator(day8)] 10 | pub fn parse_input(input: &str) -> Vec { 11 | input 12 | .lines() 13 | .map(|x| { 14 | let mut chunks = x.split(" | "); 15 | let signals = chunks 16 | .next() 17 | .unwrap() 18 | .split_whitespace() 19 | .map(String::from) 20 | .collect(); 21 | let output = chunks 22 | .next() 23 | .unwrap() 24 | .split_whitespace() 25 | .map(String::from) 26 | .collect(); 27 | (signals, output) 28 | }) 29 | .collect() 30 | } 31 | 32 | #[aoc(day8, part1)] 33 | pub fn solve_part1(input: &[SegmentsDisplay]) -> u64 { 34 | let targets = [2, 4, 3, 7]; 35 | input 36 | .iter() 37 | .map(|(_, output)| output.clone()) 38 | .flatten() 39 | .fold(0, |total, x| { 40 | total + if targets.contains(&x.len()) { 1 } else { 0 } 41 | }) 42 | } 43 | 44 | fn named_segments_frequency(signals: &[String]) -> HashMap { 45 | let mut res = HashMap::new(); 46 | 47 | for name in signals.join("").chars() { 48 | *res.entry(name).or_insert(0) += 1; 49 | } 50 | 51 | res 52 | } 53 | 54 | fn named_segment_with_freq(freqs: &HashMap, target: u64) -> char { 55 | freqs 56 | .iter() 57 | .find_map(|(name, &freq)| if freq == target { Some(name) } else { None }) 58 | .unwrap() 59 | .to_owned() 60 | } 61 | 62 | fn decode_signals(signals: &[String]) -> HashMap { 63 | let mut digits: [&str; 10] = [""; 10]; 64 | let mut segs: [char; 7] = ['*'; 7]; // segments: 0 -> unmangled(a), 1 -> unmangled(b)... 65 | 66 | // 1, 4, 7, 8 are unique, so fill those first 67 | digits[1] = signals.iter().find(|x| x.len() == 2).unwrap(); 68 | digits[8] = signals.iter().find(|x| x.len() == 7).unwrap(); 69 | digits[4] = signals.iter().find(|x| x.len() == 4).unwrap(); 70 | digits[7] = signals.iter().find(|x| x.len() == 3).unwrap(); 71 | 72 | // Find segments with unique freq count 73 | let freqs = named_segments_frequency(signals); 74 | segs[1] = named_segment_with_freq(&freqs, 6); // B 75 | segs[4] = named_segment_with_freq(&freqs, 4); // E 76 | segs[5] = named_segment_with_freq(&freqs, 9); // F 77 | segs[2] = digits[1].chars().find(|x| *x != segs[5]).unwrap(); // C 78 | segs[3] = digits[4] 79 | .chars() 80 | .find(|x| ![segs[1], segs[2], segs[5]].contains(x)) 81 | .unwrap(); // D 82 | segs[0] = digits[7] 83 | .chars() 84 | .find(|x| ![segs[2], segs[5]].contains(x)) 85 | .unwrap(); // A 86 | segs[6] = digits[8] 87 | .chars() 88 | .find(|x| !segs.contains(x)) // only remaining unknown segment 89 | .unwrap(); // G 90 | 91 | // Digit 2 is the only one without segment 'f' 92 | digits[2] = signals.iter().find(|x| !x.contains(segs[5])).unwrap(); 93 | // Digit 3 is the only one with len(5) and segments 'cf' 94 | digits[3] = signals 95 | .iter() 96 | .filter(|x| x.len() == 5) 97 | .find(|x| x.contains(segs[2]) && x.contains(segs[5])) 98 | .unwrap(); 99 | // Digit 9 is the only one with len(6) and missing segment e 100 | digits[9] = signals 101 | .iter() 102 | .filter(|x| x.len() == 6) 103 | .find(|x| !x.contains(segs[4])) 104 | .unwrap(); 105 | // Digit 5 is the only one with len(5) and missing segments c & e 106 | digits[5] = signals 107 | .iter() 108 | .filter(|x| x.len() == 5) 109 | .find(|x| !(x.contains(segs[4]) || x.contains(segs[2]))) 110 | .unwrap(); 111 | // Digit 0 is the only one with len(6) and missing segment d 112 | digits[0] = signals 113 | .iter() 114 | .filter(|x| x.len() == 6) 115 | .find(|x| !x.contains(segs[3])) 116 | .unwrap(); 117 | // Digit 6 is the only one with len(6) and missing segment c 118 | digits[6] = signals 119 | .iter() 120 | .filter(|x| x.len() == 6) 121 | .find(|x| !x.contains(segs[2])) 122 | .unwrap(); 123 | 124 | digits 125 | .iter() 126 | .enumerate() 127 | .map(|(index, digit)| (digit.chars().sorted().collect(), index as u64)) 128 | .collect() 129 | } 130 | 131 | fn output_value(display: &SegmentsDisplay) -> u64 { 132 | let (signals, output) = display; 133 | let mapping = decode_signals(signals); 134 | 135 | output 136 | .iter() 137 | .map(|chunk| chunk.chars().sorted().collect::()) 138 | .map(|chunk| mapping[&chunk]) 139 | .join("") 140 | .parse::() 141 | .unwrap() 142 | } 143 | 144 | #[aoc(day8, part2)] 145 | pub fn solve_part2(input: &[SegmentsDisplay]) -> u64 { 146 | input.iter().map(output_value).sum() 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use super::*; 152 | 153 | const INPUT: &str = r#"be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe 154 | edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc 155 | fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg 156 | fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb 157 | aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea 158 | fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb 159 | dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe 160 | bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef 161 | egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb 162 | gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce"#; 163 | 164 | #[test] 165 | fn test_day8_parse_input() { 166 | let raw = "abc def | ab\nghi jkl | mn"; 167 | assert_eq!( 168 | parse_input(raw), 169 | vec![ 170 | ( 171 | vec!["abc".to_string(), "def".to_string()], 172 | vec!["ab".to_string()] 173 | ), 174 | ( 175 | vec!["ghi".to_string(), "jkl".to_string()], 176 | vec!["mn".to_string()] 177 | ) 178 | ] 179 | ); 180 | } 181 | 182 | #[test] 183 | fn test_day8_part1() { 184 | let input = parse_input(INPUT); 185 | assert_eq!(solve_part1(&input), 26); 186 | } 187 | 188 | #[test] 189 | fn test_day8_decode_signals() { 190 | let input = 191 | "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf"; 192 | let display = parse_input(input).clone(); 193 | let (signals, _) = display.first().unwrap(); 194 | 195 | assert_eq!( 196 | decode_signals(signals), 197 | HashMap::from([ 198 | ("abcdeg".to_string(), 0), 199 | ("ab".to_string(), 1), 200 | ("acdfg".to_string(), 2), 201 | ("abcdf".to_string(), 3), 202 | ("abef".to_string(), 4), 203 | ("bcdef".to_string(), 5), 204 | ("bcdefg".to_string(), 6), 205 | ("abd".to_string(), 7), 206 | ("abcdefg".to_string(), 8), 207 | ("abcdef".to_string(), 9), 208 | ]) 209 | ); 210 | } 211 | 212 | #[test] 213 | fn test_day8_output_value() { 214 | let input = 215 | "acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf"; 216 | let display = parse_input(input).clone(); 217 | assert_eq!(output_value(display.first().unwrap()), 5353); 218 | } 219 | 220 | #[test] 221 | fn test_day8_part2() { 222 | let input = parse_input(INPUT); 223 | assert_eq!(solve_part2(&input), 61229); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aoc-2021 2 | 3 | Advent of Code 2021 with Rust. 4 | 5 | ## To build and run the project 6 | 7 | This project uses [cargo-aoc](https://github.com/gobanos/cargo-aoc). More detailed instructions can be found at that project's [README](https://github.com/gobanos/cargo-aoc/blob/master/README.md) file. 8 | 9 | 1. Create an account at adventofcode.com 10 | 2. Get the value for your session cookie and configure `cargo-aoc`: 11 | 12 | ``` 13 | cargo aoc credentials -s TOKEN 14 | ``` 15 | 16 | 3. Build and run the code with: 17 | 18 | ``` 19 | cargo aoc 20 | ``` 21 | 22 | ## Log 23 | 24 | ### Day 1 25 | 26 | I found out about the [`slice::windows`](https://doc.rust-lang.org/std/primitive.slice.html#method.windows) method in Rust, which enables both parts of today's puzzle to be solved with functional programming. 27 | 28 | For **part 1**, it's as simple as iterating the entries with a window of size `2`, and then folding the result: 29 | 30 | ```rust 31 | measurements.windows(2).fold(0, |total, window| { 32 | total + if window[1] > window[0] { 1 } else { 0 } 33 | }) 34 | ``` 35 | 36 | For **part2** we need to first create triplets (which are windows of size `3`), and then iterate over the list of triplets as if they were windows of size `2`, to be able to access both the current triplet and the previous one: 37 | 38 | ```rust 39 | let triplets: Vec<&[u64]> = measurements.windows(3).collect(); 40 | triplets.windows(2).fold(0, |total, window| { 41 | // ... 42 | }) 43 | ``` 44 | 45 | ### Day 2 46 | 47 | I created an enum type to handle the different commands. This implements the trait `std::convert::From` to parse a string into a command. Overengineering? Yes, but… 🤷🏻‍♀️ 48 | 49 | ```rust 50 | pub enum Command { 51 | Forward(i64), 52 | Up(i64), 53 | Down(i64), 54 | } 55 | 56 | impl From<&str> for Command { 57 | fn from(raw: &str) -> Self { 58 | // ... 59 | } 60 | } 61 | ``` 62 | 63 | For **part 1**, running the commands is as simple as do a `match` over that enum: 64 | 65 | ```rust 66 | fn exec(&mut self, cmd: &Command) { 67 | match cmd { 68 | Command::Forward(delta) => self.x += delta, 69 | Command::Up(delta) => self.y -= delta, 70 | Command::Down(delta) => self.y += delta, 71 | } 72 | } 73 | ``` 74 | 75 | For **part 2**, a new logic for handling the commands is introduced, plus a new variable into the state of the submarine (`aim`). So I decided to play with Rust traits a bit and have two different types for the different "submarines", each one implementing a trait that provides the `exec` method: 76 | 77 | ```rust 78 | pub trait Submarine { 79 | fn exec(&mut self, cmd: &Command); 80 | fn run(&mut self, input: &[Command]) { 81 | // ... 82 | } 83 | } 84 | 85 | pub struct SubmarineV2 { 86 | x: i64, 87 | y: i64, 88 | aim: i64, 89 | } 90 | 91 | impl Submarine for SubmarineV2 { 92 | fn exec(&mut self, cmd: &Command) { 93 | // ... 94 | } 95 | } 96 | ``` 97 | 98 | ### Day 3 99 | 100 | Today's puzzle was a good opportunity to refresh bit shifts and masking in Rust. 101 | 102 | For **part 1**, the key of my solution is this loop: 103 | 104 | ```rust 105 | for i in 0..n_bits { 106 | let mask = 2_u32.pow(i); 107 | let ones = report.iter().filter(|x| *x & mask == mask).count(); 108 | if ones > report.len() / 2 { 109 | gamma_rate += mask 110 | } 111 | } 112 | ``` 113 | 114 | Let's say we want to get the value of the second bit (counting from the left) for a number, for instance `01110`. We would need to build a mask that is `01000` so when we do and `AND` operation with those two we can get whichever value was at the position of the masking bit. 115 | 116 | For **part 2**, we use the same masking idea and keep filtering down the numbers until we have only one left. 117 | 118 | ### Day 4 119 | 120 | This was more labor-intensive that previous days, but at least it can be solved without having to implement any optimizations. 121 | 122 | To handle the bingo cards, I opted for a `Card` struct and a `Number` enum (which variants for marked an unmarked numbers). `Card` just contains a static array of 25 elements to represent the numbers. 123 | 124 | ```rust 125 | pub enum Number { 126 | Unmarked(u64), 127 | Marked(u64), 128 | } 129 | // ... 130 | pub struct Card { 131 | numbers: [Number; CARD_SIZE * CARD_SIZE], 132 | } 133 | ``` 134 | 135 | Given an index in that `numbers` array, we can easily get its corresponding row and column: 136 | 137 | ```rust 138 | let col = i % CARD_SIZE; 139 | let row = i / CARD_SIZE; 140 | ``` 141 | 142 | In **part 1**, we just need to get the first card which has a bingo. I implemented a method that can check for bingo for given range and step. For checking a row the step is `1`, since we are just looking at consecutive elements in the array. For checking a column, the step is `5`. 143 | 144 | In **part 2** the only different thing is to get the _last_ card that scores a bingo. Since apparently there's no convenient method in Rust to remove multiple indices from a `Vec` at once, I just opted to add winner cards to an ignore list, so they are not re-checked for bingo. 145 | 146 | ### Day 5 147 | 148 | Instead of virtually "drawing" or populating a grid with the points in question, I kept a list of segments and implemented a `points()` method in them that would return all the points in the grid the segment would fill. 149 | 150 | This would be super easy in any language, but I found out the hard way that Rust ranges _do not support a negative step_. 151 | 152 | My code is kind of convoluted and very verbose. I'm not exactly happy with my solution to this problem, but at least it worked. 153 | 154 | ### Day 6 155 | 156 | **Part 1** was simple, I just had an array for all the laternfish, and pushed to it when it was time for one to reproduce. 157 | 158 | ```rust 159 | fn simulation(input: &[u64], n: u64) -> u64 { 160 | let mut population = input.to_vec(); 161 | for _ in 0..n { 162 | population = tick(&population); 163 | } 164 | 165 | population.len() as u64 166 | } 167 | 168 | fn tick(fishes: &[u64]) -> Vec { 169 | let mut population = vec![]; 170 | 171 | for fish in fishes.iter() { 172 | if *fish == 0 { 173 | population.push(6); // reset counter 174 | population.push(8); // new born fish 175 | } 176 | else { 177 | population.push(fish - 1) // decrease counter 178 | } 179 | } 180 | 181 | population 182 | } 183 | ``` 184 | 185 | However, with **part 2**, which was exactly the same problem but with more timespan for the lanternfish population, it required optimization. 186 | 187 | I decided to opt for a classic [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) technique, in which I could split the result into partials that could in turn be memoized. To get that split, instead of processing every tick the whole population of laternfish, I had to do the reverse: processing each fish for the whole timespan. 188 | 189 | ```rust 190 | fn simulation(input: &[u64], n: u64) -> u64 { 191 | // ... 192 | for fish in input.to_vec().into_iter() { 193 | population_count += simulate_fish(fish as i64, n as i64, &mut cache); 194 | } 195 | // ... 196 | } 197 | ``` 198 | 199 | Then, to simulate fish I just needed to calculate when it would reproduce and how much timespan would be available to its children. This `simulate_fish` function is provided a "cache" in the form of a `HashMap` to store its partial results. 200 | 201 | ```rust 202 | fn simulate_fish(fish: i64, n: i64, cache: &mut HashMap<(i64, i64), u64>) -> u64 { 203 | // ... 204 | for i in (fish..n+fish).step_by(7) { 205 | fish_count += simulate_fish(8, n-i-1, cache); 206 | } 207 | // ... 208 | } 209 | ``` 210 | 211 | ### Day 7 212 | 213 | TODO 214 | 215 | ### Day 8 216 | 217 | I found the wording of the puzzle quite confusing for today. At any case, I solved **part 1** in a breeze. I just filtered the target lengths and reduced into a sum: 218 | 219 | ```rust 220 | let targets = [2, 4, 3, 7]; 221 | input 222 | .into_iter() 223 | .map(|(_, output)| output.clone()) 224 | .flatten() 225 | .fold(0, |total, x| { 226 | total + if targets.contains(&x.len()) { 1 } else { 0 } 227 | }) 228 | ``` 229 | 230 | I didn't find **part 2** particularly difficult, _but_. So verbose 😭. Here I struggled with casting types all the time. 231 | 232 | The key part was the bahamut of a function that I did to create a dictionary of a mangled digit string to its numeric value. 233 | 234 | I hardcoded all the logical rules: 235 | 236 | 1. Identify straight away `1`, `4`, `7` and `8` because of their unique length. 237 | 2. Find out to which mangled character corresponds which segment of the display. 238 | 3. Identify straight away segments `b`, `e` and `f` because of their unique frequency in all the digits. 239 | 4. Segment `c` is the one in `1` that is not `f` (which we already know). 240 | 5. Segment `d` is the one in `4` that is not `b`, `c`, or `f`. 241 | 6. ... 242 | 7. Identify the remaining unknown digits by checking against the known segments. 243 | 244 | Then, after that, it was only a matter of converting a list of digits into an actual integer we could sum to provide the result. 245 | 246 | ### Day 9 247 | 248 | This one was fun! Since this is the second time that I need some sort of grid or level map or sorts, I decided to create a generic `Grid` to handle this puzzle and any of upcoming ones. It took a little bit of time, but I think it will be worth it in the next days. 249 | 250 | **Part 1** can be solved by traversing the grid, and checking the 4-neighbors of every cell to see if it's a low point or not (`Grid::cell_at(i32, i32)` conveniently returns `None` if the coordinates given overflow the map). 251 | 252 | For **part 2** I implemented a recursive algorithm similar to the typical paint bucket fill in drawing software. For convenience, I created a parallel `Grid` map to mark whether a position was already part of the basin or not (so it doesn't get added twice). 253 | 254 | ### Day 10 255 | 256 | I love parsers, so I found this problem quite fun. The grammar we needed to parse was quite small, and it could just be solved by keeping a buffer of the expected closing parens/braces/whatever. We push into the buffer when we encounter an opening character (like `(` or `[`), and we pop from it when we encounter a closing character (like `)` and `]`). If the current character doesn't match the popped value, we return a syntax error. 257 | 258 | The only significant change for **part 2** was that on `Ok` we return the state of the buffer for incomplete lines, so we know which characters we'd need to autocomplete. 259 | 260 | ### Day 11 261 | 262 | This puzzle reminded me of some kind of Game of Life logic. Both **part 1** and **part 2** were similar and I didn't have to tweak it a lot. 263 | 264 | I reused the `Grid` class I did on Day 9, and added a method to get all the adjacent neighbors including diagonals. 265 | 266 | ### Day 12 267 | 268 | Today's puzzle was about pathfinding. I opted to represent the map as a graph in the form of a `HashMap`, with the name of the nodes as the key, and a list of their connections as value. 269 | 270 | I solved **part 1** with a recursive Dijkstra algorithm with some tweaks to be able to allow navigation through some nodes more than once. **Part 2** required further tweaks, put the principle stayed the same. 271 | 272 | ### Day 13 273 | 274 | One of those days in which parsing the input takes double the lines than the actual solution 🙃. 275 | 276 | Folding is made by removing the row or column folded, and then matching the top (or left) position with its corresponding bottom (or right one). Determining if there's a dot in the folded sheet just requires OR'ing the value at those positions. 277 | 278 | Like yesterday, I reused my `Grid` class for this. For simplicity's sake I opted to recreate a new grid from scratch in each fold, although just manually changing the sheet width and height would have been enough. 279 | 280 | For **part 2** I didn't have to modify my folding algorithm, but I did implement the `Display` trait for my data structure holding the sheet and the instructions, so I could see the solution printed as captcha-like ascii art. 281 | 282 | ### Day 14 283 | 284 | TODO 285 | 286 | ### Day 15 287 | 288 | Today it was a pathfinding puzzle, and I was told beforehand that it required optimization. In **part 1**, Instead of implementing Dijkstra, I opted to implement [A\*](https://www.redblobgames.com/pathfinding/a-star/introduction.html), with a binary heap as a priority queue as shown in [Rust documentation](https://doc.rust-lang.org/std/collections/binary_heap/). As the heuristic function, I opted for the usual Manhattan distance, since it's super quick and good enough for heuristics. 289 | 290 | For **Part 2** I didn't have to change the pathfinding algorithm, but tweak the input a little bit to construct the larger map. Again, my `Grid` util type is really paying off. For debugging purposes, I implemented the `std::fmt::Display` trait so I could print it on screen. 291 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------