Compare commits

..

31 Commits

Author SHA1 Message Date
77fba87ef6 day25: complete solution sill need to finish day 21 for the last star!
Some checks failed
test / AoC 2024 (push) Failing after 2m15s
2024-12-24 21:35:05 -08:00
8b8ed2a323 day24: part 1 solution
Some checks failed
test / AoC 2024 (push) Failing after 4m8s
2024-12-23 21:37:50 -08:00
6efb9e0f83 day23: add pivot to bron-kerbosch, use more hashsets for speed
Some checks failed
test / AoC 2024 (push) Failing after 4m2s
2024-12-23 02:07:49 -08:00
237ca36381 day23: clippies
Some checks failed
test / AoC 2024 (push) Failing after 4m17s
2024-12-23 00:57:52 -08:00
d3bade1bdd day23: part 2 - bron kerbosch solution 2024-12-23 00:54:23 -08:00
50a197856a day23: part 2 working (i think) but too slow 2024-12-23 00:23:36 -08:00
d3ce12693b day23: part 1 solution
Some checks failed
test / AoC 2024 (push) Failing after 3m48s
2024-12-22 21:48:59 -08:00
d31f9725f5 day22: perf and clippies, down to < 1s
Some checks failed
test / AoC 2024 (push) Failing after 4m2s
2024-12-22 02:21:24 -08:00
c5857ed449 day22: improvements
Some checks failed
test / AoC 2024 (push) Failing after 6m37s
2024-12-21 23:49:07 -08:00
40d5e820bc grid: additional impls of Coord2d 2024-12-21 23:49:06 -08:00
5d518248a8 day20: clippies and performance 2024-12-21 23:49:06 -08:00
cebdbd1007 day20: part 2 solution (slow) 2024-12-21 23:49:05 -08:00
32937aaa0e day22: part 2 solution. 38s runtime but works. 2024-12-21 23:49:04 -08:00
c681727fb3 day22: part 1 solution 2024-12-21 23:46:08 -08:00
02fc154547 grid: use coord2d more consistently 2024-12-21 21:01:16 -08:00
13f61e3a2b part2: works up to the 4th iteration, then returns too high values
Some checks failed
test / AoC 2024 (push) Failing after 3m40s
I think the issue is not testing both possible paths for direction keypads, but doing this naively blows up the compute complexity (and I got the wrong answer)
2024-12-21 02:48:39 -08:00
8958ba9361 day21: part 1 solution
Some checks failed
test / AoC 2024 (push) Failing after 3m35s
2024-12-20 23:20:21 -08:00
e60f6effa3 day20: part 1 solution
Some checks failed
test / AoC 2024 (push) Failing after 4m38s
2024-12-20 20:56:14 -08:00
c2e3422544 day19: rayon - ez mode parallelization, speed * 6
All checks were successful
test / AoC 2024 (push) Successful in 3m16s
2024-12-18 23:09:51 -08:00
6b6dededc2 day19: clippies
All checks were successful
test / AoC 2024 (push) Successful in 3m25s
2024-12-18 22:45:10 -08:00
65d498f168 day19: complete solution
Some checks failed
test / AoC 2024 (push) Failing after 3m46s
2024-12-18 22:39:32 -08:00
cdb3a7261a day19: pre puzzle boilerplate 2024-12-18 20:56:36 -08:00
dbee7d91b6 day18: clippies
All checks were successful
test / AoC 2024 (push) Successful in 3m21s
2024-12-18 15:42:23 -08:00
0fee7c3594 ci: report errors correctly
Some checks failed
test / AoC 2024 (push) Failing after 3m11s
2024-12-18 15:36:02 -08:00
824d111b18 ci: don't use '-D warnings' except for clippy run
Some checks failed
test / AoC 2024 (push) Has been cancelled
2024-12-18 15:34:15 -08:00
a1dceb6ff1 day18: perf: swap dijkstra for bfs for ~30% improvement
All checks were successful
test / AoC 2024 (push) Successful in 1m41s
2024-12-18 15:31:12 -08:00
3712b32634 grid: row and column iterators 2024-12-18 15:31:07 -08:00
2a11e17d92 day18: improve brute for for binary search for big gainz
All checks were successful
test / AoC 2024 (push) Successful in 1m48s
also avoid unnecessary path tracking work
2024-12-18 11:35:22 -08:00
1c254fff93 Revert "cargo: optimize harder"
All checks were successful
test / AoC 2024 (push) Successful in 1m50s
This reverts commit 44108c4b35.
2024-12-18 00:53:51 -08:00
44108c4b35 cargo: optimize harder 2024-12-18 00:41:40 -08:00
b588837624 day18: small optimization - avoid duplicates
not sure if the input contains dupes, but avoid doing a second path
search if the square already contains a byte, seems like a small
performance gain
2024-12-18 00:41:28 -08:00
23 changed files with 1655 additions and 64 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -19,6 +19,7 @@ jobs:
cache: true
components: clippy, rustfmt
toolchain: stable
rustflags: ""
- name: install cargo-aoc
run: cargo install --locked cargo-aoc
@ -48,16 +49,14 @@ jobs:
- name: cargo test
run: cargo test --lib
continue-on-error: true
- name: rustfmt
run: cargo fmt --all -- --check
continue-on-error: true
- name: clippy
run: cargo clippy --lib --tests -- -D warnings
continue-on-error: true
if: always()
- name: full run
run: cargo run --release
continue-on-error: true
if: always()

View File

@ -1,6 +1,6 @@
<!-- AOC TILES BEGIN -->
<h1 align="center">
2024 - 36 ⭐ - Rust
2024 - 48 ⭐ - Rust
</h1>
<a href="src/day1.rs">
<img src=".aoc_tiles/tiles/2024/01.png" width="161px">
@ -56,4 +56,25 @@
<a href="src/day18.rs">
<img src=".aoc_tiles/tiles/2024/18.png" width="161px">
</a>
<a href="src/day19.rs">
<img src=".aoc_tiles/tiles/2024/19.png" width="161px">
</a>
<a href="src/day20.rs">
<img src=".aoc_tiles/tiles/2024/20.png" width="161px">
</a>
<a href="src/day21.rs">
<img src=".aoc_tiles/tiles/2024/21.png" width="161px">
</a>
<a href="src/day22.rs">
<img src=".aoc_tiles/tiles/2024/22.png" width="161px">
</a>
<a href="src/day23.rs">
<img src=".aoc_tiles/tiles/2024/23.png" width="161px">
</a>
<a href="src/day24.rs">
<img src=".aoc_tiles/tiles/2024/24.png" width="161px">
</a>
<a href="src/day25.rs">
<img src=".aoc_tiles/tiles/2024/25.png" width="161px">
</a>
<!-- AOC TILES END -->

View File

@ -25,7 +25,7 @@ impl TrailMap {
.iter()
.enumerate()
.filter(|(_, v)| **v == b'0')
.map(|(i, _v)| self.map.coord(i as i64).unwrap())
.map(|(i, _v)| self.map.coord(i as i64).unwrap().into())
.collect_vec()
}
fn count_reachable_from(&self, pos: &(i64, i64), needle: u8, visited: &mut Grid<bool>) -> u64 {

View File

@ -1,5 +1,5 @@
use aoc_runner_derive::aoc;
use grid::Grid;
use grid::{Coord2d, Grid};
use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap},
@ -108,10 +108,10 @@ impl Maze {
})
}
fn dijkstra(&self) -> usize {
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start");
let Coord2d {x: start_x, y: start_y} = self.map.find(&b'S').expect("can't find start");
let start = (start_x as CoordType, start_y as CoordType);
let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish");
let Coord2d {x: finish_x, y: finish_y} = self.map.find(&b'E').expect("can't find finish");
let finish = (finish_x as CoordType, finish_y as CoordType);
let mut distances = HashMap::new();
@ -149,10 +149,11 @@ impl Maze {
usize::MAX
}
fn path_dijkstra(&mut self) -> (usize, Vec<Vec<Coord>>) {
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start");
let start = (start_x.try_into().unwrap(), start_y.try_into().unwrap());
let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish");
let finish = (finish_x.try_into().unwrap(), finish_y.try_into().unwrap());
let Coord2d {x: start_x, y: start_y} = self.map.find(&b'S').expect("can't find start");
let start = (start_x as CoordType, start_y as CoordType);
let Coord2d {x: finish_x, y: finish_y} = self.map.find(&b'E').expect("can't find finish");
let finish = (finish_x as CoordType, finish_y as CoordType);
let mut distances = HashMap::new();
let mut queue = BinaryHeap::with_capacity(self.map.data.len());

View File

@ -1,7 +1,10 @@
use aoc_runner_derive::aoc;
use grid::Grid;
use itertools::Itertools;
use std::{cmp::Reverse, collections::BinaryHeap};
use std::{
cmp::Reverse,
collections::{BinaryHeap, VecDeque},
};
#[derive(Clone)]
struct MemoryMap {
@ -10,6 +13,7 @@ struct MemoryMap {
}
trait PathTrack {
const DOES_WORK: bool = true;
fn new() -> Self;
fn push(&mut self, pos: (i64, i64));
fn finalize(&mut self) {}
@ -37,6 +41,15 @@ impl PathTrack for Vec<(i64, i64)> {
}
}
struct NoopTrack {}
impl PathTrack for NoopTrack {
const DOES_WORK: bool = false;
fn new() -> Self {
Self {}
}
fn push(&mut self, _: (i64, i64)) {}
}
impl MemoryMap {
fn from_str(input: &str, width: usize, height: usize) -> Self {
let map = Grid::with_shape(width, height, true);
@ -51,15 +64,14 @@ impl MemoryMap {
Self { map, byte_stream }
}
fn place_byte(&mut self, i: usize) {
let pos = self.byte_stream[i];
if self.map.set(&pos, false).is_none() {
panic!("corruption outside memory bounds");
}
self.map.set(&pos, false);
}
fn place_bytes(&mut self, n: usize) {
assert!(n < self.byte_stream.len());
for i in 0..n {
fn place_bytes(&mut self, start: usize, end: usize) {
for i in start..=end {
self.place_byte(i);
}
}
@ -71,6 +83,54 @@ impl MemoryMap {
.map(|ofs| (pos.0 + ofs.0, pos.1 + ofs.1))
}
fn bfs<T: PathTrack>(&self, start: (i64, i64)) -> Option<T> {
let goal = (self.map.width() as i64 - 1, self.map.height() as i64 - 1);
let mut visited = self.map.same_shape(false);
let mut prev = self.map.same_shape((i64::MAX, i64::MAX));
let mut queue = VecDeque::new();
visited.set(&start, true);
queue.push_back((0, start));
while let Some((depth, pos)) = queue.pop_front() {
if pos == goal {
if T::DOES_WORK {
let mut visited_pos = goal;
let mut path = T::new();
path.push(pos);
while let Some(next) = prev.get(&visited_pos) {
visited_pos = *next;
path.push(*next);
if *next == start {
path.finalize();
return Some(path);
}
}
} else {
return Some(T::new());
}
}
// if visited.get(&pos).is_some_and(|v| *v) {
// continue;
// }
let moves = self.valid_moves(&pos);
for new_pos in moves {
if visited.get(&new_pos).is_none_or(|v| !v) {
visited.set(&new_pos, true);
if T::DOES_WORK {
prev.set(&new_pos, pos);
}
queue.push_back((depth + 1, new_pos));
}
}
}
None
}
#[allow(dead_code)] // will be moved to Grid at some point
fn dijkstra<T: PathTrack>(&self, start: (i64, i64)) -> Option<T> {
let goal = (self.map.width() as i64 - 1, self.map.height() as i64 - 1);
@ -83,16 +143,20 @@ impl MemoryMap {
while let Some((cost, pos)) = queue.pop() {
if pos == goal {
let mut visited_pos = goal;
let mut path = T::new();
path.push(pos);
while let Some(next) = prev.get(&visited_pos) {
visited_pos = *next;
path.push(*next);
if *next == start {
path.finalize();
return Some(path);
if T::DOES_WORK {
let mut visited_pos = goal;
let mut path = T::new();
path.push(pos);
while let Some(next) = prev.get(&visited_pos) {
visited_pos = *next;
path.push(*next);
if *next == start {
path.finalize();
return Some(path);
}
}
} else {
return Some(T::new());
}
}
@ -104,7 +168,9 @@ impl MemoryMap {
for new_pos in moves {
if costs.get(&new_pos).is_none_or(|best_cost| cost.0 + 1 < *best_cost) {
costs.set(&new_pos, cost.0 + 1);
prev.set(&new_pos, pos);
if T::DOES_WORK {
prev.set(&new_pos, pos);
}
queue.push((Reverse(cost.0 + 1), new_pos));
}
}
@ -113,28 +179,28 @@ impl MemoryMap {
}
}
pub fn part1_impl(input: &str, width: usize, height: usize, n: usize) -> usize {
pub fn part1_impl(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> usize {
let mut map = MemoryMap::from_str(input, width, height);
map.place_bytes(n);
let path = map.dijkstra::<LengthPath>((0, 0)).expect("no path found");
map.place_bytes(0, initial_safe_byte_count - 1);
let path = map.bfs::<LengthPath>((0, 0)).expect("no path found");
path.0 - 1 // count edges, not visited nodes (start doesn't count)
}
pub fn part2_impl(input: &str, width: usize, height: usize, n: usize) -> (i64, i64) {
// My original devised solution
pub fn part2_impl_brute(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> (i64, i64) {
let mut input_map = MemoryMap::from_str(input, width, height);
input_map.place_bytes(0, initial_safe_byte_count - 1);
input_map.place_bytes(n);
let mut path = input_map.dijkstra::<Vec<(i64, i64)>>((0, 0)).expect("no path found");
println!("{:?}", path);
let mut path = input_map.bfs::<Vec<(i64, i64)>>((0, 0)).expect("no path found");
for byte in n..input_map.byte_stream.len() {
for byte in initial_safe_byte_count..input_map.byte_stream.len() {
input_map.place_byte(byte);
// If it obstructs our best path, we need to do a new path search
if let Some((obs_at, _)) = path.iter().find_position(|v| *v == &input_map.byte_stream[byte]) {
let (before, _) = path.split_at(obs_at);
if let Some(new_path) = input_map.dijkstra::<Vec<(i64, i64)>>(path[obs_at - 1]) {
if let Some(new_path) = input_map.bfs::<Vec<(i64, i64)>>(path[obs_at - 1]) {
path = [before, &new_path].concat();
} else {
return input_map.byte_stream[byte];
@ -144,6 +210,25 @@ pub fn part2_impl(input: &str, width: usize, height: usize, n: usize) -> (i64, i
panic!("no bytes block route");
}
// Optimized based on others' ideas
pub fn part2_impl(input: &str, width: usize, height: usize, initial_safe_byte_count: usize) -> (i64, i64) {
let mut input_map = MemoryMap::from_str(input, width, height);
input_map.place_bytes(0, initial_safe_byte_count - 1);
// for the unplaced bytes, binary search for the partition point, given the predicate that a path is reachable
// when all bytes up to that n have been placed
let possible_problems = (initial_safe_byte_count..input_map.byte_stream.len()).collect_vec();
let solution = possible_problems.partition_point(|byte| {
// avoiding this clone by rolling back the byte placements instead is slower
let mut local_map = input_map.clone();
local_map.place_bytes(initial_safe_byte_count, *byte);
local_map.bfs::<NoopTrack>((0, 0)).is_some()
}) + initial_safe_byte_count;
input_map.byte_stream[solution]
}
#[aoc(day18, part1)]
pub fn part1(input: &str) -> usize {
part1_impl(input, 71, 71, 1024)
@ -193,4 +278,9 @@ mod tests {
fn part2_example() {
assert_eq!(part2_impl(EXAMPLE, 7, 7, 12), (6, 1));
}
#[test]
fn part2_example_brute() {
assert_eq!(part2_impl_brute(EXAMPLE, 7, 7, 12,), (6, 1));
}
}

192
src/day19.rs Normal file
View File

@ -0,0 +1,192 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rustc_hash::FxHashMap;
use std::fmt::{Display, Write};
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
enum Stripe {
White = b'w',
Blue = b'u',
Black = b'b',
Red = b'r',
Green = b'g',
}
impl From<&u8> for Stripe {
fn from(value: &u8) -> Self {
match value {
b'w' => Self::White,
b'u' => Self::Blue,
b'b' => Self::Black,
b'r' => Self::Red,
b'g' => Self::Green,
_ => unimplemented!(),
}
}
}
impl From<&Stripe> for char {
fn from(val: &Stripe) -> Self {
let v = *val as u8;
v.into()
}
}
impl Display for Stripe {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_char(self.into())
}
}
#[derive(Debug, Clone)]
struct Design {
stripes: Vec<Stripe>,
}
impl From<&[u8]> for Design {
fn from(input: &[u8]) -> Self {
let stripes = input.iter().map(|c| c.into()).collect();
Self { stripes }
}
}
impl Display for Design {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for stripe in &self.stripes {
f.write_char(stripe.into())?;
}
Ok(())
}
}
#[derive(Debug)]
struct Onsen {
towels: Vec<Design>,
designs: Vec<Design>,
}
impl Display for Onsen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.towels.iter().join(", "))?;
writeln!(f)?;
writeln!(f)?;
for d in &self.designs {
d.fmt(f)?;
writeln!(f)?;
}
Ok(())
}
}
impl Onsen {
fn possible(&self, d: &[Stripe]) -> bool {
if d.is_empty() {
return true;
}
for t in &self.towels {
if d.starts_with(&t.stripes) && self.possible(d.split_at(t.stripes.len()).1) {
return true;
}
}
false
}
// Count the ways to construct a given substring
fn ways<'a>(
&'a self,
d: &'a [Stripe],
mut cache: FxHashMap<&'a [Stripe], i64>,
) -> (FxHashMap<&'a [Stripe], i64>, i64) {
if d.is_empty() {
return (cache, 1);
}
if cache.contains_key(d) {
let val = cache[d];
return (cache, val);
}
let mut count = 0;
for t in &self.towels {
if d.starts_with(&t.stripes) {
let res_count;
(cache, res_count) = self.ways(&d[t.stripes.len()..d.len()], cache);
count += res_count;
}
}
cache.insert(d, count);
(cache, count)
}
fn count_possible(&self) -> i64 {
self.designs
.clone()
.into_par_iter()
.map(|d| self.possible(&d.stripes))
.filter(|p| *p)
.count() as i64
}
fn count_ways(&self) -> i64 {
self.designs
.clone()
.into_par_iter()
.map(|d| self.ways(&d.stripes, FxHashMap::default()).1)
.sum::<i64>()
}
}
fn parse(input: &str) -> Onsen {
let mut lines = input.lines();
let towels = lines
.next()
.unwrap()
.split(&[',', ' '])
.filter(|s| !s.is_empty())
.map(|s| s.as_bytes().into())
.collect();
lines.next().unwrap(); // discard empty line
let designs = lines.map(|l| l.as_bytes().into()).collect();
Onsen { towels, designs }
}
#[aoc(day19, part1)]
fn part1(input: &str) -> i64 {
let onsen = parse(input);
onsen.count_possible()
}
#[aoc(day19, part2)]
fn part2(input: &str) -> i64 {
let onsen = parse(input);
onsen.count_ways()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "r, wr, b, g, bwu, rb, gb, br
brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 6);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), 16);
}
}

186
src/day20.rs Normal file
View File

@ -0,0 +1,186 @@
use aoc_runner_derive::aoc;
use grid::{AsCoord2d, Coord2d, Grid};
use itertools::Itertools;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::collections::VecDeque;
struct RaceTrack {
map: Grid<u8>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
struct State {
pos: Coord2d,
cost: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct CheatState {
s: State,
}
const DIRECTIONS: [(i64, i64); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
impl RaceTrack {
fn valid_moves<'a>(&'a self, CheatState { s: state }: &'a CheatState) -> impl Iterator<Item = CheatState> + 'a {
DIRECTIONS
.iter()
.map(|dir| state.pos + dir)
.filter_map(move |pos| match &self.map.get(&pos) {
Some(b'.') | Some(b'S') | Some(b'E') => Some(CheatState {
s: State {
pos,
cost: state.cost + 1,
},
}),
_ => None,
})
}
fn path_costs(&self, start: Coord2d, goal: Coord2d) -> Grid<Option<u64>> {
let mut queue = VecDeque::new();
let mut visited = self.map.same_shape(None);
let start_state = CheatState {
s: State { pos: start, cost: 0 },
};
visited.set(&start, Some(0));
queue.push_back(start_state);
while let Some(state) = queue.pop_front() {
if state.s.pos == goal {
return visited;
}
let moves = self.valid_moves(&state);
for new_state in moves {
if visited.get(&new_state.s.pos).unwrap().is_some() {
continue;
}
visited.set(&new_state.s.pos, Some(new_state.s.cost));
queue.push_back(new_state);
}
}
panic!("no path");
}
fn find_cheats(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, min: u64) -> i64 {
let mut n = 0;
for pos in path {
let local_cost = costs.get(pos).unwrap().unwrap();
for ofs in DIRECTIONS {
let cheat_exit = (pos.x() + ofs.0 * 2, pos.y() + ofs.1 * 2);
if let Some(Some(cheat_cost)) = costs.get(&cheat_exit) {
if *cheat_cost > local_cost + 2 {
let cheat_savings = cheat_cost - local_cost - 2;
if cheat_savings >= min {
n += 1;
}
}
}
}
}
n
}
fn taxi_dist<A: AsCoord2d, B: AsCoord2d>(from: &A, to: &B) -> u64 {
from.x().abs_diff(to.x()) + from.y().abs_diff(to.y())
}
fn find_cheats_n(&self, path: &Vec<Coord2d>, costs: &Grid<Option<u64>>, max_length: u64, min: u64) -> i64 {
path.par_iter()
.map_with(costs, |costs, pos| {
let from_cost = costs.get(pos).unwrap().unwrap();
let mut n = 0;
for x in pos.x - max_length as i64 - 1..=pos.x + max_length as i64 {
for y in pos.y - max_length as i64 - 1..=pos.y + max_length as i64 {
let dist = Self::taxi_dist(pos, &(x, y));
if dist <= max_length && dist >= 2 {
if let Some(Some(to_cost)) = costs.get(&(x, y)) {
if *to_cost > (from_cost + dist) && (to_cost - (from_cost + dist) >= min) {
n += 1;
}
}
}
}
}
n
})
.sum()
}
}
fn parse(input: &str) -> RaceTrack {
let map = input.as_bytes().into();
RaceTrack { map }
}
fn part1_impl(input: &str, cheat_min: u64) -> i64 {
let track = parse(input);
let start = track.map.find(&b'S').unwrap();
let goal = track.map.find(&b'E').unwrap();
let costs = track.path_costs(start, goal);
let path_squares = costs
.data
.iter()
.enumerate()
.filter(|(_i, c)| c.is_some())
.filter_map(|(i, _)| track.map.coord(i as i64))
.collect_vec();
track.find_cheats(&path_squares, &costs, cheat_min)
}
fn part2_impl(input: &str, max_length: u64, cheat_min: u64) -> i64 {
let track = parse(input);
let start = track.map.find(&b'S').unwrap();
let goal = track.map.find(&b'E').unwrap();
let costs = track.path_costs(start, goal);
let path_squares = costs
.data
.iter()
.enumerate()
.filter(|(_i, c)| c.is_some())
.filter_map(|(i, _)| track.map.coord(i as i64))
.collect_vec();
track.find_cheats_n(&path_squares, &costs, max_length, cheat_min)
}
#[aoc(day20, part1)]
pub fn part1(input: &str) -> i64 {
part1_impl(input, 100)
}
#[aoc(day20, part2)]
pub fn part2(input: &str) -> i64 {
part2_impl(input, 20, 100)
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############";
#[test]
fn part1_example() {
assert_eq!(part1_impl(EXAMPLE, 0), 44);
}
#[test]
fn part2_example() {
assert_eq!(part2_impl(EXAMPLE, 2, 0), 44);
assert_eq!(part2_impl(EXAMPLE, 20, 50), 285);
}
}

240
src/day21.rs Normal file
View File

@ -0,0 +1,240 @@
use aoc_runner_derive::aoc;
use rustc_hash::FxHashMap;
use std::iter::repeat_n;
#[derive(Clone, Debug)]
enum KeypadRobot {
Number(NumberKeypadRobot),
Direction(DirectionKeypadRobot),
}
impl KeypadRobot {
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
match self {
Self::Number(r) => r.press(target),
Self::Direction(r) => r.press(target),
}
}
}
#[derive(Clone, Debug)]
struct NumberKeypadRobot {
pointing_at: u8,
}
impl NumberKeypadRobot {
fn pos_of(button: u8) -> (i8, i8) {
match button {
b'7' => (0, 0),
b'8' => (1, 0),
b'9' => (2, 0),
b'4' => (0, 1),
b'5' => (1, 1),
b'6' => (2, 1),
b'1' => (0, 2),
b'2' => (1, 2),
b'3' => (2, 2),
b'X' => (0, 3),
b'0' => (1, 3),
b'A' => (2, 3),
c => unimplemented!("unexpected character {}", c),
}
}
}
impl NumberKeypadRobot {
fn new() -> Self {
Self { pointing_at: b'A' }
}
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
let cur_pos = Self::pos_of(self.pointing_at);
let goal_pos = Self::pos_of(target);
let x_ofs = goal_pos.0 - cur_pos.0;
let y_ofs = goal_pos.1 - cur_pos.1;
let mut paths = Vec::new();
// NOTE: no need to consider zig-zags since those paths will always require more button presses going back and forth
if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') {
let mut x_first = Vec::new();
x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
x_first.push(b'A');
paths.push({
let mut h = FxHashMap::default();
h.insert(x_first, 1);
h
});
}
if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') {
let mut y_first = Vec::new();
y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
y_first.push(b'A');
paths.push({
let mut h = FxHashMap::default();
h.insert(y_first, 1);
h
});
}
if paths.is_empty() {
panic!("all paths lead to the void");
}
paths.dedup();
self.pointing_at = target;
paths
}
}
#[derive(Clone, Debug)]
struct DirectionKeypadRobot {
pointing_at: u8,
child: Option<Box<KeypadRobot>>,
id: usize,
}
impl DirectionKeypadRobot {
fn pos_of(target: u8) -> (i8, i8) {
match target {
b'X' => (0, 0),
b'^' => (1, 0),
b'A' => (2, 0),
b'<' => (0, 1),
b'v' => (1, 1),
b'>' => (2, 1),
c => unimplemented!("unexpected char {}", c),
}
}
fn move_to(&mut self, target: u8) -> Vec<u8> {
let cur_pos = Self::pos_of(self.pointing_at);
let goal_pos = Self::pos_of(target);
let x_ofs = goal_pos.0 - cur_pos.0;
let y_ofs = goal_pos.1 - cur_pos.1;
self.pointing_at = target;
if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') {
let mut x_first = Vec::new();
x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
x_first.push(b'A');
return x_first;
}
if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') {
let mut y_first = Vec::new();
y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize));
y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize));
y_first.push(b'A');
return y_first;
}
panic!("all routes lead to the void");
}
fn path_to(&mut self, moves: &Vec<u8>) -> Vec<u8> {
let prev_point = self.pointing_at;
let mut path = Vec::new();
for m in moves {
path.append(&mut self.move_to(*m));
}
self.pointing_at = prev_point;
path
}
fn new(id: usize, child: Option<Box<KeypadRobot>>) -> Self {
Self {
id,
pointing_at: b'A',
child,
}
}
fn press(&mut self, target: u8) -> Vec<FxHashMap<Vec<u8>, usize>> {
let child_frequencies = self.child.as_mut().unwrap().press(target);
let mut my_frequencies = Vec::new();
for freq_set in child_frequencies {
let mut local_freqs = FxHashMap::default();
for (moves, count) in freq_set {
assert_eq!(self.pointing_at, b'A');
let path = self.path_to(&moves);
for path_move in path.split_inclusive(|m| *m == b'A') {
let entry = local_freqs.entry(path_move.to_vec()).or_insert(0);
*entry = *entry + count;
}
}
my_frequencies.push(local_freqs);
}
my_frequencies
}
}
struct Code(Vec<u8>);
impl Code {
fn num_val(&self) -> i64 {
String::from_utf8_lossy(&self.0.as_slice()[0..3]).parse().unwrap()
}
}
fn parse(input: &str) -> Vec<Code> {
let mut codes = Vec::new();
for code in input.lines() {
codes.push(Code(code.as_bytes().to_vec()))
}
codes
}
fn run_robots(code: &Code, n: usize) -> i64 {
let numpad = Box::new(KeypadRobot::Number(NumberKeypadRobot::new()));
let mut robot = Box::new(KeypadRobot::Direction(DirectionKeypadRobot::new(0, Some(numpad))));
for i in 1..n {
let new_robot = Box::new(KeypadRobot::Direction(DirectionKeypadRobot::new(i, Some(robot))));
robot = new_robot
}
// let mut sol_freqs = Vec::new();
let mut sum = 0;
for button in &code.0 {
let paths = robot.press(*button).to_vec();
let best = paths
.iter()
.map(|bp| bp.iter().map(|(k, v)| k.len() * v).sum::<usize>())
.min()
.unwrap();
sum += best;
}
return sum as i64 * code.num_val();
}
#[aoc(day21, part1)]
fn part1(input: &str) -> i64 {
let codes = parse(input);
codes.iter().map(|c| run_robots(c, 2)).sum::<i64>() as i64
}
#[aoc(day21, part2)]
fn part2(input: &str) -> i64 {
let codes = parse(input);
for i in 0..26 {
let res = codes.iter().map(|c| run_robots(c, i)).sum::<i64>() as i64;
println!("{i}: {res}");
}
0
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "029A
980A
179A
456A
379A";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 126384);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), 0);
}
}

155
src/day22.rs Normal file
View File

@ -0,0 +1,155 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rustc_hash::FxHashMap;
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
struct Change {
price: i8,
delta: i8,
}
type Secret = u64;
fn evolve_secret(mut n: Secret) -> Secret {
n = ((n * 64) ^ n) % 16777216;
n = ((n / 32) ^ n) % 16777216;
n = ((n * 2048) ^ n) % 16777216;
n
}
fn rounds(mut secret: Secret, n: Secret) -> Secret {
for _ in 0..n {
secret = evolve_secret(secret)
}
secret
}
fn prices(mut secret: Secret, n: usize) -> Vec<i8> {
let mut prices = vec![(secret % 10) as i8];
for _ in 1..n {
secret = evolve_secret(secret);
prices.push((secret % 10) as i8);
}
prices
}
fn build_profit_map(prices: &[i8]) -> FxHashMap<[i8; 4], i8> {
let mut profits = FxHashMap::default();
let changes = prices
.windows(2)
.map(|a| Change {
price: a[1],
delta: a[1] - a[0],
})
.collect_vec();
for i in 3..changes.len() {
let seq: [i8; 4] = changes[i - 3..=i]
.iter()
.map(|c| c.delta)
.collect_vec()
.try_into()
.unwrap();
profits.entry(seq).or_insert(changes[i].price);
}
profits
}
fn profit_for_sequence(changes: &[FxHashMap<[i8; 4], i8>], seq: &[i8]) -> i64 {
changes
.iter()
.filter_map(|inner| inner.get(seq).map(|v| *v as i64))
.sum()
}
fn find_best_sequence(changes: &[FxHashMap<[i8; 4], i8>]) -> [i8; 4] {
let possible_seqs = (0..4).map(|_| (-9..=9i8)).multi_cartesian_product().collect_vec();
let (best_seq, _best_profit) = possible_seqs
.par_iter()
.map_with(changes, |changes, seq| (seq, profit_for_sequence(changes, seq)))
.max_by(|(_, a), (_, b)| a.cmp(b))
.unwrap();
best_seq.as_slice().try_into().unwrap()
}
fn parse(input: &str) -> Vec<Secret> {
input.lines().map(|l| l.parse().unwrap()).collect()
}
#[aoc(day22, part1)]
pub fn part1(input: &str) -> Secret {
let secrets = parse(input);
secrets.iter().map(|s| rounds(*s, 2000)).sum::<Secret>()
}
#[aoc(day22, part2)]
pub fn part2(input: &str) -> i64 {
let secrets = parse(input);
let price_changes = secrets
.iter()
.map(|s| build_profit_map(&prices(*s, 2000)))
.collect_vec();
let seq = find_best_sequence(&price_changes);
profit_for_sequence(&price_changes, &seq)
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "1
10
100
2024";
const EXAMPLE2: &str = "1
2
3
2024";
#[test]
fn evolution() {
assert_eq!(evolve_secret(123), 15887950);
assert_eq!(evolve_secret(15887950), 16495136);
assert_eq!(evolve_secret(16495136), 527345);
}
#[test]
fn test_rounds() {
assert_eq!(rounds(1, 2000), 8685429);
assert_eq!(rounds(10, 2000), 4700978);
}
#[test]
fn test_prices() {
assert_eq!(prices(123, 10), vec![3, 0, 6, 5, 4, 4, 6, 4, 4, 2]);
}
#[test]
fn test_profit() {
assert_eq!(
profit_for_sequence(&vec![build_profit_map(&prices(123, 10))], &[-1, -1, 0, 2]),
6
);
let secrets = parse(EXAMPLE2);
let price_changes = secrets
.iter()
.map(|s| build_profit_map(&prices(*s, 2000)))
.collect_vec();
assert_eq!(profit_for_sequence(&price_changes, &[-2, 1, -1, 3]), 23);
}
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 37327623);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE2), 23);
}
}

194
src/day23.rs Normal file
View File

@ -0,0 +1,194 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fmt::{Debug, Display};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
struct Node([char; 2]);
impl Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.0.iter().join("")))
}
}
impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.0.iter().join("")))
}
}
impl From<[char; 2]> for Node {
fn from(value: [char; 2]) -> Self {
Self(value)
}
}
impl TryFrom<Vec<char>> for Node {
type Error = <[char; 2] as TryFrom<Vec<char>>>::Error;
fn try_from(value: Vec<char>) -> Result<Self, Self::Error> {
let array: [char; 2] = value.try_into()?;
Ok(Self(array))
}
}
struct Network {
nodes: FxHashSet<Node>,
edges: FxHashMap<Node, FxHashSet<Node>>,
}
impl Network {
fn groups_3(&self) -> FxHashSet<Vec<Node>> {
let mut sets = FxHashSet::default();
for n in &self.nodes {
let neighbours = self.edges.get(n).unwrap();
for neigh in neighbours {
let neighbours2 = self.edges.get(neigh).unwrap();
for neigh2 in neighbours2 {
let neighbours3 = self.edges.get(neigh2).unwrap();
if neighbours3.contains(n) {
let mut set = vec![*n, *neigh, *neigh2];
set.sort();
sets.insert(set);
}
}
}
}
sets
}
// Had to study Wikipedia for this one
// https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
fn bron_kerbosch(
&self,
r: FxHashSet<Node>,
mut p: FxHashSet<Node>,
mut x: FxHashSet<Node>,
) -> Vec<FxHashSet<Node>> {
let mut results = Vec::new();
if p.is_empty() && x.is_empty() {
return vec![r];
} else if p.is_empty() {
return Vec::new();
}
// choose the pivot with the most neighbours, to minimize the size of p_iter
let p_iter = if let Some(pivot) = p.union(&x).max_by(|a, b| self.edges[a].len().cmp(&self.edges[b].len())) {
FxHashSet::from_iter(p.difference(self.edges.get(pivot).unwrap()).copied())
} else {
p.clone()
};
for node in &p_iter {
let mut new_r = r.clone();
new_r.insert(*node);
let neighbours = FxHashSet::from_iter(self.edges.get(node).unwrap().iter().copied());
let new_p = FxHashSet::from_iter(p.intersection(&neighbours).copied());
let new_x = FxHashSet::from_iter(x.intersection(&neighbours).copied());
results.extend(self.bron_kerbosch(new_r, new_p, new_x).into_iter());
p.remove(node);
x.insert(*node);
}
results
}
fn maximal_subgraphs(&self) -> Vec<FxHashSet<Node>> {
self.bron_kerbosch(
FxHashSet::default(),
FxHashSet::from_iter(self.nodes.iter().copied()),
FxHashSet::default(),
)
}
}
impl From<&str> for Network {
fn from(input: &str) -> Self {
let mut nodes = FxHashSet::default();
let mut edges = FxHashMap::default();
for line in input.lines() {
let (node1, node2) = line.split_once('-').unwrap();
let (node1, node2): (Node, Node) = (
node1.chars().collect_vec().try_into().unwrap(),
node2.chars().collect_vec().try_into().unwrap(),
);
if !nodes.contains(&node1) {
nodes.insert(node1);
}
if !nodes.contains(&node2) {
nodes.insert(node2);
}
edges.entry(node1).or_insert(FxHashSet::default()).insert(node2);
edges.entry(node2).or_insert(FxHashSet::default()).insert(node1);
}
Self { nodes, edges }
}
}
fn parse(input: &str) -> Network {
input.into()
}
#[aoc(day23, part1)]
pub fn part1(input: &str) -> i64 {
let network = parse(input);
let sets = network.groups_3();
let t_count = sets.iter().filter(|set| set.iter().any(|s| s.0[0] == 't')).count();
t_count as i64
}
#[aoc(day23, part2)]
pub fn part2(input: &str) -> String {
let network = parse(input);
let best_sets = network.maximal_subgraphs();
let largest_set = best_sets.iter().max_by(|a, b| a.len().cmp(&b.len())).unwrap();
let mut largest = largest_set.iter().collect_vec();
largest.sort();
largest.iter().join(",")
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 7);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), "co,de,ka,ta");
}
}

229
src/day24.rs Normal file
View File

@ -0,0 +1,229 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use nom::And;
use regex::Regex;
use rustc_hash::FxHashMap;
#[derive(Copy, Clone, Debug)]
enum Op {
And,
Or,
Xor,
Constant,
}
impl From<&str> for Op {
fn from(value: &str) -> Self {
match value {
"AND" => Self::And,
"OR" => Self::Or,
"XOR" => Self::Xor,
s => panic!("invalid operation {}", s),
}
}
}
#[derive(Clone, Debug)]
struct Gate {
op: Op,
value: Option<bool>,
arguments: [String; 2],
}
impl Gate {
fn eval(&self, machine: &GateMachine) -> bool {
match self.op {
Op::And => machine.val_of(&self.arguments[0]) && machine.val_of(&self.arguments[1]),
Op::Or => machine.val_of(&self.arguments[0]) || machine.val_of(&self.arguments[1]),
Op::Xor => machine.val_of(&self.arguments[0]) ^ machine.val_of(&self.arguments[1]),
Op::Constant => self.value.unwrap(),
}
}
}
#[derive(Debug)]
struct GateMachine {
gates: FxHashMap<String, Gate>,
}
impl GateMachine {
fn val_of(&self, gate: &str) -> bool {
println!("gate: {}", gate);
if let Some(val) = self.gates[gate].value {
val
} else {
self.gates[gate].eval(self)
}
}
}
fn parse(input: &str) -> GateMachine {
let mut gates = FxHashMap::default();
for line in input.lines() {
println!("{line}");
let const_re = Regex::new(r"^([xyz][0-9]{2}): ([01])$").unwrap();
let gate_re = Regex::new(r"^([a-z0-9]{3}) (AND|XOR|OR) ([a-z0-9]{3}) -> ([a-z0-9]{3})$").unwrap();
if let Some(caps) = const_re.captures(line) {
println!(" is const: {:?}", caps);
gates.insert(
caps[1].to_string(),
Gate {
op: Op::Constant,
value: if &caps[2] == "1" { Some(true) } else { Some(false) },
arguments: [String::new(), String::new()],
},
);
} else if let Some(caps) = gate_re.captures(line) {
println!(" is gate: {:?}", caps);
gates.insert(
caps[4].to_string(),
Gate {
op: Op::from(&caps[2]),
value: None,
arguments: [caps[1].to_string(), caps[3].to_string()],
},
);
}
}
GateMachine { gates }
}
#[aoc(day24, part1)]
pub fn part1(input: &str) -> i64 {
let machine = parse(input);
let z_gates = machine
.gates
.keys()
.filter(|k| k.starts_with('z'))
.map(|s| (s, s.split_at(1).1.parse::<usize>().unwrap()));
let bit_vals = z_gates
.map(|(name, bit)| if machine.val_of(name) { 1 << bit } else { 0 })
.fold(0, |accum, val| accum | val);
bit_vals
}
#[aoc(day24, part2)]
pub fn part2(input: &str) -> i64 {
0
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE1: &str = "x00: 1
x01: 1
x02: 1
y00: 0
y01: 1
y02: 0
x00 AND y00 -> z00
x01 XOR y01 -> z01x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1
ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj
x02 OR y02 -> z02";
const EXAMPLE2: &str = "x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1
ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE1), 4);
assert_eq!(part1(EXAMPLE2), 2024);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE1), 0);
}
}

143
src/day25.rs Normal file
View File

@ -0,0 +1,143 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
enum LockKey {
Lock,
Key,
}
#[derive(Clone, Debug)]
struct LockPile {
keys: Vec<Vec<usize>>,
locks: Vec<Vec<usize>>,
}
fn parse_grid(lines: &Vec<&str>) -> (LockKey, Vec<usize>) {
assert_eq!(lines.len(), 7);
if lines[0].chars().all(|c| c == '#') {
// lock
let mut pins = vec![0; 5];
for row in 1..lines.len() {
let row_s = lines[row];
for i in 0..row_s.len() {
if row_s.chars().nth(i) == Some('#') {
pins[i] = row
}
}
}
(LockKey::Lock, pins)
} else if lines[6].chars().all(|c| c == '#') {
// key
let mut pins = vec![5; 5];
for row in (1..lines.len()).rev() {
let row_s = lines[row];
for i in 0..row_s.len() {
if row_s.chars().nth(i) == Some('#') {
pins[i] = 6 - row
}
}
}
(LockKey::Key, pins)
} else {
panic!("not a lock or a key: {:?}", lines);
}
}
fn parse(input: &str) -> LockPile {
let mut locks = Vec::new();
let mut keys = Vec::new();
let mut accum: Vec<&str> = Vec::new();
for line in input.lines() {
if line == "" {
let (lk, pins) = parse_grid(&accum);
match lk {
LockKey::Lock => locks.push(pins),
LockKey::Key => keys.push(pins),
}
accum.clear();
} else {
accum.push(line);
}
}
if accum.len() != 0 {
let (lk, pins) = parse_grid(&accum);
match lk {
LockKey::Lock => locks.push(pins),
LockKey::Key => keys.push(pins),
}
}
LockPile { keys, locks }
}
fn test_lock_key(lock: &Vec<usize>, key: &Vec<usize>) -> bool {
!lock.iter().zip(key.iter()).any(|(lp, kp)| lp + kp > 5)
}
#[aoc(day25, part1)]
pub fn part1(input: &str) -> i64 {
let lockpile = parse(input);
lockpile
.locks
.iter()
.cartesian_product(lockpile.keys.iter())
.filter(|(l, k)| test_lock_key(l, k))
.count() as i64
}
#[aoc(day25, part2)]
pub fn part2(_input: &str) -> String {
"run the other solutions for day 25 part 2!".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "#####
.####
.####
.####
.#.#.
.#...
.....
#####
##.##
.#.##
...##
...#.
...#.
.....
.....
#....
#....
#...#
#.#.#
#.###
#####
.....
.....
#.#..
###..
###.#
###.#
#####
.....
.....
.....
#....
#.#..
#.#.#
#####";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 3);
}
#[test]
fn part2_example() {}
}

View File

@ -99,13 +99,12 @@ pub struct Map {
impl<T: BufRead> From<T> for Map {
fn from(input: T) -> Self {
let grid = Grid::from(input);
let mut visited_from: Grid<DirectionSet> = Grid::new(grid.width() as i64);
visited_from.data.resize(grid.data.len(), DirectionSet::empty());
let visited_from = grid.same_shape(DirectionSet::empty());
let guard_pos = grid.find(&b'^').expect("Guard not found");
let guard_facing = FacingDirection::Up;
Self {
grid,
guard_pos,
guard_pos: guard_pos.into(),
guard_facing,
visited_from,
path: Vec::new(),

View File

@ -42,9 +42,9 @@ impl AntennaMap {
// permutations generates both pairs, ie. ((1,2),(2,1)) and ((2,1),(1,2)) so we don't need
// to consider the 'negative' side of the line, which will be generated by the other pair
let (a, b) = (pair[0], pair[1]);
let offset = (a.0 - b.0, a.1 - b.1);
let offset = (a.x - b.x, a.y - b.y);
for i in (start..).map_while(|i| if Some(i - start) != reps { Some(i as i64) } else { None }) {
let node_pos = (a.0 + i * offset.0, a.1 + i * offset.1);
let node_pos = (a.x + i * offset.0, a.y + i * offset.1);
if antinodes.set(&node_pos, true).is_none() {
// left the grid
break;

View File

@ -8,7 +8,14 @@ pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;
pub mod day2;
pub mod day20;
pub mod day21;
pub mod day22;
pub mod day23;
pub mod day24;
pub mod day25;
pub mod day3;
pub mod day4;
pub mod day5;

View File

@ -1,7 +1,7 @@
use std::{
fmt::{Debug, Display, Formatter, Write},
io::{BufRead, Cursor},
iter::repeat,
iter::repeat_n,
mem::swap,
ops::{Add, AddAssign, Sub},
str::FromStr,
@ -39,6 +39,16 @@ impl<T: AsCoord2d> Add<T> for &Coord2d {
}
}
impl<T: AsCoord2d> Add<&T> for Coord2d {
type Output = Coord2d;
fn add(self, rhs: &T) -> Self::Output {
Coord2d {
x: self.x() + rhs.x(),
y: self.y() + rhs.y(),
}
}
}
impl AsCoord2d for Coord2d {
fn to_coord(self) -> Coord2d {
self
@ -82,29 +92,101 @@ where
}
}
#[derive(Clone, Eq, PartialEq)]
impl<T> AsCoord2d for &(T, T)
where
T: Copy + TryInto<i64>,
<T as TryInto<i64>>::Error: Debug,
{
fn to_coord(self) -> Coord2d {
Coord2d {
x: self.0.try_into().unwrap(),
y: self.1.try_into().unwrap(),
}
}
fn x(&self) -> i64 {
self.0.try_into().unwrap()
}
fn y(&self) -> i64 {
self.1.try_into().unwrap()
}
}
impl From<Coord2d> for (i64, i64) {
fn from(value: Coord2d) -> Self {
(value.x, value.y)
}
}
#[derive(Debug)]
pub struct GridRowIter<'a, T> {
iter: std::slice::Iter<'a, T>,
}
impl<'a, T: Clone + Eq + PartialEq + Debug> GridRowIter<'a, T> {
fn new(grid: &'a Grid<T>, y: i64) -> Self {
let iter = grid.data[y as usize * grid.width()..(y as usize + 1) * grid.width()].iter();
Self { iter }
}
}
impl<'a, T> Iterator for GridRowIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Debug)]
pub struct GridColIter<'a, T> {
grid: &'a Grid<T>,
stride: usize,
cur: usize,
}
impl<'a, T: Clone + Eq + PartialEq + Debug> GridColIter<'a, T> {
fn new(grid: &'a Grid<T>, x: i64) -> Self {
Self {
grid,
stride: grid.width(),
cur: x as usize,
}
}
}
impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for GridColIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let cur = self.cur;
self.cur += self.stride;
if cur < self.grid.data.len() {
Some(&self.grid.data[cur])
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.grid.height() - 1, Some(self.grid.height() - 1))
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Grid<T> {
pub data: Vec<T>,
width: i64,
}
impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
pub fn new(width: i64) -> Self {
Self {
data: Vec::new(),
width,
}
}
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill`
pub fn same_shape<NT: Clone + Eq + PartialEq + Debug>(&self, fill: NT) -> Grid<NT> {
Grid {
data: Vec::from_iter(repeat(fill).take(self.width() * self.height())),
width: self.width,
}
Grid::with_shape(self.width(), self.height(), fill)
}
/// Returns a new [Grid] with the given shape (width x height), filled with `fill`
pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
Self {
data: Vec::from_iter(repeat(fill).take(width * height)),
data: Vec::from_iter(repeat_n(fill, width * height)),
width: width as i64,
}
}
@ -117,13 +199,28 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
c.y() * self.width + c.x()
}
pub fn coord(&self, pos: i64) -> Option<(i64, i64)> {
pub fn coord(&self, pos: i64) -> Option<Coord2d> {
if pos < 0 || pos >= self.data.len() as i64 {
None
} else {
Some((pos % self.width, pos / self.width))
Some(Coord2d {
x: pos % self.width,
y: pos / self.width,
})
}
}
// pub fn coord_iter(&self) -> CoordIter<_> {
// CoordIter { pos: 0, grid: self }
// }
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
if c.x() < 0 || c.x() >= self.width {
return false;
}
if c.y() < 0 || c.y() as usize >= self.height() {
return false;
}
return true;
}
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
if c.x() < 0 || c.x() >= self.width {
return None;
@ -178,15 +275,31 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
}
}
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
if x < self.width() as i64 && x >= 0 {
Some((0..self.height()).map(|y| self.get(&(x, y as i64)).unwrap()).collect())
pub fn row_iter(&self, y: i64) -> Option<GridRowIter<T>> {
if (y as usize) < self.height() {
Some(GridRowIter::new(self, y))
} else {
None
}
}
pub fn find(&self, haystack: &T) -> Option<(i64, i64)> {
pub fn col(&self, x: i64) -> Option<Vec<&T>> {
if let Some(iter) = self.col_iter(x) {
Some(iter.collect())
} else {
None
}
}
pub fn col_iter(&self, x: i64) -> Option<GridColIter<T>> {
if (x as usize) < self.width() {
Some(GridColIter::new(self, x))
} else {
None
}
}
pub fn find(&self, haystack: &T) -> Option<Coord2d> {
self.coord(
self.data
.iter()
@ -350,6 +463,28 @@ FBCG";
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice()));
}
#[test]
fn row_iter() {
let grid = unchecked_load();
assert_eq!(
grid.row_iter(2).unwrap().collect::<Vec<_>>(),
[&b'I', &b'J', &b'K', &b'L']
);
assert!(grid.row_iter(-1).is_none());
assert!(grid.row_iter(4).is_none());
}
#[test]
fn col_iter() {
let grid = unchecked_load();
assert_eq!(
grid.col_iter(2).unwrap().collect::<Vec<_>>(),
[&b'C', &b'G', &b'K', &b'C']
);
assert!(grid.col_iter(-1).is_none());
assert!(grid.col_iter(4).is_none());
}
// #[test]
// fn window_compare() {
// let grid = unchecked_load();