1
0

Compare commits

10 Commits

Author SHA1 Message Date
85de6b1539 day 18 performance improvement 2025-10-21 15:00:51 +02:00
944faf2159 dbg made nicer 2025-10-21 11:33:48 +02:00
d25a45cb52 day 18 part 2 faster with binary search 2025-10-21 11:29:29 +02:00
2d34e77b13 runner 2025-10-21 11:10:59 +02:00
0e5d98d1bc day 18 rust part 2 2025-10-20 23:49:10 +02:00
318643632e day 18 rust part 1 2025-10-20 23:20:20 +02:00
1c668afb4e fixed test compilation 2025-10-20 22:54:41 +02:00
2225c6c3e4 readme update 2025-10-14 19:13:32 +02:00
e659074a05 day 21 part 2 2025-10-14 18:51:13 +02:00
aad74883a2 day 21 part 1 2025-10-14 18:36:58 +02:00
7 changed files with 461 additions and 2 deletions

View File

@@ -1,3 +1,9 @@
# Advent of Rust 2024
A few of the solutions for [Advent of Code 2024](https://adventofcode.com/2024) in Rust.
A few of the solutions for [Advent of Code 2024](https://adventofcode.com/2024) in Rust.
## Points
I finally (as of October 2025) finished all existing puzzles and am a part of the 500 star club!
![Screenshot from Advent of Code saying "Totals stars: 500"](assets/500stars.png)

BIN
assets/500stars.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

147
src/day18.rs Normal file
View File

@@ -0,0 +1,147 @@
use std::{collections::VecDeque, fs::read_to_string};
use itertools::Itertools;
use crate::utils::grid::Grid;
pub fn day_main() {
let input = read_to_string("input/day18.txt").unwrap();
let input = input.trim();
println!(" part1: {}", part1(input));
println!(" part2: {}", part2(input));
}
type RiddleResult = usize;
fn part1(input: &str) -> RiddleResult {
solve1(input, 1024, 70)
}
fn solve1(input: &str, n: usize, coord_max: i64) -> usize {
let points = parse(input);
sp(&points[..n], coord_max).unwrap()
}
fn sp(points: &[(i64, i64)], coord_max: i64) -> Option<usize> {
let mut pcheck = Grid::from_default(coord_max + 1, coord_max + 1);
for p in points {
pcheck.set(*p, true);
}
let mut visited = Grid::from_default(coord_max + 1, coord_max + 1);
let mut queue = VecDeque::from_iter([(0, 0, 0)]);
while let Some((x, y, c)) = queue.pop_front() {
if x == coord_max && y == coord_max {
return Some(c);
}
if visited.get((x, y)) == Some(&true) {
continue;
}
visited.set((x, y), true);
for (dx, dy) in [(0, 1), (1, 0), (0, -1), (-1, 0)] {
let (a, b) = (x + dx, y + dy);
if (0..=coord_max).contains(&a)
&& (0..=coord_max).contains(&b)
&& pcheck.get((a, b)) == Some(&false)
{
queue.push_back((a, b, c + 1));
}
}
}
None
}
fn parse(input: &str) -> Vec<(i64, i64)> {
input
.trim()
.lines()
.map(|line| {
line.split(",")
.map(|v| v.parse().unwrap())
.collect_tuple()
.unwrap()
})
.collect_vec()
}
fn part2(input: &str) -> String {
solve2(input, 1024, 70)
}
fn solve2(input: &str, fixed: usize, max_coord: i64) -> String {
let points = parse(input);
// we want the first point with which there is no shortest path
let mut i = fixed;
let mut jump = (points.len() - fixed) / 2;
#[cfg(debug_assertions)]
let mut loops = 0;
loop {
#[cfg(debug_assertions)]
{
loops += 1;
}
let l = sp(&points[..i - 1], max_coord);
let r = sp(&points[..i], max_coord);
if l.is_some() && r.is_none() {
#[cfg(debug_assertions)]
{
let iters_needed = i - fixed;
dbg!(loops, iters_needed);
}
return format!("{},{}", points[i - 1].0, points[i - 1].1);
}
if l.is_some() {
i += jump;
} else {
i -= jump;
}
jump /= 2;
if jump == 0 {
jump += 1;
}
}
}
#[cfg(test)]
mod test {
use crate::day18::{solve1, solve2};
const TEST_INPUT: &str = r"5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0
";
#[test]
fn test1() {
assert_eq!(solve1(TEST_INPUT, 12, 6), 22);
}
#[test]
fn test2() {
assert_eq!(solve2(TEST_INPUT, 12, 6), "6,1");
}
}

299
src/day21.rs Normal file
View File

@@ -0,0 +1,299 @@
use std::{collections::HashMap, fs::read_to_string};
use itertools::Itertools;
pub fn day_main() {
let input = read_to_string("input/day21.txt").unwrap();
let input = input.trim();
println!(" part1: {}", part1(input));
println!(" part2: {}", part2(input));
}
type RiddleResult = i64;
fn part1(input: &str) -> RiddleResult {
let mut p = Precomputed::new(2);
input
.lines()
.map(|line| p.shortest(line) * value(line))
.sum()
}
fn value(line: &str) -> i64 {
line[0..3].parse::<i64>().unwrap()
}
fn part2(input: &str) -> RiddleResult {
let mut p = Precomputed::new(25);
input
.lines()
.map(|line| p.shortest(line) * value(line))
.sum()
}
struct Precomputed {
num_sp: HashMap<(char, char), Vec<&'static str>>,
arrow_sp: HashMap<(char, char), Vec<&'static str>>,
robot_layers: i64,
cache: HashMap<(char, char, i64), i64>,
}
impl Precomputed {
fn new(robot_layers: i64) -> Self {
let num_sp = num_sp();
let arrow_sp = arrow_sp();
Self {
num_sp,
arrow_sp,
robot_layers,
cache: HashMap::new(),
}
}
fn shortest(&mut self, digit_pad: &str) -> RiddleResult {
format!("A{digit_pad}")
.chars()
.tuple_windows()
.map(|(a, b)| {
let x = self
.num_sp
.get(&(a, b))
.unwrap()
.clone()
.iter()
.map(|sp| format!("A{sp}A")) // we add the A in the next layer at the end of each sequence
.map(|sp| {
let x = if sp.len() > 1 {
sp.chars()
.tuple_windows()
.map(|(c, d)| self.get(c, d, self.robot_layers))
.sum::<i64>()
} else {
1
};
// println!(" sp {a} to {b} (NUM): {sp} -- {x}");
x
})
.min()
.unwrap();
// println!("{a}{b}: {x}");
x
})
// .inspect(|r| println!("shortest part: {r}"))
.sum()
}
fn get(&mut self, a: char, b: char, n: i64) -> i64 {
if n == 0 {
return 1;
}
if let Some(result) = self.cache.get(&(a, b, n)) {
return *result;
}
let paths = self.arrow_sp.get(&(a, b)).unwrap().clone();
let result = paths
.iter()
.map(|sp| format!("A{sp}A"))
.map(|sp| {
let x = if sp.len() > 1 {
sp.chars()
.tuple_windows()
.map(|(c, d)| self.get(c, d, n - 1))
.sum::<i64>()
} else {
1
};
// println!(
// "{}sp {a} to {b} ({n}): {sp} -- {x}",
// " ".repeat(self.robot_layers as usize + 2 - n as usize)
// );
x
})
.min()
.unwrap();
self.cache.insert((a, b, n), result);
result
}
}
fn arrow_sp() -> HashMap<(char, char), Vec<&'static str>> {
let mut starters = HashMap::new();
starters.insert(('A', '<'), vec!["<v<", "v<<"]);
starters.insert(('A', '^'), vec!["<"]);
starters.insert(('A', '>'), vec!["v"]);
starters.insert(('A', 'v'), vec!["<v", "v<"]);
starters.insert(('<', '^'), vec![">^"]);
starters.insert(('<', '>'), vec![">>"]);
starters.insert(('<', 'v'), vec![">"]);
starters.insert(('^', '>'), vec![">v", "v>"]);
starters.insert(('^', 'v'), vec!["v"]);
starters.insert(('>', 'v'), vec!["<"]);
let mut result = starters.clone();
for ((from, to), paths) in starters {
result.insert((to, from), invert(&paths));
}
for c in "A<>v^".chars() {
result.insert((c, c), vec![""]);
}
result
}
fn num_sp() -> HashMap<(char, char), Vec<&'static str>> {
let mut starters = HashMap::new();
starters.insert(('A', '0'), vec!["<"]);
starters.insert(('A', '1'), vec!["<^<", "^<<"]);
starters.insert(('A', '2'), vec!["<^", "^<"]);
starters.insert(('A', '3'), vec!["^"]);
starters.insert(('A', '4'), vec!["<^<^", "<^^<", "^<<^", "^<^<", "^^<<"]);
starters.insert(('A', '5'), vec!["<^^", "^<^", "^^<"]);
starters.insert(('A', '6'), vec!["^^"]);
starters.insert(
('A', '7'),
vec![
"<^<^^", "<^^<^", "<^^^<", "^<<^^", "^<^<^", "^<^^<", "^^^<<",
],
);
starters.insert(('A', '8'), vec!["<^^^", "^<^^", "^^<^", "^^^<"]);
starters.insert(('A', '9'), vec!["^^^"]);
starters.insert(('0', '1'), vec!["^<"]);
starters.insert(('0', '2'), vec!["^"]);
starters.insert(('0', '3'), vec!["^>", ">^"]);
starters.insert(('0', '4'), vec!["^<^", "^^<"]);
starters.insert(('0', '5'), vec!["^^"]);
starters.insert(('0', '6'), vec!["^^>", "^>^"]);
starters.insert(('0', '7'), vec!["^<^^", "^^<^", "^^^<"]);
starters.insert(('0', '8'), vec!["^^^"]);
starters.insert(('0', '9'), vec!["^^^>", "^^>^", "^>^^", ">^^^"]);
starters.insert(('1', '2'), vec![">"]);
starters.insert(('1', '3'), vec![">>"]);
starters.insert(('1', '4'), vec!["^"]);
starters.insert(('1', '5'), vec!["^>", ">^"]);
starters.insert(('1', '6'), vec!["^>>", ">^>", ">>^"]);
starters.insert(('1', '7'), vec!["^^"]);
starters.insert(('1', '8'), vec!["^^>", "^>^", ">^^"]);
starters.insert(('1', '9'), vec!["^^>>", "^>^>", "^>>^", ">^>^", ">>^^"]);
starters.insert(('2', '3'), vec![">"]);
starters.insert(('2', '4'), vec!["<^", "^<"]);
starters.insert(('2', '5'), vec!["^"]);
starters.insert(('2', '6'), vec!["^>", ">^"]);
starters.insert(('2', '7'), vec!["<^^", "^<^", "^^<"]);
starters.insert(('2', '8'), vec!["^^"]);
starters.insert(('2', '9'), vec!["^^>", "^>^", ">^^"]);
starters.insert(('3', '4'), vec!["<<^", "<^<", "^<<"]);
starters.insert(('3', '5'), vec!["<^", "^<"]);
starters.insert(('3', '6'), vec!["^"]);
starters.insert(
('3', '7'),
vec!["<<^^", "<^<^", "<^^<", "^<^<", "^<<^", "^^<<"],
);
starters.insert(('3', '8'), vec!["<^^", "^<^", "^^<"]);
starters.insert(('3', '9'), vec!["^^"]);
starters.insert(('4', '5'), vec![">"]);
starters.insert(('4', '6'), vec![">>"]);
starters.insert(('4', '7'), vec!["^"]);
starters.insert(('4', '8'), vec!["^>", ">^"]);
starters.insert(('4', '9'), vec!["^>>", ">^>", ">>^"]);
starters.insert(('5', '6'), vec![">"]);
starters.insert(('5', '7'), vec!["<^", "^<"]);
starters.insert(('5', '8'), vec!["^"]);
starters.insert(('5', '9'), vec!["^>", ">^"]);
starters.insert(('6', '7'), vec!["<<^", "<^<", "^<<"]);
starters.insert(('6', '8'), vec!["<^", "^<"]);
starters.insert(('6', '9'), vec!["^"]);
starters.insert(('7', '8'), vec![">"]);
starters.insert(('7', '9'), vec![">>"]);
starters.insert(('8', '9'), vec![">"]);
let mut result = starters.clone();
for ((from, to), paths) in starters {
result.insert((to, from), invert(&paths));
}
result
}
fn invert(paths: &Vec<&'static str>) -> Vec<&'static str> {
paths.iter().map(|path| reverse_path(path)).collect()
}
fn reverse_path(path: &str) -> &'static str {
path.chars()
.rev()
.map(|d| opposite(d))
.collect::<String>()
.leak()
}
fn opposite(d: char) -> char {
match d {
'<' => '>',
'^' => 'v',
'>' => '<',
'v' => '^',
_ => panic!("unknown direction {d}"),
}
}
#[cfg(test)]
mod test {
use crate::day21::Precomputed;
use super::{part1, part2};
const TEST_INPUT: &str = r"029A
980A
179A
456A
379A
";
#[test]
fn example1() {
assert_eq!(part1(TEST_INPUT), 126384);
}
#[test]
fn example1_mini() {
assert_eq!(part1("029A"), 68 * 29);
}
#[test]
fn shortest_human() {
let mut precomputed = Precomputed::new(2);
assert_eq!(precomputed.get('<', '^', 0), 1);
}
#[test]
fn shortest_1st() {
let mut p = Precomputed::new(2);
assert_eq!(p.get('A', '^', 1), 2);
}
#[test]
fn shortest_2nd() {
let mut p = Precomputed::new(2);
assert_eq!(p.get('A', '^', 2), 8);
}
#[test]
fn test2() {
assert_eq!(part2(TEST_INPUT), 0);
}
}

View File

@@ -28,7 +28,6 @@ fn part1(input: &str) -> RiddleResult {
}
}
let sets = sets.iter().unique().collect_vec();
dbg!(sets.len());
sets.iter()
.filter(|set| set.iter().any(|t| t.starts_with("t")))
.count()

View File

@@ -15,7 +15,9 @@ pub mod day14;
pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;
pub mod day21;
pub mod day22;
pub mod day23;
pub mod day24;

View File

@@ -22,7 +22,9 @@ fn main() {
(15, day15::day_main),
(16, day16::day_main),
(17, day17::day_main),
(18, day18::day_main),
(19, day19::day_main),
(21, day21::day_main),
(22, day22::day_main),
(23, day23::day_main),
(23, day23::day_main),
@@ -32,12 +34,16 @@ fn main() {
]);
let day: Option<u8> = args().nth(1).and_then(|a| a.parse().ok());
let Some(day) = day else {
let start = Instant::now();
mains
.iter()
.sorted_by_key(|entry| entry.0)
.for_each(|(d, f)| {
run(*d, f);
});
let duration = start.elapsed();
println!();
println!("{COLOR}{ITALIC}All tasks took {duration:?}{RESET_FORMATTING}");
return;
};