+4
Cargo.toml
+4
Cargo.toml
+2
README.md
+2
README.md
···
7
8
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.
9
10

···
7
8
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.
9
10
+
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.
11
+
12

+28
src/2025/day2/rust/common.rs
+28
src/2025/day2/rust/common.rs
···
···
1
+
/// Parse a range string like "11-22" into (start, end).
2
+
pub fn parse_range(range: &str) -> (u64, u64) {
3
+
let parts: Vec<&str> = range.split('-').collect();
4
+
(parts[0].parse().expect("valid start"), parts[1].parse().expect("valid end"))
5
+
}
6
+
7
+
/// Check if a product ID is invalid (digits repeated exactly twice).
8
+
pub fn is_invalid_id(num: u64) -> bool {
9
+
let s = num.to_string(); let len = s.len();
10
+
if len % 2 != 0 { return false; } // Must have even number of digits to split in half.
11
+
let (first_half, second_half) = s.split_at(len / 2); // Split in half and compare.
12
+
first_half == second_half // If both halves are equal, it's invalid, return true.
13
+
}
14
+
15
+
/// Check if a product ID is invalid (digits repeated at least twice) for Part B.
16
+
pub fn is_invalid_id_part_b(num: u64) -> bool {
17
+
let s = num.to_string(); let len = s.len();
18
+
for pattern_len in 1..=len / 2 { // Try each possible pattern length from 1 to len/2.
19
+
if len % pattern_len == 0 { // Pattern length must divide evenly.
20
+
let pattern = &s[0..pattern_len];
21
+
let repetitions = len / pattern_len;
22
+
if repetitions >= 2 { // Must repeat at least twice.
23
+
if pattern.repeat(repetitions) == s { return true; }
24
+
}
25
+
}
26
+
}
27
+
false
28
+
}
+28
src/2025/day2/rust/mod.rs
+28
src/2025/day2/rust/mod.rs
···
···
1
+
mod common;
2
+
mod part_a;
3
+
mod part_b;
4
+
5
+
use part_a::part_a;
6
+
use part_b::part_b;
7
+
8
+
pub fn main() {
9
+
let input = include_str!("../input.txt");
10
+
let example_input = include_str!("../input_example.txt");
11
+
println!("Example Part A: {}", part_a(example_input));
12
+
println!("Part A: {}", part_a(input));
13
+
println!("Example Part B: {}", part_b(example_input));
14
+
println!("Part B: {}", part_b(input));
15
+
}
16
+
17
+
#[cfg(test)]
18
+
mod tests {
19
+
use super::*;
20
+
#[test]
21
+
fn test_part_a_example() { assert_eq!(part_a(include_str!("../input_example.txt")), 1227775554); }
22
+
#[test]
23
+
fn test_part_a() { assert_eq!(part_a(include_str!("../input.txt")), 28146997880); }
24
+
#[test]
25
+
fn test_part_b_example() { assert_eq!(part_b(include_str!("../input_example.txt")), 4174379265); }
26
+
#[test]
27
+
fn test_part_b() { assert_eq!(part_b(include_str!("../input.txt")), 40028128307); }
28
+
}
+14
src/2025/day2/rust/part_a.rs
+14
src/2025/day2/rust/part_a.rs
···
···
1
+
use super::common::{is_invalid_id, parse_range};
2
+
3
+
/// Sum all invalid product IDs across all ranges.
4
+
pub fn part_a(input: &str) -> u64 {
5
+
input
6
+
.lines()
7
+
.flat_map(|line| line.split(',')) // Split by commas.
8
+
.filter(|s| !s.trim().is_empty()) // Filter empty strings.
9
+
.map(|range| {
10
+
let (start, end) = parse_range(range.trim());
11
+
(start..=end).filter(|&n| is_invalid_id(n)).sum::<u64>()
12
+
})
13
+
.sum()
14
+
}
+14
src/2025/day2/rust/part_b.rs
+14
src/2025/day2/rust/part_b.rs
···
···
1
+
use super::common::{parse_range, is_invalid_id_part_b};
2
+
3
+
/// Sum all invalid product IDs (repeated at least twice) across all ranges.
4
+
pub fn part_b(input: &str) -> u64 {
5
+
input
6
+
.lines()
7
+
.flat_map(|line| line.split(',')) // Split by commas.
8
+
.filter(|s| !s.trim().is_empty()) // Filter empty strings.
9
+
.map(|range| {
10
+
let (start, end) = parse_range(range.trim());
11
+
(start..=end).filter(|&n| is_invalid_id_part_b(n)).sum::<u64>()
12
+
})
13
+
.sum()
14
+
}