mirror of
https://github.com/RussellChamp/advent-of-code-2025-cursor.git
synced 2026-10-27 23:52:34 +00:00
Refactor days 1-2, optimize day 3 part 1 with suffix max
This commit is contained in:
1 parent
0b5c8fa10f
commit
d650dce61b
6 files changed
+133
-149
No files matched your search
Generated
+16
@@ -23,6 +23,7 @@ version = "0.12.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dhat",
|
||||
"itertools",
|
||||
"pico-args",
|
||||
"tinyjson",
|
||||
]
|
||||
@@ -126,6 +127,12 @@ dependencies = [
|
||||
"thousands",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
@@ -155,6 +162,15 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
|
||||
@@ -24,6 +24,7 @@ test_lib = []
|
||||
# Template dependencies
|
||||
chrono = { version = "0.4.38", optional = true }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
itertools = "0.14.0"
|
||||
pico-args = "0.5.0"
|
||||
tinyjson = "2.5.1"
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
|
||||
|
||||
| Day | Part 1 | Part 2 |
|
||||
| :---: | :---: | :---: |
|
||||
| [Day 1](./src/bin/01.rs) | `113.0µs` | `119.9µs` |
|
||||
| [Day 2](./src/bin/02.rs) | `54.7ms` | `71.6ms` |
|
||||
| [Day 3](./src/bin/03.rs) | `802.9µs` | `131.1µs` |
|
||||
| [Day 1](./src/bin/01.rs) | `102.5µs` | `123.5µs` |
|
||||
| [Day 2](./src/bin/02.rs) | `45.0ms` | `69.6ms` |
|
||||
| [Day 3](./src/bin/03.rs) | `85.1µs` | `121.6µs` |
|
||||
|
||||
**Total: 127.47ms**
|
||||
**Total: 115.03ms**
|
||||
<!--- benchmarking table --->
|
||||
|
||||
---
|
||||
|
||||
+63
-70
@@ -1,81 +1,74 @@
|
||||
advent_of_code::solution!(1);
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let mut position: i32 = 50;
|
||||
let mut count = 0u64;
|
||||
|
||||
for line in input.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let direction = &line[0..1];
|
||||
let distance: i32 = line[1..].parse().ok()?;
|
||||
|
||||
match direction {
|
||||
"L" => position = (position - distance).rem_euclid(100),
|
||||
"R" => position = (position + distance).rem_euclid(100),
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
if position == 0 {
|
||||
count += 1;
|
||||
}
|
||||
/// Parse a rotation instruction like "L68" or "R14" into (direction, distance)
|
||||
fn parse_rotation(line: &str) -> Option<(char, i32)> {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let direction = line.chars().next()?;
|
||||
let distance: i32 = line[1..].parse().ok()?;
|
||||
Some((direction, distance))
|
||||
}
|
||||
|
||||
/// Apply a rotation and return the new position
|
||||
fn apply_rotation(position: i32, direction: char, distance: i32) -> i32 {
|
||||
match direction {
|
||||
'L' => (position - distance).rem_euclid(100),
|
||||
'R' => (position + distance).rem_euclid(100),
|
||||
_ => position,
|
||||
}
|
||||
}
|
||||
|
||||
/// Count how many times we pass through 0 during a rotation
|
||||
fn count_zeros(position: i32, direction: char, distance: i32) -> i32 {
|
||||
match direction {
|
||||
'L' => {
|
||||
if position == 0 {
|
||||
distance / 100
|
||||
} else if distance >= position {
|
||||
(distance - position) / 100 + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
'R' => {
|
||||
if position == 0 {
|
||||
distance / 100
|
||||
} else if distance >= 100 - position {
|
||||
(distance + position - 100) / 100 + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let count = input
|
||||
.lines()
|
||||
.filter_map(parse_rotation)
|
||||
.fold((50i32, 0u64), |(pos, count), (dir, dist)| {
|
||||
let new_pos = apply_rotation(pos, dir, dist);
|
||||
let new_count = count + if new_pos == 0 { 1 } else { 0 };
|
||||
(new_pos, new_count)
|
||||
})
|
||||
.1;
|
||||
|
||||
Some(count)
|
||||
}
|
||||
|
||||
pub fn part_two(input: &str) -> Option<u64> {
|
||||
let mut position: i32 = 50;
|
||||
let mut count = 0u64;
|
||||
|
||||
for line in input.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let direction = &line[0..1];
|
||||
let distance: i32 = line[1..].parse().ok()?;
|
||||
|
||||
// Count how many times we pass through 0 during this rotation
|
||||
let zeros = match direction {
|
||||
"L" => {
|
||||
// Moving left: we hit 0 at clicks p, p+100, p+200, ... (if p > 0)
|
||||
// or at clicks 100, 200, 300, ... (if p = 0)
|
||||
if position == 0 {
|
||||
distance / 100
|
||||
} else if distance >= position {
|
||||
(distance - position) / 100 + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
"R" => {
|
||||
// Moving right: we hit 0 at click (100-p), then (100-p)+100, etc.
|
||||
// or at clicks 100, 200, 300, ... (if p = 0)
|
||||
if position == 0 {
|
||||
distance / 100
|
||||
} else if distance >= 100 - position {
|
||||
(distance + position - 100) / 100 + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
count += zeros as u64;
|
||||
|
||||
// Update position
|
||||
match direction {
|
||||
"L" => position = (position - distance).rem_euclid(100),
|
||||
"R" => position = (position + distance).rem_euclid(100),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let count = input
|
||||
.lines()
|
||||
.filter_map(parse_rotation)
|
||||
.fold((50i32, 0u64), |(pos, count), (dir, dist)| {
|
||||
let zeros = count_zeros(pos, dir, dist) as u64;
|
||||
let new_pos = apply_rotation(pos, dir, dist);
|
||||
(new_pos, count + zeros)
|
||||
})
|
||||
.1;
|
||||
|
||||
Some(count)
|
||||
}
|
||||
|
||||
+35
-68
@@ -1,41 +1,22 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
advent_of_code::solution!(2);
|
||||
|
||||
/// Parse a range string like "11-22" into (start, end)
|
||||
fn parse_range(s: &str) -> Option<(u64, u64)> {
|
||||
s.trim()
|
||||
.split('-')
|
||||
.map(|x| x.parse::<u64>().ok())
|
||||
.collect_tuple()
|
||||
.and_then(|(a, b)| Some((a?, b?)))
|
||||
}
|
||||
|
||||
/// Check if a number is "invalid" - made of a pattern repeated exactly twice
|
||||
fn is_invalid_part1(n: u64) -> bool {
|
||||
let s = n.to_string();
|
||||
let len = s.len();
|
||||
// Must have even length for pattern to repeat exactly twice
|
||||
if len % 2 != 0 {
|
||||
return false;
|
||||
}
|
||||
let half = len / 2;
|
||||
s[..half] == s[half..]
|
||||
}
|
||||
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let mut sum = 0u64;
|
||||
|
||||
// Parse comma-separated ranges, handling potential whitespace/newlines
|
||||
for part in input.split(',') {
|
||||
let part = part.trim();
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse "start-end" range
|
||||
let mut range_parts = part.split('-');
|
||||
let start: u64 = range_parts.next()?.parse().ok()?;
|
||||
let end: u64 = range_parts.next()?.parse().ok()?;
|
||||
|
||||
// Check each number in the range
|
||||
for n in start..=end {
|
||||
if is_invalid_part1(n) {
|
||||
sum += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(sum)
|
||||
len % 2 == 0 && s[..len / 2] == s[len / 2..]
|
||||
}
|
||||
|
||||
/// Check if a number is "invalid" - made of a pattern repeated at least twice
|
||||
@@ -44,50 +25,36 @@ fn is_invalid_part2(n: u64) -> bool {
|
||||
let len = s.len();
|
||||
|
||||
// Try each possible pattern length (1 to len/2)
|
||||
for pattern_len in 1..=len / 2 {
|
||||
(1..=len / 2).any(|pattern_len| {
|
||||
// Pattern can only work if it divides evenly
|
||||
if len % pattern_len != 0 {
|
||||
continue;
|
||||
len % pattern_len == 0 && {
|
||||
let pattern = &s[..pattern_len];
|
||||
// Check if all chunks match the pattern
|
||||
s.as_bytes()
|
||||
.chunks(pattern_len)
|
||||
.all(|chunk| chunk == pattern.as_bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let pattern = &s[..pattern_len];
|
||||
let mut is_match = true;
|
||||
pub fn part_one(input: &str) -> Option<u64> {
|
||||
let sum = input
|
||||
.split(',')
|
||||
.filter_map(|part| parse_range(part))
|
||||
.flat_map(|(start, end)| start..=end)
|
||||
.filter(|&n| is_invalid_part1(n))
|
||||
.sum();
|
||||
|
||||
// Check if the entire string is made of this pattern repeated
|
||||
for i in (pattern_len..len).step_by(pattern_len) {
|
||||
if &s[i..i + pattern_len] != pattern {
|
||||
is_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_match {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
Some(sum)
|
||||
}
|
||||
|
||||
pub fn part_two(input: &str) -> Option<u64> {
|
||||
let mut sum = 0u64;
|
||||
|
||||
for part in input.split(',') {
|
||||
let part = part.trim();
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut range_parts = part.split('-');
|
||||
let start: u64 = range_parts.next()?.parse().ok()?;
|
||||
let end: u64 = range_parts.next()?.parse().ok()?;
|
||||
|
||||
for n in start..=end {
|
||||
if is_invalid_part2(n) {
|
||||
sum += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
let sum = input
|
||||
.split(',')
|
||||
.filter_map(|part| parse_range(part))
|
||||
.flat_map(|(start, end)| start..=end)
|
||||
.filter(|&n| is_invalid_part2(n))
|
||||
.sum();
|
||||
|
||||
Some(sum)
|
||||
}
|
||||
|
||||
+14
-7
@@ -2,13 +2,20 @@ advent_of_code::solution!(3);
|
||||
|
||||
/// Find the maximum 2-digit number by selecting exactly 2 batteries from a bank
|
||||
fn max_joltage_2(bank: &[u8]) -> u64 {
|
||||
let mut max = 0u64;
|
||||
let n = bank.len();
|
||||
|
||||
// For each position as the tens digit
|
||||
for i in 0..bank.len() - 1 {
|
||||
// Precompute suffix maximums: suffix_max[i] = max digit from position i to end
|
||||
// This avoids O(n) scan for each position, making overall O(n) instead of O(n²)
|
||||
let mut suffix_max = vec![0u8; n];
|
||||
suffix_max[n - 1] = bank[n - 1];
|
||||
for i in (0..n - 1).rev() {
|
||||
suffix_max[i] = bank[i].max(suffix_max[i + 1]);
|
||||
}
|
||||
|
||||
let mut max = 0u64;
|
||||
for i in 0..n - 1 {
|
||||
let tens = bank[i] as u64;
|
||||
// Find max digit after position i
|
||||
let units = *bank[i + 1..].iter().max().unwrap() as u64;
|
||||
let units = suffix_max[i + 1] as u64; // O(1) lookup instead of O(n) scan
|
||||
let joltage = tens * 10 + units;
|
||||
max = max.max(joltage);
|
||||
}
|
||||
@@ -43,7 +50,7 @@ fn max_joltage_k(bank: &[u8], k: usize) -> u64 {
|
||||
for remaining in (1..=k).rev() {
|
||||
// We can pick from [start, n - remaining] inclusive
|
||||
let end = n - remaining;
|
||||
|
||||
|
||||
// Find the FIRST position of the max digit in this range
|
||||
let mut best_pos = 0;
|
||||
let mut best_digit = bank[start];
|
||||
@@ -53,7 +60,7 @@ fn max_joltage_k(bank: &[u8], k: usize) -> u64 {
|
||||
best_pos = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result = result * 10 + best_digit as u64;
|
||||
start = start + best_pos + 1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user