From 11b29a4d57476c7130df172fa36f24098f9154e8 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Wed, 11 Dec 2024 15:45:52 -0800 Subject: [PATCH] chore: refactor for cargo-aoc / codspeed --- .gitignore | 2 +- 1/Cargo.lock | 7 - 1/Cargo.toml | 6 - 1/src/main.rs | 105 ----------- 10/Cargo.lock | 30 --- 10/Cargo.toml | 8 - 11/Cargo.lock | 25 --- 11/Cargo.toml | 7 - 11/src/main.rs | 135 ------------- 2/Cargo.lock | 7 - 2/Cargo.toml | 6 - 2/src/main.rs | 133 ------------- 3/Cargo.lock | 71 ------- 3/Cargo.toml | 8 - 3/src/main.rs | 94 --------- 4/Cargo.lock | 7 - 4/Cargo.toml | 6 - 5/Cargo.lock | 7 - 5/Cargo.toml | 6 - 6/Cargo.lock | 21 --- 6/Cargo.toml | 8 - 7/Cargo.lock | 94 --------- 7/Cargo.toml | 9 - 8/Cargo.lock | 30 --- 8/Cargo.toml | 8 - 9/Cargo.lock | 25 --- 9/Cargo.toml | 7 - Cargo.lock | 272 +++++++++++++++++++++++++++ Cargo.toml | 14 ++ libs/grid/Cargo.lock | 7 - src/day1.rs | 90 +++++++++ 10/src/main.rs => src/day10.rs | 73 ++----- src/day11.rs | 102 ++++++++++ src/day2.rs | 105 +++++++++++ src/day3.rs | 66 +++++++ 4/src/main.rs => src/day4.rs | 82 ++------ 5/src/main.rs => src/day5.rs | 133 +++++-------- 6/src/main.rs => src/day6.rs | 74 +++----- 7/src/main.rs => src/day7.rs | 81 +++----- 8/src/main.rs => src/day8.rs | 74 ++------ 9/src/main.rs => src/day9.rs | 71 +++---- src/lib.rs | 15 ++ {libs => utils}/grid/Cargo.toml | 3 + {libs/grid/src => utils/grid}/lib.rs | 21 ++- 44 files changed, 850 insertions(+), 1305 deletions(-) delete mode 100644 1/Cargo.lock delete mode 100644 1/Cargo.toml delete mode 100644 1/src/main.rs delete mode 100644 10/Cargo.lock delete mode 100644 10/Cargo.toml delete mode 100644 11/Cargo.lock delete mode 100644 11/Cargo.toml delete mode 100644 11/src/main.rs delete mode 100644 2/Cargo.lock delete mode 100644 2/Cargo.toml delete mode 100644 2/src/main.rs delete mode 100644 3/Cargo.lock delete mode 100644 3/Cargo.toml delete mode 100644 3/src/main.rs delete mode 100644 4/Cargo.lock delete mode 100644 4/Cargo.toml delete mode 100644 5/Cargo.lock delete mode 100644 5/Cargo.toml delete mode 100644 6/Cargo.lock delete mode 100644 6/Cargo.toml delete mode 100644 7/Cargo.lock delete mode 100644 7/Cargo.toml delete mode 100644 8/Cargo.lock delete mode 100644 8/Cargo.toml delete mode 100644 9/Cargo.lock delete mode 100644 9/Cargo.toml create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 libs/grid/Cargo.lock create mode 100644 src/day1.rs rename 10/src/main.rs => src/day10.rs (60%) create mode 100644 src/day11.rs create mode 100644 src/day2.rs create mode 100644 src/day3.rs rename 4/src/main.rs => src/day4.rs (60%) rename 5/src/main.rs => src/day5.rs (50%) rename 6/src/main.rs => src/day6.rs (77%) rename 7/src/main.rs => src/day7.rs (57%) rename 8/src/main.rs => src/day8.rs (54%) rename 9/src/main.rs => src/day9.rs (72%) create mode 100644 src/lib.rs rename {libs => utils}/grid/Cargo.toml (76%) rename {libs/grid/src => utils/grid}/lib.rs (88%) diff --git a/.gitignore b/.gitignore index 09ae99b..4b861a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ **/target input flamegraph.svg -perf.data +perf.data* guesses diff --git a/1/Cargo.lock b/1/Cargo.lock deleted file mode 100644 index ae5bfe8..0000000 --- a/1/Cargo.lock +++ /dev/null @@ -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" diff --git a/1/Cargo.toml b/1/Cargo.toml deleted file mode 100644 index fb6b7ec..0000000 --- a/1/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "day1" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/1/src/main.rs b/1/src/main.rs deleted file mode 100644 index 9852efc..0000000 --- a/1/src/main.rs +++ /dev/null @@ -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>; - -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, - right: Vec, -} - -impl From> for Locations { - fn from(input: Lines) -> 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::().unwrap()); - right.push(parts[1].parse::().unwrap()); - } - Self { left, right } - } -} - -impl Locations { - fn sort(&mut self) { - self.left.sort(); - self.right.sort(); - } - fn right_count(&self) -> HashMap { - let mut right_count: HashMap = 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(input: Lines) -> 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(input: Lines) -> 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::() -} - -#[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); - } -} diff --git a/10/Cargo.lock b/10/Cargo.lock deleted file mode 100644 index 6df79a2..0000000 --- a/10/Cargo.lock +++ /dev/null @@ -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", -] diff --git a/10/Cargo.toml b/10/Cargo.toml deleted file mode 100644 index 8b9b86f..0000000 --- a/10/Cargo.toml +++ /dev/null @@ -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" diff --git a/11/Cargo.lock b/11/Cargo.lock deleted file mode 100644 index b5abc4f..0000000 --- a/11/Cargo.lock +++ /dev/null @@ -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", -] diff --git a/11/Cargo.toml b/11/Cargo.toml deleted file mode 100644 index 9f1e0f2..0000000 --- a/11/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "day11" -version = "0.1.0" -edition = "2021" - -[dependencies] -itertools = "0.13.0" diff --git a/11/src/main.rs b/11/src/main.rs deleted file mode 100644 index d39f7a8..0000000 --- a/11/src/main.rs +++ /dev/null @@ -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>; - -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); - -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>) -> 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(mut input: Lines) -> u64 { - let stones = input.next().unwrap().unwrap().as_str().into(); - blink_stones(&stones, 25) -} - -// PROBLEM 2 solution -fn problem2(mut input: Lines) -> 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); - } -} diff --git a/2/Cargo.lock b/2/Cargo.lock deleted file mode 100644 index 63c2f60..0000000 --- a/2/Cargo.lock +++ /dev/null @@ -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" diff --git a/2/Cargo.toml b/2/Cargo.toml deleted file mode 100644 index cd3d088..0000000 --- a/2/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "day2" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/2/src/main.rs b/2/src/main.rs deleted file mode 100644 index e9f751f..0000000 --- a/2/src/main.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::fs::File; -use std::io::{BufRead, BufReader, Lines}; -use std::time::{Duration, Instant}; - -// BOILERPLATE -type InputIter = Lines>; - -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>, -} - -impl From> for Reports { - fn from(lines: Lines) -> Self { - let mut reports = Vec::new(); - for line in lines.map(|i| i.unwrap()) { - reports.push( - line.split_ascii_whitespace() - .map(|record| record.parse::().unwrap()) - .collect(), - ) - } - Reports { reports } - } -} - -impl Reports { - fn is_safe(report: &Vec) -> 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) -> 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(input: Lines) -> u64 { - let reports = Reports::from(input); - reports.count_safe() -} - -// PROBLEM 2 solution -fn problem2(input: Lines) -> 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); - } -} diff --git a/3/Cargo.lock b/3/Cargo.lock deleted file mode 100644 index 918bc6f..0000000 --- a/3/Cargo.lock +++ /dev/null @@ -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" diff --git a/3/Cargo.toml b/3/Cargo.toml deleted file mode 100644 index 2b7e6ab..0000000 --- a/3/Cargo.toml +++ /dev/null @@ -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"] } diff --git a/3/src/main.rs b/3/src/main.rs deleted file mode 100644 index 838e5a2..0000000 --- a/3/src/main.rs +++ /dev/null @@ -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>; - -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(input: Lines) -> 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::().unwrap() - * std::str::from_utf8(&m[2]).unwrap().parse::().unwrap(); - } - } - sum -} - -// PROBLEM 2 solution -fn problem2(input: Lines) -> 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::().unwrap() - * std::str::from_utf8(&m[3]).unwrap().parse::().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); - } -} diff --git a/4/Cargo.lock b/4/Cargo.lock deleted file mode 100644 index e92c02c..0000000 --- a/4/Cargo.lock +++ /dev/null @@ -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" diff --git a/4/Cargo.toml b/4/Cargo.toml deleted file mode 100644 index 0ebbbc8..0000000 --- a/4/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "day4" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/5/Cargo.lock b/5/Cargo.lock deleted file mode 100644 index 7a62bb0..0000000 --- a/5/Cargo.lock +++ /dev/null @@ -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" diff --git a/5/Cargo.toml b/5/Cargo.toml deleted file mode 100644 index 49bdb49..0000000 --- a/5/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "day5" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/6/Cargo.lock b/6/Cargo.lock deleted file mode 100644 index 4027548..0000000 --- a/6/Cargo.lock +++ /dev/null @@ -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" diff --git a/6/Cargo.toml b/6/Cargo.toml deleted file mode 100644 index 98281f9..0000000 --- a/6/Cargo.toml +++ /dev/null @@ -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" } diff --git a/7/Cargo.lock b/7/Cargo.lock deleted file mode 100644 index a174188..0000000 --- a/7/Cargo.lock +++ /dev/null @@ -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", -] diff --git a/7/Cargo.toml b/7/Cargo.toml deleted file mode 100644 index f1bf891..0000000 --- a/7/Cargo.toml +++ /dev/null @@ -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" diff --git a/8/Cargo.lock b/8/Cargo.lock deleted file mode 100644 index 46525ab..0000000 --- a/8/Cargo.lock +++ /dev/null @@ -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", -] diff --git a/8/Cargo.toml b/8/Cargo.toml deleted file mode 100644 index 2e9e497..0000000 --- a/8/Cargo.toml +++ /dev/null @@ -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" diff --git a/9/Cargo.lock b/9/Cargo.lock deleted file mode 100644 index 46e4d66..0000000 --- a/9/Cargo.lock +++ /dev/null @@ -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", -] diff --git a/9/Cargo.toml b/9/Cargo.toml deleted file mode 100644 index 02224b3..0000000 --- a/9/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "day9" -version = "0.1.0" -edition = "2021" - -[dependencies] -itertools = "0.13.0" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ea5800a --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2d836b5 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/libs/grid/Cargo.lock b/libs/grid/Cargo.lock deleted file mode 100644 index 1c1a197..0000000 --- a/libs/grid/Cargo.lock +++ /dev/null @@ -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" diff --git a/src/day1.rs b/src/day1.rs new file mode 100644 index 0000000..bd99662 --- /dev/null +++ b/src/day1.rs @@ -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, + right: Vec, +} + +impl From> for Locations { + fn from(input: Lines) -> 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::().unwrap()); + right.push(parts[1].parse::().unwrap()); + } + Self { left, right } + } +} + +impl Locations { + fn sort(&mut self) { + self.left.sort(); + self.right.sort(); + } + fn right_count(&self) -> HashMap { + let mut right_count: HashMap = 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::() +} + +#[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); + } +} \ No newline at end of file diff --git a/10/src/main.rs b/src/day10.rs similarity index 60% rename from 10/src/main.rs rename to src/day10.rs index 2f7c83a..bf8f39c 100644 --- a/10/src/main.rs +++ b/src/day10.rs @@ -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>; - -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, } @@ -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::() as u64 + .sum::() } } // PROBLEM 1 solution - -fn problem1(input: Lines) -> 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(input: Lines) -> u64 { } // PROBLEM 2 solution -fn problem2(input: Lines) -> 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::() as u64 + .sum::() } #[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); } } diff --git a/src/day11.rs b/src/day11.rs new file mode 100644 index 0000000..c81b951 --- /dev/null +++ b/src/day11.rs @@ -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; + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +struct Stone(IntType); +struct Stones(Vec); + +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) -> 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); + } +} diff --git a/src/day2.rs b/src/day2.rs new file mode 100644 index 0000000..a5a8fea --- /dev/null +++ b/src/day2.rs @@ -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>, +} + +impl From> for Reports { + fn from(lines: Lines) -> Self { + let mut reports = Vec::new(); + for line in lines.map(|i| i.unwrap()) { + reports.push( + line.split_ascii_whitespace() + .map(|record| record.parse::().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); + } +} diff --git a/src/day3.rs b/src/day3.rs new file mode 100644 index 0000000..7f2359c --- /dev/null +++ b/src/day3.rs @@ -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 { + input.lines().map(|l| l.unwrap()).collect() +} + +// PROBLEM 1 solution +#[aoc(day3, part1)] +pub fn part1(input: &Vec) -> 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::().unwrap() + * std::str::from_utf8(&m[2]).unwrap().parse::().unwrap(); + } + } + sum +} + +// PROBLEM 2 solution +#[aoc(day3, part2)] +pub fn part2(input: &Vec) -> 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::().unwrap() + * std::str::from_utf8(&m[3]).unwrap().parse::().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); + } +} diff --git a/4/src/main.rs b/src/day4.rs similarity index 60% rename from 4/src/main.rs rename to src/day4.rs index 4c99410..f379b45 100644 --- a/4/src/main.rs +++ b/src/day4.rs @@ -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>; - -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, } @@ -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(input: Lines) -> 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(input: Lines) -> u64 { } // PROBLEM 2 solution -fn problem2(input: Lines) -> 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); } } diff --git a/5/src/main.rs b/src/day5.rs similarity index 50% rename from 5/src/main.rs rename to src/day5.rs index 9e64bf0..a480a46 100644 --- a/5/src/main.rs +++ b/src/day5.rs @@ -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>; - -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>) { + 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> = lines + .by_ref() + .map(|l| l.unwrap().split(',').map(|n| n.parse::().unwrap()).collect()) + .collect(); + (rules, updates) } trait Rule: Debug { - fn check(&self, pages: &Vec) -> bool; - fn fail_pos(&self, pages: &Vec) -> 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) -> 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) -> 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 for BeforeRule { } #[derive(Debug)] -struct OrderingRules { +pub struct OrderingRules { rules: Vec>, } impl OrderingRules { - fn check(&self, pages: &Vec) -> bool { + fn check(&self, pages: &[u64]) -> bool { self.rules.iter().all(|p| p.check(pages)) } - fn fail_pos(&self, pages: &Vec) -> 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(mut input: Lines) -> 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> = input - .by_ref() - .map(|l| l.unwrap().split(',').map(|n| n.parse::().unwrap()).collect()) - .collect(); - +#[aoc(day5, part1)] +pub fn part1((rules, updates): &(OrderingRules, Vec>)) -> 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(mut input: Lines) -> u64 { } // PROBLEM 2 solution -fn problem2(mut input: Lines) -> 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> = input - .by_ref() - .map(|l| l.unwrap().split(',').map(|n| n.parse::().unwrap()).collect()) - .collect(); - +#[aoc(day5, part2)] +pub fn part2((rules, updates): &(OrderingRules, Vec>)) -> 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(mut input: Lines) -> 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); } } diff --git a/6/src/main.rs b/src/day6.rs similarity index 77% rename from 6/src/main.rs rename to src/day6.rs index b9c0d70..e6c2cd5 100644 --- a/6/src/main.rs +++ b/src/day6.rs @@ -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>; +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 for DirectionSet { } #[derive(Clone)] -struct Map { - grid: grid::Grid, - visited_from: grid::Grid, +pub struct Map { + grid: Grid, + visited_from: Grid, guard_facing: FacingDirection, guard_pos: (i64, i64), path: Vec<((i64, i64), FacingDirection)>, @@ -122,8 +95,8 @@ struct Map { impl From> for Map { fn from(input: Lines) -> Self { - let grid = grid::Grid::from(input); - let mut visited_from: grid::Grid = grid::Grid::new(grid.width() as i64); + let grid = Grid::from(input); + let mut visited_from: Grid = 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(input: Lines) -> u64 { - let mut map = Map::from(input); +#[aoc(day6, part1)] +pub fn part1(map: &Map) -> u64 { + let mut map = map.clone(); map.run_guard::(); (map.grid.count(b'X') + map.grid.count(b'-') + map.grid.count(b'|') + map.grid.count(b'^')) as u64 } // PROBLEM 2 solution -fn problem2(input: Lines) -> 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::(); @@ -231,10 +204,9 @@ fn problem2(input: Lines) -> 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); } } diff --git a/7/src/main.rs b/src/day7.rs similarity index 57% rename from 7/src/main.rs rename to src/day7.rs index b3e8dc8..715f38b 100644 --- a/7/src/main.rs +++ b/src/day7.rs @@ -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>; - -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, longest_cal: usize, } @@ -94,22 +65,18 @@ impl Calibrations { fn make_operator_sets(operators: &[Operator], n_opers: usize) -> Vec>> { (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) -> 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(input: Lines) -> 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(input: Lines) -> 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); } } diff --git a/8/src/main.rs b/src/day8.rs similarity index 54% rename from 8/src/main.rs rename to src/day8.rs index 68a03ac..3e2638c 100644 --- a/8/src/main.rs +++ b/src/day8.rs @@ -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>; - -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, } @@ -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(input: Lines) -> 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(input: Lines) -> 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); } } diff --git a/9/src/main.rs b/src/day9.rs similarity index 72% rename from 9/src/main.rs rename to src/day9.rs index 6e58608..73da006 100644 --- a/9/src/main.rs +++ b/src/day9.rs @@ -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>; - -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, files: Vec, frees: Vec, @@ -118,9 +91,9 @@ impl DiskMap { } // PROBLEM 1 solution - -fn problem1(input: Lines) -> 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(input: Lines) -> u64 { } // PROBLEM 2 solution -fn problem2(input: Lines) -> 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(input: Lines) -> 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); } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6af90ea --- /dev/null +++ b/src/lib.rs @@ -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 } diff --git a/libs/grid/Cargo.toml b/utils/grid/Cargo.toml similarity index 76% rename from libs/grid/Cargo.toml rename to utils/grid/Cargo.toml index d55031c..6507dcf 100644 --- a/libs/grid/Cargo.toml +++ b/utils/grid/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] + +[lib] +path = "lib.rs" diff --git a/libs/grid/src/lib.rs b/utils/grid/lib.rs similarity index 88% rename from libs/grid/src/lib.rs rename to utils/grid/lib.rs index 5c61ddc..90255f1 100644 --- a/libs/grid/src/lib.rs +++ b/utils/grid/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 From> for Grid { } } -impl Display for Grid { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +// impl> Display for Grid { +// 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 { + 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') }