├── .gitignore ├── day1 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day10 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day11 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day12 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day13 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day14 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day15 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day18 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day2 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day21 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day24 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day3 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day5 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day6 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day7 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day9 ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── day8 ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── day16 ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs ├── day4 ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs ├── day19 ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *input.txt 2 | -------------------------------------------------------------------------------- /day1/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day1" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day10/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day10" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day11/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day11" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day12/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day12" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day13/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day13" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day14/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day14" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day15/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day15" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day18/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day18" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day2/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day2" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day21/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day21" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day24/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day24" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day3/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day3" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day5/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day5" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day6/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day6" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day7/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day7" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day9/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "day9" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /day1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day10/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day10" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day11/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day11" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day12/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day12" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day13/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day13" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day14/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day14" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day15/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day15" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day18/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day18" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day21/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day21" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day24/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day24" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day5" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day6/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day6" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day7" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day9/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day9" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /day8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day8" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | num = "0.4.1" 10 | -------------------------------------------------------------------------------- /day16/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day16" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rayon = "1.8.0" 10 | -------------------------------------------------------------------------------- /day4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day4" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | lazy_static = "1.4.0" 10 | regex = "1.10.2" 11 | -------------------------------------------------------------------------------- /day19/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "day19" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | lazy_static = "1.4.0" 10 | regex = "1.10.2" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /day1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn part1(input: &str) -> u32 { 2 | input 3 | .lines() 4 | .map(|line| { 5 | let iter = line.chars().filter_map(|c| c.to_digit(10)); 6 | iter.clone().next().unwrap() * 10 + iter.last().unwrap() 7 | }) 8 | .sum() 9 | } 10 | 11 | fn part2(input: &str) -> u32 { 12 | input 13 | .lines() 14 | .map(|line| { 15 | let line = line 16 | .replace("nineight", "98") 17 | .replace("eighthree", "83") 18 | .replace("eightwo", "82") 19 | .replace("twone", "21") 20 | .replace("oneight", "18") 21 | .replace("threeight", "38") 22 | .replace("fiveight", "58") 23 | .replace("sevenine", "79") 24 | .replace("one", "1") 25 | .replace("two", "2") 26 | .replace("three", "3") 27 | .replace("four", "4") 28 | .replace("five", "5") 29 | .replace("six", "6") 30 | .replace("seven", "7") 31 | .replace("eight", "8") 32 | .replace("nine", "9"); 33 | let iter = line.chars().filter(|c| c.is_ascii_digit()); 34 | iter.clone().next().unwrap().to_digit(10).unwrap() * 10 35 | + iter.last().unwrap().to_digit(10).unwrap() 36 | }) 37 | .sum() 38 | } 39 | 40 | fn main() { 41 | let input = include_str!("../input.txt"); 42 | println!("{}", part1(input)); 43 | println!("{}", part2(input)); 44 | } 45 | -------------------------------------------------------------------------------- /day9/src/main.rs: -------------------------------------------------------------------------------- 1 | fn parse(input_str: &str) -> Vec> { 2 | input_str 3 | .lines() 4 | .map(|line| { 5 | line.split_whitespace() 6 | .map(|num| num.parse().unwrap()) 7 | .collect() 8 | }) 9 | .collect() 10 | } 11 | 12 | fn get_history(sequence: &[i64]) -> Vec> { 13 | let mut history: Vec> = vec![]; 14 | let mut current_sequence = sequence.to_vec(); 15 | while current_sequence.iter().any(|x| *x != 0) { 16 | history.push(current_sequence.clone()); 17 | current_sequence = current_sequence.windows(2).map(|x| x[1] - x[0]).collect(); 18 | } 19 | history 20 | } 21 | 22 | fn part1(parsed_input: &[Vec]) -> i64 { 23 | parsed_input 24 | .iter() 25 | .map(|sequence| { 26 | get_history(sequence) 27 | .iter() 28 | .map(|sequence| sequence.last().unwrap()) 29 | .sum::() 30 | }) 31 | .sum() 32 | } 33 | 34 | fn part2(parsed_input: &[Vec]) -> i64 { 35 | parsed_input 36 | .iter() 37 | .map(|sequence| { 38 | get_history(sequence) 39 | .iter() 40 | .enumerate() 41 | .map(|(idx, sequence)| { 42 | sequence.first().unwrap() * if idx % 2 == 0 { 1 } else { -1 } 43 | }) 44 | .sum::() 45 | }) 46 | .sum() 47 | } 48 | 49 | fn main() { 50 | let input = parse(include_str!("../input.txt")); 51 | println!("{}", part1(&input)); 52 | println!("{}", part2(&input)); 53 | } 54 | -------------------------------------------------------------------------------- /day19/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "day19" 16 | version = "0.1.0" 17 | dependencies = [ 18 | "lazy_static", 19 | "regex", 20 | ] 21 | 22 | [[package]] 23 | name = "lazy_static" 24 | version = "1.4.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 27 | 28 | [[package]] 29 | name = "memchr" 30 | version = "2.6.4" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 33 | 34 | [[package]] 35 | name = "regex" 36 | version = "1.10.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 39 | dependencies = [ 40 | "aho-corasick", 41 | "memchr", 42 | "regex-automata", 43 | "regex-syntax", 44 | ] 45 | 46 | [[package]] 47 | name = "regex-automata" 48 | version = "0.4.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 51 | dependencies = [ 52 | "aho-corasick", 53 | "memchr", 54 | "regex-syntax", 55 | ] 56 | 57 | [[package]] 58 | name = "regex-syntax" 59 | version = "0.8.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 62 | -------------------------------------------------------------------------------- /day4/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "day4" 16 | version = "0.1.0" 17 | dependencies = [ 18 | "lazy_static", 19 | "regex", 20 | ] 21 | 22 | [[package]] 23 | name = "lazy_static" 24 | version = "1.4.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 27 | 28 | [[package]] 29 | name = "memchr" 30 | version = "2.6.4" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 33 | 34 | [[package]] 35 | name = "regex" 36 | version = "1.10.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 39 | dependencies = [ 40 | "aho-corasick", 41 | "memchr", 42 | "regex-automata", 43 | "regex-syntax", 44 | ] 45 | 46 | [[package]] 47 | name = "regex-automata" 48 | version = "0.4.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 51 | dependencies = [ 52 | "aho-corasick", 53 | "memchr", 54 | "regex-syntax", 55 | ] 56 | 57 | [[package]] 58 | name = "regex-syntax" 59 | version = "0.8.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 62 | -------------------------------------------------------------------------------- /day6/src/main.rs: -------------------------------------------------------------------------------- 1 | fn get_numbers_from_line(line: &str) -> Vec { 2 | line.split(' ') 3 | .skip(1) 4 | .map(|num| num.trim()) 5 | .filter(|num| !num.is_empty()) 6 | .map(|num| num.parse().unwrap()) 7 | .collect() 8 | } 9 | 10 | fn get_distance_for_hold_time(hold_time: u64, total_time: u64) -> u64 { 11 | (total_time - hold_time) * hold_time 12 | } 13 | 14 | fn part1(times: &[u64], distances: &[u64]) -> u64 { 15 | (0..times.len()) 16 | .map(|i| { 17 | (1..times[i]) 18 | .map(|hold_time| { 19 | if get_distance_for_hold_time(hold_time, times[i]) > distances[i] { 20 | 1 21 | } else { 22 | 0 23 | } 24 | }) 25 | .sum::() 26 | }) 27 | .product() 28 | } 29 | 30 | fn concat_numbers(numbers: &[u64]) -> u64 { 31 | numbers 32 | .iter() 33 | .flat_map(|t| t.to_string().chars().collect::>()) 34 | .collect::() 35 | .parse() 36 | .unwrap() 37 | } 38 | 39 | fn part2(times: &[u64], distances: &[u64]) -> u64 { 40 | let time: u64 = concat_numbers(times); 41 | let distance: u64 = concat_numbers(distances); 42 | (1..time) 43 | .map(|hold_time| { 44 | if get_distance_for_hold_time(hold_time, time) > distance { 45 | 1 46 | } else { 47 | 0 48 | } 49 | }) 50 | .sum() 51 | } 52 | 53 | fn main() { 54 | let mut input_lines = include_str!("../input.txt").lines(); 55 | let times = get_numbers_from_line(input_lines.next().unwrap()); 56 | let distances = get_numbers_from_line(input_lines.next().unwrap()); 57 | println!("{}", part1(×, &distances)); 58 | println!("{}", part2(×, &distances)); 59 | } 60 | -------------------------------------------------------------------------------- /day13/src/main.rs: -------------------------------------------------------------------------------- 1 | fn parse(input_str: &str) -> Vec> { 2 | input_str 3 | .split("\n\n") 4 | .map(|split| split.lines().map(|l| l.to_string()).collect()) 5 | .collect() 6 | } 7 | 8 | fn diff(str1: &str, str2: &str) -> usize { 9 | str1.chars() 10 | .zip(str2.chars()) 11 | .filter(|(c1, c2)| c1 != c2) 12 | .count() 13 | } 14 | 15 | fn check_mirror_correctness(block: &[String], lines_above: usize) -> usize { 16 | let mut errors = 0; 17 | if lines_above <= block.len() / 2 { 18 | for i in 0..lines_above { 19 | errors += diff(&block[i], &block[2 * lines_above - i - 1]); 20 | } 21 | } else { 22 | for i in lines_above..block.len() { 23 | errors += diff(&block[i], &block[2 * lines_above - i - 1]); 24 | } 25 | } 26 | errors 27 | } 28 | 29 | fn transpose(block: &[String]) -> Vec { 30 | let mut res = vec![]; 31 | for c in 0..block[0].len() { 32 | let mut row = "".to_string(); 33 | for r in block { 34 | row.push(*r.as_bytes().get(c).unwrap() as char); 35 | } 36 | res.push(row); 37 | } 38 | res 39 | } 40 | 41 | fn mirror_sum(blocks: &[Vec], error_count: usize) -> usize { 42 | let mut sum = 0; 43 | 'outer: for block in blocks { 44 | for above in 1..block.len() { 45 | if check_mirror_correctness(block, above) == error_count { 46 | sum += 100 * above; 47 | continue 'outer; 48 | } 49 | } 50 | let block_t = transpose(block); 51 | for above in 1..block_t.len() { 52 | if check_mirror_correctness(&block_t, above) == error_count { 53 | sum += above; 54 | } 55 | } 56 | } 57 | sum 58 | } 59 | 60 | fn part1(blocks: &[Vec]) -> usize { 61 | mirror_sum(blocks, 0) 62 | } 63 | 64 | fn part2(blocks: &[Vec]) -> usize { 65 | mirror_sum(blocks, 1) 66 | } 67 | 68 | fn main() { 69 | let input = parse(include_str!("../input.txt")); 70 | println!("{}", part1(&input)); 71 | println!("{}", part2(&input)); 72 | } 73 | -------------------------------------------------------------------------------- /day4/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lazy_static::lazy_static; 4 | use regex::Regex; 5 | 6 | #[derive(Debug)] 7 | struct Card { 8 | id: u32, 9 | winning_numbers: Vec, 10 | have_numbers: Vec, 11 | } 12 | 13 | lazy_static! { 14 | static ref CARD_REGEX: Regex = Regex::new(r"Card *(\d*):(( ?\d* )*)\|(( ?\d* ?)*)").unwrap(); 15 | } 16 | 17 | fn get_numbers_from_capture(capture_string: &str) -> Vec { 18 | capture_string 19 | .trim() 20 | .split(' ') 21 | .filter(|s| !s.is_empty()) 22 | .map(|num| num.trim().parse().unwrap()) 23 | .collect() 24 | } 25 | 26 | impl Card { 27 | fn from_line(line: &str) -> Self { 28 | let captures = CARD_REGEX.captures(line).unwrap(); 29 | Self { 30 | id: captures.get(1).unwrap().as_str().parse().unwrap(), 31 | winning_numbers: get_numbers_from_capture(captures.get(2).unwrap().as_str()), 32 | have_numbers: get_numbers_from_capture(captures.get(4).unwrap().as_str()), 33 | } 34 | } 35 | fn points_worth(&self) -> u32 { 36 | self.have_numbers 37 | .iter() 38 | .filter(|n| self.winning_numbers.contains(n)) 39 | .count() as u32 40 | } 41 | } 42 | 43 | fn part1(cards: &[Card]) -> u32 { 44 | cards 45 | .iter() 46 | .map(|card| match card.points_worth() { 47 | 0 => 0, 48 | c => (c - 1) << 1, 49 | }) 50 | .sum() 51 | } 52 | 53 | fn part2(cards: &[Card]) -> u32 { 54 | let mut amounts: HashMap = HashMap::from_iter(cards.iter().map(|card| (card.id, 1))); 55 | for card in cards { 56 | let card_amount = *amounts.get(&card.id).unwrap(); 57 | for i in card.id + 1..=card.id + card.points_worth() { 58 | if let Some(amount) = amounts.get_mut(&i) { 59 | *amount += card_amount; 60 | } 61 | } 62 | } 63 | amounts.values().sum() 64 | } 65 | 66 | fn main() { 67 | let input = include_str!("../input.txt"); 68 | let cards: Vec = input.lines().map(Card::from_line).collect(); 69 | println!("{}", part1(&cards)); 70 | println!("{}", part2(&cards)); 71 | } 72 | -------------------------------------------------------------------------------- /day8/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use num::integer::lcm; 4 | 5 | type Nodes = HashMap; 6 | 7 | fn node_from_line(line: &str) -> (String, (String, String)) { 8 | ( 9 | line.chars().take(3).collect(), 10 | ( 11 | line.chars().skip(7).take(3).collect(), 12 | line.chars().skip(12).take(3).collect(), 13 | ), 14 | ) 15 | } 16 | 17 | fn parse(input_str: &str) -> (String, Nodes) { 18 | ( 19 | input_str.lines().next().unwrap().to_string(), 20 | input_str.lines().skip(2).map(node_from_line).collect(), 21 | ) 22 | } 23 | 24 | fn get_next_node(instruction: &char, current_node: &str, nodes: &Nodes) -> String { 25 | let children = nodes.get(current_node).unwrap(); 26 | match instruction { 27 | 'L' => children.0.clone(), 28 | 'R' => children.1.clone(), 29 | _ => panic!(), 30 | } 31 | } 32 | 33 | fn part1(instructions: &str, nodes: &Nodes) -> u32 { 34 | let mut current_node = "AAA".to_string(); 35 | let mut steps = 0; 36 | for instruction in instructions.chars().cycle() { 37 | if current_node == "ZZZ" { 38 | break; 39 | } 40 | current_node = get_next_node(&instruction, ¤t_node, nodes); 41 | steps += 1; 42 | } 43 | steps 44 | } 45 | 46 | fn part2(instructions: &str, nodes: &Nodes) -> u64 { 47 | nodes 48 | .keys() 49 | .filter(|node| node.ends_with('A')) 50 | .map(|start_node| { 51 | let mut steps: u64 = 0; 52 | let mut current_node = start_node.clone(); 53 | for instruction in instructions.chars().cycle() { 54 | if current_node.ends_with('Z') { 55 | break; 56 | } 57 | current_node = get_next_node(&instruction, ¤t_node, nodes); 58 | steps += 1; 59 | } 60 | steps 61 | }) 62 | .fold(1, lcm) 63 | } 64 | 65 | fn main() { 66 | let input = include_str!("../input.txt"); 67 | let (instructions, nodes) = parse(input); 68 | println!("{}", part1(&instructions, &nodes)); 69 | println!("{}", part2(&instructions, &nodes)); 70 | } 71 | -------------------------------------------------------------------------------- /day5/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Map { 3 | maps: Vec<(u32, u32, u32)>, 4 | } 5 | 6 | impl Map { 7 | fn from_block(block: &str) -> Self { 8 | Self { 9 | maps: block 10 | .lines() 11 | .skip(1) 12 | .map(|l| { 13 | let mut val_iter = l.split(' ').map(|n| n.parse().unwrap()); 14 | ( 15 | val_iter.next().unwrap(), 16 | val_iter.next().unwrap(), 17 | val_iter.next().unwrap(), 18 | ) 19 | }) 20 | .collect(), 21 | } 22 | } 23 | fn get_mapped_value(&self, src: u32) -> u32 { 24 | for map in &self.maps { 25 | if src >= map.1 && src <= map.1 + map.2 { 26 | return map.0 + (src - map.1); 27 | } 28 | } 29 | src 30 | } 31 | } 32 | 33 | fn parse(input: &str) -> (Vec, Vec) { 34 | ( 35 | input 36 | .lines() 37 | .next() 38 | .unwrap() 39 | .split(':') 40 | .last() 41 | .unwrap() 42 | .trim() 43 | .split(' ') 44 | .map(|n| n.parse().unwrap()) 45 | .collect(), 46 | input.split("\n\n").skip(1).map(Map::from_block).collect(), 47 | ) 48 | } 49 | 50 | fn map_to_end(seed: u32, maps: &[Map]) -> u32 { 51 | let mut current_val = seed; 52 | for map in maps { 53 | current_val = map.get_mapped_value(current_val); 54 | } 55 | current_val 56 | } 57 | 58 | fn part1(seeds: &[u32], maps: &[Map]) -> u32 { 59 | seeds 60 | .iter() 61 | .map(|seed| map_to_end(*seed, maps)) 62 | .min() 63 | .unwrap() 64 | } 65 | 66 | fn part2(seeds: &[u32], maps: &[Map]) -> u32 { 67 | seeds 68 | .iter() 69 | .zip(seeds.iter().skip(1)) 70 | .step_by(2) 71 | .flat_map(|(start, length)| *start..start + length) 72 | .map(|seed| map_to_end(seed, maps)) 73 | .min() 74 | .unwrap() 75 | } 76 | 77 | fn main() { 78 | let input = include_str!("../input.txt"); 79 | let (seeds, maps) = parse(input); 80 | println!("{}", part1(&seeds, &maps)); 81 | println!("{}", part2(&seeds, &maps)); 82 | } 83 | -------------------------------------------------------------------------------- /day16/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "crossbeam-deque" 19 | version = "0.8.4" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" 22 | dependencies = [ 23 | "cfg-if", 24 | "crossbeam-epoch", 25 | "crossbeam-utils", 26 | ] 27 | 28 | [[package]] 29 | name = "crossbeam-epoch" 30 | version = "0.9.17" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" 33 | dependencies = [ 34 | "autocfg", 35 | "cfg-if", 36 | "crossbeam-utils", 37 | ] 38 | 39 | [[package]] 40 | name = "crossbeam-utils" 41 | version = "0.8.18" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" 44 | dependencies = [ 45 | "cfg-if", 46 | ] 47 | 48 | [[package]] 49 | name = "day16" 50 | version = "0.1.0" 51 | dependencies = [ 52 | "rayon", 53 | ] 54 | 55 | [[package]] 56 | name = "either" 57 | version = "1.9.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 60 | 61 | [[package]] 62 | name = "rayon" 63 | version = "1.8.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" 66 | dependencies = [ 67 | "either", 68 | "rayon-core", 69 | ] 70 | 71 | [[package]] 72 | name = "rayon-core" 73 | version = "1.12.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" 76 | dependencies = [ 77 | "crossbeam-deque", 78 | "crossbeam-utils", 79 | ] 80 | -------------------------------------------------------------------------------- /day8/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "day8" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "num", 16 | ] 17 | 18 | [[package]] 19 | name = "num" 20 | version = "0.4.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" 23 | dependencies = [ 24 | "num-bigint", 25 | "num-complex", 26 | "num-integer", 27 | "num-iter", 28 | "num-rational", 29 | "num-traits", 30 | ] 31 | 32 | [[package]] 33 | name = "num-bigint" 34 | version = "0.4.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" 37 | dependencies = [ 38 | "autocfg", 39 | "num-integer", 40 | "num-traits", 41 | ] 42 | 43 | [[package]] 44 | name = "num-complex" 45 | version = "0.4.4" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" 48 | dependencies = [ 49 | "num-traits", 50 | ] 51 | 52 | [[package]] 53 | name = "num-integer" 54 | version = "0.1.45" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 57 | dependencies = [ 58 | "autocfg", 59 | "num-traits", 60 | ] 61 | 62 | [[package]] 63 | name = "num-iter" 64 | version = "0.1.43" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 67 | dependencies = [ 68 | "autocfg", 69 | "num-integer", 70 | "num-traits", 71 | ] 72 | 73 | [[package]] 74 | name = "num-rational" 75 | version = "0.4.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 78 | dependencies = [ 79 | "autocfg", 80 | "num-bigint", 81 | "num-integer", 82 | "num-traits", 83 | ] 84 | 85 | [[package]] 86 | name = "num-traits" 87 | version = "0.2.17" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 90 | dependencies = [ 91 | "autocfg", 92 | ] 93 | -------------------------------------------------------------------------------- /day15/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | struct Lens { 3 | label: String, 4 | focal_length: usize, 5 | } 6 | 7 | impl PartialEq for Lens { 8 | fn eq(&self, other: &Self) -> bool { 9 | self.label.eq(&other.label) 10 | } 11 | } 12 | 13 | fn parse(input_str: &str) -> Vec { 14 | input_str 15 | .replace('\n', "") 16 | .split(',') 17 | .map(|s| s.to_string()) 18 | .collect() 19 | } 20 | 21 | fn hash(s: &str) -> u32 { 22 | let mut current_value = 0; 23 | for char in s.chars() { 24 | current_value += char as u32; 25 | current_value *= 17; 26 | current_value %= 256; 27 | } 28 | current_value 29 | } 30 | 31 | fn part1(input: &[String]) -> u32 { 32 | input.iter().map(|s| hash(s)).sum() 33 | } 34 | 35 | fn part2(input: &[String]) -> usize { 36 | let mut boxes: Vec> = vec![vec![]; 256]; 37 | for item in input { 38 | let label: String; 39 | let operation: char; 40 | let focal_length: usize; 41 | if item.ends_with('-') { 42 | operation = '-'; 43 | label = item.chars().take(item.len() - 1).collect(); 44 | focal_length = 0; 45 | } else { 46 | operation = '='; 47 | label = item.chars().take(item.len() - 2).collect(); 48 | focal_length = item.chars().next_back().unwrap().to_digit(10).unwrap() as usize; 49 | } 50 | let box_idx = hash(&label) as usize; 51 | let lens = Lens { 52 | label, 53 | focal_length, 54 | }; 55 | let target_lens = boxes[box_idx] 56 | .iter() 57 | .enumerate() 58 | .find(|(_, l)| l == &&lens) 59 | .map(|(idx, _)| idx); 60 | match operation { 61 | '-' => { 62 | if let Some(idx) = target_lens { 63 | boxes[box_idx].remove(idx); 64 | } 65 | } 66 | _ => { 67 | if let Some(idx) = target_lens { 68 | boxes[box_idx][idx] = lens; 69 | } else { 70 | boxes[box_idx].push(lens); 71 | } 72 | } 73 | } 74 | } 75 | let mut sum = 0; 76 | for (box_idx, lens_box) in boxes.iter().enumerate() { 77 | for (lens_idx, lens) in lens_box.iter().enumerate() { 78 | sum += lens.focal_length * (lens_idx + 1) * (box_idx + 1); 79 | } 80 | } 81 | sum 82 | } 83 | 84 | fn main() { 85 | let input = parse(include_str!("../input.txt")); 86 | println!("{}", part1(&input)); 87 | println!("{}", part2(&input)); 88 | } 89 | -------------------------------------------------------------------------------- /day24/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | struct Hailstone { 3 | px: isize, 4 | py: isize, 5 | pz: isize, 6 | vx: isize, 7 | vy: isize, 8 | vz: isize, 9 | } 10 | 11 | type Position = (f64, f64); 12 | 13 | impl Hailstone { 14 | fn from_line(line: &str) -> Self { 15 | let line = line.replace(',', ""); 16 | let split: Vec<_> = line.split_whitespace().collect(); 17 | Self { 18 | px: split[0].parse().unwrap(), 19 | py: split[1].parse().unwrap(), 20 | pz: split[2].parse().unwrap(), 21 | vx: split[4].parse().unwrap(), 22 | vy: split[5].parse().unwrap(), 23 | vz: split[6].parse().unwrap(), 24 | } 25 | } 26 | fn get_intersection(&self, other: &Hailstone) -> Option { 27 | let denominator = -self.vx * other.vy + other.vx * self.vy; 28 | if denominator == 0 { 29 | return None; 30 | } 31 | let t1 = (-(other.px - self.px) * other.vy + other.vx * (other.py - self.py)) as f64 32 | / denominator as f64; 33 | let t2 = ((other.py - self.py) * self.vx - self.vy * (other.px - self.px)) as f64 34 | / denominator as f64; 35 | let pos1 = ( 36 | self.px as f64 + self.vx as f64 * t1, 37 | self.py as f64 + self.vy as f64 * t1, 38 | ); 39 | if t1 >= 0. && t2 >= 0. { 40 | Some(pos1) 41 | } else { 42 | None 43 | } 44 | } 45 | } 46 | 47 | fn parse(input_str: &str) -> Vec { 48 | input_str.lines().map(Hailstone::from_line).collect() 49 | } 50 | 51 | fn check_bounds(pos: &Position, lower_bound: f64, upper_bound: f64) -> bool { 52 | pos.0 >= lower_bound && pos.0 <= upper_bound && pos.1 >= lower_bound && pos.1 <= upper_bound 53 | } 54 | 55 | fn part1(hailstones: &[Hailstone]) -> usize { 56 | hailstones 57 | .iter() 58 | .enumerate() 59 | .map(|(h1_idx, h1)| { 60 | hailstones 61 | .iter() 62 | .skip(h1_idx) 63 | .map(|h2| { 64 | if let Some(pos) = h1.get_intersection(h2) { 65 | if check_bounds(&pos, 200000000000000., 400000000000000.) { 66 | 1 67 | } else { 68 | 0 69 | } 70 | } else { 71 | 0 72 | } 73 | }) 74 | .sum::() 75 | }) 76 | .sum() 77 | } 78 | 79 | fn main() { 80 | let input = parse(include_str!("../input.txt")); 81 | println!("{}", part1(&input)); 82 | } 83 | -------------------------------------------------------------------------------- /day2/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | struct Draw { 3 | red: u32, 4 | green: u32, 5 | blue: u32, 6 | } 7 | 8 | impl Draw { 9 | fn from_string(string: &str) -> Self { 10 | let mut result = Self { 11 | red: 0, 12 | green: 0, 13 | blue: 0, 14 | }; 15 | let split = string.split(','); 16 | for color in split { 17 | let mut split = color.trim().split(' '); 18 | let count: u32 = split.next().unwrap().parse().unwrap(); 19 | match split.next().unwrap().trim() { 20 | "red" => result.red = count, 21 | "green" => result.green = count, 22 | "blue" => result.blue = count, 23 | _ => panic!(), 24 | } 25 | } 26 | result 27 | } 28 | 29 | fn is_possible(&self) -> bool { 30 | if self.red > 12 || self.green > 13 || self.blue > 14 { 31 | return false; 32 | } 33 | true 34 | } 35 | fn as_tuple(&self) -> (u32, u32, u32) { 36 | (self.red, self.green, self.blue) 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | struct Game { 42 | id: u32, 43 | draws: Vec, 44 | } 45 | 46 | impl Game { 47 | fn from_line(line: &str) -> Self { 48 | let mut split = line.split(':'); 49 | Self { 50 | id: split 51 | .next() 52 | .unwrap() 53 | .split(' ') 54 | .last() 55 | .unwrap() 56 | .parse() 57 | .unwrap(), 58 | draws: split 59 | .next() 60 | .unwrap() 61 | .split(';') 62 | .map(Draw::from_string) 63 | .collect(), 64 | } 65 | } 66 | 67 | fn is_possible(&self) -> bool { 68 | self.draws.iter().filter(|draw| draw.is_possible()).count() == self.draws.len() 69 | } 70 | 71 | fn power(&self) -> u32 { 72 | let tuple_draws: Vec<_> = self.draws.iter().map(|draw| draw.as_tuple()).collect(); 73 | tuple_draws.iter().map(|(r, _, _)| r).max().unwrap_or(&0) 74 | * tuple_draws.iter().map(|(_, g, _)| g).max().unwrap_or(&0) 75 | * tuple_draws.iter().map(|(_, _, b)| b).max().unwrap_or(&0) 76 | } 77 | } 78 | 79 | fn part1(games: &[Game]) -> u32 { 80 | games 81 | .iter() 82 | .filter(|game| game.is_possible()) 83 | .map(|game| game.id) 84 | .sum() 85 | } 86 | 87 | fn part2(games: &[Game]) -> u32 { 88 | games.iter().map(|game| game.power()).sum() 89 | } 90 | 91 | fn main() { 92 | let input = include_str!("../input.txt"); 93 | let games: Vec = input.lines().map(Game::from_line).collect(); 94 | println!("{}", part1(&games)); 95 | println!("{}", part2(&games)); 96 | } 97 | -------------------------------------------------------------------------------- /day11/src/main.rs: -------------------------------------------------------------------------------- 1 | type Position = (usize, usize); 2 | 3 | fn parse_input(input_str: &str) -> Vec> { 4 | input_str 5 | .lines() 6 | .map(|l| { 7 | l.chars() 8 | .map(|c| match c { 9 | '#' => 1, 10 | _ => 0, 11 | }) 12 | .collect() 13 | }) 14 | .collect() 15 | } 16 | 17 | fn find_empty_spaces(map: &[Vec]) -> (Vec, Vec) { 18 | ( 19 | (0..map.len()) 20 | .filter(|l_idx| map[*l_idx].iter().all(|s| s == &0)) 21 | .collect(), 22 | (0..map[0].len()) 23 | .filter(|col_idx| map.iter().all(|l| l[*col_idx] == 0)) 24 | .collect(), 25 | ) 26 | } 27 | 28 | fn galaxy_positions(map: &[Vec]) -> Vec { 29 | let mut res = vec![]; 30 | for (l_idx, l) in map.iter().enumerate() { 31 | for (r_idx, r) in l.iter().enumerate() { 32 | if r == &1 { 33 | res.push((l_idx, r_idx)); 34 | } 35 | } 36 | } 37 | res 38 | } 39 | 40 | fn expanded_galaxy_positions(map: &[Vec], expansion: usize) -> Vec { 41 | let (empty_rows, empty_columns) = find_empty_spaces(map); 42 | let mut new_positions = vec![]; 43 | for position in galaxy_positions(map) { 44 | new_positions.push(( 45 | position.0 46 | + empty_rows 47 | .iter() 48 | .filter(|r_idx| r_idx < &&position.0) 49 | .count() 50 | * expansion, 51 | position.1 52 | + empty_columns 53 | .iter() 54 | .filter(|c_idx| c_idx < &&position.1) 55 | .count() 56 | * expansion, 57 | )); 58 | } 59 | new_positions 60 | } 61 | 62 | fn all_galaxy_pairs(positions: &[Position]) -> Vec<(Position, Position)> { 63 | positions 64 | .iter() 65 | .enumerate() 66 | .flat_map(|(pos_idx, pos)| { 67 | positions 68 | .iter() 69 | .skip(pos_idx + 1) 70 | .map(|p| (*pos, *p)) 71 | .collect::>() 72 | }) 73 | .collect() 74 | } 75 | 76 | fn manhattan_distance(pos1: Position, pos2: Position) -> usize { 77 | ((pos1.0 as i64 - pos2.0 as i64).abs() + (pos1.1 as i64 - pos2.1 as i64).abs()) 78 | .try_into() 79 | .unwrap() 80 | } 81 | 82 | fn total_distance(map: &[Vec], expansion: usize) -> usize { 83 | all_galaxy_pairs(&expanded_galaxy_positions(map, expansion)) 84 | .iter() 85 | .map(|(pos1, pos2)| manhattan_distance(*pos1, *pos2)) 86 | .sum() 87 | } 88 | 89 | fn part1(map: &[Vec]) -> usize { 90 | total_distance(map, 1) 91 | } 92 | 93 | fn part2(map: &[Vec]) -> usize { 94 | total_distance(map, 999999) 95 | } 96 | 97 | fn main() { 98 | let map = parse_input(include_str!("../input.txt")); 99 | println!("{}", part1(&map)); 100 | println!("{}", part2(&map)); 101 | } 102 | -------------------------------------------------------------------------------- /day21/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq)] 2 | enum Tile { 3 | Start, 4 | Plot, 5 | Rock, 6 | } 7 | use std::collections::HashSet; 8 | 9 | use Tile::*; 10 | 11 | impl Tile { 12 | fn from_char(c: char) -> Self { 13 | match c { 14 | 'S' => Start, 15 | '#' => Rock, 16 | _ => Plot, 17 | } 18 | } 19 | } 20 | 21 | fn parse(input_str: &str) -> Vec> { 22 | input_str 23 | .lines() 24 | .map(|l| l.chars().map(Tile::from_char).collect()) 25 | .collect() 26 | } 27 | 28 | type Position = (usize, usize); 29 | 30 | fn get_neighboring_plots(map: &[Vec], pos: Position) -> Vec { 31 | let mut res = vec![]; 32 | if let Some(row) = map.get(pos.0.wrapping_sub(1)) { 33 | if let Some(t) = row.get(pos.1) { 34 | if t != &Rock { 35 | res.push((pos.0 - 1, pos.1)); 36 | } 37 | } 38 | } 39 | if let Some(row) = map.get(pos.0 + 1) { 40 | if let Some(t) = row.get(pos.1) { 41 | if t != &Rock { 42 | res.push((pos.0 + 1, pos.1)); 43 | } 44 | } 45 | } 46 | if let Some(row) = map.get(pos.0) { 47 | if let Some(t) = row.get(pos.1.wrapping_sub(1)) { 48 | if t != &Rock { 49 | res.push((pos.0, pos.1 - 1)); 50 | } 51 | } 52 | } 53 | if let Some(row) = map.get(pos.0) { 54 | if let Some(t) = row.get(pos.1 + 1) { 55 | if t != &Rock { 56 | res.push((pos.0, pos.1 + 1)); 57 | } 58 | } 59 | } 60 | res 61 | } 62 | 63 | #[allow(dead_code)] 64 | fn print_map(map: &[Vec], positions: &HashSet) { 65 | for (r_idx, r) in map.iter().enumerate() { 66 | for (t_idx, t) in r.iter().enumerate() { 67 | if positions.contains(&(r_idx, t_idx)) { 68 | print!("O"); 69 | continue; 70 | } 71 | match t { 72 | Rock => print!("#"), 73 | Start => print!("S"), 74 | Plot => print!("."), 75 | } 76 | } 77 | println!(); 78 | } 79 | } 80 | 81 | fn part1(map: &[Vec], iterations: usize) -> usize { 82 | let mut current_positions = HashSet::new(); 83 | 'outer: for (r_idx, r) in map.iter().enumerate() { 84 | for (t_idx, t) in r.iter().enumerate() { 85 | if t == &Start { 86 | current_positions.insert((r_idx, t_idx)); 87 | break 'outer; 88 | } 89 | } 90 | } 91 | for _ in 0..iterations { 92 | let mut new_positions = HashSet::new(); 93 | for pos in ¤t_positions { 94 | let neighboring = get_neighboring_plots(map, *pos); 95 | new_positions.extend(&mut neighboring.iter()); 96 | } 97 | current_positions = new_positions; 98 | } 99 | current_positions.len() 100 | } 101 | 102 | fn main() { 103 | let input = parse(include_str!("../input.txt")); 104 | println!("{}", part1(&input, 64)); 105 | } 106 | -------------------------------------------------------------------------------- /day3/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | struct Position { 3 | x: usize, 4 | y: usize, 5 | } 6 | 7 | impl Position { 8 | fn new(x: usize, y: usize) -> Self { 9 | Self { x, y } 10 | } 11 | 12 | fn is_adjacent(&self, other: &Position) -> bool { 13 | (self.x as isize - other.x as isize).abs() <= 1 14 | && (self.y as isize - other.y as isize).abs() <= 1 15 | } 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | struct Symbol { 20 | symbol: char, 21 | position: Position, 22 | } 23 | 24 | impl Symbol { 25 | fn new(symbol: char, position: Position) -> Self { 26 | Self { symbol, position } 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | struct Number { 32 | value: String, 33 | positions: Vec, 34 | } 35 | 36 | impl Number { 37 | fn new() -> Self { 38 | Self { 39 | value: "".to_string(), 40 | positions: vec![], 41 | } 42 | } 43 | fn value_as_int(&self) -> u32 { 44 | self.value.parse().expect("Invalid value") 45 | } 46 | fn is_adjacent(&self, position: &Position) -> bool { 47 | self.positions.iter().any(|p| p.is_adjacent(position)) 48 | } 49 | } 50 | 51 | fn parse_input(input: &str) -> (Vec, Vec) { 52 | let mut symbols: Vec = vec![]; 53 | let mut numbers: Vec = vec![]; 54 | for (y, line) in input.lines().enumerate() { 55 | let mut current_number = Number::new(); 56 | let mut at_number = false; 57 | for (x, char) in line.chars().enumerate() { 58 | match char { 59 | '0'..='9' => { 60 | at_number = true; 61 | current_number.positions.push(Position::new(x, y)); 62 | current_number.value.push(char) 63 | } 64 | _ => { 65 | if at_number { 66 | numbers.push(current_number); 67 | current_number = Number::new(); 68 | at_number = false; 69 | } 70 | if char != '.' { 71 | symbols.push(Symbol::new(char, Position::new(x, y))) 72 | } 73 | } 74 | } 75 | } 76 | if at_number { 77 | numbers.push(current_number); 78 | } 79 | } 80 | (symbols, numbers) 81 | } 82 | 83 | fn part1(symbols: &[Symbol], numbers: &[Number]) -> u32 { 84 | numbers 85 | .iter() 86 | .filter(|number| { 87 | symbols 88 | .iter() 89 | .map(|s| &s.position) 90 | .any(|s| number.is_adjacent(s)) 91 | }) 92 | .map(|num| num.value_as_int()) 93 | .sum() 94 | } 95 | 96 | fn part2(symbols: &[Symbol], numbers: &[Number]) -> u32 { 97 | symbols 98 | .iter() 99 | .filter(|symbol| symbol.symbol == '*') 100 | .map(|s| numbers.iter().filter(|n| n.is_adjacent(&s.position))) 101 | .filter(|adj_num| adj_num.clone().count() == 2) 102 | .map(|adj_num| adj_num.fold(1, |a, n| a * n.value_as_int())) 103 | .sum() 104 | } 105 | 106 | fn main() { 107 | let input = include_str!("../input.txt"); 108 | let (symbols, numbers) = parse_input(input); 109 | println!("{}", part1(&symbols, &numbers)); 110 | println!("{}", part2(&symbols, &numbers)); 111 | } 112 | -------------------------------------------------------------------------------- /day18/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Clone, Copy)] 2 | enum Direction { 3 | Up, 4 | Down, 5 | Left, 6 | Right, 7 | } 8 | use Direction::*; 9 | 10 | impl Direction { 11 | fn from_char(c: char) -> Self { 12 | match c { 13 | 'U' => Up, 14 | 'D' => Down, 15 | 'L' => Left, 16 | 'R' => Right, 17 | _ => panic!(), 18 | } 19 | } 20 | fn from_color(col: u32) -> Self { 21 | match col & 15 { 22 | 0 => Right, 23 | 1 => Down, 24 | 2 => Left, 25 | 3 => Up, 26 | _ => panic!(), 27 | } 28 | } 29 | fn as_position(&self) -> Position { 30 | match self { 31 | Up => (-1, 0), 32 | Down => (1, 0), 33 | Left => (0, -1), 34 | Right => (0, 1), 35 | } 36 | } 37 | } 38 | 39 | #[derive(Debug, Clone)] 40 | struct Instruction { 41 | direction: Direction, 42 | distance: u32, 43 | color: u32, 44 | } 45 | 46 | impl Instruction { 47 | fn from_line(line: &str) -> Self { 48 | let mut iter = line.split_whitespace(); 49 | Self { 50 | direction: Direction::from_char(iter.next().unwrap().chars().next().unwrap()), 51 | distance: iter.next().unwrap().parse().unwrap(), 52 | color: u32::from_str_radix( 53 | &iter 54 | .next() 55 | .unwrap() 56 | .chars() 57 | .filter(|c| c.is_ascii_hexdigit()) 58 | .collect::(), 59 | 16, 60 | ) 61 | .unwrap(), 62 | } 63 | } 64 | } 65 | 66 | type Position = (isize, isize); 67 | 68 | fn parse(input_str: &str) -> Vec { 69 | input_str.lines().map(Instruction::from_line).collect() 70 | } 71 | 72 | fn shoelace(positions: &[Position]) -> isize { 73 | let mut prev_point = positions.first().unwrap(); 74 | let mut total_area: isize = 0; 75 | for position in positions.iter().skip(1) { 76 | total_area += (prev_point.0 + position.0) * (prev_point.1 - position.1); 77 | prev_point = position; 78 | } 79 | total_area.abs() / 2 80 | } 81 | 82 | fn calc_area(instructions: &[Instruction], part2: bool) -> isize { 83 | let mut corner_positions: Vec = vec![(0, 0)]; 84 | let mut current_position = (0, 0); 85 | let mut perimiter = 0; 86 | let mut direction; 87 | let mut distance; 88 | for instruction in instructions { 89 | if part2 { 90 | direction = Direction::from_color(instruction.color).as_position(); 91 | distance = (instruction.color >> 4) as isize; 92 | } else { 93 | direction = instruction.direction.as_position(); 94 | distance = instruction.distance as isize; 95 | } 96 | perimiter += distance; 97 | current_position = ( 98 | current_position.0 + distance * direction.0, 99 | current_position.1 + distance * direction.1, 100 | ); 101 | corner_positions.push(current_position); 102 | } 103 | shoelace(&corner_positions) + perimiter / 2 + 1 104 | } 105 | 106 | fn part1(input: &[Instruction]) -> isize { 107 | calc_area(input, false) 108 | } 109 | 110 | fn part2(input: &[Instruction]) -> isize { 111 | calc_area(input, true) 112 | } 113 | 114 | fn main() { 115 | let input = parse(include_str!("../input.txt")); 116 | println!("{}", part1(&input)); 117 | println!("{}", part2(&input)); 118 | } 119 | -------------------------------------------------------------------------------- /day12/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Display}; 2 | 3 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 4 | enum Tile { 5 | Operational, 6 | Damaged, 7 | Unknown, 8 | } 9 | 10 | type Cache = HashMap<(Vec, Vec), usize>; 11 | 12 | impl Display for Tile { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | match self { 15 | Operational => write!(f, "."), 16 | Damaged => write!(f, "#"), 17 | Unknown => write!(f, "?"), 18 | } 19 | } 20 | } 21 | use Tile::*; 22 | 23 | fn parse(input_str: &str) -> Vec<(Vec, Vec)> { 24 | input_str 25 | .lines() 26 | .map(|line| { 27 | let mut split = line.split(' '); 28 | ( 29 | split 30 | .next() 31 | .unwrap() 32 | .chars() 33 | .map(|c| match c { 34 | '.' => Operational, 35 | '#' => Damaged, 36 | _ => Unknown, 37 | }) 38 | .collect(), 39 | split 40 | .next() 41 | .unwrap() 42 | .split(',') 43 | .map(|d| d.parse().unwrap()) 44 | .collect(), 45 | ) 46 | }) 47 | .collect() 48 | } 49 | 50 | fn handle_operational(tiles: &[Tile], groups: &[u8], cache: &mut Cache) -> usize { 51 | calc_combinations(&tiles[1..], groups, cache) 52 | } 53 | 54 | fn handle_damaged(tiles: &[Tile], groups: &[u8], cache: &mut Cache) -> usize { 55 | let next_group_size = *groups.first().unwrap() as usize; 56 | if tiles 57 | .iter() 58 | .take(next_group_size) 59 | .filter(|t| t != &&Operational) 60 | .count() 61 | != next_group_size 62 | { 63 | return 0; 64 | } 65 | if tiles.len() == next_group_size { 66 | if groups.len() == 1 { 67 | return 1; 68 | } 69 | return 0; 70 | } 71 | if matches!(tiles[next_group_size], Unknown | Operational) { 72 | return calc_combinations(&tiles[next_group_size + 1..], &groups[1..], cache); 73 | } 74 | 0 75 | } 76 | 77 | fn calc_combinations(tiles: &[Tile], groups: &[u8], cache: &mut Cache) -> usize { 78 | if groups.is_empty() { 79 | if !tiles.contains(&Damaged) { 80 | return 1; 81 | } 82 | return 0; 83 | } 84 | if tiles.is_empty() { 85 | return 0; 86 | } 87 | if let Some(val) = cache.get(&(tiles.to_vec(), groups.to_vec())) { 88 | return *val; 89 | } 90 | let res = match tiles.first().unwrap() { 91 | Operational => handle_operational(tiles, groups, cache), 92 | Damaged => handle_damaged(tiles, groups, cache), 93 | Unknown => handle_operational(tiles, groups, cache) + handle_damaged(tiles, groups, cache), 94 | }; 95 | cache.insert((tiles.to_vec(), groups.to_vec()), res); 96 | res 97 | } 98 | 99 | fn part1(parsed_input: &[(Vec, Vec)]) -> usize { 100 | let mut cache = HashMap::new(); 101 | parsed_input 102 | .iter() 103 | .map(|(tiles, groups)| calc_combinations(tiles, groups, &mut cache)) 104 | .sum() 105 | } 106 | 107 | fn part2(parsed_input: &[(Vec, Vec)]) -> usize { 108 | let new_input: Vec<_> = parsed_input 109 | .iter() 110 | .map(|(tiles, groups)| { 111 | let mut new_tiles = tiles.clone(); 112 | new_tiles.push(Unknown); 113 | new_tiles = new_tiles.repeat(5); 114 | new_tiles.pop(); 115 | (new_tiles, groups.repeat(5)) 116 | }) 117 | .collect(); 118 | let mut cache = HashMap::new(); 119 | new_input 120 | .iter() 121 | .map(|(tiles, groups)| calc_combinations(tiles, groups, &mut cache)) 122 | .sum() 123 | } 124 | 125 | fn main() { 126 | let input = parse(include_str!("../input.txt")); 127 | println!("{}", part1(&input)); 128 | println!("{}", part2(&input)); 129 | } 130 | -------------------------------------------------------------------------------- /day7/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | fn card_to_int(card: char, part2: bool) -> u32 { 4 | if let Some(v) = card.to_digit(10) { 5 | return v; 6 | } 7 | match card { 8 | 'A' => 14, 9 | 'K' => 13, 10 | 'Q' => 12, 11 | 'J' => { 12 | if part2 { 13 | 0 14 | } else { 15 | 11 16 | } 17 | } 18 | 19 | 'T' => 10, 20 | _ => panic!(), 21 | } 22 | } 23 | 24 | #[derive(PartialEq, PartialOrd, Eq, Debug)] 25 | enum HandType { 26 | HighCard, 27 | OnePair, 28 | TwoPair, 29 | ThreeOfAKind, 30 | FullHouse, 31 | FourOfAKind, 32 | FiveOfAKind, 33 | } 34 | 35 | #[derive(PartialEq, Eq, Debug)] 36 | struct Hand { 37 | cards: Vec, 38 | hand_type: HandType, 39 | bid: u32, 40 | } 41 | 42 | impl Hand { 43 | fn from_line(line: &str, part2: bool) -> Self { 44 | let mut line_iter = line.split(' '); 45 | let cards: Vec = line_iter 46 | .next() 47 | .unwrap() 48 | .chars() 49 | .map(|c| card_to_int(c, part2)) 50 | .collect(); 51 | let bid: u32 = line_iter.next().unwrap().parse().unwrap(); 52 | let mut card_amounts: HashMap = HashMap::from_iter( 53 | cards 54 | .clone() 55 | .into_iter() 56 | .map(|card| (card, cards.iter().filter(|c| **c == card).count() as u32)), 57 | ); 58 | if part2 && card_amounts.contains_key(&0) { 59 | let j_amount = *card_amounts.get(&0).unwrap(); 60 | let mut amounts_vec: Vec<(u32, u32)> = card_amounts.clone().into_iter().collect(); 61 | amounts_vec.sort_unstable_by(|(_, v1), (_, v2)| v2.cmp(v1)); 62 | let most_frequent = match amounts_vec[0] { 63 | (0, _) => amounts_vec.get(1).unwrap_or(&(0, 0)).0, 64 | _ => amounts_vec[0].0, 65 | }; 66 | if most_frequent != 0 { 67 | if let Some(x) = card_amounts.get_mut(&most_frequent) { 68 | *x += j_amount; 69 | } 70 | card_amounts.remove(&0); 71 | } 72 | } 73 | let hand_type = match card_amounts.keys().len() { 74 | 1 => HandType::FiveOfAKind, 75 | 2 => { 76 | if card_amounts.values().any(|x| x == &4) { 77 | HandType::FourOfAKind 78 | } else { 79 | HandType::FullHouse 80 | } 81 | } 82 | 3 => { 83 | if card_amounts.values().any(|x| x == &3) { 84 | HandType::ThreeOfAKind 85 | } else { 86 | HandType::TwoPair 87 | } 88 | } 89 | 4 => HandType::OnePair, 90 | 5 => HandType::HighCard, 91 | _ => panic!(), 92 | }; 93 | Self { 94 | cards, 95 | hand_type, 96 | bid, 97 | } 98 | } 99 | } 100 | 101 | impl PartialOrd for Hand { 102 | fn partial_cmp(&self, other: &Self) -> Option { 103 | Some(self.cmp(other)) 104 | } 105 | } 106 | 107 | impl Ord for Hand { 108 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 109 | let cmp = self.hand_type.partial_cmp(&other.hand_type).unwrap(); 110 | if cmp != std::cmp::Ordering::Equal { 111 | return cmp; 112 | } 113 | for i in 0..5 { 114 | match self.cards[i].cmp(&other.cards[i]) { 115 | std::cmp::Ordering::Equal => continue, 116 | ord => return ord, 117 | } 118 | } 119 | std::cmp::Ordering::Equal 120 | } 121 | } 122 | 123 | fn get_total_winnings(hands: &[Hand]) -> u32 { 124 | let mut hands: Vec<_> = hands.iter().collect(); 125 | hands.sort_unstable(); 126 | (0..hands.len()) 127 | .map(|i| hands[i].bid * (i + 1) as u32) 128 | .sum() 129 | } 130 | 131 | fn main() { 132 | let input = include_str!("../input.txt"); 133 | let hands: Vec = input.lines().map(|l| Hand::from_line(l, false)).collect(); 134 | println!("{}", get_total_winnings(&hands)); 135 | let hands: Vec = input.lines().map(|l| Hand::from_line(l, true)).collect(); 136 | println!("{}", get_total_winnings(&hands)); 137 | } 138 | -------------------------------------------------------------------------------- /day14/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 2 | enum Tile { 3 | RoundRock, 4 | CubeRock, 5 | Empty, 6 | } 7 | use std::{collections::HashMap, fmt::Display}; 8 | 9 | use Tile::*; 10 | 11 | impl Display for Tile { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | RoundRock => write!(f, "O"), 15 | CubeRock => write!(f, "#"), 16 | Empty => write!(f, "."), 17 | } 18 | } 19 | } 20 | 21 | fn parse(input_str: &str) -> Vec> { 22 | input_str 23 | .lines() 24 | .map(|line| { 25 | line.chars() 26 | .map(|c| match c { 27 | 'O' => RoundRock, 28 | '#' => CubeRock, 29 | _ => Empty, 30 | }) 31 | .collect() 32 | }) 33 | .collect() 34 | } 35 | 36 | fn print_map(map: &[Vec]) { 37 | println!( 38 | "{}", 39 | map.iter() 40 | .map(|l| l.iter().map(|t| t.to_string()).collect::() + "\n") 41 | .collect::() 42 | ); 43 | } 44 | 45 | fn tilt_north(map: &[Vec]) -> Vec> { 46 | let mut new_map = map.to_vec(); 47 | for (l_idx, line) in map.iter().enumerate() { 48 | for (c_idx, tile) in line.iter().enumerate() { 49 | if tile == &RoundRock { 50 | let mut found = false; 51 | for i in (0..l_idx).rev() { 52 | if matches!(new_map[i][c_idx], CubeRock | RoundRock) { 53 | if l_idx != i + 1 { 54 | new_map[i + 1][c_idx] = RoundRock; 55 | new_map[l_idx][c_idx] = Empty; 56 | } 57 | found = true; 58 | break; 59 | } 60 | } 61 | if !found && l_idx != 0 { 62 | new_map[0][c_idx] = RoundRock; 63 | new_map[l_idx][c_idx] = Empty; 64 | } 65 | } 66 | } 67 | } 68 | new_map 69 | } 70 | 71 | fn transpose(map: &[Vec]) -> Vec> { 72 | let mut res = vec![]; 73 | for c in 0..map[0].len() { 74 | let mut row = vec![]; 75 | for r in map { 76 | row.push(r[c].clone()); 77 | } 78 | res.push(row); 79 | } 80 | res 81 | } 82 | 83 | fn reverse_lines(map: &[Vec]) -> Vec> { 84 | let mut new = map.to_vec(); 85 | new.reverse(); 86 | new 87 | } 88 | 89 | fn reverse_columns(map: &[Vec]) -> Vec> { 90 | map.to_vec() 91 | .iter_mut() 92 | .map(|l| { 93 | l.reverse(); 94 | l.to_vec() 95 | }) 96 | .collect() 97 | } 98 | 99 | fn north_beam_support(map: &[Vec]) -> usize { 100 | map.iter() 101 | .enumerate() 102 | .map(|(l_idx, line)| line.iter().filter(|t| t == &&RoundRock).count() * (map.len() - l_idx)) 103 | .sum() 104 | } 105 | 106 | fn part1(map: &[Vec]) -> usize { 107 | north_beam_support(&tilt_north(map)) 108 | } 109 | 110 | fn cycle_map(map: &[Vec]) -> Vec> { 111 | // north 112 | let mut current_map = tilt_north(map); 113 | // west 114 | current_map = transpose(&tilt_north(&transpose(¤t_map))); 115 | // south 116 | current_map = reverse_lines(&tilt_north(&reverse_lines(¤t_map))); 117 | // east 118 | current_map = reverse_columns(&transpose(&tilt_north(&transpose(&reverse_columns( 119 | ¤t_map, 120 | ))))); 121 | current_map 122 | } 123 | 124 | fn part2(map: &[Vec]) -> usize { 125 | let mut map_history: HashMap>, usize> = HashMap::new(); 126 | let mut current_map = map.to_vec(); 127 | let mut count = 0; 128 | while map_history.get(¤t_map).is_none() { 129 | map_history.insert(current_map.clone(), count); 130 | current_map = cycle_map(¤t_map); 131 | count += 1; 132 | } 133 | let cycle_start = map_history.get(¤t_map).unwrap(); 134 | for _ in 0..(1_000_000_000 - cycle_start) % (count - cycle_start) { 135 | current_map = cycle_map(¤t_map); 136 | } 137 | north_beam_support(¤t_map) 138 | } 139 | 140 | fn main() { 141 | let input = parse(include_str!("../input.txt")); 142 | println!("{}", part1(&input)); 143 | println!("{}", part2(&input)); 144 | } 145 | -------------------------------------------------------------------------------- /day19/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lazy_static::lazy_static; 4 | use regex::Regex; 5 | 6 | #[derive(Debug, Clone)] 7 | struct Part { 8 | values: HashMap, 9 | } 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | enum Feature { 13 | X, 14 | M, 15 | A, 16 | S, 17 | } 18 | 19 | impl Feature { 20 | fn from_char(c: char) -> Self { 21 | match c { 22 | 'x' => Self::X, 23 | 'm' => Self::M, 24 | 'a' => Self::A, 25 | 's' => Self::S, 26 | _ => panic!(), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | struct Rule { 33 | target_feature: Feature, 34 | operator: char, 35 | threshold: usize, 36 | target_workflow: String, 37 | } 38 | 39 | #[derive(Debug, Clone)] 40 | struct Workflow { 41 | rules: Vec, 42 | final_workflow: String, 43 | } 44 | 45 | lazy_static! { 46 | static ref CONDITION_REGEX: Regex = Regex::new(r"([xmas])(<|>)(\d*):(\w*),").unwrap(); 47 | } 48 | 49 | fn parse(input_str: &str) -> (HashMap, Vec) { 50 | let mut split = input_str.split("\n\n"); 51 | ( 52 | split 53 | .next() 54 | .unwrap() 55 | .lines() 56 | .map(|l| { 57 | let mut name_split = l.split('{'); 58 | let name = name_split.next().unwrap().to_string(); 59 | let rest = name_split.next().unwrap(); 60 | let condition_caputres = CONDITION_REGEX.captures_iter(rest); 61 | let mut rules = vec![]; 62 | for captures in condition_caputres { 63 | rules.push(Rule { 64 | target_feature: Feature::from_char( 65 | captures.get(1).unwrap().as_str().chars().next().unwrap(), 66 | ), 67 | operator: captures.get(2).unwrap().as_str().chars().next().unwrap(), 68 | threshold: captures.get(3).unwrap().as_str().parse().unwrap(), 69 | target_workflow: captures.get(4).unwrap().as_str().to_string(), 70 | }) 71 | } 72 | let last = rest.split(',').last().unwrap(); 73 | let final_workflow: String = last.chars().take(last.len() - 1).collect(); 74 | ( 75 | name, 76 | Workflow { 77 | rules, 78 | final_workflow, 79 | }, 80 | ) 81 | }) 82 | .collect(), 83 | split 84 | .next() 85 | .unwrap() 86 | .lines() 87 | .map(|l| { 88 | let mut vals = l.split(',').map(|s| { 89 | s.chars() 90 | .filter(|c| c.is_ascii_digit()) 91 | .collect::() 92 | .parse() 93 | .unwrap() 94 | }); 95 | let mut values = HashMap::new(); 96 | values.insert(Feature::X, vals.next().unwrap()); 97 | values.insert(Feature::M, vals.next().unwrap()); 98 | values.insert(Feature::A, vals.next().unwrap()); 99 | values.insert(Feature::S, vals.next().unwrap()); 100 | Part { values } 101 | }) 102 | .collect(), 103 | ) 104 | } 105 | 106 | fn find_next_workflow(workflow: &Workflow, part: &Part) -> String { 107 | for rule in &workflow.rules { 108 | match rule.operator { 109 | '<' => { 110 | if part.values.get(&rule.target_feature).unwrap() < &rule.threshold { 111 | return rule.target_workflow.clone(); 112 | } 113 | } 114 | '>' => { 115 | if part.values.get(&rule.target_feature).unwrap() > &rule.threshold { 116 | return rule.target_workflow.clone(); 117 | } 118 | } 119 | _ => panic!(), 120 | } 121 | } 122 | workflow.final_workflow.clone() 123 | } 124 | 125 | fn part1(workflows: &HashMap, parts: &[Part]) -> usize { 126 | parts 127 | .iter() 128 | .map(|part| { 129 | let mut current_workflow = "in".to_string(); 130 | while current_workflow != "R" && current_workflow != "A" { 131 | current_workflow = 132 | find_next_workflow(workflows.get(¤t_workflow).unwrap(), part); 133 | } 134 | if current_workflow == "A" { 135 | part.values.values().sum::() 136 | } else { 137 | 0 138 | } 139 | }) 140 | .sum() 141 | } 142 | 143 | fn main() { 144 | let (workflows, parts) = parse(include_str!("../input.txt")); 145 | println!("{}", part1(&workflows, &parts)); 146 | } 147 | -------------------------------------------------------------------------------- /day10/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | type Position = (usize, usize); 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | enum Direction { 7 | North, 8 | East, 9 | South, 10 | West, 11 | } 12 | use Direction::*; 13 | 14 | impl Direction { 15 | fn from_positions(old_pos: Position, new_pos: Position) -> Self { 16 | if old_pos.1 < new_pos.1 { 17 | East 18 | } else if old_pos.1 > new_pos.1 { 19 | West 20 | } else if old_pos.0 < new_pos.0 { 21 | South 22 | } else { 23 | North 24 | } 25 | } 26 | fn to_position(&self, old_pos: Position) -> Position { 27 | match self { 28 | North => (old_pos.0 - 1, old_pos.1), 29 | East => (old_pos.0, old_pos.1 + 1), 30 | South => (old_pos.0 + 1, old_pos.1), 31 | West => (old_pos.0, old_pos.1 - 1), 32 | } 33 | } 34 | fn invert(&self) -> Self { 35 | match self { 36 | North => South, 37 | East => West, 38 | South => North, 39 | West => East, 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone)] 45 | struct Pipe(Vec); 46 | 47 | impl Pipe { 48 | fn from_char(c: char) -> Self { 49 | Self(match c { 50 | '|' => vec![North, South], 51 | '-' => vec![West, East], 52 | 'L' => vec![North, East], 53 | 'J' => vec![North, West], 54 | '7' => vec![South, West], 55 | 'F' => vec![South, East], 56 | 'S' => vec![North, East, South, West], 57 | _ => vec![], 58 | }) 59 | } 60 | } 61 | 62 | fn parse(input_str: &str) -> (Vec>, Position) { 63 | let mut pipe_map: Vec> = input_str 64 | .lines() 65 | .map(|line| line.chars().map(Pipe::from_char).collect()) 66 | .collect(); 67 | // find start position 68 | let mut start_position = (0, 0); 69 | 'outer: for (l_idx, l) in pipe_map.iter().enumerate() { 70 | for (r_idx, pipe) in l.iter().enumerate() { 71 | if pipe.0.len() == 4 { 72 | start_position = (l_idx, r_idx); 73 | break 'outer; 74 | } 75 | } 76 | } 77 | let mut start_pipe = vec![]; 78 | if let Some(l) = pipe_map.get(start_position.0 - 1) { 79 | if let Some(p) = l.get(start_position.1) { 80 | if p.0.contains(&South) { 81 | start_pipe.push(North); 82 | } 83 | } 84 | } 85 | if let Some(l) = pipe_map.get(start_position.0 + 1) { 86 | if let Some(p) = l.get(start_position.1) { 87 | if p.0.contains(&North) { 88 | start_pipe.push(South); 89 | } 90 | } 91 | } 92 | if let Some(p) = pipe_map[start_position.0].get(start_position.1 - 1) { 93 | if p.0.contains(&East) { 94 | start_pipe.push(West); 95 | } 96 | } 97 | if let Some(p) = pipe_map[start_position.0].get(start_position.1 + 1) { 98 | if p.0.contains(&West) { 99 | start_pipe.push(East); 100 | } 101 | } 102 | // replace start position with actual pipe 103 | pipe_map[start_position.0][start_position.1] = Pipe(start_pipe); 104 | (pipe_map, start_position) 105 | } 106 | 107 | fn find_next_pos(pipe_map: &[Vec], prev_pos: Position, current_pos: Position) -> Position { 108 | let from_direction = Direction::from_positions(prev_pos, current_pos); 109 | pipe_map[current_pos.0][current_pos.1] 110 | .0 111 | .iter() 112 | .find(|dir| dir != &&from_direction.invert()) 113 | .unwrap() 114 | .to_position(current_pos) 115 | } 116 | 117 | fn find_loop(pipe_map: &[Vec], start_pos: Position) -> Vec { 118 | let mut loop_positions: Vec = vec![start_pos]; 119 | loop_positions.push( 120 | pipe_map[start_pos.0][start_pos.1] 121 | .0 122 | .first() 123 | .unwrap() 124 | .to_position(start_pos), 125 | ); 126 | while loop_positions.last().unwrap() != &start_pos { 127 | loop_positions.push(find_next_pos( 128 | pipe_map, 129 | *loop_positions.iter().rev().nth(1).unwrap(), 130 | *loop_positions.last().unwrap(), 131 | )); 132 | } 133 | loop_positions 134 | } 135 | 136 | fn is_inside_loop( 137 | pipe_map: &[Vec], 138 | loop_positions: &HashSet, 139 | position: Position, 140 | ) -> bool { 141 | let mut crossings = 0; 142 | let mut current_pos = position; 143 | // we go up from the position and check how often we cross the loop 144 | while current_pos.0 != 0 { 145 | current_pos = (current_pos.0 - 1, current_pos.1); 146 | if !loop_positions.contains(¤t_pos) { 147 | continue; 148 | } 149 | if pipe_map[current_pos.0][current_pos.1].0.contains(&East) { 150 | crossings += 1; 151 | } 152 | } 153 | crossings % 2 != 0 154 | } 155 | 156 | fn part1(pipe_map: &[Vec], start_pos: Position) -> usize { 157 | (find_loop(pipe_map, start_pos).len() - 1) / 2 158 | } 159 | 160 | fn part2(pipe_map: &[Vec], start_pos: Position) -> usize { 161 | let loop_positions: HashSet = HashSet::from_iter(find_loop(pipe_map, start_pos)); 162 | let mut enclosed_tiles = 0; 163 | for pos1 in loop_positions.iter().map(|(x, _)| x).min().unwrap() + 1 164 | ..loop_positions.iter().map(|(x, _)| x).max().unwrap() - 1 165 | { 166 | for pos2 in loop_positions.iter().map(|(_, y)| y).min().unwrap() + 1 167 | ..loop_positions.iter().map(|(_, y)| y).max().unwrap() - 1 168 | { 169 | if loop_positions.contains(&(pos1, pos2)) { 170 | continue; 171 | } 172 | if is_inside_loop(pipe_map, &loop_positions, (pos1, pos2)) { 173 | enclosed_tiles += 1; 174 | } 175 | } 176 | } 177 | enclosed_tiles 178 | } 179 | 180 | fn main() { 181 | let (pipe_map, start_pos) = parse(include_str!("../input.txt")); 182 | println!("{}", part1(&pipe_map, start_pos)); 183 | println!("{}", part2(&pipe_map, start_pos)); 184 | } 185 | -------------------------------------------------------------------------------- /day16/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, thread, time::Duration}; 2 | use rayon::prelude::*; 3 | 4 | fn parse(input_str: &str) -> Vec> { 5 | input_str 6 | .lines() 7 | .map(|l| { 8 | l.chars() 9 | .map(|c| match c { 10 | '|' => Tile::new(NorthSouthSplitter), 11 | '-' => Tile::new(WestEastSplitter), 12 | '/' => Tile::new(UpMirror), 13 | '\\' => Tile::new(DownMirror), 14 | _ => Tile::new(Empty), 15 | }) 16 | .collect() 17 | }) 18 | .collect() 19 | } 20 | 21 | type Position = (isize, isize); 22 | 23 | #[derive(Debug, Clone)] 24 | struct Tile { 25 | tile_type: TileType, 26 | energized: Option, 27 | } 28 | 29 | impl Display for Tile { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | match self.tile_type { 32 | NorthSouthSplitter => write!(f, "|"), 33 | WestEastSplitter => write!(f, "-"), 34 | UpMirror => write!(f, "/"), 35 | DownMirror => write!(f, "\\"), 36 | Empty => { 37 | if let Some(d) = self.energized { 38 | write!(f, "{d}") 39 | } else { 40 | write!(f, ".") 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | impl Tile { 48 | fn new(tile_type: TileType) -> Self { 49 | Self { 50 | tile_type, 51 | energized: None, 52 | } 53 | } 54 | } 55 | 56 | #[derive(Debug, PartialEq, Clone, Copy)] 57 | enum TileType { 58 | NorthSouthSplitter, 59 | WestEastSplitter, 60 | UpMirror, 61 | DownMirror, 62 | Empty, 63 | } 64 | use TileType::*; 65 | 66 | #[derive(Debug, Clone, Copy, PartialEq)] 67 | enum Direction { 68 | North, 69 | East, 70 | South, 71 | West, 72 | } 73 | use Direction::*; 74 | 75 | impl Display for Direction { 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 | match self { 78 | North => write!(f, "^"), 79 | East => write!(f, ">"), 80 | South => write!(f, "v"), 81 | West => write!(f, "<"), 82 | } 83 | } 84 | } 85 | 86 | impl Direction { 87 | fn as_position(&self) -> Position { 88 | match self { 89 | North => (-1, 0), 90 | East => (0, 1), 91 | South => (1, 0), 92 | West => (0, -1), 93 | } 94 | } 95 | } 96 | 97 | #[derive(Debug, Clone)] 98 | struct Beam { 99 | position: Position, 100 | direction: Direction, 101 | } 102 | 103 | impl Beam { 104 | fn step_in_direction(&mut self) { 105 | let direction = self.direction.as_position(); 106 | self.position = ( 107 | (self.position.0 + direction.0), 108 | (self.position.1 + direction.1), 109 | ); 110 | } 111 | 112 | fn move_beam(&mut self, map: &mut [Vec]) -> Option { 113 | map[self.position.0 as usize][self.position.1 as usize].energized = Some(self.direction); 114 | match map[self.position.0 as usize][self.position.1 as usize].tile_type { 115 | Empty => { 116 | self.step_in_direction(); 117 | None 118 | } 119 | UpMirror => { 120 | match self.direction { 121 | North => self.direction = East, 122 | East => self.direction = North, 123 | South => self.direction = West, 124 | West => self.direction = South, 125 | } 126 | self.step_in_direction(); 127 | None 128 | } 129 | DownMirror => { 130 | match self.direction { 131 | North => self.direction = West, 132 | East => self.direction = South, 133 | South => self.direction = East, 134 | West => self.direction = North, 135 | } 136 | self.step_in_direction(); 137 | None 138 | } 139 | NorthSouthSplitter => { 140 | if matches!(self.direction, West | East) { 141 | self.direction = North; 142 | Some(Beam { 143 | position: self.position, 144 | direction: South, 145 | }) 146 | } else { 147 | self.step_in_direction(); 148 | None 149 | } 150 | } 151 | WestEastSplitter => { 152 | if matches!(self.direction, North | South) { 153 | self.direction = West; 154 | Some(Beam { 155 | position: self.position, 156 | direction: East, 157 | }) 158 | } else { 159 | self.step_in_direction(); 160 | None 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | fn print_map(map: &[Vec]) { 168 | println!( 169 | "{}", 170 | map.iter() 171 | .map(|l| l.iter().map(|t| t.to_string()).collect::() + "\n") 172 | .collect::() 173 | ) 174 | } 175 | 176 | const PRINT: bool = false; 177 | 178 | fn calc_total_energized(map: &[Vec], start_beam: Beam) -> usize { 179 | let mut map = map.to_vec(); 180 | let mut beams = vec![start_beam]; 181 | while !beams.is_empty() { 182 | if PRINT { 183 | print_map(&map); 184 | thread::sleep(Duration::from_millis(50)); 185 | } 186 | // remove out of bounds beams and beam that travel on already travelled routes 187 | beams.retain(|beam| { 188 | 0 <= beam.position.0 189 | && beam.position.0 < map.len() as isize 190 | && 0 <= beam.position.1 191 | && beam.position.1 < map[0].len() as isize 192 | && { 193 | match map[beam.position.0 as usize][beam.position.1 as usize].energized { 194 | None => true, 195 | Some(d) => d != beam.direction, 196 | } 197 | } 198 | }); 199 | let mut new_beams = vec![]; 200 | for beam in &mut beams { 201 | if let Some(b) = beam.move_beam(&mut map) { 202 | new_beams.push(b); 203 | } 204 | } 205 | beams.append(&mut new_beams); 206 | } 207 | map.iter() 208 | .map(|l| { 209 | l.iter() 210 | .map(|t| if t.energized.is_some() { 1 } else { 0 }) 211 | .sum::() 212 | }) 213 | .sum() 214 | } 215 | 216 | fn part1(map: &[Vec]) -> usize { 217 | calc_total_energized( 218 | map, 219 | Beam { 220 | position: (0, 0), 221 | direction: East, 222 | }, 223 | ) 224 | } 225 | 226 | fn part2(map: &[Vec]) -> usize { 227 | let mut possible_start_beams = vec![]; 228 | for l in 0..map.len() { 229 | possible_start_beams.push(Beam { 230 | position: (l as isize, 0), 231 | direction: East, 232 | }); 233 | possible_start_beams.push(Beam { 234 | position: (l as isize, map[0].len() as isize - 1), 235 | direction: West, 236 | }); 237 | } 238 | for c in 0..map[0].len() { 239 | possible_start_beams.push(Beam { 240 | position: (0, c as isize), 241 | direction: South, 242 | }); 243 | possible_start_beams.push(Beam { 244 | position: (map.len() as isize - 1, c as isize), 245 | direction: North, 246 | }); 247 | } 248 | possible_start_beams 249 | .par_iter() 250 | .map(|beam| calc_total_energized(map, beam.clone())) 251 | .max() 252 | .unwrap() 253 | } 254 | 255 | fn main() { 256 | let input = parse(include_str!("../input.txt")); 257 | println!("{}", part1(&input)); 258 | println!("{}", part2(&input)); 259 | } 260 | --------------------------------------------------------------------------------