chore: refactor for cargo-aoc / codspeed
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| **/target | ||||
| input | ||||
| flamegraph.svg | ||||
| perf.data | ||||
| perf.data* | ||||
| guesses | ||||
|   | ||||
							
								
								
									
										7
									
								
								1/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								1/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day1" | ||||
| version = "0.1.0" | ||||
| @@ -1,6 +0,0 @@ | ||||
| [package] | ||||
| name = "day1" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
							
								
								
									
										105
									
								
								1/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								1/src/main.rs
									
									
									
									
									
								
							| @@ -1,105 +0,0 @@ | ||||
| use std::collections::HashMap; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::iter::zip; | ||||
| use std::time::Instant; | ||||
|  | ||||
| // BOILERPLATE | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
|  | ||||
| fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(get_input()); | ||||
|     let duration = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); | ||||
|  | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(get_input()); | ||||
|     let duration = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); | ||||
| } | ||||
|  | ||||
| struct Locations { | ||||
|     left: Vec<u64>, | ||||
|     right: Vec<u64>, | ||||
| } | ||||
|  | ||||
| impl<T: BufRead> From<Lines<T>> for Locations { | ||||
|     fn from(input: Lines<T>) -> Self { | ||||
|         let mut left = Vec::new(); | ||||
|         let mut right = Vec::new(); | ||||
|         for line in input.map(|i| i.unwrap()) { | ||||
|             let parts: Vec<&str> = line.split_ascii_whitespace().collect(); | ||||
|             left.push(parts[0].parse::<u64>().unwrap()); | ||||
|             right.push(parts[1].parse::<u64>().unwrap()); | ||||
|         } | ||||
|         Self { left, right } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Locations { | ||||
|     fn sort(&mut self) { | ||||
|         self.left.sort(); | ||||
|         self.right.sort(); | ||||
|     } | ||||
|     fn right_count(&self) -> HashMap<u64, u64> { | ||||
|         let mut right_count: HashMap<u64, u64> = HashMap::new(); | ||||
|         for rval in &self.right { | ||||
|             right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1); | ||||
|         } | ||||
|         right_count | ||||
|     } | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
|  | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut locations = Locations::from(input); | ||||
|     locations.sort(); | ||||
|  | ||||
|     zip(locations.left, locations.right) | ||||
|         .map(|(l, r)| u64::abs_diff(l, r)) | ||||
|         .sum() | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let locations = Locations::from(input); | ||||
|     let right_count = locations.right_count(); | ||||
|     locations | ||||
|         .left | ||||
|         .iter() | ||||
|         .map(|l| l * right_count.get(l).unwrap_or(&0)) | ||||
|         .sum::<u64>() | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|  | ||||
|     const EXAMPLE: &str = &"3   4 | ||||
| 4   3 | ||||
| 2   5 | ||||
| 1   3 | ||||
| 3   9 | ||||
| 3   3"; | ||||
|  | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 11); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 31); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								10/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								10/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,30 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day10" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "grid", | ||||
|  "itertools", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "grid" | ||||
| version = "0.1.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
| @@ -1,8 +0,0 @@ | ||||
| [package] | ||||
| name = "day10" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| grid = { version = "0.1.0", path = "../libs/grid" } | ||||
| itertools = "0.13.0" | ||||
							
								
								
									
										25
									
								
								11/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								11/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,25 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day11" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "itertools", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
| @@ -1,7 +0,0 @@ | ||||
| [package] | ||||
| name = "day11" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| itertools = "0.13.0" | ||||
							
								
								
									
										135
									
								
								11/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								11/src/main.rs
									
									
									
									
									
								
							| @@ -1,135 +0,0 @@ | ||||
| use std::collections::HashMap; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::iter::repeat; | ||||
| use std::time::{Duration, Instant}; | ||||
|  | ||||
| use itertools::Itertools; | ||||
|  | ||||
| // BOILERPLATE | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
|  | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
|  | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
|  | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] | ||||
| struct Stone(u64); | ||||
| struct Stones(Vec<Stone>); | ||||
|  | ||||
| impl From<&str> for Stones { | ||||
|     fn from(input: &str) -> Self { | ||||
|         Stones( | ||||
|             input | ||||
|                 .split_ascii_whitespace() | ||||
|                 .map(|v| Stone(v.parse().unwrap())) | ||||
|                 .collect_vec(), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| enum BlinkResult { | ||||
|     One(Stone), | ||||
|     Two(Stone, Stone), | ||||
| } | ||||
|  | ||||
| impl Stone { | ||||
|     fn blink_once(self) -> BlinkResult { | ||||
|         let n_digits = if self.0 == 0 { 1 } else { self.0.ilog10() + 1 }; | ||||
|         if self.0 == 0 { | ||||
|             BlinkResult::One(Stone(1)) | ||||
|         } else if n_digits % 2 == 0 { | ||||
|             let parts = (self.0 / 10u64.pow(n_digits / 2), self.0 % 10u64.pow(n_digits / 2)); | ||||
|             BlinkResult::Two(Stone(parts.0), Stone(parts.1)) | ||||
|         } else { | ||||
|             BlinkResult::One(Stone(self.0 * 2024)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn count_blinks(stone: Stone, blink: usize, cache: &mut Vec<HashMap<Stone, u64>>) -> u64 { | ||||
|     if cache[blink].contains_key(&stone) { | ||||
|         return cache[blink][&stone]; | ||||
|     } | ||||
|     let stones = stone.blink_once(); | ||||
|     let result = if blink == 0 { | ||||
|         match stones { | ||||
|             BlinkResult::One(_) => 1, | ||||
|             BlinkResult::Two(_, _) => 2, | ||||
|         } | ||||
|     } else { | ||||
|         match stones { | ||||
|             BlinkResult::One(s) => count_blinks(s, blink - 1, cache), | ||||
|             BlinkResult::Two(s1, s2) => count_blinks(s1, blink - 1, cache) + count_blinks(s2, blink - 1, cache), | ||||
|         } | ||||
|     }; | ||||
|     cache[blink].insert(stone, result); | ||||
|     result | ||||
| } | ||||
|  | ||||
| fn blink_stones(stones: &Stones, blinks: usize) -> u64 { | ||||
|     let mut cache = Vec::from_iter(repeat(HashMap::new()).take(blinks)); | ||||
|     stones | ||||
|         .0 | ||||
|         .iter() | ||||
|         .map(|stone| count_blinks(*stone, blinks - 1, &mut cache)) | ||||
|         .sum() | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
|  | ||||
| fn problem1<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
|     let stones = input.next().unwrap().unwrap().as_str().into(); | ||||
|     blink_stones(&stones, 25) | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| fn problem2<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
|     let stones = input.next().unwrap().unwrap().as_str().into(); | ||||
|     blink_stones(&stones, 75) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|  | ||||
|     const EXAMPLE: &str = &"125 17"; | ||||
|  | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 55312); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 65601038650482); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								2/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								2/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day2" | ||||
| version = "0.1.0" | ||||
| @@ -1,6 +0,0 @@ | ||||
| [package] | ||||
| name = "day2" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
							
								
								
									
										133
									
								
								2/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								2/src/main.rs
									
									
									
									
									
								
							| @@ -1,133 +0,0 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
|  | ||||
| // BOILERPLATE | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
|  | ||||
| fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
|  | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
|  | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
|  | ||||
| struct Reports { | ||||
|     reports: Vec<Vec<u64>>, | ||||
| } | ||||
|  | ||||
| impl<T: BufRead> From<Lines<T>> for Reports { | ||||
|     fn from(lines: Lines<T>) -> Self { | ||||
|         let mut reports = Vec::new(); | ||||
|         for line in lines.map(|i| i.unwrap()) { | ||||
|             reports.push( | ||||
|                 line.split_ascii_whitespace() | ||||
|                     .map(|record| record.parse::<u64>().unwrap()) | ||||
|                     .collect(), | ||||
|             ) | ||||
|         } | ||||
|         Reports { reports } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Reports { | ||||
|     fn is_safe(report: &Vec<u64>) -> bool { | ||||
|         let mut ascending: bool = true; | ||||
|         let mut descending: bool = true; | ||||
|         for (a, b) in report.iter().zip(report.iter().skip(1)) { | ||||
|             if a > b { | ||||
|                 ascending = false | ||||
|             } | ||||
|             if a < b { | ||||
|                 descending = false; | ||||
|             } | ||||
|             let ad = a.abs_diff(*b); | ||||
|             if !(ad >= 1 && ad <= 3) || (!ascending && !descending) { | ||||
|                 return false; | ||||
|             }; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|     fn count_safe(&self) -> u64 { | ||||
|         self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64 | ||||
|     } | ||||
|     fn is_dumb_dampened_safe(report: &Vec<u64>) -> bool { | ||||
|         if Self::is_safe(report) { | ||||
|             return true; | ||||
|         } | ||||
|         for i in 0..report.len() { | ||||
|             let mut new_vec = report.clone(); | ||||
|             new_vec.remove(i); | ||||
|             if Self::is_safe(&new_vec) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         false | ||||
|     } | ||||
|     fn dampened_count_safe(&self) -> u64 { | ||||
|         self.reports | ||||
|             .iter() | ||||
|             .filter(|report| Self::is_dumb_dampened_safe(report)) | ||||
|             .count() as u64 | ||||
|     } | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
|  | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let reports = Reports::from(input); | ||||
|     reports.count_safe() | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let reports = Reports::from(input); | ||||
|     reports.dampened_count_safe() | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|  | ||||
|     const EXAMPLE: &str = &"7 6 4 2 1 | ||||
| 1 2 7 8 9 | ||||
| 9 7 6 2 1 | ||||
| 1 3 2 4 5 | ||||
| 8 6 4 4 1 | ||||
| 1 3 6 7 9"; | ||||
|  | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 2); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 4); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										71
									
								
								3/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										71
									
								
								3/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,71 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "aho-corasick" | ||||
| version = "1.1.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" | ||||
| dependencies = [ | ||||
|  "memchr", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "day3" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "nom", | ||||
|  "regex", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "memchr" | ||||
| version = "2.7.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" | ||||
|  | ||||
| [[package]] | ||||
| name = "minimal-lexical" | ||||
| version = "0.2.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" | ||||
|  | ||||
| [[package]] | ||||
| name = "nom" | ||||
| version = "7.1.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" | ||||
| dependencies = [ | ||||
|  "memchr", | ||||
|  "minimal-lexical", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex" | ||||
| version = "1.11.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" | ||||
| dependencies = [ | ||||
|  "aho-corasick", | ||||
|  "memchr", | ||||
|  "regex-automata", | ||||
|  "regex-syntax", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex-automata" | ||||
| version = "0.4.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" | ||||
| dependencies = [ | ||||
|  "aho-corasick", | ||||
|  "memchr", | ||||
|  "regex-syntax", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex-syntax" | ||||
| version = "0.8.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" | ||||
| @@ -1,8 +0,0 @@ | ||||
| [package] | ||||
| name = "day3" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| nom = "7.1.3" | ||||
| regex = { version = "1.11.1", default-features = false, features = ["perf", "std"] } | ||||
| @@ -1,94 +0,0 @@ | ||||
| use regex::bytes::Regex; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
|  | ||||
| // BOILERPLATE | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
|  | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
|  | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
|  | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
|  | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut sum = 0u64; | ||||
|     let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap(); | ||||
|     for line in input.map(|i| i.unwrap()) { | ||||
|         let line = line.as_bytes(); | ||||
|         for m in re.captures_iter(line) { | ||||
|             sum += std::str::from_utf8(&m[1]).unwrap().parse::<u64>().unwrap() | ||||
|                 * std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap(); | ||||
|         } | ||||
|     } | ||||
|     sum | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut sum = 0u64; | ||||
|     let mut do_mul = true; | ||||
|     let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap(); | ||||
|     for line in input.map(|i| i.unwrap()) { | ||||
|         let line = line.as_bytes(); | ||||
|         for m in re.captures_iter(line) { | ||||
|             match std::str::from_utf8(&m[1]).unwrap() { | ||||
|                 "do()" => do_mul = true, | ||||
|                 "don't()" => do_mul = false, | ||||
|                 _ if do_mul => { | ||||
|                     sum += std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap() | ||||
|                         * std::str::from_utf8(&m[3]).unwrap().parse::<u64>().unwrap() | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     sum | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|  | ||||
|     const EXAMPLE1: &str = &"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"; | ||||
|     const EXAMPLE2: &str = &"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"; | ||||
|  | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE1); | ||||
|         assert_eq!(problem1(c.lines()), 161); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE2); | ||||
|         assert_eq!(problem2(c.lines()), 48); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								4/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								4/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day4" | ||||
| version = "0.1.0" | ||||
| @@ -1,6 +0,0 @@ | ||||
| [package] | ||||
| name = "day4" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
							
								
								
									
										7
									
								
								5/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								5/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day5" | ||||
| version = "0.1.0" | ||||
| @@ -1,6 +0,0 @@ | ||||
| [package] | ||||
| name = "day5" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
							
								
								
									
										21
									
								
								6/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								6/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,21 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "2.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" | ||||
|  | ||||
| [[package]] | ||||
| name = "day6" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "grid", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "grid" | ||||
| version = "0.1.0" | ||||
| @@ -1,8 +0,0 @@ | ||||
| [package] | ||||
| name = "day6" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| bitflags = "2.6.0" | ||||
| grid = { version = "0.1.0", path = "../libs/grid" } | ||||
							
								
								
									
										94
									
								
								7/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										94
									
								
								7/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,94 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-deque" | ||||
| version = "0.8.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" | ||||
| dependencies = [ | ||||
|  "crossbeam-epoch", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-epoch" | ||||
| version = "0.9.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" | ||||
| dependencies = [ | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-utils" | ||||
| version = "0.8.20" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" | ||||
|  | ||||
| [[package]] | ||||
| name = "day7" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "itertools", | ||||
|  "rayon", | ||||
|  "thread_local", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "once_cell" | ||||
| version = "1.20.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" | ||||
|  | ||||
| [[package]] | ||||
| name = "rayon" | ||||
| version = "1.10.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" | ||||
| dependencies = [ | ||||
|  "either", | ||||
|  "rayon-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rayon-core" | ||||
| version = "1.12.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" | ||||
| dependencies = [ | ||||
|  "crossbeam-deque", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "thread_local" | ||||
| version = "1.1.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "once_cell", | ||||
| ] | ||||
| @@ -1,9 +0,0 @@ | ||||
| [package] | ||||
| name = "day7" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| itertools = "0.13.0" | ||||
| rayon = "1.10.0" | ||||
| thread_local = "1.1.8" | ||||
							
								
								
									
										30
									
								
								8/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								8/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,30 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day8" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "grid", | ||||
|  "itertools", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "grid" | ||||
| version = "0.1.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
| @@ -1,8 +0,0 @@ | ||||
| [package] | ||||
| name = "day8" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| grid = { version = "0.1.0", path = "../libs/grid" } | ||||
| itertools = "0.13.0" | ||||
							
								
								
									
										25
									
								
								9/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								9/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,25 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "day9" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "itertools", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
| @@ -1,7 +0,0 @@ | ||||
| [package] | ||||
| name = "day9" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| itertools = "0.13.0" | ||||
							
								
								
									
										272
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "aho-corasick" | ||||
| version = "1.1.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" | ||||
| dependencies = [ | ||||
|  "memchr", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "aoc-runner" | ||||
| version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d21ef9204ad206a5a3e918e9920da04e1118ad91ce4f23570be964b9d6b9dfcb" | ||||
|  | ||||
| [[package]] | ||||
| name = "aoc-runner-derive" | ||||
| version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ba8b944269d3fee645d281b1335e1797044db497bb02d0098cc3fdb8900069cc" | ||||
| dependencies = [ | ||||
|  "aoc-runner-internal", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 1.0.109", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "aoc-runner-internal" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "274b0ba7f3669a45ec0aaacf94eb032a749de880ab776091576cca94037c9982" | ||||
| dependencies = [ | ||||
|  "serde", | ||||
|  "serde_derive", | ||||
|  "serde_json", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "aoc2024" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "aoc-runner", | ||||
|  "aoc-runner-derive", | ||||
|  "bitflags", | ||||
|  "grid", | ||||
|  "itertools", | ||||
|  "rayon", | ||||
|  "regex", | ||||
|  "thread_local", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "2.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" | ||||
|  | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-deque" | ||||
| version = "0.8.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" | ||||
| dependencies = [ | ||||
|  "crossbeam-epoch", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-epoch" | ||||
| version = "0.9.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" | ||||
| dependencies = [ | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crossbeam-utils" | ||||
| version = "0.8.20" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" | ||||
|  | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" | ||||
|  | ||||
| [[package]] | ||||
| name = "grid" | ||||
| version = "0.1.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "itoa" | ||||
| version = "1.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" | ||||
|  | ||||
| [[package]] | ||||
| name = "memchr" | ||||
| version = "2.7.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" | ||||
|  | ||||
| [[package]] | ||||
| name = "once_cell" | ||||
| version = "1.20.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" | ||||
|  | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.92" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" | ||||
| dependencies = [ | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.37" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rayon" | ||||
| version = "1.10.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" | ||||
| dependencies = [ | ||||
|  "either", | ||||
|  "rayon-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rayon-core" | ||||
| version = "1.12.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" | ||||
| dependencies = [ | ||||
|  "crossbeam-deque", | ||||
|  "crossbeam-utils", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex" | ||||
| version = "1.11.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" | ||||
| dependencies = [ | ||||
|  "aho-corasick", | ||||
|  "memchr", | ||||
|  "regex-automata", | ||||
|  "regex-syntax", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex-automata" | ||||
| version = "0.4.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" | ||||
| dependencies = [ | ||||
|  "aho-corasick", | ||||
|  "memchr", | ||||
|  "regex-syntax", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "regex-syntax" | ||||
| version = "0.8.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" | ||||
|  | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" | ||||
|  | ||||
| [[package]] | ||||
| name = "serde" | ||||
| version = "1.0.216" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" | ||||
| dependencies = [ | ||||
|  "serde_derive", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_derive" | ||||
| version = "1.0.216" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.90", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_json" | ||||
| version = "1.0.133" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" | ||||
| dependencies = [ | ||||
|  "itoa", | ||||
|  "memchr", | ||||
|  "ryu", | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "1.0.109" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.90" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "thread_local" | ||||
| version = "1.1.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "once_cell", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-ident" | ||||
| version = "1.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" | ||||
							
								
								
									
										14
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [package] | ||||
| name = "aoc2024" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| aoc-runner = "0.3.0" | ||||
| aoc-runner-derive = "0.3.0" | ||||
| bitflags = "2.6.0" | ||||
| grid = { version = "0.1.0", path = "utils/grid" } | ||||
| itertools = "0.13.0" | ||||
| rayon = "1.10.0" | ||||
| regex = "1.11.1" | ||||
| thread_local = "1.1.8" | ||||
							
								
								
									
										7
									
								
								libs/grid/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								libs/grid/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,7 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "grid" | ||||
| version = "0.1.0" | ||||
							
								
								
									
										90
									
								
								src/day1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/day1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use std::collections::HashMap; | ||||
| use std::io::{BufRead, Lines}; | ||||
|  | ||||
| #[aoc_generator(day1)] | ||||
| pub fn get_input(input: &[u8]) -> Locations { | ||||
|     Locations::from(input.lines()) | ||||
| } | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Locations { | ||||
|     left: Vec<u64>, | ||||
|     right: Vec<u64>, | ||||
| } | ||||
|  | ||||
| impl<T: BufRead> From<Lines<T>> for Locations { | ||||
|     fn from(input: Lines<T>) -> Self { | ||||
|         let mut left = Vec::new(); | ||||
|         let mut right = Vec::new(); | ||||
|         for line in input.map(|i| i.unwrap()) { | ||||
|             let parts: Vec<&str> = line.split_ascii_whitespace().collect(); | ||||
|             left.push(parts[0].parse::<u64>().unwrap()); | ||||
|             right.push(parts[1].parse::<u64>().unwrap()); | ||||
|         } | ||||
|         Self { left, right } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Locations { | ||||
|     fn sort(&mut self) { | ||||
|         self.left.sort(); | ||||
|         self.right.sort(); | ||||
|     } | ||||
|     fn right_count(&self) -> HashMap<u64, u64> { | ||||
|         let mut right_count: HashMap<u64, u64> = HashMap::new(); | ||||
|         for rval in &self.right { | ||||
|             right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1); | ||||
|         } | ||||
|         right_count | ||||
|     } | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
| #[aoc(day1, part1)] | ||||
| pub fn part1(locations: &Locations) -> u64 { | ||||
|     let mut locations = locations.clone(); | ||||
|     locations.sort(); | ||||
|  | ||||
|     locations | ||||
|         .left | ||||
|         .iter() | ||||
|         .zip(locations.right) | ||||
|         .map(|(l, r)| u64::abs_diff(*l, r)) | ||||
|         .sum() | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| #[aoc(day1, part2)] | ||||
| pub fn part2(locations: &Locations) -> u64 { | ||||
|     let right_count = locations.right_count(); | ||||
|     locations | ||||
|         .left | ||||
|         .iter() | ||||
|         .map(|l| l * right_count.get(l).unwrap_or(&0)) | ||||
|         .sum::<u64>() | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::day1::*; | ||||
|  | ||||
|     const EXAMPLE: &[u8] = b"3   4 | ||||
| 4   3 | ||||
| 2   5 | ||||
| 1   3 | ||||
| 3   9 | ||||
| 3   3"; | ||||
|  | ||||
|     #[test] | ||||
|     fn part1_example() { | ||||
|         let input = get_input(EXAMPLE); | ||||
|         assert_eq!(part1(&input), 11); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn part2_example() { | ||||
|         let input = get_input(EXAMPLE); | ||||
|         assert_eq!(part2(&input), 31); | ||||
|     } | ||||
| } | ||||
| @@ -1,43 +1,14 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use grid::Grid; | ||||
| use itertools::Itertools; | ||||
| use std::io::{BufRead, Lines}; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| #[aoc_generator(day10)] | ||||
| pub fn get_input(input: &[u8]) -> TrailMap { | ||||
|     TrailMap::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
| 
 | ||||
| struct TrailMap { | ||||
| pub struct TrailMap { | ||||
|     map: Grid<u8>, | ||||
| } | ||||
| 
 | ||||
| @@ -66,8 +37,6 @@ impl TrailMap { | ||||
|         let our_val = self.map.get(pos.0, pos.1).unwrap(); | ||||
|         if our_val == needle { | ||||
|             return 1; | ||||
|         } else if our_val == needle { | ||||
|             return 0; | ||||
|         } | ||||
|         // adjacents that are +1
 | ||||
|         [(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
 | ||||
| @@ -92,15 +61,13 @@ impl TrailMap { | ||||
|             .filter(|(_, val)| *val == Some(our_val + 1)) // only interested if it's our value + 1
 | ||||
|             .map(|(pos, _)| pos) // discard the value
 | ||||
|             .map(|mov| self.count_paths_to(&mov, needle)) | ||||
|             .sum::<u64>() as u64 | ||||
|             .sum::<u64>() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let map: TrailMap = input.into(); | ||||
| 
 | ||||
| #[aoc(day10, part1)] | ||||
| pub fn part1(map: &TrailMap) -> u64 { | ||||
|     map.trailheads() | ||||
|         .iter() | ||||
|         .map(|pos| { | ||||
| @@ -111,21 +78,19 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let map: TrailMap = input.into(); | ||||
| 
 | ||||
| #[aoc(day10, part2)] | ||||
| pub fn part2(map: &TrailMap) -> u64 { | ||||
|     map.trailheads() | ||||
|         .iter() | ||||
|         .map(|pos| map.count_paths_to(pos, b'9')) | ||||
|         .sum::<u64>() as u64 | ||||
|         .sum::<u64>() | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day10::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"89010123
 | ||||
|     const EXAMPLE: &[u8] = b"89010123
 | ||||
| 78121874 | ||||
| 87430965 | ||||
| 96549874 | ||||
| @@ -135,14 +100,12 @@ mod tests { | ||||
| 10456732";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 36); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 36); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 81); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 81); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										102
									
								
								src/day11.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/day11.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use itertools::Itertools; | ||||
| use std::collections::HashMap; | ||||
| use std::iter::repeat; | ||||
|  | ||||
| type IntType = u64; | ||||
| type CacheType = HashMap<Stone, IntType>; | ||||
|  | ||||
| #[derive(Clone, Debug, Hash, PartialEq, Eq)] | ||||
| struct Stone(IntType); | ||||
| struct Stones(Vec<Stone>); | ||||
|  | ||||
| enum BlinkResult { | ||||
|     One(Stone), | ||||
|     Two(Stone, Stone), | ||||
| } | ||||
|  | ||||
| impl From<&str> for Stones { | ||||
|     fn from(input: &str) -> Self { | ||||
|         Stones( | ||||
|             input | ||||
|                 .split_ascii_whitespace() | ||||
|                 .map(|v| Stone(v.parse().unwrap())) | ||||
|                 .collect_vec(), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[aoc_generator(day11)] | ||||
| fn parse(input: &str) -> Stones { | ||||
|     Stones::from(input) | ||||
| } | ||||
|  | ||||
| impl Stone { | ||||
|     fn blink_once(&self) -> BlinkResult { | ||||
|         let n_digits = if self.0 == 0 { 1 } else { self.0.ilog10() + 1 }; | ||||
|         if self.0 == 0 { | ||||
|             BlinkResult::One(Stone(1)) | ||||
|         } else if n_digits % 2 == 0 { | ||||
|             let split_factor = (10 as IntType).pow(n_digits / 2); | ||||
|             let parts = (self.0 / split_factor, self.0 % split_factor); | ||||
|             BlinkResult::Two(Stone(parts.0), Stone(parts.1)) | ||||
|         } else { | ||||
|             BlinkResult::One(Stone(&self.0 * 2024)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn count_blinks(stone: &Stone, blink: usize, cache: &mut Vec<CacheType>) -> IntType { | ||||
|     if cache[blink].contains_key(&stone) { | ||||
|         return cache[blink][&stone].clone(); | ||||
|     } | ||||
|     let stones = stone.blink_once(); | ||||
|     let result = if blink == 0 { | ||||
|         match stones { | ||||
|             BlinkResult::One(_) => 1, | ||||
|             BlinkResult::Two(_, _) => 2, | ||||
|         } | ||||
|     } else { | ||||
|         match stones { | ||||
|             BlinkResult::One(s) => count_blinks(&s, blink - 1, cache), | ||||
|             BlinkResult::Two(s1, s2) => count_blinks(&s1, blink - 1, cache) + count_blinks(&s2, blink - 1, cache), | ||||
|         } | ||||
|     }; | ||||
|     cache[blink].insert(stone.clone(), result); | ||||
|     cache[blink][&stone].clone() | ||||
| } | ||||
|  | ||||
| fn blink_stones(stones: &Stones, blinks: usize) -> IntType { | ||||
|     let mut cache = Vec::from_iter(repeat(CacheType::new()).take(blinks)); | ||||
|     stones | ||||
|         .0 | ||||
|         .iter() | ||||
|         .map(|stone| count_blinks(stone, blinks - 1, &mut cache)) | ||||
|         .sum() | ||||
| } | ||||
|  | ||||
| #[aoc(day11, part1)] | ||||
| fn part1(stones: &Stones) -> IntType { | ||||
|     blink_stones(stones, 25) | ||||
| } | ||||
|  | ||||
| #[aoc(day11, part2)] | ||||
| fn part2(stones: &Stones) -> IntType { | ||||
|     blink_stones(stones, 75) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     pub const EXAMPLE: &str = &"125 17"; | ||||
|  | ||||
|     #[test] | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&parse(EXAMPLE)), 55312); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&parse(EXAMPLE)), 65601038650482); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										105
									
								
								src/day2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/day2.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use std::io::{BufRead, Lines}; | ||||
|  | ||||
| #[aoc_generator(day2)] | ||||
| pub fn get_input(input: &[u8]) -> Reports { | ||||
|     Reports::from(input.lines()) | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Reports { | ||||
|     reports: Vec<Vec<u64>>, | ||||
| } | ||||
|  | ||||
| impl<T: BufRead> From<Lines<T>> for Reports { | ||||
|     fn from(lines: Lines<T>) -> Self { | ||||
|         let mut reports = Vec::new(); | ||||
|         for line in lines.map(|i| i.unwrap()) { | ||||
|             reports.push( | ||||
|                 line.split_ascii_whitespace() | ||||
|                     .map(|record| record.parse::<u64>().unwrap()) | ||||
|                     .collect(), | ||||
|             ) | ||||
|         } | ||||
|         Reports { reports } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Reports { | ||||
|     fn is_safe(report: &[u64]) -> bool { | ||||
|         let mut ascending: bool = true; | ||||
|         let mut descending: bool = true; | ||||
|         for (a, b) in report.iter().zip(report.iter().skip(1)) { | ||||
|             if a > b { | ||||
|                 ascending = false | ||||
|             } | ||||
|             if a < b { | ||||
|                 descending = false; | ||||
|             } | ||||
|             let ad = a.abs_diff(*b); | ||||
|             if !(1..=3).contains(&ad) || (!ascending && !descending) { | ||||
|                 return false; | ||||
|             }; | ||||
|         } | ||||
|         true | ||||
|     } | ||||
|     fn count_safe(&self) -> u64 { | ||||
|         self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64 | ||||
|     } | ||||
|     fn is_dumb_dampened_safe(report: &[u64]) -> bool { | ||||
|         if Self::is_safe(report) { | ||||
|             return true; | ||||
|         } | ||||
|         for i in 0..report.len() { | ||||
|             let mut new_vec = report.to_owned(); | ||||
|             new_vec.remove(i); | ||||
|             if Self::is_safe(&new_vec) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         false | ||||
|     } | ||||
|     fn dampened_count_safe(&self) -> u64 { | ||||
|         self.reports | ||||
|             .iter() | ||||
|             .filter(|report| Self::is_dumb_dampened_safe(report)) | ||||
|             .count() as u64 | ||||
|     } | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
| #[aoc(day2, part1)] | ||||
| pub fn part1(input: &Reports) -> u64 { | ||||
|     input.count_safe() | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| #[aoc(day2, part2)] | ||||
| pub fn part2(input: &Reports) -> u64 { | ||||
|     input.dampened_count_safe() | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::day2::*; | ||||
|  | ||||
|     const EXAMPLE: &[u8] = b"7 6 4 2 1 | ||||
| 1 2 7 8 9 | ||||
| 9 7 6 2 1 | ||||
| 1 3 2 4 5 | ||||
| 8 6 4 4 1 | ||||
| 1 3 6 7 9"; | ||||
|  | ||||
|     #[test] | ||||
|     fn part1_example() { | ||||
|         let input = get_input(EXAMPLE); | ||||
|         println!("{:?}", input); | ||||
|         assert_eq!(part1(&input), 2); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn part2_example() { | ||||
|         let input = get_input(EXAMPLE); | ||||
|         assert_eq!(part2(&input), 4); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										66
									
								
								src/day3.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/day3.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use regex::bytes::Regex; | ||||
| use std::io::BufRead; | ||||
|  | ||||
| #[aoc_generator(day3)] | ||||
| pub fn get_input(input: &[u8]) -> Vec<String> { | ||||
|     input.lines().map(|l| l.unwrap()).collect() | ||||
| } | ||||
|  | ||||
| // PROBLEM 1 solution | ||||
| #[aoc(day3, part1)] | ||||
| pub fn part1(input: &Vec<String>) -> u64 { | ||||
|     let mut sum = 0u64; | ||||
|     let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap(); | ||||
|     for line in input { | ||||
|         let line = line.as_bytes(); | ||||
|         for m in re.captures_iter(line) { | ||||
|             sum += std::str::from_utf8(&m[1]).unwrap().parse::<u64>().unwrap() | ||||
|                 * std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap(); | ||||
|         } | ||||
|     } | ||||
|     sum | ||||
| } | ||||
|  | ||||
| // PROBLEM 2 solution | ||||
| #[aoc(day3, part2)] | ||||
| pub fn part2(input: &Vec<String>) -> u64 { | ||||
|     let mut sum = 0u64; | ||||
|     let mut do_mul = true; | ||||
|     let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap(); | ||||
|     for line in input { | ||||
|         let line = line.as_bytes(); | ||||
|         for m in re.captures_iter(line) { | ||||
|             match std::str::from_utf8(&m[1]).unwrap() { | ||||
|                 "do()" => do_mul = true, | ||||
|                 "don't()" => do_mul = false, | ||||
|                 _ if do_mul => { | ||||
|                     sum += std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap() | ||||
|                         * std::str::from_utf8(&m[3]).unwrap().parse::<u64>().unwrap() | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     sum | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::day3::*; | ||||
|  | ||||
|     const EXAMPLE1: &[u8] = b"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"; | ||||
|     const EXAMPLE2: &[u8] = b"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"; | ||||
|  | ||||
|     #[test] | ||||
|     fn part1_example() { | ||||
|         let input = get_input(EXAMPLE1); | ||||
|         assert_eq!(part1(&input), 161); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn part2_example() { | ||||
|         let input = get_input(EXAMPLE2); | ||||
|         assert_eq!(part2(&input), 48); | ||||
|     } | ||||
| } | ||||
| @@ -1,40 +1,12 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use std::io::{BufRead, Lines}; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| #[aoc_generator(day4)] | ||||
| pub fn get_input(input: &[u8]) -> WordSearch { | ||||
|     WordSearch::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
| 
 | ||||
| struct WordSearch { | ||||
| pub struct WordSearch { | ||||
|     rows: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| @@ -81,22 +53,12 @@ impl WordSearch { | ||||
|         for x in 0..width { | ||||
|             for y in 0..height { | ||||
|                 // check down-right
 | ||||
|                 if x <= width - needle.len() && y <= height - needle.len() { | ||||
|                     if (0..needle.len()) | ||||
|                         .into_iter() | ||||
|                         .all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into()) | ||||
|                     { | ||||
|                         count += 1 | ||||
|                     } | ||||
|                 if x <= width - needle.len() && y <= height - needle.len() && (0..needle.len()).all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into()) { | ||||
|                     count += 1 | ||||
|                 } | ||||
|                 // check down-left
 | ||||
|                 if x >= needle.len() - 1 && y <= height - needle.len() { | ||||
|                     if (0..needle.len()) | ||||
|                         .into_iter() | ||||
|                         .all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into()) | ||||
|                     { | ||||
|                         count += 1 | ||||
|                     } | ||||
|                 if x >= needle.len() - 1 && y <= height - needle.len() && (0..needle.len()).all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into()) { | ||||
|                     count += 1 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -138,11 +100,10 @@ impl WordSearch { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| #[aoc(day4, part1)] | ||||
| pub fn part1(ws: &WordSearch) -> u64 { | ||||
|     let needle = "XMAS"; | ||||
|     let needle_rev: String = needle.chars().rev().collect(); | ||||
|     let ws = WordSearch::from(input); | ||||
|     ws.count_forward(needle) | ||||
|         + ws.count_forward(&needle_rev) | ||||
|         + ws.count_vertical(needle) | ||||
| @@ -152,17 +113,16 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let ws = WordSearch::from(input); | ||||
| #[aoc(day4, part2)] | ||||
| pub fn part2(ws: &WordSearch) -> u64 { | ||||
|     ws.count_x_mas() | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day4::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"MMMSXXMASM
 | ||||
|     const EXAMPLE: &[u8] = b"MMMSXXMASM
 | ||||
| MSAMXMSMSA | ||||
| AMXSXMAAMM | ||||
| MSAMASMSMX | ||||
| @@ -174,14 +134,12 @@ MAMMMXMMMM | ||||
| MXMXAXMASX";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 18); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 18); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 9); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 9); | ||||
|     } | ||||
| } | ||||
| @@ -1,43 +1,29 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use std::fmt::Debug; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| use std::io::BufRead; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| #[aoc_generator(day5)] | ||||
| pub fn get_input(input: &[u8]) -> (OrderingRules, Vec<Vec<u64>>) { | ||||
|     let mut lines = input.lines(); | ||||
|     let rules = OrderingRules { | ||||
|         rules: lines | ||||
|             .by_ref() | ||||
|             .map_while(|l| match l { | ||||
|                 Ok(line) if !line.is_empty() => Some(Box::new(BeforeRule::from(line)) as _), | ||||
|                 _ => None, | ||||
|             }) | ||||
|             .collect(), | ||||
|     }; | ||||
|     let updates: Vec<Vec<u64>> = lines | ||||
|         .by_ref() | ||||
|         .map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect()) | ||||
|         .collect(); | ||||
|     (rules, updates) | ||||
| } | ||||
| 
 | ||||
| trait Rule: Debug { | ||||
|     fn check(&self, pages: &Vec<u64>) -> bool; | ||||
|     fn fail_pos(&self, pages: &Vec<u64>) -> Option<(usize, usize)>; | ||||
|     fn check(&self, pages: &[u64]) -> bool; | ||||
|     fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)>; | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| @@ -47,7 +33,7 @@ struct BeforeRule { | ||||
| } | ||||
| 
 | ||||
| impl Rule for BeforeRule { | ||||
|     fn check(&self, pages: &Vec<u64>) -> bool { | ||||
|     fn check(&self, pages: &[u64]) -> bool { | ||||
|         let mut seen_a = false; | ||||
|         let mut seen_b = false; | ||||
|         for page in pages { | ||||
| @@ -63,15 +49,15 @@ impl Rule for BeforeRule { | ||||
|                 seen_b = true | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|         true | ||||
|     } | ||||
|     fn fail_pos(&self, pages: &Vec<u64>) -> Option<(usize, usize)> { | ||||
|     fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)> { | ||||
|         let mut a_pos = None; | ||||
|         let mut b_pos = None; | ||||
|         for (pos, page) in pages.iter().enumerate() { | ||||
|             if *page == self.a { | ||||
|                 if b_pos.is_some() { | ||||
|                     return Some((b_pos.unwrap(), pos)); | ||||
|                 if let Some(b_pos) = b_pos { | ||||
|                     return Some((b_pos, pos)); | ||||
|                 } | ||||
|                 a_pos = Some(pos); | ||||
|             } else if *page == self.b { | ||||
| @@ -93,19 +79,19 @@ impl From<String> for BeforeRule { | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct OrderingRules { | ||||
| pub struct OrderingRules { | ||||
|     rules: Vec<Box<dyn Rule>>, | ||||
| } | ||||
| 
 | ||||
| impl OrderingRules { | ||||
|     fn check(&self, pages: &Vec<u64>) -> bool { | ||||
|     fn check(&self, pages: &[u64]) -> bool { | ||||
|         self.rules.iter().all(|p| p.check(pages)) | ||||
|     } | ||||
|     fn fail_pos(&self, pages: &Vec<u64>) -> Option<(usize, usize)> { | ||||
|     fn fail_pos(&self, pages: &[u64]) -> Option<(usize, usize)> { | ||||
|         for rule in &self.rules { | ||||
|             let fail_pos = rule.fail_pos(pages); | ||||
|             if fail_pos.is_some() { | ||||
|                 return fail_pos | ||||
|                 return fail_pos; | ||||
|             } | ||||
|         } | ||||
|         None | ||||
| @@ -123,25 +109,11 @@ impl OrderingRules { | ||||
| // }
 | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
|     let rules = OrderingRules { | ||||
|         rules: input | ||||
|             .by_ref() | ||||
|             .map_while(|l| match l { | ||||
|                 Ok(line) if line != "" => Some(Box::new(BeforeRule::from(line)) as _), | ||||
|                 _ => None, | ||||
|             }) | ||||
|             .collect(), | ||||
|     }; | ||||
|     let updates: Vec<Vec<u64>> = input | ||||
|         .by_ref() | ||||
|         .map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect()) | ||||
|         .collect(); | ||||
| 
 | ||||
| #[aoc(day5, part1)] | ||||
| pub fn part1((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 { | ||||
|     let mut res = 0; | ||||
|     for update in updates { | ||||
|         if rules.check(&update) { | ||||
|         if rules.check(update) { | ||||
|             res += update[update.len() / 2] | ||||
|         } | ||||
|     } | ||||
| @@ -149,30 +121,18 @@ fn problem1<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
|     let rules = OrderingRules { | ||||
|         rules: input | ||||
|             .by_ref() | ||||
|             .map_while(|l| match l { | ||||
|                 Ok(line) if line != "" => Some(Box::new(BeforeRule::from(line)) as _), | ||||
|                 _ => None, | ||||
|             }) | ||||
|             .collect(), | ||||
|     }; | ||||
|     let updates: Vec<Vec<u64>> = input | ||||
|         .by_ref() | ||||
|         .map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect()) | ||||
|         .collect(); | ||||
| 
 | ||||
| #[aoc(day5, part2)] | ||||
| pub fn part2((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 { | ||||
|     let mut updates = updates.clone(); | ||||
|     let mut res = 0; | ||||
|     for mut update in updates { | ||||
|     for update in updates.as_mut_slice() { | ||||
|         let mut did_swaps = false; | ||||
|         while let Some((a,b)) = rules.fail_pos(&update) { | ||||
|         while let Some((a, b)) = rules.fail_pos(update) { | ||||
|             did_swaps = true; | ||||
|             update.swap(a, b); | ||||
|         } | ||||
|         if did_swaps { | ||||
|             if !rules.check(&update) { | ||||
|             if !rules.check(update) { | ||||
|                 panic!("update still fails after swaps") | ||||
|             } | ||||
| 
 | ||||
| @@ -184,10 +144,9 @@ fn problem2<T: BufRead>(mut input: Lines<T>) -> u64 { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day5::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"47|53
 | ||||
|     const EXAMPLE: &[u8] = b"47|53
 | ||||
| 97|13 | ||||
| 97|61 | ||||
| 97|47 | ||||
| @@ -217,14 +176,12 @@ mod tests { | ||||
| 97,13,75,29,47";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 143); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 143); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 123); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 123); | ||||
|     } | ||||
| } | ||||
| @@ -1,40 +1,14 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use bitflags::bitflags; | ||||
| use std::fmt; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::io::{BufRead, Lines}; | ||||
| use std::ops::BitAnd; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| use grid::Grid; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| #[aoc_generator(day6)] | ||||
| pub fn get_input(input: &[u8]) -> Map { | ||||
|     Map::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| #[repr(u8)] | ||||
| @@ -74,7 +48,6 @@ enum StepOutcome { | ||||
| enum RunOutcome { | ||||
|     LeftMap, | ||||
|     LoopFound, | ||||
|     Stuck, | ||||
| } | ||||
| 
 | ||||
| bitflags! { | ||||
| @@ -112,9 +85,9 @@ impl BitAnd<FacingDirection> for DirectionSet { | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| struct Map { | ||||
|     grid: grid::Grid<u8>, | ||||
|     visited_from: grid::Grid<DirectionSet>, | ||||
| pub struct Map { | ||||
|     grid: Grid<u8>, | ||||
|     visited_from: Grid<DirectionSet>, | ||||
|     guard_facing: FacingDirection, | ||||
|     guard_pos: (i64, i64), | ||||
|     path: Vec<((i64, i64), FacingDirection)>, | ||||
| @@ -122,8 +95,8 @@ struct Map { | ||||
| 
 | ||||
| impl<T: BufRead> From<Lines<T>> for Map { | ||||
|     fn from(input: Lines<T>) -> Self { | ||||
|         let grid = grid::Grid::from(input); | ||||
|         let mut visited_from: grid::Grid<DirectionSet> = grid::Grid::new(grid.width() as i64); | ||||
|         let grid = Grid::from(input); | ||||
|         let mut visited_from: Grid<DirectionSet> = Grid::new(grid.width() as i64); | ||||
|         visited_from.data.resize(grid.data.len(), DirectionSet::empty()); | ||||
|         let guard_pos = grid.find(b'^').expect("Guard not found"); | ||||
|         let guard_facing = FacingDirection::Up; | ||||
| @@ -190,17 +163,17 @@ impl Map { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut map = Map::from(input); | ||||
| #[aoc(day6, part1)] | ||||
| pub fn part1(map: &Map) -> u64 { | ||||
|     let mut map = map.clone(); | ||||
|     map.run_guard::<false>(); | ||||
| 
 | ||||
|     (map.grid.count(b'X') + map.grid.count(b'-') + map.grid.count(b'|') + map.grid.count(b'^')) as u64 | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let input_map = Map::from(input); | ||||
| #[aoc(day6, part2)] | ||||
| pub fn part2(input_map: &Map) -> u64 { | ||||
|     // Use the solution from problem 1 to reduce the number of positions where obstacle placement will change the path
 | ||||
|     let mut path_map = input_map.clone(); | ||||
|     path_map.run_guard::<true>(); | ||||
| @@ -231,10 +204,9 @@ fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day6::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"....#.....
 | ||||
|     const EXAMPLE: &[u8] = b"....#.....
 | ||||
| .........# | ||||
| .......... | ||||
| ..#....... | ||||
| @@ -246,14 +218,12 @@ mod tests { | ||||
| ......#...";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 41); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 41); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 6); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 6); | ||||
|     } | ||||
| } | ||||
| @@ -1,41 +1,12 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use itertools::Itertools; | ||||
| use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; | ||||
| use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; | ||||
| use std::io::{BufRead, Lines}; | ||||
| use thread_local::ThreadLocal; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| #[aoc_generator(day7)] | ||||
| pub fn get_input(input: &[u8]) -> Calibrations { | ||||
|     Calibrations::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| @@ -46,7 +17,7 @@ struct Calibration { | ||||
| 
 | ||||
| impl From<&str> for Calibration { | ||||
|     fn from(value: &str) -> Self { | ||||
|         let (result, rest) = value.splitn(2, ':').next_tuple().unwrap(); | ||||
|         let (result, rest) = value.split_once(':').unwrap(); | ||||
|         Self { | ||||
|             result: result.parse().unwrap(), | ||||
|             numbers: rest.split_ascii_whitespace().map(|s| s.parse().unwrap()).collect(), | ||||
| @@ -55,7 +26,7 @@ impl From<&str> for Calibration { | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct Calibrations { | ||||
| pub struct Calibrations { | ||||
|     cals: Vec<Calibration>, | ||||
|     longest_cal: usize, | ||||
| } | ||||
| @@ -94,22 +65,18 @@ impl Calibrations { | ||||
|     fn make_operator_sets(operators: &[Operator], n_opers: usize) -> Vec<Vec<Vec<Operator>>> { | ||||
|         (0..n_opers) | ||||
|             .map(|k| { | ||||
|                 std::iter::repeat_n(operators.iter().map(|v| *v), k) | ||||
|                 std::iter::repeat_n(operators.iter().copied(), k) | ||||
|                     .multi_cartesian_product() | ||||
|                     .collect() | ||||
|             }) | ||||
|             .collect() | ||||
|     } | ||||
|     fn check_oper_set(cal: &Calibration, oper_set: &Vec<Operator>) -> bool { | ||||
|     fn check_oper_set(cal: &Calibration, oper_set: &[Operator]) -> bool { | ||||
|         let accum = oper_set | ||||
|             .iter() | ||||
|             .zip(cal.numbers.iter().skip(1)) | ||||
|             .fold(cal.numbers[0], |accum, (oper, val)| oper.exec(accum, *val)); | ||||
|         if accum == cal.result { | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|         accum == cal.result | ||||
|     } | ||||
|     fn possible(&self, operators: &[Operator]) -> u64 { | ||||
|         let operator_sets = Calibrations::make_operator_sets(operators, self.longest_cal); | ||||
| @@ -120,7 +87,7 @@ impl Calibrations { | ||||
|                 let tl = ThreadLocal::new(); | ||||
|                 if operator_sets[n_opers] | ||||
|                     .par_iter() | ||||
|                     .find_any(|oper_set| Self::check_oper_set(&cal, &oper_set)) | ||||
|                     .find_any(|oper_set| Self::check_oper_set(cal, oper_set)) | ||||
|                     .is_some() | ||||
|                 { | ||||
|                     let cal_local = tl.get_or(|| cal.clone()); | ||||
| @@ -133,26 +100,24 @@ impl Calibrations { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let cals = Calibrations::from(input); | ||||
| #[aoc(day7, part1)] | ||||
| pub fn part1(cals: &Calibrations) -> u64 { | ||||
|     let operators = [Operator::Add, Operator::Multiply]; | ||||
|     cals.possible(&operators) | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let cals = Calibrations::from(input); | ||||
| #[aoc(day7, part2)] | ||||
| pub fn part2(cals: &Calibrations) -> u64 { | ||||
|     let operators = [Operator::Add, Operator::Multiply, Operator::Concatenate]; | ||||
|     cals.possible(&operators) | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day7::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"190: 10 19
 | ||||
|     const EXAMPLE: &[u8] = b"190: 10 19
 | ||||
| 3267: 81 40 27 | ||||
| 83: 17 5 | ||||
| 156: 15 6 | ||||
| @@ -163,14 +128,12 @@ mod tests { | ||||
| 292: 11 6 16 20";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 3749); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 3749); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 11387); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 11387); | ||||
|     } | ||||
| } | ||||
| @@ -1,43 +1,15 @@ | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use grid::Grid; | ||||
| use itertools::Itertools; | ||||
| use std::collections::HashSet; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| use std::io::{BufRead, Lines}; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| #[aoc_generator(day8)] | ||||
| pub fn get_input(input: &[u8]) -> AntennaMap { | ||||
|     AntennaMap::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| } | ||||
| 
 | ||||
| struct AntennaMap { | ||||
| pub struct AntennaMap { | ||||
|     map: Grid<u8>, | ||||
| } | ||||
| 
 | ||||
| @@ -69,13 +41,7 @@ impl AntennaMap { | ||||
|                 // to consider the 'negative' side of the line, which will be generated by the other pair
 | ||||
|                 let (a, b) = (pair[0], pair[1]); | ||||
|                 let offset = (a.0 - b.0, a.1 - b.1); | ||||
|                 for i in (start..).map_while(|i| { | ||||
|                     if Some(i - start) != reps { | ||||
|                         Some(i as i64) | ||||
|                     } else { | ||||
|                         None | ||||
|                     } | ||||
|                 }) { | ||||
|                 for i in (start..).map_while(|i| if Some(i - start) != reps { Some(i as i64) } else { None }) { | ||||
|                     let node_pos = (a.0 + i * offset.0, a.1 + i * offset.1); | ||||
|                     if !antinodes.set(node_pos.0, node_pos.1, true) { | ||||
|                         // left the grid
 | ||||
| @@ -89,28 +55,24 @@ impl AntennaMap { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let map = AntennaMap::from(input); | ||||
| 
 | ||||
| #[aoc(day8, part1)] | ||||
| pub fn part1(map: &AntennaMap) -> u64 { | ||||
|     let antinodes = map.find_antinodes(1, Some(1)); | ||||
|     antinodes.count(true) as u64 | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let map = AntennaMap::from(input); | ||||
| 
 | ||||
| #[aoc(day8, part2)] | ||||
| pub fn part2(map: &AntennaMap) -> u64 { | ||||
|     let antinodes = map.find_antinodes(0, None); | ||||
|     antinodes.count(true) as u64 | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day8::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"............
 | ||||
|     const EXAMPLE: &[u8] = b"............
 | ||||
| ........0... | ||||
| .....0...... | ||||
| .......0.... | ||||
| @@ -124,14 +86,12 @@ mod tests { | ||||
| ............";
 | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 14); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 14); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 34); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 34); | ||||
|     } | ||||
| } | ||||
| @@ -1,40 +1,11 @@ | ||||
| use std::fmt::{Display, Write}; | ||||
| use std::fs::File; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| use aoc_runner_derive::{aoc, aoc_generator}; | ||||
| use itertools::Itertools; | ||||
| use std::fmt::{Display, Write}; | ||||
| use std::io::{BufRead, Lines}; | ||||
| 
 | ||||
| // BOILERPLATE
 | ||||
| type InputIter = Lines<BufReader<File>>; | ||||
| 
 | ||||
| pub fn get_input() -> InputIter { | ||||
|     let f = File::open("input").unwrap(); | ||||
|     let br = BufReader::new(f); | ||||
|     br.lines() | ||||
| } | ||||
| 
 | ||||
| fn duration_format(duration: Duration) -> String { | ||||
|     match duration.as_secs_f64() { | ||||
|         x if x > 1.0 => format!("{:.3}s", x), | ||||
|         x if x > 0.010 => format!("{:.3}ms", x * 1e3), | ||||
|         x => format!("{:.3}us", x * 1e6), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans1 = problem1(input); | ||||
|     let duration1 = start.elapsed(); | ||||
|     println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); | ||||
| 
 | ||||
|     let input = get_input(); | ||||
|     let start = Instant::now(); | ||||
|     let ans2 = problem2(input); | ||||
|     let duration2 = start.elapsed(); | ||||
|     println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); | ||||
|     println!("Total duration: {}", duration_format(duration1 + duration2)); | ||||
| #[aoc_generator(day9)] | ||||
| pub fn get_input(input: &[u8]) -> DiskMap { | ||||
|     DiskMap::from(input.lines()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||||
| @@ -43,13 +14,15 @@ enum Unit { | ||||
|     Free, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| struct Inode { | ||||
|     id: usize, | ||||
|     pos: usize, | ||||
|     len: u8, | ||||
| } | ||||
| 
 | ||||
| struct DiskMap { | ||||
| #[derive(Clone)] | ||||
| pub struct DiskMap { | ||||
|     map: Vec<Unit>, | ||||
|     files: Vec<Inode>, | ||||
|     frees: Vec<Inode>, | ||||
| @@ -118,9 +91,9 @@ impl DiskMap { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 1 solution
 | ||||
| 
 | ||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut map = DiskMap::from(input); | ||||
| #[aoc(day9, part1)] | ||||
| pub fn part1(map: &DiskMap) -> u64 { | ||||
|     let mut map = map.to_owned(); | ||||
|     let mut last_free = 0; | ||||
|     for file in map.files.iter().rev() { | ||||
|         let frees = map | ||||
| @@ -148,8 +121,9 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| } | ||||
| 
 | ||||
| // PROBLEM 2 solution
 | ||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
|     let mut map = DiskMap::from(input); | ||||
| #[aoc(day9, part2)] | ||||
| pub fn part2(map: &DiskMap) -> u64 { | ||||
|     let mut map = map.to_owned(); | ||||
|     for file in map.files.iter().rev() { | ||||
|         let free = map.frees.iter_mut().find(|inode| inode.len >= file.len); // find the first entry in the free space map large enough
 | ||||
|         if let Some(free) = free { | ||||
| @@ -176,20 +150,17 @@ fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::*; | ||||
|     use std::io::Cursor; | ||||
|     use crate::day9::*; | ||||
| 
 | ||||
|     const EXAMPLE: &str = &"2333133121414131402"; | ||||
|     const EXAMPLE: &[u8] = b"2333133121414131402"; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem1_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem1(c.lines()), 1928); | ||||
|     fn part1_example() { | ||||
|         assert_eq!(part1(&get_input(EXAMPLE)), 1928); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn problem2_example() { | ||||
|         let c = Cursor::new(EXAMPLE); | ||||
|         assert_eq!(problem2(c.lines()), 2858); | ||||
|     fn part2_example() { | ||||
|         assert_eq!(part2(&get_input(EXAMPLE)), 2858); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| mod day11; | ||||
| use aoc_runner_derive::aoc_lib; | ||||
|  | ||||
| mod day1; | ||||
| mod day2; | ||||
| mod day3; | ||||
| mod day4; | ||||
| mod day5; | ||||
| mod day6; | ||||
| mod day7; | ||||
| mod day8; | ||||
| mod day9; | ||||
| mod day10; | ||||
|  | ||||
| aoc_lib! { year = 2024 } | ||||
| @@ -4,3 +4,6 @@ version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
| 
 | ||||
| [lib] | ||||
| path = "lib.rs" | ||||
| @@ -1,5 +1,5 @@ | ||||
| use std::{ | ||||
|     fmt::{self, Debug, Display, Formatter, Write}, | ||||
|     fmt::{Debug, Display, Formatter, Write}, | ||||
|     io::{BufRead, Lines}, | ||||
|     iter::repeat, | ||||
| }; | ||||
| @@ -135,12 +135,25 @@ impl<T: BufRead> From<Lines<T>> for Grid<u8> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Copy + Eq + PartialEq + Display + Debug> Display for Grid<T> { | ||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||||
| // impl<T: Copy + Eq + PartialEq + Display + Debug + Into<char>> Display for Grid<T> {
 | ||||
| //     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 | ||||
| //         for y in 0..self.height() {
 | ||||
| //             for x in 0..self.width() {
 | ||||
| //                 f.write_fmt(format_args!("{}",self.get(x as i64, y as i64).unwrap() as char))?;
 | ||||
| //             }
 | ||||
| //             f.write_char('\n')?;
 | ||||
| //         }
 | ||||
| //         f.write_char('\n')
 | ||||
| //     }
 | ||||
| // }
 | ||||
| 
 | ||||
| impl Display for Grid<u8> { | ||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||||
|         for y in 0..self.height() { | ||||
|             for x in 0..self.width() { | ||||
|                 self.get(x as i64, y as i64).fmt(f)?; | ||||
|                 f.write_fmt(format_args!("{}",self.get(x as i64, y as i64).unwrap() as char))?; | ||||
|             } | ||||
|             f.write_char('\n')?; | ||||
|         } | ||||
|         f.write_char('\n') | ||||
|     } | ||||
		Reference in New Issue
	
	Block a user