From b01db2cd304bd485b4ea1f767b904d781216fd9c Mon Sep 17 00:00:00 2001 From: Timothy Quilling Date: Tue, 2 Dec 2025 00:58:33 -0500 Subject: [PATCH] add 2025 day2 in rust --- Cargo.toml | 4 ++++ README.md | 2 ++ src/2025/day2/rust/common.rs | 28 ++++++++++++++++++++++++++++ src/2025/day2/rust/mod.rs | 28 ++++++++++++++++++++++++++++ src/2025/day2/rust/part_a.rs | 14 ++++++++++++++ src/2025/day2/rust/part_b.rs | 14 ++++++++++++++ 6 files changed, 90 insertions(+) create mode 100644 src/2025/day2/rust/common.rs create mode 100644 src/2025/day2/rust/mod.rs create mode 100644 src/2025/day2/rust/part_a.rs create mode 100644 src/2025/day2/rust/part_b.rs diff --git a/Cargo.toml b/Cargo.toml index fc2dabb..c4c57c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,10 @@ path = "src/2024/day8/rust/mod.rs" name = "2025_day1" path = "src/2025/day1/rust/mod.rs" +[[bin]] +name = "2025_day2" +path = "src/2025/day2/rust/mod.rs" + [lints.rust] unsafe_code = "forbid" diff --git a/README.md b/README.md index 1d28fe5..fa5c201 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,6 @@ I try to leave comments along the way to explain my reasoning, but if you have a Jump straight to my 2025 Day 1 solution's [Dial struct here!](https://tangled.org/quilling.dev/advent-of-code/blob/main/src/2025/day1/rust/common.rs) Modulo arithmetic handles the dial rotation wraparound logic, and for the second part we just determine the magnitude of the rotation using division before applying it. +For 2025 Day 2 I used some straightforward [pattern checking](https://tangled.org/quilling.dev/advent-of-code/blob/main/src/2025/day2/rust/common.rs) which is easily repeated on substrings in a loop for part two. + ![image](https://github.com/Teqed/advent-of-code-2023/assets/5181964/a00fb449-1b3b-4e33-a999-6eef20c773f4) diff --git a/src/2025/day2/rust/common.rs b/src/2025/day2/rust/common.rs new file mode 100644 index 0000000..11f949f --- /dev/null +++ b/src/2025/day2/rust/common.rs @@ -0,0 +1,28 @@ +/// Parse a range string like "11-22" into (start, end). +pub fn parse_range(range: &str) -> (u64, u64) { + let parts: Vec<&str> = range.split('-').collect(); + (parts[0].parse().expect("valid start"), parts[1].parse().expect("valid end")) +} + +/// Check if a product ID is invalid (digits repeated exactly twice). +pub fn is_invalid_id(num: u64) -> bool { + let s = num.to_string(); let len = s.len(); + if len % 2 != 0 { return false; } // Must have even number of digits to split in half. + let (first_half, second_half) = s.split_at(len / 2); // Split in half and compare. + first_half == second_half // If both halves are equal, it's invalid, return true. +} + +/// Check if a product ID is invalid (digits repeated at least twice) for Part B. +pub fn is_invalid_id_part_b(num: u64) -> bool { + let s = num.to_string(); let len = s.len(); + for pattern_len in 1..=len / 2 { // Try each possible pattern length from 1 to len/2. + if len % pattern_len == 0 { // Pattern length must divide evenly. + let pattern = &s[0..pattern_len]; + let repetitions = len / pattern_len; + if repetitions >= 2 { // Must repeat at least twice. + if pattern.repeat(repetitions) == s { return true; } + } + } + } + false +} \ No newline at end of file diff --git a/src/2025/day2/rust/mod.rs b/src/2025/day2/rust/mod.rs new file mode 100644 index 0000000..4933d2e --- /dev/null +++ b/src/2025/day2/rust/mod.rs @@ -0,0 +1,28 @@ +mod common; +mod part_a; +mod part_b; + +use part_a::part_a; +use part_b::part_b; + +pub fn main() { + let input = include_str!("../input.txt"); + let example_input = include_str!("../input_example.txt"); + println!("Example Part A: {}", part_a(example_input)); + println!("Part A: {}", part_a(input)); + println!("Example Part B: {}", part_b(example_input)); + println!("Part B: {}", part_b(input)); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_a_example() { assert_eq!(part_a(include_str!("../input_example.txt")), 1227775554); } + #[test] + fn test_part_a() { assert_eq!(part_a(include_str!("../input.txt")), 28146997880); } + #[test] + fn test_part_b_example() { assert_eq!(part_b(include_str!("../input_example.txt")), 4174379265); } + #[test] + fn test_part_b() { assert_eq!(part_b(include_str!("../input.txt")), 40028128307); } +} diff --git a/src/2025/day2/rust/part_a.rs b/src/2025/day2/rust/part_a.rs new file mode 100644 index 0000000..62d68af --- /dev/null +++ b/src/2025/day2/rust/part_a.rs @@ -0,0 +1,14 @@ +use super::common::{is_invalid_id, parse_range}; + +/// Sum all invalid product IDs across all ranges. +pub fn part_a(input: &str) -> u64 { + input + .lines() + .flat_map(|line| line.split(',')) // Split by commas. + .filter(|s| !s.trim().is_empty()) // Filter empty strings. + .map(|range| { + let (start, end) = parse_range(range.trim()); + (start..=end).filter(|&n| is_invalid_id(n)).sum::() + }) + .sum() +} diff --git a/src/2025/day2/rust/part_b.rs b/src/2025/day2/rust/part_b.rs new file mode 100644 index 0000000..70fc91a --- /dev/null +++ b/src/2025/day2/rust/part_b.rs @@ -0,0 +1,14 @@ +use super::common::{parse_range, is_invalid_id_part_b}; + +/// Sum all invalid product IDs (repeated at least twice) across all ranges. +pub fn part_b(input: &str) -> u64 { + input + .lines() + .flat_map(|line| line.split(',')) // Split by commas. + .filter(|s| !s.trim().is_empty()) // Filter empty strings. + .map(|range| { + let (start, end) = parse_range(range.trim()); + (start..=end).filter(|&n| is_invalid_id_part_b(n)).sum::() + }) + .sum() +} -- 2.51.2