Compare commits

...

25 Commits

Author SHA1 Message Date
263f2389a9
Add day25 2024-12-25 10:41:00 +01:00
6efeba3111
Add day24 part1 2024-12-24 10:42:36 +01:00
95b654234f
Add day23 2024-12-23 13:15:12 +01:00
9326dd4094
Re-run all benchmarks 2024-12-22 17:32:25 +01:00
b1aef02295
Speed up day22 2024-12-22 17:20:13 +01:00
2d3c79bf43
Add day21 2024-12-22 17:13:04 +01:00
0af0645dda
Add day22 2024-12-22 15:12:25 +01:00
6dc6f04368
Clean up day20 2024-12-20 13:31:25 +01:00
dfa6d05f59
Parallel day20 2024-12-20 13:22:57 +01:00
1baa3342cc
Add day20 2024-12-20 10:28:05 +01:00
ea53bf8a14
Clean up day19 2024-12-19 09:52:02 +01:00
cee0f7c935
Add day19 2024-12-19 09:43:35 +01:00
c72a5f9c6c
Clean up day18 2024-12-18 13:32:57 +01:00
fbda6337f0
Add day 17 and 18 benchmarks 2024-12-18 13:30:05 +01:00
2807f51648
Optimize day18 2024-12-18 13:28:02 +01:00
1e4a334908
Add day18 2024-12-18 10:06:49 +01:00
f6fa43766a
Add day17 part2 2024-12-17 21:15:23 +01:00
b569484664
Clean up day17 part1 2024-12-17 19:52:07 +01:00
bc1d58a2c8
Add day17 part1 2024-12-17 16:20:01 +01:00
bf8ba5356c
Add benchmarks 2024-12-16 15:34:57 +01:00
099af5ecb9
Clean up day16 2024-12-16 14:59:20 +01:00
673c0eadb1
Add day16 2024-12-16 13:45:51 +01:00
35c3e99fbc
Add day15 part2 2024-12-15 14:03:00 +01:00
b813526dfe
Add day15 part 1 2024-12-15 09:55:29 +01:00
4f5c52e553
Faster day14 solution 2024-12-14 18:37:18 +01:00
37 changed files with 2351 additions and 6 deletions

34
README.md Normal file
View File

@ -0,0 +1,34 @@
# Advent of Code 2024
## Benchmarks
I am not always optimizing for speed, but here are some benchmarks anyway.
_Measured with `hyperfine -N --warmup 20 <binary>`. Results include both parts.
Input is included in the binary during compilation, but parsed during runtime._
CPU: Ryzen 5 3600 (6c/12t)
| Day | Mean [ms] | Min [ms] | Max [ms] |
| :------ | ----------: | -------: | -------: |
| `day1` | 0.6 ± 0.0 | 0.6 | 1.6 |
| `day2` | 1.2 ± 0.0 | 1.2 | 1.5 |
| `day3` | 1.5 ± 0.0 | 1.5 | 1.8 |
| `day4` | 0.8 ± 0.0 | 0.8 | 1.1 |
| `day5` | 1.2 ± 0.0 | 1.1 | 2.0 |
| `day6` | 59.0 ± 1.3 | 57.6 | 63.4 |
| `day8` | 0.7 ± 0.0 | 0.7 | 0.9 |
| `day9` | 474.1 ± 0.6 | 473.1 | 475.4 |
| `day10` | 1.6 ± 0.0 | 1.6 | 1.9 |
| `day11` | 13.9 ± 0.4 | 13.4 | 15.1 |
| `day12` | 53.6 ± 0.4 | 52.6 | 54.8 |
| `day13` | 1.2 ± 0.0 | 1.2 | 1.5 |
| `day14` | 55.0 ± 0.3 | 54.8 | 57.0 |
| `day15` | 8.0 ± 0.2 | 7.9 | 8.7 |
| `day16` | 62.7 ± 0.1 | 62.4 | 63.1 |
| `day17` | 0.7 ± 0.0 | 0.7 | 1.9 |
| `day18` | 106.4 ± 0.3 | 106.0 | 107.7 |
| `day19` | 51.9 ± 0.4 | 51.6 | 53.7 |
| `day20` | 5.4 ± 0.2 | 5.0 | 6.4 |
| `day21` | 1.3 ± 0.0 | 1.2 | 1.5 |
| `day22` | 302.3 ± 0.8 | 301.3 | 304.1 |

16
day14/Cargo.lock generated
View File

@ -16,15 +16,31 @@ name = "day14"
version = "0.1.0"
dependencies = [
"glam",
"itertools",
"regex",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "memchr"
version = "2.7.4"

View File

@ -5,4 +5,5 @@ edition = "2021"
[dependencies]
glam = "0.29.2"
itertools = "0.13.0"
regex = "1.11.1"

View File

@ -1,4 +1,5 @@
use glam::IVec2;
use itertools::Itertools;
use regex::Regex;
#[derive(Debug, Clone, Eq, PartialEq)]
@ -76,12 +77,13 @@ fn print_map(robots: &[Robot], dimensions: &IVec2) -> String {
}
fn has_tree(robots: &[Robot]) -> bool {
let check: Vec<_> = (0..16).map(|i| IVec2::new(i, 0)).collect();
robots.iter().any(|p| {
check
.iter()
.all(|c| robots.iter().any(|p2| p2.position == p.position + c))
})
robots.len() == robots.iter().map(|r| r.position).unique().count()
// let check: Vec<_> = (0..16).map(|i| IVec2::new(i, 0)).collect();
// robots.iter().any(|p| {
// check
// .iter()
// .all(|c| robots.iter().any(|p2| p2.position == p.position + c))
// })
}
fn part2(robots: &mut [Robot], dimensions: &IVec2) -> i32 {

16
day15/Cargo.lock generated Normal file
View File

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day15"
version = "0.1.0"
dependencies = [
"glam",
]
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"

7
day15/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "day15"
version = "0.1.0"
edition = "2021"
[dependencies]
glam = "0.29.2"

219
day15/src/main.rs Normal file
View File

@ -0,0 +1,219 @@
use std::{collections::VecDeque, fmt::Display};
use glam::IVec2;
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
enum Tile {
Wall,
Robot,
Box,
BoxL,
BoxR,
Empty,
}
use Tile::*;
impl Display for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Wall => write!(f, "#"),
Robot => write!(f, "@"),
Box => write!(f, "O"),
BoxL => write!(f, "["),
BoxR => write!(f, "]"),
Empty => write!(f, "."),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
use Direction::*;
impl Direction {
fn as_vec(&self) -> IVec2 {
match self {
Up => IVec2::new(-1, 0),
Down => IVec2::new(1, 0),
Left => IVec2::new(0, -1),
Right => IVec2::new(0, 1),
}
}
}
#[derive(Debug, Clone)]
struct State {
map: Vec<Vec<Tile>>,
directions: VecDeque<Direction>,
robot_pos: IVec2,
}
impl Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.map
.iter()
.map(|l| l.iter().map(|t| t.to_string()).collect::<String>() + "\n")
.collect::<String>()
)
}
}
impl State {
fn widen(&mut self) {
self.map = self
.map
.iter()
.map(|l| {
l.iter()
.flat_map(|t| match t {
Empty => [Empty, Empty],
Wall => [Wall, Wall],
Box => [BoxL, BoxR],
Robot => [Robot, Empty],
e => [*e, *e],
})
.collect()
})
.collect();
self.robot_pos *= IVec2::new(1, 2);
}
}
fn parse(input: &str) -> State {
let (map, directions) = input.split_once("\n\n").unwrap();
let mut robot_pos = IVec2::new(0, 0);
State {
map: map
.lines()
.enumerate()
.map(|(l_idx, l)| {
l.chars()
.enumerate()
.map(|(c_idx, c)| match c {
'#' => Wall,
'@' => {
robot_pos.x = l_idx as i32;
robot_pos.y = c_idx as i32;
Robot
}
'O' => Box,
_ => Empty,
})
.collect()
})
.collect(),
directions: directions
.replace("\n", "")
.chars()
.map(|c| match c {
'<' => Left,
'^' => Up,
'>' => Right,
'v' => Down,
_ => panic!(),
})
.collect(),
robot_pos,
}
}
#[inline]
fn get_tile<'a>(map: &'a mut [Vec<Tile>], idx: &IVec2) -> &'a mut Tile {
&mut map[idx.x as usize][idx.y as usize]
}
fn can_be_moved(state: &State, pos: &IVec2, direction: &IVec2, to_move: &mut Vec<IVec2>) -> bool {
let tile_in_front = state.map[(pos + direction).x as usize][(pos + direction).y as usize];
if tile_in_front == Wall {
return false;
}
if tile_in_front == Empty {
return true;
}
let to_check_dir = match tile_in_front {
BoxL => Right.as_vec(),
BoxR => Left.as_vec(),
_ => IVec2::new(0, 0),
};
if direction == &Up.as_vec() || direction == &Down.as_vec() {
to_move.push(pos + direction);
to_move.push(pos + direction + to_check_dir);
return can_be_moved(state, &(pos + direction), direction, to_move)
&& can_be_moved(state, &(pos + direction + to_check_dir), direction, to_move);
}
to_move.push(pos + direction);
can_be_moved(state, &(pos + direction), direction, to_move)
}
fn do_move(state: &mut State) {
let dir = state.directions.pop_front().unwrap().as_vec();
let pos_in_front = state.robot_pos + dir;
if get_tile(&mut state.map, &pos_in_front) == &Wall {
return;
}
if get_tile(&mut state.map, &pos_in_front) == &Empty {
*get_tile(&mut state.map, &pos_in_front) = Robot;
*get_tile(&mut state.map, &state.robot_pos) = Empty;
state.robot_pos = pos_in_front;
return;
}
let mut to_move = Vec::new();
if can_be_moved(state, &state.robot_pos, &dir, &mut to_move) {
let mut new_map = state.map.clone();
for pos in &to_move {
*get_tile(&mut new_map, pos) = Empty;
}
for pos in &to_move {
*get_tile(&mut new_map, &(pos + dir)) = *get_tile(&mut state.map, pos);
}
state.map = new_map;
*get_tile(&mut state.map, &pos_in_front) = Robot;
*get_tile(&mut state.map, &state.robot_pos) = Empty;
state.robot_pos = pos_in_front;
}
}
fn get_gps_sum(state: &State) -> usize {
state
.map
.iter()
.enumerate()
.flat_map(|(l_idx, l)| {
l.iter().enumerate().map(move |(t_idx, t)| {
if t == &Box || t == &BoxL {
100 * l_idx + t_idx
} else {
0
}
})
})
.sum()
}
fn part12(state: &mut State) -> usize {
while !state.directions.is_empty() {
do_move(state);
}
get_gps_sum(state)
}
fn main() {
let mut input = parse(include_str!("../input.txt"));
println!("{}", part12(&mut input.clone()));
input.widen();
println!("{}", part12(&mut input));
}

126
day16/Cargo.lock generated Normal file
View File

@ -0,0 +1,126 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "day16"
version = "0.1.0"
dependencies = [
"ahash",
"glam",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[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 = "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 = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

8
day16/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "day16"
version = "0.1.0"
edition = "2021"
[dependencies]
ahash = "0.8.11"
glam = "0.29.2"

235
day16/src/main.rs Normal file
View File

@ -0,0 +1,235 @@
use std::{cmp::Reverse, collections::BinaryHeap};
use ahash::{AHashMap, AHashSet};
use glam::IVec2;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Tile {
Empty,
Wall,
Start,
End,
}
use Tile::*;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Direction {
North,
South,
West,
East,
}
use Direction::*;
impl Direction {
fn as_vec(&self) -> IVec2 {
match self {
North => IVec2::new(-1, 0),
South => IVec2::new(1, 0),
West => IVec2::new(0, -1),
East => IVec2::new(0, 1),
}
}
fn turn_left(&self) -> Self {
match self {
North => West,
South => East,
West => South,
East => North,
}
}
fn turn_right(&self) -> Self {
match self {
North => East,
South => West,
West => North,
East => South,
}
}
fn turn_around(&self) -> Self {
match self {
North => South,
South => North,
West => East,
East => West,
}
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct Node {
score: u32,
direction: Direction,
position: IVec2,
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.score.cmp(&other.score)
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn parse(input: &str) -> (Vec<Vec<Tile>>, IVec2, IVec2) {
let mut start_pos = IVec2::new(0, 0);
let mut end_pos = IVec2::new(0, 0);
(
input
.lines()
.enumerate()
.map(|(l_idx, l)| {
l.chars()
.enumerate()
.map(|(c_idx, c)| match c {
'#' => Wall,
'S' => {
start_pos.x = l_idx as i32;
start_pos.y = c_idx as i32;
Start
}
'E' => {
end_pos.x = l_idx as i32;
end_pos.y = c_idx as i32;
End
}
_ => Empty,
})
.collect()
})
.collect(),
start_pos,
end_pos,
)
}
fn get_scores(grid: &[Vec<Tile>], start_pos: &IVec2) -> AHashMap<(IVec2, Direction), u32> {
// dijkstra
let mut to_visit = BinaryHeap::new();
let mut visited = AHashMap::new();
to_visit.push(Reverse(Node {
score: 0,
direction: East,
position: *start_pos,
}));
while let Some(curr) = to_visit.pop() {
let curr = curr.0;
if visited
.get(&(curr.position, curr.direction))
.map_or(false, |s| s < &curr.score)
{
continue;
}
visited.insert((curr.position, curr.direction), curr.score);
let pos_in_front = curr.position + curr.direction.as_vec();
if grid[pos_in_front.x as usize][pos_in_front.y as usize] != Wall {
to_visit.push(Reverse(Node {
score: curr.score + 1,
direction: curr.direction,
position: curr.position + curr.direction.as_vec(),
}));
}
to_visit.push(Reverse(Node {
score: curr.score + 1000,
direction: curr.direction.turn_left(),
position: curr.position,
}));
to_visit.push(Reverse(Node {
score: curr.score + 1000,
direction: curr.direction.turn_right(),
position: curr.position,
}));
}
visited
}
fn part1(scores: &AHashMap<(IVec2, Direction), u32>, end_pos: &IVec2) -> u32 {
*scores
.iter()
.filter(|((p, _), _)| p == end_pos)
.map(|(_, v)| v)
.min()
.unwrap()
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Visit {
position: IVec2,
direction: Direction,
score: u32,
}
fn part2(scores: &AHashMap<(IVec2, Direction), u32>, end_pos: &IVec2) -> usize {
// bfs from end to start along shortest paths
let shortest_path_len = part1(scores, end_pos);
let mut seen: AHashSet<IVec2> = AHashSet::new();
let mut to_visit: Vec<Visit> = scores
.iter()
.filter(|((p, _), val)| val == &&shortest_path_len && p == end_pos)
.map(|((p, d), s)| Visit {
position: *p,
direction: *d,
score: *s,
})
.collect();
while let Some(curr) = to_visit.pop() {
seen.insert(curr.position);
let pos_behind = curr.position + curr.direction.turn_around().as_vec();
if scores
.get(&(pos_behind, curr.direction))
.map_or(false, |v| v == &(curr.score - 1))
{
to_visit.push(Visit {
position: pos_behind,
direction: curr.direction,
score: curr.score - 1,
});
}
let new_score = curr.score - 1000;
if scores
.get(&(curr.position, curr.direction.turn_left()))
.map_or(false, |v| v == &new_score)
{
to_visit.push(Visit {
position: curr.position,
direction: curr.direction.turn_left(),
score: new_score,
});
}
if scores
.get(&(curr.position, curr.direction.turn_right()))
.map_or(false, |v| v == &new_score)
{
to_visit.push(Visit {
position: curr.position,
direction: curr.direction.turn_right(),
score: new_score,
});
}
}
seen.len()
}
fn main() {
let (grid, start_pos, end_pos) = parse(include_str!("../input.txt"));
let scores = get_scores(&grid, &start_pos);
println!("{}", part1(&scores, &end_pos));
println!("{}", part2(&scores, &end_pos));
}

7
day17/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day17"
version = "0.1.0"

6
day17/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "day17"
version = "0.1.0"
edition = "2021"
[dependencies]

127
day17/src/main.rs Normal file
View File

@ -0,0 +1,127 @@
#[derive(Debug, Clone)]
struct State {
reg_a: usize,
reg_b: usize,
reg_c: usize,
prog: Vec<u8>,
pc: usize,
out: Vec<u8>,
}
impl State {
fn get_combo_operand(&self) -> usize {
match self.prog[self.pc + 1] {
x @ 0..=3 => x as usize,
4 => self.reg_a,
5 => self.reg_b,
6 => self.reg_c,
_ => panic!(),
}
}
fn exec_instruction(&mut self) {
let literal_op = self.prog[self.pc + 1];
match self.prog[self.pc] {
0 => self.reg_a >>= self.get_combo_operand(),
1 => self.reg_b ^= literal_op as usize,
2 => self.reg_b = self.get_combo_operand() & 7,
3 => {
if self.reg_a != 0 {
self.pc = literal_op as usize;
return;
}
}
4 => self.reg_b ^= self.reg_c,
5 => self.out.push((self.get_combo_operand() & 7) as u8),
6 => self.reg_b = self.reg_a >> self.get_combo_operand(),
7 => self.reg_c = self.reg_a >> self.get_combo_operand(),
_ => panic!("Invalid instruction"),
}
self.pc += 2;
}
}
fn parse(input: &str) -> State {
let (registers, program) = input.split_once("\n\n").unwrap();
let &[a, b, c] = registers
.lines()
.map(|l| l.split(' ').last().unwrap().parse().unwrap())
.collect::<Vec<_>>()
.as_slice()
else {
panic!()
};
State {
reg_a: a,
reg_b: b,
reg_c: c,
prog: program
.split_once(' ')
.unwrap()
.1
.trim()
.split(',')
.map(|i| i.parse().unwrap())
.collect(),
pc: 0,
out: vec![],
}
}
fn get_output(state: &mut State) -> Vec<u8> {
while state.pc < state.prog.len() - 1 {
state.exec_instruction();
}
state.out.clone()
}
fn part1(state: &mut State) -> String {
get_output(state)
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(",")
}
// B <- A % 8
// B <- B ^ 3
// C <- A >> B
// B <- B ^ C
// A <- A >> 3
// B <- B ^ 5
// print B & 7
// jmp 0 if A
fn find_input(state: &State, instructions: &[u8], curr_a: usize) -> Vec<usize> {
let mut candidates = vec![];
if instructions.is_empty() {
return vec![curr_a];
}
let curr = instructions.last().unwrap();
for i in 0..8 {
let mut new_state = state.clone();
let new_a = (curr_a << 3) + i;
new_state.reg_a = new_a;
if get_output(&mut new_state)[0] == *curr {
candidates.push(new_a);
}
}
let mut results = vec![];
for candidate in candidates {
results.extend(find_input(
state,
&instructions[0..instructions.len() - 1],
candidate,
));
}
results
}
fn part2(state: &State) -> usize {
*find_input(state, &state.prog, 0).iter().min().unwrap()
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part1(&mut input.clone()));
println!("{}", part2(&input));
}

126
day18/Cargo.lock generated Normal file
View File

@ -0,0 +1,126 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "day18"
version = "0.1.0"
dependencies = [
"ahash",
"glam",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[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 = "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 = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

8
day18/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "day18"
version = "0.1.0"
edition = "2021"
[dependencies]
ahash = "0.8.11"
glam = "0.29.2"

149
day18/src/main.rs Normal file
View File

@ -0,0 +1,149 @@
use std::{cmp::Reverse, collections::BinaryHeap};
use ahash::AHashSet;
use glam::IVec2;
enum Direction {
Up,
Down,
Left,
Right,
}
use Direction::*;
impl Direction {
fn to_vec(&self) -> IVec2 {
match self {
Up => IVec2::new(-1, 0),
Down => IVec2::new(1, 0),
Left => IVec2::new(0, -1),
Right => IVec2::new(0, 1),
}
}
fn all() -> Vec<Self> {
vec![Up, Down, Left, Right]
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Node {
score: u32,
position: IVec2,
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.score.cmp(&other.score)
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
type Distance = (u32, Vec<IVec2>);
fn parse(input: &str) -> Vec<IVec2> {
input
.lines()
.map(|l| {
let (y, x) = l.split_once(',').unwrap();
IVec2 {
x: x.parse().unwrap(),
y: y.parse().unwrap(),
}
})
.collect()
}
fn get_score<'a>(scores: &'a mut [Vec<Distance>], pos: &IVec2) -> &'a mut Distance {
&mut scores[pos.x as usize][pos.y as usize]
}
fn find_path(corrupt_pos: &AHashSet<IVec2>, dimensions: &IVec2) -> Distance {
let mut scores =
vec![vec![(u32::MAX, vec![]); dimensions.y as usize + 1]; dimensions.x as usize + 1];
let mut to_visit = BinaryHeap::new();
let start_pos = IVec2::new(0, 0);
get_score(&mut scores, &start_pos).0 = 0;
to_visit.push(Reverse(Node {
score: 0,
position: start_pos,
}));
while let Some(Reverse(curr)) = to_visit.pop() {
if curr.position == *dimensions {
return (curr.score, get_score(&mut scores, &curr.position).1.clone());
}
if curr.score > get_score(&mut scores, &curr.position).0 {
continue;
}
for direction in Direction::all() {
let new_pos = curr.position + direction.to_vec();
if new_pos.cmplt(start_pos).any()
|| new_pos.cmpgt(*dimensions).any()
|| corrupt_pos.contains(&new_pos)
{
continue;
}
let new_score = curr.score + 1;
if new_score < get_score(&mut scores, &new_pos).0 {
get_score(&mut scores, &new_pos).0 = new_score;
let mut prev_positions = get_score(&mut scores, &curr.position).1.clone();
prev_positions.push(curr.position);
get_score(&mut scores, &new_pos).1.extend(prev_positions);
to_visit.push(Reverse(Node {
score: new_score,
position: new_pos,
}));
}
}
}
(u32::MAX, vec![])
}
fn part1(positions: &[IVec2], dimensions: &IVec2) -> u32 {
find_path(
&AHashSet::from_iter(positions[..1024].iter().map(|i| i.to_owned())),
dimensions,
)
.0
}
fn part2(positions: &[IVec2], dimensions: &IVec2) -> String {
let mut prev_path = AHashSet::new();
for i in 1024..positions.len() {
if !prev_path.is_empty() && !prev_path.contains(&positions[i - 1]) {
continue;
}
let (len, path) = find_path(
&AHashSet::from_iter(positions[..i].iter().map(|i| i.to_owned())),
dimensions,
);
prev_path = AHashSet::from_iter(path);
if len == u32::MAX {
return positions
.get(i - 1)
.map(|p| format!("{},{}", p.y, p.x))
.unwrap();
}
}
panic!("not found")
}
fn main() {
let input = parse(include_str!("../input.txt"));
let size = IVec2::new(70, 70);
println!("{}", part1(&input, &size));
println!("{}", part2(&input, &size));
}

119
day19/Cargo.lock generated Normal file
View File

@ -0,0 +1,119 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "day19"
version = "0.1.0"
dependencies = [
"ahash",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[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 = "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 = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

7
day19/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "day19"
version = "0.1.0"
edition = "2021"
[dependencies]
ahash = "0.8.11"

88
day19/src/main.rs Normal file
View File

@ -0,0 +1,88 @@
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
enum Color {
White,
Blue,
Black,
Red,
Green,
}
use ahash::AHashMap;
use Color::*;
impl From<char> for Color {
fn from(value: char) -> Self {
match value {
'w' => White,
'u' => Blue,
'b' => Black,
'r' => Red,
'g' => Green,
_ => panic!(),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct ParsedInput {
available_patterns: Vec<Vec<Color>>,
designs: Vec<Vec<Color>>,
}
fn parse(input: &str) -> ParsedInput {
let (patterns, designs) = input.split_once("\n\n").unwrap();
ParsedInput {
available_patterns: patterns
.trim_end()
.split(", ")
.map(|p| p.chars().map(|c| c.into()).collect())
.collect(),
designs: designs
.lines()
.map(|d| d.chars().map(|c| c.into()).collect())
.collect(),
}
}
fn count_possible(
design: &[Color],
available_patterns: &[Vec<Color>],
cache: &mut AHashMap<Vec<Color>, usize>,
) -> usize {
if design.is_empty() {
return 1;
}
if let Some(res) = cache.get(design) {
return *res;
}
let count = available_patterns
.iter()
.filter(|p| design.starts_with(p))
.map(|p| count_possible(&design[p.len()..], available_patterns, cache))
.sum();
cache.insert(design.to_vec(), count);
count
}
fn part1(parsed_input: &ParsedInput) -> usize {
let mut cache = AHashMap::new();
parsed_input
.designs
.iter()
.filter(|d| count_possible(d, &parsed_input.available_patterns, &mut cache) > 0)
.count()
}
fn part2(parsed_input: &ParsedInput) -> usize {
let mut cache = AHashMap::new();
parsed_input
.designs
.iter()
.map(|d| count_possible(d, &parsed_input.available_patterns, &mut cache))
.sum()
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part1(&input));
println!("{}", part2(&input));
}

68
day20/Cargo.lock generated Normal file
View File

@ -0,0 +1,68 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
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.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "day20"
version = "0.1.0"
dependencies = [
"glam",
"rayon",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
[[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",
]

8
day20/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "day20"
version = "0.1.0"
edition = "2021"
[dependencies]
glam = "0.29.2"
rayon = "1.10.0"

139
day20/src/main.rs Normal file
View File

@ -0,0 +1,139 @@
use glam::IVec2;
use rayon::prelude::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Direction {
Up,
Left,
Down,
Right,
}
use Direction::*;
impl Direction {
fn to_vec(self) -> IVec2 {
match self {
Up => IVec2::new(-1, 0),
Left => IVec2::new(0, -1),
Down => IVec2::new(1, 0),
Right => IVec2::new(0, 1),
}
}
fn all() -> Vec<Direction> {
vec![Up, Left, Down, Right]
}
fn turn(&self) -> Self {
match self {
Up => Right,
Left => Up,
Down => Left,
Right => Down,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
struct Tile {
tiletype: bool,
distance: usize,
}
fn parse(input: &str) -> (Vec<Vec<Tile>>, IVec2, IVec2) {
let mut start_pos = IVec2::new(0, 0);
let mut end_pos = IVec2::new(0, 0);
(
input
.lines()
.enumerate()
.map(|(l_idx, l)| {
l.chars()
.enumerate()
.map(|(c_idx, c)| Tile {
tiletype: match c {
'#' => true,
'S' => {
start_pos.x = l_idx as i32;
start_pos.y = c_idx as i32;
false
}
'E' => {
end_pos.x = l_idx as i32;
end_pos.y = c_idx as i32;
false
}
_ => false,
},
distance: usize::MAX,
})
.collect()
})
.collect(),
start_pos,
end_pos,
)
}
fn get_distances(
map: &mut [Vec<Tile>],
pos: &IVec2,
distance: &usize,
path: &mut Vec<IVec2>,
end_pos: &IVec2,
) {
let curr_tile = &mut map[pos.x as usize][pos.y as usize];
curr_tile.distance = *distance;
if pos == end_pos {
return;
}
for direction in Direction::all() {
let new_tile_pos = pos + direction.to_vec();
let new_tile = map[new_tile_pos.x as usize][new_tile_pos.y as usize];
if !new_tile.tiletype && new_tile.distance > distance + 1 {
path.push(new_tile_pos);
get_distances(map, &new_tile_pos, &(distance + 1), path, end_pos);
break;
}
}
}
fn part12(map: &[Vec<Tile>], path: &[IVec2], max_distance: i32) -> usize {
let combinations: Vec<(i32, i32)> = (1..=max_distance)
.flat_map(|i| (0..=(max_distance - i)).map(move |j| (i, j)))
.collect();
path.par_iter()
.map(|tile| {
Direction::all()
.iter()
.map(|direction| {
combinations
.iter()
.filter(|(i, j)| {
let new_tile_pos =
tile + i * direction.to_vec() + j * direction.turn().to_vec();
map.get(new_tile_pos.x as usize)
.and_then(|l| l.get(new_tile_pos.y as usize))
.map_or(false, |t| {
t.distance
>= map[tile.x as usize][tile.y as usize].distance
+ 100
+ *i as usize
+ *j as usize
&& !t.tiletype
})
})
.count()
})
.sum::<usize>()
})
.sum()
}
fn main() {
let (mut map, start_pos, end_pos) = parse(include_str!("../input.txt"));
let mut path = vec![start_pos];
get_distances(&mut map, &start_pos, &0, &mut path, &end_pos);
println!("{}", part12(&map, &path, 2));
println!("{}", part12(&map, &path, 20));
}

131
day21/Cargo.lock generated Normal file
View File

@ -0,0 +1,131 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day21"
version = "0.1.0"
dependencies = [
"glam",
"itertools",
"phf",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[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 = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[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 = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"

9
day21/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "day21"
version = "0.1.0"
edition = "2021"
[dependencies]
phf = { version = "0.11.2", features = ["macros"] }
glam = "0.29.2"
itertools = "0.13.0"

165
day21/src/main.rs Normal file
View File

@ -0,0 +1,165 @@
use std::{collections::HashMap, fmt::Display, iter};
use glam::IVec2;
use itertools::Itertools;
use phf::phf_map;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Pad {
Keypad,
Numpad,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
fn to_vec(self) -> IVec2 {
match self {
Direction::Up => IVec2::new(-1, 0),
Direction::Down => IVec2::new(1, 0),
Direction::Left => IVec2::new(0, -1),
Direction::Right => IVec2::new(0, 1),
}
}
}
impl Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Direction::Up => write!(f, "^"),
Direction::Down => write!(f, "v"),
Direction::Left => write!(f, "<"),
Direction::Right => write!(f, ">"),
}
}
}
static NUMPAD_POSITIONS: phf::Map<char, IVec2> = phf_map! {
'7' => IVec2::new(0, 0),
'8' => IVec2::new(0, 1),
'9' => IVec2::new(0, 2),
'4' => IVec2::new(1, 0),
'5' => IVec2::new(1, 1),
'6' => IVec2::new(1, 2),
'1' => IVec2::new(2, 0),
'2' => IVec2::new(2, 1),
'3' => IVec2::new(2, 2),
'0' => IVec2::new(3, 1),
'A' => IVec2::new(3, 2),
};
static KEYPAD_POSITIONS: phf::Map<char, IVec2> = phf_map! {
'^' => IVec2::new(0, 1),
'A' => IVec2::new(0, 2),
'<' => IVec2::new(1, 0),
'v' => IVec2::new(1, 1),
'>' => IVec2::new(1, 2),
};
fn parse(input: &str) -> Vec<String> {
input.lines().map(|l| l.to_string()).collect()
}
fn directions(pos1: &IVec2, pos2: &IVec2) -> Vec<Vec<Direction>> {
let diff = pos2 - pos1;
let mut path_directions = vec![];
if diff.x > 0 {
path_directions.extend((0..diff.x).map(|_| Direction::Down));
} else {
path_directions.extend((0..diff.x.abs()).map(|_| Direction::Up));
}
if diff.y > 0 {
path_directions.extend((0..diff.y).map(|_| Direction::Right));
} else {
path_directions.extend((0..diff.y.abs()).map(|_| Direction::Left));
}
let reverse = path_directions.iter().rev().map(|d| d.to_owned()).collect();
if reverse == path_directions {
vec![path_directions]
} else {
vec![path_directions, reverse]
}
}
fn is_allowed_path(pos1: &IVec2, directions: &[Direction], banned_pos: &IVec2) -> bool {
let mut pos = *pos1;
for direction in directions {
pos += direction.to_vec();
if pos == *banned_pos {
return false;
}
}
true
}
fn get_paths(pos1: &IVec2, pos2: &IVec2, banned_pos: &IVec2) -> Vec<Vec<Direction>> {
directions(pos1, pos2)
.iter()
.filter(|d| is_allowed_path(pos1, d, banned_pos))
.map(|d| d.to_owned())
.collect()
}
fn min_length(code: &str, pads: &[Pad], cache: &mut HashMap<(String, usize), usize>) -> usize {
// credits: RubixDev
if pads.is_empty() {
return code.len();
}
if let Some(val) = cache.get(&(code.to_string(), pads.len())) {
return *val;
}
let result = std::iter::once('A')
.chain(code.chars())
.tuple_windows()
.map(|(start, end)| {
match pads[0] {
Pad::Numpad => get_paths(
NUMPAD_POSITIONS.get(&start).unwrap(),
NUMPAD_POSITIONS.get(&end).unwrap(),
&IVec2::new(3, 0),
),
Pad::Keypad => get_paths(
KEYPAD_POSITIONS.get(&start).unwrap(),
KEYPAD_POSITIONS.get(&end).unwrap(),
&IVec2::new(0, 0),
),
}
.into_iter()
.map(|dirs| dirs.iter().map(|d| d.to_string()).collect::<String>() + "A")
})
.multi_cartesian_product()
.map(|combination| {
combination
.iter()
.map(|c| min_length(c, &pads[1..], cache))
.sum::<usize>()
})
.min()
.unwrap();
cache.insert((code.to_string(), pads.len()), result);
result
}
fn part12(input: &[String], keypad_robot_count: usize) -> usize {
let mut cache = HashMap::new();
let pads: Vec<Pad> = iter::once(Pad::Numpad)
.chain((0..keypad_robot_count).map(|_| Pad::Keypad))
.collect();
input
.iter()
.map(|s| s[..s.len() - 1].parse::<usize>().unwrap() * min_length(s, &pads, &mut cache))
.sum()
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part12(&input, 2));
println!("{}", part12(&input, 25));
}

119
day22/Cargo.lock generated Normal file
View File

@ -0,0 +1,119 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "day22"
version = "0.1.0"
dependencies = [
"ahash",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[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 = "syn"
version = "2.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

7
day22/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "day22"
version = "0.1.0"
edition = "2021"
[dependencies]
ahash = "0.8.11"

76
day22/src/main.rs Normal file
View File

@ -0,0 +1,76 @@
use ahash::{AHashMap, AHashSet};
fn parse(input: &str) -> Vec<usize> {
input.lines().map(|l| l.parse().unwrap()).collect()
}
fn evolve_number(num: &mut usize) {
*num ^= *num << 6;
*num &= 0xffffff;
*num ^= *num >> 5;
*num &= 0xffffff;
*num ^= *num << 11;
*num &= 0xffffff;
}
fn part1(initial_numbers: &[usize]) -> usize {
initial_numbers
.iter()
.map(|num| {
let mut res = *num;
(0..2000).for_each(|_| evolve_number(&mut res));
res
})
.sum()
}
fn part2(numbers: &[usize]) -> isize {
let mut sequence_bananas: AHashMap<(i8, i8, i8, i8), Vec<i8>> = AHashMap::new();
for number in numbers {
let mut costs_and_changes: Vec<(i8, i8)> = vec![];
let mut curr_magic = *number;
for _ in 0..2000 {
let cost = (curr_magic % 10) as i8;
costs_and_changes.push((cost, costs_and_changes.last().map_or(0, |l| cost - l.0)));
evolve_number(&mut curr_magic);
}
let sequence_costs = costs_and_changes
.windows(4)
.skip(1)
.map(|w| ((w[0].1, w[1].1, w[2].1, w[3].1), w[3].0));
let mut seen_sequences = AHashSet::new();
for (k, v) in sequence_costs {
if seen_sequences.contains(&k) {
continue;
}
sequence_bananas
.entry(k)
.and_modify(|v2| v2.push(v))
.or_insert(vec![v]);
seen_sequences.insert(k);
}
}
sequence_bananas
.get(
sequence_bananas
.iter()
.max_by(|a, b| {
a.1.iter()
.map(|i| *i as isize)
.sum::<isize>()
.cmp(&b.1.iter().map(|i| *i as isize).sum::<isize>())
})
.unwrap()
.0,
)
.unwrap()
.iter()
.map(|i| *i as isize)
.sum()
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part1(&input));
println!("{}", part2(&input));
}

63
day23/Cargo.lock generated Normal file
View File

@ -0,0 +1,63 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day23"
version = "0.1.0"
dependencies = [
"itertools",
"petgraph",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "indexmap"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "petgraph"
version = "0.6.5"
source = "git+https://github.com/qoqosz/petgraph?branch=feature%2Fmaximal_cliques#cee17df1398adeae9f4696d3f4caba5662b37d63"
dependencies = [
"fixedbitset",
"indexmap",
]

8
day23/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "day23"
version = "0.1.0"
edition = "2021"
[dependencies]
itertools = "0.13.0"
petgraph = { git = "https://github.com/qoqosz/petgraph", branch = "feature/maximal_cliques"}

71
day23/src/main.rs Normal file
View File

@ -0,0 +1,71 @@
use std::collections::{HashMap, HashSet};
use itertools::Itertools;
use petgraph::algo::maximal_cliques;
use petgraph::prelude::*;
fn parse(input: &str) -> Vec<(String, String)> {
input
.lines()
.map(|l| l.split_once("-").unwrap())
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect()
}
fn build_graph(edges: Vec<(String, String)>) -> UnGraph<String, ()> {
let mut graph = UnGraph::new_undirected();
let mut node_indices = HashMap::new();
for (a, b) in edges {
let from_index = *node_indices
.entry(a.clone())
.or_insert_with(|| graph.add_node(a));
let to_index = *node_indices
.entry(b.clone())
.or_insert_with(|| graph.add_node(b));
graph.add_edge(from_index, to_index, ());
}
graph
}
fn part1(graph: &UnGraph<String, ()>) -> usize {
let mut cliques3 = HashSet::new();
for edge in graph.edge_references() {
for &w in graph
.neighbors(edge.source())
.collect::<HashSet<_>>()
.intersection(&graph.neighbors(edge.target()).collect::<HashSet<_>>())
{
let mut clique3 = [edge.source(), edge.target(), w];
clique3.sort_unstable();
cliques3.insert(clique3);
}
}
cliques3
.iter()
.filter(|clique| {
clique
.iter()
.any(|n_idx| graph.node_weight(*n_idx).unwrap().starts_with("t"))
})
.count()
}
fn part2(graph: &Graph<String, (), Undirected>) -> String {
maximal_cliques(graph)
.iter()
.max_by_key(|x| x.len())
.unwrap()
.iter()
.map(|n_idx| graph.node_weight(*n_idx).unwrap().to_string())
.sorted_unstable()
.join(",")
}
fn main() {
let input = parse(include_str!("../input.txt"));
let graph = build_graph(input);
println!("{}", part1(&graph));
println!("{}", part2(&graph));
}

7
day24/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day24"
version = "0.1.0"

6
day24/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "day24"
version = "0.1.0"
edition = "2021"
[dependencies]

105
day24/src/main.rs Normal file
View File

@ -0,0 +1,105 @@
use std::{cmp::Reverse, collections::HashMap};
#[derive(Debug, Clone, Eq, PartialEq)]
enum Gate {
InputGate(bool),
CalcGate(CalcGate),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum Operation {
And,
Or,
Xor,
}
impl From<&str> for Operation {
fn from(value: &str) -> Self {
match value {
"AND" => Self::And,
"OR" => Self::Or,
"XOR" => Self::Xor,
_ => panic!(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct CalcGate {
operation: Operation,
lhs: String,
rhs: String,
}
fn parse(input: &str) -> HashMap<String, Gate> {
let mut res = HashMap::new();
let (inputs, calcs) = input.split_once("\n\n").unwrap();
res.extend(inputs.lines().map(|l| {
let (name, val) = l.split_once(": ").unwrap();
(name.to_string(), Gate::InputGate(val == "1"))
}));
res.extend(calcs.lines().map(|l| {
let mut iter = l.split(' ');
let lhs = iter.next().unwrap();
let op = iter.next().unwrap();
let rhs = iter.next().unwrap();
let name = iter.nth(1).unwrap();
(
name.to_string(),
Gate::CalcGate(CalcGate {
operation: Operation::from(op),
lhs: lhs.to_string(),
rhs: rhs.to_string(),
}),
)
}));
res
}
fn get_gate_value(
gate: &str,
gates: &HashMap<String, Gate>,
cache: &mut HashMap<String, bool>,
) -> bool {
if let Some(v) = cache.get(gate) {
return *v;
}
let res = match gates.get(gate).unwrap() {
Gate::InputGate(val) => *val,
Gate::CalcGate(calc_gate) => match calc_gate.operation {
Operation::And => {
get_gate_value(&calc_gate.lhs, gates, cache)
& get_gate_value(&calc_gate.rhs, gates, cache)
}
Operation::Or => {
get_gate_value(&calc_gate.lhs, gates, cache)
| get_gate_value(&calc_gate.rhs, gates, cache)
}
Operation::Xor => {
get_gate_value(&calc_gate.lhs, gates, cache)
^ get_gate_value(&calc_gate.rhs, gates, cache)
}
},
};
cache.insert(gate.to_string(), res);
res
}
fn part1(gates: &HashMap<String, Gate>) -> usize {
let mut output_bits: Vec<_> = gates.keys().filter(|g| g.starts_with("z")).collect();
let mut cache = HashMap::new();
output_bits.sort_unstable_by_key(|v| Reverse(v.to_string()));
let mut res: usize = 0;
for bit in output_bits {
res <<= 1;
if get_gate_value(bit, gates, &mut cache) {
res += 1;
}
}
res
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part1(&input));
}

7
day25/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "day25"
version = "0.1.0"

6
day25/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "day25"
version = "0.1.0"
edition = "2021"
[dependencies]

49
day25/src/main.rs Normal file
View File

@ -0,0 +1,49 @@
#[derive(Debug, Clone)]
struct ParsedInput {
keys: Vec<[u8; 5]>,
locks: Vec<[u8; 5]>,
}
fn parse_key(grid: &[Vec<char>]) -> [u8; 5] {
let mut res = [0; 5];
for (l_idx, line) in grid.iter().skip(1).enumerate().rev().skip(1) {
for (c_idx, c) in line.iter().enumerate() {
if c == &'#' && res[c_idx] == 0 {
res[c_idx] = l_idx as u8 + 1;
}
}
}
res
}
fn parse(input: &str) -> ParsedInput {
let mut keys = vec![];
let mut locks = vec![];
for block in input.split("\n\n") {
let mut grid: Vec<Vec<char>> = block.lines().map(|l| l.chars().collect()).collect();
if grid[0][0] == '#' {
locks.push(parse_key(&grid));
} else {
grid.reverse();
keys.push(parse_key(&grid));
}
}
ParsedInput { keys, locks }
}
fn overlaps(key: &[u8; 5], lock: &[u8; 5]) -> bool {
key.iter().zip(lock.iter()).any(|(k, l)| 5 - k < *l)
}
fn part1(parsed_input: &ParsedInput) -> usize {
parsed_input
.locks
.iter()
.map(|l| parsed_input.keys.iter().filter(|k| !overlaps(k, l)).count())
.sum()
}
fn main() {
let input = parse(include_str!("../input.txt"));
println!("{}", part1(&input));
}