Compare commits

..

111 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
9a6ca66059 grid: microoptimizations
All checks were successful
test / AoC 2024 (push) Successful in 1m49s
2024-12-17 23:52:14 -08:00
2729799fa2 day18: optimize
* Implement generic path tracing, so part 1 doesn't store and return the full path,
  just the count.
* Improve part2 impl to only run path search if the block fell on our
  last path.
* Start part2 path search from the best path position immediately before
  the placed block.
2024-12-17 23:51:09 -08:00
8d2fbc0fcb repo: more aggressive optimization 2024-12-17 23:41:48 -08:00
4f48d839b2 day18: part 2 solution + some optimizations
All checks were successful
test / AoC 2024 (push) Successful in 2m47s
2024-12-17 22:13:49 -08:00
414569537e day18: part 2 solution 2024-12-17 22:00:10 -08:00
5036866663 day18: part 1 solution 2024-12-17 21:52:41 -08:00
9be86e2cc2 day17: clippies
All checks were successful
test / AoC 2024 (push) Successful in 2m38s
2024-12-17 01:03:06 -08:00
5bcead2691 day17: part 2 solution
All checks were successful
test / AoC 2024 (push) Successful in 1m43s
2024-12-17 00:46:18 -08:00
c99d8a400a day17: part 1 solution
All checks were successful
test / AoC 2024 (push) Successful in 1m54s
2024-12-16 22:29:55 -08:00
5e8b974137 clippies!
All checks were successful
test / AoC 2024 (push) Successful in 1m44s
2024-12-16 14:54:48 -08:00
28a88e1aa7 day14: replace RE parser with nom consuming parser
Almost 100x speedup on parsing phase avoiding RE compile, saves ~60ms
2024-12-16 14:47:48 -08:00
33615b015f cargo: update and add nom, misc 2024-12-16 14:44:30 -08:00
b7a1f05b1e utils: add misc and implement CustomBounded 2024-12-16 14:41:32 -08:00
de7ee8f0f6 day14: don't print robots map
All checks were successful
test / AoC 2024 (push) Successful in 3m43s
2024-12-16 11:40:10 -08:00
c31d653612 day16: minor performance and refactoring
All checks were successful
test / AoC 2024 (push) Successful in 3m44s
2024-12-16 01:33:02 -08:00
20e6889572 day16: improve perf again by going to i16 positions
All checks were successful
test / AoC 2024 (push) Successful in 5m29s
map dimensions fit in i16, so use those in data structures

make grid support generic position type as long as they implement to i64
2024-12-16 01:06:39 -08:00
755fbbc53d day16: refactor, optimize
split path recording and best cost functions for big gainz

use i32 instead of i64 positions to shrink data structures for some
gainz
2024-12-16 00:58:15 -08:00
4b85a90635 day16: part 2 solution
All checks were successful
test / AoC 2024 (push) Successful in 3m7s
2024-12-15 23:22:14 -08:00
2cf8527c4f grid: add add_mut() 2024-12-15 23:21:59 -08:00
6283ff37f9 day16: part 1 solution
All checks were successful
test / AoC 2024 (push) Successful in 2m56s
2024-12-15 22:06:21 -08:00
f2186d18d3 ci: allow test failures, fix input getting script mistake
All checks were successful
test / AoC 2024 (push) Successful in 4m16s
2024-12-15 01:06:09 -08:00
c261ee56fe day15: cleanup, clippies
Some checks failed
test / AoC 2024 (push) Has been cancelled
2024-12-15 01:03:08 -08:00
411d6aa26d day15: cleanup & use the same implementation for both parts
Some checks failed
test / AoC 2024 (push) Failing after 2m17s
2024-12-15 00:56:07 -08:00
c2c0145219 day15: part 2 submitted solution
Some checks failed
test / AoC 2024 (push) Failing after 2m27s
2024-12-15 00:50:19 -08:00
4dfdaca58c day15: part 1 solution + beginning of part 2
Some checks failed
test / AoC 2024 (push) Has been cancelled
2024-12-15 00:49:44 -08:00
003bc3212d pre-commit: check clippies and format in pre-commit to avoid CI fail
All checks were successful
test / AoC 2024 (push) Successful in 2m55s
2024-12-13 23:51:50 -08:00
b060de20c7 day11: clippies
All checks were successful
test / AoC 2024 (push) Successful in 3m7s
2024-12-13 23:44:46 -08:00
bc7ec50c94 CI: get real inputs, and do a full run 2024-12-13 23:44:29 -08:00
8ae2115b52 CI: create fake inputs so tests can run
All checks were successful
test / AoC 2024 (push) Successful in 1m27s
2024-12-13 23:06:23 -08:00
74a6b16924 chore: rust fmt 2024-12-13 23:06:07 -08:00
e8a38e7b24 day14: pretty print a tree 2024-12-13 23:05:58 -08:00
c6153663b5 day14: complete solution
Some checks failed
test / AoC 2024 (push) Failing after 58s
2024-12-13 22:53:07 -08:00
8b011941c4 CI: make a workspace
Some checks failed
test / AoC 2024 (push) Failing after 1m7s
2024-12-13 18:09:22 -08:00
d88f907c03 clippies
Some checks failed
test / AoC 2024 (push) Failing after 1m14s
2024-12-13 17:55:28 -08:00
d6d81a0c29 CI: run tests
Some checks failed
test / AoC 2024 (push) Failing after 1m22s
2024-12-13 17:52:42 -08:00
ed184fc92c day12/day13: codspeed compat (&str input) 2024-12-13 17:42:44 -08:00
a1774d1f73 grid: impl FromStr for Grid<u8> 2024-12-13 17:28:45 -08:00
3bbf05b30c pre-commit 2024-12-13 02:24:18 -08:00
6a8a7a9ad1 pre-commit update 2024-12-13 02:22:55 -08:00
1a6d37f4f3 clippies 2024-12-13 02:17:57 -08:00
35637cece1 readme 2024-12-13 02:15:41 -08:00
ebf5a0a489 day13: complete solution 2024-12-13 02:12:18 -08:00
4aa7e9f43c day3: perf - branchless 2024-12-12 17:56:28 -08:00
a5439062a4 day3: performance - avoid converting &[u8] to &str 2024-12-12 17:38:42 -08:00
3658183deb day7: performance. DFS with pruning. 2024-12-12 16:02:13 -08:00
38cba37b06 day5: perf: switch to sort-based implementation 2024-12-12 14:50:00 -08:00
6022d2cc39 multiple: Use FxHashMap for s p e e d 2024-12-12 14:07:22 -08:00
447ff5c62c grid: improve ergonomics with more trait impls and other improvements
update puzzles to pass tests, some performance gains
2024-12-12 02:14:29 -08:00
c213bbbc27 day12: optimization and cleanup 2024-12-12 01:24:14 -08:00
a56fc933c9 day12: complete solution 2024-12-11 22:27:59 -08:00
31eb500832 day7: slight performance improvement from skipping ThreadLoacls 2024-12-11 19:40:45 -08:00
4c14c6092e bugfix: count off by one, thought ^ was left in the map, but it gets
replaced by X
2024-12-11 18:59:58 -08:00
de535303d4 day11: don't use aoc_generator 2024-12-11 18:43:57 -08:00
50b6d045e7 multiple: genericize Grid over coordinate type, parallelism for day 11 2024-12-11 18:43:13 -08:00
d2defae8a2 chore: make day modules public 2024-12-11 18:40:53 -08:00
cd8900d936 chore: enable LTO for release builds 2024-12-11 18:40:29 -08:00
11b29a4d57 chore: refactor for cargo-aoc / codspeed 2024-12-11 15:45:52 -08:00
3bfde9fd9b day11: some cleanup, slight performance 2024-12-11 03:11:17 -08:00
2e239681ce day11: performance 2024-12-11 00:55:47 -08:00
a7354b6ed7 day11: part 2 solution 2024-12-10 23:40:03 -08:00
462918b382 day11: part 1 solution 2024-12-10 21:55:00 -08:00
8af11a6092 day10: performance. slight gain from memoizing visited positions in p1. 2024-12-10 17:07:00 -08:00
d9d55b069f day10: optimization
Part 2 doesn't actually require tracking state at all, it's enough to
just follow the rules and count every DFS that lands on a target square.

Part 1 optimized by only tracking which targets have been previously
visited, instead of all squares.

Both parts, pass position reference to recursive calls instead of by
value for tiny improvement.
2024-12-10 16:20:15 -08:00
0716dde8b1 day10: complete solution 2024-12-09 21:59:39 -08:00
206c1fca85 day9: optimize part 2 2024-12-09 00:23:40 -08:00
f7cf4f1e9f day9: optimize part 1 2024-12-08 23:58:47 -08:00
b08c8fbd80 day9: use part 2 'inodes' table to speed up part 1 2024-12-08 23:51:51 -08:00
2bc751dd0d day9: complete solution
previous commit should actually work for part 1 if you don't truncate
the input.
2024-12-08 23:34:15 -08:00
a6ea5b4155 day9: second rewrite, still failing 2024-12-08 22:57:20 -08:00
622877843e day8: refactor and cleanup 2024-12-07 22:49:57 -08:00
47e40942e2 day8: complete solution 2024-12-07 21:43:56 -08:00
3e0bc0d5cc day7: parallel solution, < 100ms
This is definitely a problem that calls for DFS, but I couldn't be
bothered.
2024-12-06 22:52:00 -08:00
868c3e56fc day7: complete solution 2024-12-06 21:40:25 -08:00
fee37aebd0 day6: further performance improvement, ~55ms total runtime
We only need to run the guard forward from the step before the obstacle
we placed, instead of starting at the original starting position.

The code starts to look ugly...
2024-12-06 17:05:36 -08:00
b1918bbebf day6: performance - only check obstacles on the original path
700ms -> 150ms
2024-12-06 10:59:43 -08:00
1cd535c2aa chore: clippies 2024-12-06 10:52:31 -08:00
1dd3ce5862 day6: performance improvement 2024-12-06 10:40:28 -08:00
145d779e83 day6: complete solution + grid lib (used in solution) 2024-12-05 22:41:47 -08:00
49c37800a0 day5: complete solution 2024-12-04 22:41:33 -08:00
e2cd0fe9cc day3: code format 2024-12-04 16:41:39 -08:00
72 changed files with 5720 additions and 493 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

62
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,62 @@
name: test
on:
push:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
tests:
name: AoC 2024
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: setup toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: true
components: clippy, rustfmt
toolchain: stable
rustflags: ""
- name: install cargo-aoc
run: cargo install --locked cargo-aoc
- name: get date
id: date
run: |
echo date=$(date +%Y%m%d) > $GITHUB_OUTPUT
- name: Get cached inputs
id: inputs
uses: actions/cache@v4
with:
path: input
key: ${{ steps.date.outputs.date }}
- name: Get inputs from source
if: steps.inputs.outputs.cache-hit != 'true'
run: |
cargo aoc credentials ${{ secrets.AOC_SESSION }}
for i in $(seq 1 25); do
dayfmt=$(printf %02d $i)
if [ ${{ steps.date.outputs.date }} -ge 202412${dayfmt} ]; then
cargo aoc input --year 2024 --day $i
fi
done
- name: cargo test
run: cargo test --lib
- name: rustfmt
run: cargo fmt --all -- --check
- name: clippy
run: cargo clippy --lib --tests -- -D warnings
if: always()
- name: full run
run: cargo run --release
if: always()

5
.gitignore vendored
View File

@ -1,4 +1,7 @@
**/target
input
flamegraph.svg
perf.data
perf.data*
guesses
.aoc_tiles/*
!.aoc_tiles/tiles/

30
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,30 @@
repos:
- repo: local
hooks:
- id: rust-linting
name: Rust linting
entry: cargo fmt --all --
pass_filenames: true
types: [file, rust]
language: system
- id: rust-clippy
name: Rust clippy
entry: cargo clippy --lib --all-features --tests -- -D warnings
pass_filenames: false
types: [file, rust]
language: system
- repo: https://github.com/LiquidFun/aoc_tiles
rev: 0.6.2
hooks:
- id: aoc-tiles
# Optionally use these arguments. Auto add tiles to git adds the tiles to git,
# possibly amends your commit by creating the tile images and updating the README.
# Language sorting shows the preference of the order of the languages to use.
# Exclude paterns are globs which can be used to exclude files when creating
# the tiles. See the customization section in the README for more flags.
# Simply remove the comments (#) below for args and the flags you want.
args:
- --auto-add-tiles-to-git=amend
- --overwrite-year=2024
# - --language-sorting=jl,kt,py,rs
# - --exclude-patterns=2021/*/*.apl,2021/*/*.py,2021/*/*.cpp

View File

@ -1,105 +0,0 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::iter::zip;
use std::time::Instant;
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
let start = Instant::now();
let ans1 = problem1(get_input());
let duration = start.elapsed();
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
let start = Instant::now();
let ans2 = problem2(get_input());
let duration = start.elapsed();
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
}
struct Locations {
left: Vec<u64>,
right: Vec<u64>,
}
impl<T: BufRead> From<Lines<T>> for Locations {
fn from(input: Lines<T>) -> Self {
let mut left = Vec::new();
let mut right = Vec::new();
for line in input.map(|i| i.unwrap()) {
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
left.push(parts[0].parse::<u64>().unwrap());
right.push(parts[1].parse::<u64>().unwrap());
}
Self { left, right }
}
}
impl Locations {
fn sort(&mut self) {
self.left.sort();
self.right.sort();
}
fn right_count(&self) -> HashMap<u64, u64> {
let mut right_count: HashMap<u64, u64> = HashMap::new();
for rval in &self.right {
right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1);
}
right_count
}
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut locations = Locations::from(input);
locations.sort();
zip(locations.left, locations.right)
.map(|(l, r)| u64::abs_diff(l, r))
.sum()
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let locations = Locations::from(input);
let right_count = locations.right_count();
locations
.left
.iter()
.map(|l| l * right_count.get(l).unwrap_or(&0))
.sum::<u64>()
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &"3 4
4 3
2 5
1 3
3 9
3 3";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 11);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 31);
}
}

7
2/Cargo.lock generated
View File

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

View File

@ -1,133 +0,0 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::time::{Duration, Instant};
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn duration_format(duration: Duration) -> String {
match duration.as_secs_f64() {
x if x > 1.0 => format!("{:.3}s", x),
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
x => format!("{:.3}us", x * 1e6),
}
}
fn main() {
let input = get_input();
let start = Instant::now();
let ans1 = problem1(input);
let duration1 = start.elapsed();
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
let input = get_input();
let start = Instant::now();
let ans2 = problem2(input);
let duration2 = start.elapsed();
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
println!("Total duration: {}", duration_format(duration1 + duration2));
}
struct Reports {
reports: Vec<Vec<u64>>,
}
impl<T: BufRead> From<Lines<T>> for Reports {
fn from(lines: Lines<T>) -> Self {
let mut reports = Vec::new();
for line in lines.map(|i| i.unwrap()) {
reports.push(
line.split_ascii_whitespace()
.map(|record| record.parse::<u64>().unwrap())
.collect(),
)
}
Reports { reports }
}
}
impl Reports {
fn is_safe(report: &Vec<u64>) -> bool {
let mut ascending: bool = true;
let mut descending: bool = true;
for (a, b) in report.iter().zip(report.iter().skip(1)) {
if a > b {
ascending = false
}
if a < b {
descending = false;
}
let ad = a.abs_diff(*b);
if !(ad >= 1 && ad <= 3) || (!ascending && !descending) {
return false;
};
}
return true;
}
fn count_safe(&self) -> u64 {
self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64
}
fn is_dumb_dampened_safe(report: &Vec<u64>) -> bool {
if Self::is_safe(report) {
return true;
}
for i in 0..report.len() {
let mut new_vec = report.clone();
new_vec.remove(i);
if Self::is_safe(&new_vec) {
return true;
}
}
false
}
fn dampened_count_safe(&self) -> u64 {
self.reports
.iter()
.filter(|report| Self::is_dumb_dampened_safe(report))
.count() as u64
}
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let reports = Reports::from(input);
reports.count_safe()
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let reports = Reports::from(input);
reports.dampened_count_safe()
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &"7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 2);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 4);
}
}

71
3/Cargo.lock generated
View File

@ -1,71 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "day3"
version = "0.1.0"
dependencies = [
"nom",
"regex",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"

View File

@ -1,8 +0,0 @@
[package]
name = "day3"
version = "0.1.0"
edition = "2021"
[dependencies]
nom = "7.1.3"
regex = { version = "1.11.1", default-features = false, features = ["perf", "std"] }

View File

@ -1,90 +0,0 @@
use regex::bytes::Regex;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::time::{Duration, Instant};
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
pub fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn duration_format(duration: Duration) -> String {
match duration.as_secs_f64() {
x if x > 1.0 => format!("{:.3}s", x),
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
x => format!("{:.3}us", x * 1e6),
}
}
fn main() {
let input = get_input();
let start = Instant::now();
let ans1 = problem1(input);
let duration1 = start.elapsed();
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
let input = get_input();
let start = Instant::now();
let ans2 = problem2(input);
let duration2 = start.elapsed();
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
println!("Total duration: {}", duration_format(duration1 + duration2));
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let mut sum = 0u64;
let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap();
for line in input.map(|i| i.unwrap()) {
let line = line.as_bytes();
for m in re.captures_iter(line) {
sum += std::str::from_utf8(&m[1]).unwrap().parse::<u64>().unwrap() * std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap();
}
}
sum
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let mut sum = 0u64;
let mut do_mul = true;
let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap();
for line in input.map(|i| i.unwrap()) {
let line = line.as_bytes();
for m in re.captures_iter(line) {
match std::str::from_utf8(&m[1]).unwrap() {
"do()" => do_mul = true,
"don't()" => do_mul = false,
_ if do_mul => sum += std::str::from_utf8(&m[2]).unwrap().parse::<u64>().unwrap() * std::str::from_utf8(&m[3]).unwrap().parse::<u64>().unwrap(),
_ => {}
}
}
}
sum
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE1: &str = &"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
const EXAMPLE2: &str = &"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE1);
assert_eq!(problem1(c.lines()), 161);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE2);
assert_eq!(problem2(c.lines()), 48);
}
}

7
4/Cargo.lock generated
View File

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

View File

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

666
Cargo.lock generated Normal file
View File

@ -0,0 +1,666 @@
# 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",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "aoc-runner"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d21ef9204ad206a5a3e918e9920da04e1118ad91ce4f23570be964b9d6b9dfcb"
[[package]]
name = "aoc-runner-derive"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba8b944269d3fee645d281b1335e1797044db497bb02d0098cc3fdb8900069cc"
dependencies = [
"aoc-runner-internal",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "aoc-runner-internal"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "274b0ba7f3669a45ec0aaacf94eb032a749de880ab776091576cca94037c9982"
dependencies = [
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "aoc2024"
version = "0.1.0"
dependencies = [
"aoc-runner",
"aoc-runner-derive",
"atoi",
"bitflags",
"cached",
"colored",
"grid",
"itertools",
"misc",
"nom",
"rayon",
"regex",
"rustc-hash",
"thread_local",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cached"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae"
dependencies = [
"ahash",
"cached_proc_macro",
"cached_proc_macro_types",
"hashbrown",
"once_cell",
"thiserror",
"web-time",
]
[[package]]
name = "cached_proc_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colored"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys",
]
[[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 = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.90",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.90",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "grid"
version = "0.1.0"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "misc"
version = "0.1.0"
dependencies = [
"num-traits",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[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 = "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",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "serde_json"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[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 = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[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 = "wasm-bindgen"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.90",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[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 2.0.90",
]

25
Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
edition = "2021"
name = "aoc2024"
version = "0.1.0"
[dependencies]
aoc-runner = "0.3.0"
aoc-runner-derive = "0.3.0"
atoi = "2.0.0"
bitflags = "2.6.0"
cached = "0.54.0"
colored = "2.1.0"
grid = {version = "0.1.0", path = "utils/grid"}
itertools = "0.13.0"
misc = {path = "utils/misc"}
nom = "7.1.3"
rayon = "1.10.0"
regex = "1.11.1"
rustc-hash = "2.1.0"
thread_local = "1.1.8"
[profile.release]
lto = true
opt-level = 3
overflow-checks = false

80
README.md Normal file
View File

@ -0,0 +1,80 @@
<!-- AOC TILES BEGIN -->
<h1 align="center">
2024 - 48 ⭐ - Rust
</h1>
<a href="src/day1.rs">
<img src=".aoc_tiles/tiles/2024/01.png" width="161px">
</a>
<a href="src/day2.rs">
<img src=".aoc_tiles/tiles/2024/02.png" width="161px">
</a>
<a href="src/day3.rs">
<img src=".aoc_tiles/tiles/2024/03.png" width="161px">
</a>
<a href="src/day4.rs">
<img src=".aoc_tiles/tiles/2024/04.png" width="161px">
</a>
<a href="src/day5.rs">
<img src=".aoc_tiles/tiles/2024/05.png" width="161px">
</a>
<a href="src/day6.rs">
<img src=".aoc_tiles/tiles/2024/06.png" width="161px">
</a>
<a href="src/day7.rs">
<img src=".aoc_tiles/tiles/2024/07.png" width="161px">
</a>
<a href="src/day8.rs">
<img src=".aoc_tiles/tiles/2024/08.png" width="161px">
</a>
<a href="src/day9.rs">
<img src=".aoc_tiles/tiles/2024/09.png" width="161px">
</a>
<a href="src/day10.rs">
<img src=".aoc_tiles/tiles/2024/10.png" width="161px">
</a>
<a href="src/day11.rs">
<img src=".aoc_tiles/tiles/2024/11.png" width="161px">
</a>
<a href="src/day12.rs">
<img src=".aoc_tiles/tiles/2024/12.png" width="161px">
</a>
<a href="src/day13.rs">
<img src=".aoc_tiles/tiles/2024/13.png" width="161px">
</a>
<a href="src/day14.rs">
<img src=".aoc_tiles/tiles/2024/14.png" width="161px">
</a>
<a href="src/day15.rs">
<img src=".aoc_tiles/tiles/2024/15.png" width="161px">
</a>
<a href="src/day16.rs">
<img src=".aoc_tiles/tiles/2024/16.png" width="161px">
</a>
<a href="src/day17.rs">
<img src=".aoc_tiles/tiles/2024/17.png" width="161px">
</a>
<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 -->

92
src/day1.rs Normal file
View File

@ -0,0 +1,92 @@
use aoc_runner_derive::{aoc, aoc_generator};
use rustc_hash::FxHashMap;
use std::io::{BufRead, Lines};
type HashMap<K, V> = FxHashMap<K, V>;
#[aoc_generator(day1)]
pub fn get_input(input: &[u8]) -> Locations {
Locations::from(input.lines())
}
#[derive(Clone)]
pub struct Locations {
left: Vec<u64>,
right: Vec<u64>,
}
impl<T: BufRead> From<Lines<T>> for Locations {
fn from(input: Lines<T>) -> Self {
let mut left = Vec::new();
let mut right = Vec::new();
for line in input.map(|i| i.unwrap()) {
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
left.push(parts[0].parse::<u64>().unwrap());
right.push(parts[1].parse::<u64>().unwrap());
}
Self { left, right }
}
}
impl Locations {
fn sort(&mut self) {
self.left.sort();
self.right.sort();
}
fn right_count(&self) -> HashMap<u64, u64> {
let mut right_count: HashMap<u64, u64> = HashMap::default();
for rval in &self.right {
right_count.insert(*rval, *right_count.get(rval).unwrap_or(&0) + 1);
}
right_count
}
}
// PROBLEM 1 solution
#[aoc(day1, part1)]
pub fn part1(locations: &Locations) -> u64 {
let mut locations = locations.clone();
locations.sort();
locations
.left
.iter()
.zip(locations.right)
.map(|(l, r)| u64::abs_diff(*l, r))
.sum()
}
// PROBLEM 2 solution
#[aoc(day1, part2)]
pub fn part2(locations: &Locations) -> u64 {
let right_count = locations.right_count();
locations
.left
.iter()
.map(|l| l * right_count.get(l).unwrap_or(&0))
.sum::<u64>()
}
#[cfg(test)]
mod tests {
use crate::day1::*;
const EXAMPLE: &[u8] = b"3 4
4 3
2 5
1 3
3 9
3 3";
#[test]
fn part1_example() {
let input = get_input(EXAMPLE);
assert_eq!(part1(&input), 11);
}
#[test]
fn part2_example() {
let input = get_input(EXAMPLE);
assert_eq!(part2(&input), 31);
}
}

111
src/day10.rs Normal file
View File

@ -0,0 +1,111 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::Grid;
use itertools::Itertools;
use std::io::BufRead;
#[aoc_generator(day10)]
pub fn get_input(input: &[u8]) -> TrailMap {
TrailMap::from(input)
}
pub struct TrailMap {
map: Grid<u8>,
}
impl<T: BufRead> From<T> for TrailMap {
fn from(input: T) -> Self {
Self { map: input.into() }
}
}
impl TrailMap {
fn trailheads(&self) -> Vec<(i64, i64)> {
self.map
.data
.iter()
.enumerate()
.filter(|(_, v)| **v == b'0')
.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 {
if visited.get(pos) == Some(&true) {
return 0;
} else {
visited.set(pos, true);
}
let our_val = self.map.get(pos).unwrap();
if *our_val == needle {
return 1;
}
// adjacents that are +1
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
.iter()
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
.map(|(pos, _)| pos) // discard the value
.map(|pos| self.count_reachable_from(&pos, needle, visited))
.sum()
}
fn count_paths_to(&self, pos: &(i64, i64), needle: u8) -> u64 {
let our_val = self.map.get(pos).unwrap();
if *our_val == needle {
return 1;
}
[(-1, 0), (1, 0), (0, -1), (0, 1)] // left, right, up, down
.iter()
.map(|(x_ofs, y_ofs)| (pos.0 + x_ofs, pos.1 + y_ofs)) // get target position
.map(|target_pos| (target_pos, self.map.get(&target_pos))) // get value at that position
.filter(|(_, val)| *val == Some(&(our_val + 1))) // only interested if it's our value + 1
.map(|(pos, _)| pos) // discard the value
.map(|mov| self.count_paths_to(&mov, needle))
.sum::<u64>()
}
}
// PROBLEM 1 solution
#[aoc(day10, part1)]
pub fn part1(map: &TrailMap) -> u64 {
map.trailheads()
.iter()
.map(|pos| {
let mut visited = Grid::with_shape(map.map.width(), map.map.height(), false);
map.count_reachable_from(pos, b'9', &mut visited)
})
.sum()
}
// PROBLEM 2 solution
#[aoc(day10, part2)]
pub fn part2(map: &TrailMap) -> u64 {
map.trailheads()
.iter()
.map(|pos| map.count_paths_to(pos, b'9'))
.sum::<u64>()
}
#[cfg(test)]
mod tests {
use crate::day10::*;
const EXAMPLE: &[u8] = b"89010123
78121874
87430965
96549874
45678903
32019012
01329801
10456732";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 36);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 81);
}
}

103
src/day11.rs Normal file
View File

@ -0,0 +1,103 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use rustc_hash::FxHashMap;
use std::iter::repeat;
type IntType = u64;
type CacheType = FxHashMap<Stone, IntType>;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Stone(IntType);
struct Stones(Vec<Stone>);
enum BlinkResult {
One(Stone),
Two(Stone, Stone),
}
impl From<&str> for Stones {
fn from(input: &str) -> Self {
Stones(
input
.split_ascii_whitespace()
.map(|v| Stone(v.parse().unwrap()))
.collect_vec(),
)
}
}
fn parse(input: &str) -> Stones {
Stones::from(input)
}
impl Stone {
fn blink_once(&self) -> BlinkResult {
let n_digits = if self.0 == 0 { 1 } else { self.0.ilog10() + 1 };
if self.0 == 0 {
BlinkResult::One(Stone(1))
} else if n_digits % 2 == 0 {
let split_factor = (10 as IntType).pow(n_digits / 2);
let parts = (self.0 / split_factor, self.0 % split_factor);
BlinkResult::Two(Stone(parts.0), Stone(parts.1))
} else {
BlinkResult::One(Stone(&self.0 * 2024))
}
}
}
fn count_blinks(stone: &Stone, blink: usize, cache: &mut Vec<CacheType>) -> IntType {
if cache[blink].contains_key(stone) {
return cache[blink][stone];
}
let stones = stone.blink_once();
let result = if blink == 0 {
match stones {
BlinkResult::One(_) => 1,
BlinkResult::Two(_, _) => 2,
}
} else {
match stones {
BlinkResult::One(s) => count_blinks(&s, blink - 1, cache),
BlinkResult::Two(s1, s2) => count_blinks(&s1, blink - 1, cache) + count_blinks(&s2, blink - 1, cache),
}
};
cache[blink].insert(stone.clone(), result);
cache[blink][stone]
}
fn blink_stones(stones: Stones, blinks: usize) -> IntType {
let mut cache = Vec::from_iter(repeat(CacheType::default()).take(blinks));
stones
.0
.iter()
.map(|stone| count_blinks(stone, blinks - 1, &mut cache))
.sum()
}
#[aoc(day11, part1)]
pub fn part1(input: &str) -> IntType {
let stones = parse(input);
blink_stones(stones, 25)
}
#[aoc(day11, part2)]
pub fn part2(input: &str) -> IntType {
let stones = parse(input);
blink_stones(stones, 75)
}
#[cfg(test)]
mod tests {
use super::*;
pub const EXAMPLE: &str = "125 17";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 55312);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), 65601038650482);
}
}

176
src/day12.rs Normal file
View File

@ -0,0 +1,176 @@
use std::str::FromStr;
use aoc_runner_derive::aoc;
use grid::{Coord2d, Grid};
pub struct Farm {
map: Grid<u8>,
}
impl FromStr for Farm {
type Err = <Grid<u8> as FromStr>::Err;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Ok(Self { map: input.parse()? })
}
}
impl Farm {
fn compute_region(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
let our_plant = self.map.get(pos).unwrap();
visited.set(pos, true);
[(-1i64, 0i64), (1, 0), (0, -1), (0, 1)]
.map(|ofs| pos + ofs)
.iter()
.fold((1, 0), |(area, perimeter), adj| {
match self.map.get(adj) {
Some(plant) if plant == our_plant => {
if visited.get(adj) == Some(&false) {
// add the perimeter of the growth from there if not visited yet
let (add_area, add_perimeter) = self.compute_region(adj, visited);
(area + add_area, perimeter + add_perimeter)
} else {
(area, perimeter)
}
}
Some(_) | None => (area, perimeter + 1),
}
})
}
fn regions_cost(&self) -> u64 {
let mut visited = Grid::with_shape(self.map.width(), self.map.height(), false);
let mut cost = 0;
for y in 0..self.map.height() {
for x in 0..self.map.width() {
cost += match visited.get(&(x, y)) {
Some(false) => {
let (area, perim) = self.compute_region(
&Coord2d {
x: x as i64,
y: y as i64,
},
&mut visited,
);
area * perim
}
Some(_) | None => 0,
}
}
}
cost
}
fn count_corners(&self, pos: &Coord2d) -> u64 {
// NOTE: Iterating twice is faster than combining conditions in one pass
// BAB
// AAA has 4 inside corners (pos at centre). check that for AA A's exist and B doesn't for each rotation
// BAB AB
let our_plant = self.map.get(pos);
let inside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
.iter()
.filter(|inside_corner| {
self.map.get(&(pos + **inside_corner)) != our_plant
&& self.map.get(&(pos + (inside_corner.0, 0))) == our_plant
&& self.map.get(&(pos + (0, inside_corner.1))) == our_plant
})
.count();
// BBB
// BAB has 4 outside corners (pos at centre). check that for AB the B are both not equal to A for each rot
// BBB BB B
let outside_corners = [(1i64, 1i64), (-1, 1), (1, -1), (-1, -1)]
.iter()
.filter(|outside_corner| {
self.map.get(&(pos + (outside_corner.0, 0))) != our_plant
&& self.map.get(&(pos + (0, outside_corner.1))) != our_plant
})
.count();
(inside_corners + outside_corners) as u64
}
fn region_corners(&self, pos: &Coord2d, visited: &mut Grid<bool>) -> (u64, u64) {
let our_plant = self.map.get(pos).unwrap();
visited.set(pos, true);
[(-1i64, 0i64), (1, 0), (0, -1), (0, 1)]
.map(|ofs| pos + ofs)
.iter()
.fold((1, self.count_corners(pos)), |(area, corners), adj| {
match self.map.get(adj) {
Some(plant) if plant == our_plant => {
if visited.get(adj) == Some(&false) {
// add the perimeter of the growth from there if not visited yet
let (n_area, n_corners) = self.region_corners(adj, visited);
(area + n_area, corners + n_corners)
} else {
(area, corners)
}
}
Some(_) | None => (area, corners),
}
})
}
fn regions_discount_cost(&self) -> u64 {
let mut visited = self.map.same_shape(false);
let mut cost = 0;
for y in 0..self.map.height() {
for x in 0..self.map.width() {
cost += match visited.get(&(x, y)) {
Some(false) => {
let (area, corners) = self.region_corners(
&Coord2d {
x: x as i64,
y: y as i64,
},
&mut visited,
);
area * corners
}
Some(_) | None => 0,
}
}
}
cost
}
}
fn parse(input: &str) -> Farm {
input.parse().unwrap()
}
#[aoc(day12, part1)]
pub fn part1(input: &str) -> u64 {
let farm = parse(input);
farm.regions_cost()
}
#[aoc(day12, part2)]
pub fn part2(input: &str) -> u64 {
let farm = parse(input);
farm.regions_discount_cost()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 1930);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), 1206);
}
}

135
src/day13.rs Normal file
View File

@ -0,0 +1,135 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use regex::Regex;
use std::str::Lines;
#[derive(Debug, Clone)]
pub struct MachineAction(i64, i64);
#[derive(Debug, Clone)]
struct ClawMachine {
button_a: MachineAction,
button_b: MachineAction,
prize: (i64, i64),
}
impl ClawMachine {
fn consume_from_input(input: &mut Lines) -> Option<Self> {
// consume any empty lines at the front
let ofs_re = Regex::new(r"X([+-]\d+), Y([+-]\d+)").unwrap();
let prize_re = Regex::new(r"X=(\d+), Y=(\d+)").unwrap();
// consume 3 lines - a, b, prize
if let Some((a_line, b_line, prize_line)) = input.filter(|l| !l.is_empty()).take(3).collect_tuple() {
let a_caps = ofs_re.captures(a_line).unwrap();
let b_caps = ofs_re.captures(b_line).unwrap();
let prize_caps = prize_re.captures(prize_line).unwrap();
let button_a = MachineAction(
a_caps.get(1).unwrap().as_str().parse().unwrap(),
a_caps.get(2).unwrap().as_str().parse().unwrap(),
);
let button_b = MachineAction(
b_caps.get(1).unwrap().as_str().parse().unwrap(),
b_caps.get(2).unwrap().as_str().parse().unwrap(),
);
let prize = (
prize_caps.get(1).unwrap().as_str().parse().unwrap(),
prize_caps.get(2).unwrap().as_str().parse().unwrap(),
);
Some(Self {
button_a,
button_b,
prize,
})
} else {
None
}
}
fn cost(moves: (i64, i64)) -> i64 {
moves.0 * 3 + moves.1
}
fn cheapest_prize(&self) -> Option<i64> {
let remainder_a = (self.button_b.0 * self.prize.1 - self.button_b.1 * self.prize.0)
% (self.button_b.0 * self.button_a.1 - self.button_a.0 * self.button_b.1);
let a = (self.button_b.0 * self.prize.1 - self.button_b.1 * self.prize.0)
/ (self.button_b.0 * self.button_a.1 - self.button_a.0 * self.button_b.1);
let remainder_b = (self.prize.0 - (self.button_a.0 * a)) % self.button_b.0;
let b = (self.prize.0 - (self.button_a.0 * a)) / self.button_b.0;
if remainder_a == 0 && remainder_b == 0 {
Some(Self::cost((a, b)))
} else {
None
}
}
fn offset(&mut self, offset: i64) {
self.prize = (self.prize.0 + offset, self.prize.1 + offset)
}
}
#[derive(Debug, Clone)]
struct ClawMachines {
machines: Vec<ClawMachine>,
}
impl From<&str> for ClawMachines {
fn from(input: &str) -> Self {
let mut machines = Vec::new();
let mut lines = input.lines();
while let Some(machine) = ClawMachine::consume_from_input(&mut lines) {
machines.push(machine);
}
Self { machines }
}
}
fn parse(input: &str) -> ClawMachines {
ClawMachines::from(input)
}
#[aoc(day13, part1)]
fn part1(input: &str) -> i64 {
let machines = parse(input);
machines.machines.iter().filter_map(|m| m.cheapest_prize()).sum()
}
#[aoc(day13, part2)]
fn part2(input: &str) -> i64 {
let mut machines = parse(input);
machines
.machines
.iter_mut()
.filter_map(|m| {
m.offset(10000000000000);
m.cheapest_prize()
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176
Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450
Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE), 480);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE), 875318608908);
}
}

178
src/day14.rs Normal file
View File

@ -0,0 +1,178 @@
use aoc_runner_derive::aoc;
use colored::Colorize;
use grid::{AsCoord2d, Grid};
use misc::CustomWrapped;
use nom::{
bytes::complete::tag,
character::complete::digit1,
combinator::{map_res, opt, recognize},
sequence::{preceded, separated_pair},
IResult,
};
use std::{fmt::Display, str::FromStr};
type Coord = (CustomWrapped<i64>, CustomWrapped<i64>);
struct Robot {
pos: Coord,
vel: (i64, i64),
}
struct Robots {
robots: Vec<Robot>,
width: i64,
height: i64,
}
#[derive(Debug, Eq, PartialEq)]
enum Quadrant {
NW = 0,
NE = 1,
SW = 2,
SE = 3,
}
fn nom_i64(input: &str) -> IResult<&str, i64> {
let (i, number) = map_res(recognize(preceded(opt(tag("-")), digit1)), i64::from_str)(input)?;
Ok((i, number))
}
fn nom_i64_pair(input: &str) -> IResult<&str, (i64, i64)> {
let (i, pair) = separated_pair(nom_i64, tag(","), nom_i64)(input)?;
Ok((i, pair))
}
impl Robot {
fn from_str(s: &str, bounds: (i64, i64)) -> Self {
let (s, pos) = preceded(tag("p="), nom_i64_pair)(s).unwrap();
let (_, vel) = preceded(tag(" v="), nom_i64_pair)(s).unwrap();
Self {
pos: (CustomWrapped::new(pos.0, bounds.0), CustomWrapped::new(pos.1, bounds.1)),
vel,
}
}
fn step(&mut self, count: i64) {
self.pos.0 += self.vel.x() * count;
self.pos.1 += self.vel.y() * count;
}
fn quad(&self, bounds: (i64, i64)) -> Option<Quadrant> {
let splits = (bounds.0 / 2, bounds.1 / 2);
if self.pos.0 < splits.0 && self.pos.1 < splits.1 {
Some(Quadrant::NW)
} else if self.pos.0 > splits.0 && self.pos.1 < splits.1 {
Some(Quadrant::NE)
} else if self.pos.0 < splits.0 && self.pos.1 > splits.1 {
Some(Quadrant::SW)
} else if self.pos.0 > splits.0 && self.pos.1 > splits.1 {
Some(Quadrant::SE)
} else {
None
}
}
}
impl Robots {
fn from_vec(robots: Vec<Robot>, width: i64, height: i64) -> Self {
Self { robots, width, height }
}
fn as_grid(&self) -> Grid<usize> {
let mut grid = Grid::with_shape(self.width as usize, self.height as usize, 0usize);
for r in &self.robots {
grid.increment(&(r.pos.0.val, r.pos.1.val), 1usize);
}
grid
}
fn count_quads(&self) -> [u64; 4] {
let mut counts = [0; 4];
for r in &self.robots {
if let Some(q) = r.quad((self.width, self.height)) {
counts[q as usize] += 1
}
}
counts
}
fn step(&mut self, count: i64) {
for robot in &mut self.robots {
robot.step(count)
}
}
}
impl Display for Robots {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let grid = self.as_grid();
for row in 0..grid.height() {
for col in 0..grid.width() {
if *grid.get(&(col, row)).unwrap() != 0 {
"".green().fmt(f)?;
} else {
" ".color(colored::Color::Black).fmt(f)?;
}
}
writeln!(f)?
}
Ok(())
}
}
fn parse(input: &str, width: i64, height: i64) -> Vec<Robot> {
input.lines().map(|l| Robot::from_str(l, (width, height))).collect()
}
fn part1_impl(input: &str, width: i64, height: i64) -> u64 {
let mut robots = Robots::from_vec(parse(input, width, height), width, height);
robots.step(100);
let counts = robots.count_quads();
counts.iter().product()
}
#[aoc(day14, part1)]
pub fn part1(input: &str) -> u64 {
part1_impl(input, 101, 103)
}
#[aoc(day14, part2)]
pub fn part2(input: &str) -> u64 {
let width = 101;
let height = 103;
let mut robots = Robots::from_vec(parse(input, width, height), width, height);
for i in 1.. {
robots.step(1);
// collect into lines
let g = robots.as_grid();
if g.data
.chunk_by(|a, b| *a != 0 && *b != 0)
.filter(|c| !c.is_empty() && c[0] != 0)
.any(|c| c.len() > width as usize / 10)
{
return i;
}
}
unreachable!()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3";
#[test]
fn part1_example() {
assert_eq!(part1_impl(EXAMPLE, 11, 7), 12);
}
// part 2 does not converge using the test vector
// #[test]
// fn part2_example() {
// // assert_eq!(part2(EXAMPLE), 0);
// }
}

241
src/day15.rs Normal file
View File

@ -0,0 +1,241 @@
use aoc_runner_derive::aoc;
use grid::{AsCoord2d, Coord2d, Grid};
use itertools::Itertools;
use std::{fmt::Display, io::Cursor, str::FromStr};
struct Warehouse {
map: Grid<u8>,
robot_pos: Coord2d,
}
impl Display for Warehouse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.map.fmt(f)
}
}
impl Warehouse {
fn step_robot(&mut self, dir: Move) {
let start = self.robot_pos;
if self.push(&start, &dir) {
self.robot_pos = &self.robot_pos + dir.ofs();
}
}
fn push(&mut self, pos: &Coord2d, dir: &Move) -> bool {
if self.can_push(pos, dir) {
let target = pos + dir.ofs();
match self.map.get(&target).unwrap() {
b'#' => {}
b'.' => self.map.swap(target, pos),
b'O' => {
self.push(&target, dir);
self.map.swap(target, pos);
}
b'[' | b']' if *dir == Move::Left || *dir == Move::Right => {
self.push(&target, dir);
self.map.swap(target, pos)
}
b']' => {
// move both parts
self.push(&target, dir);
self.push(&(&target + (-1, 0)), dir);
self.map.swap(target, pos);
}
b'[' => {
self.push(&target, dir);
self.push(&(&target + (1, 0)), dir);
self.map.swap(target, pos);
}
c => panic!("unexpected char {}", c),
}
return true;
}
false
}
fn can_push(&mut self, pos: &Coord2d, dir: &Move) -> bool {
let target = pos + dir.ofs();
match self.map.get(&target).unwrap() {
b'#' => false,
b'.' => true,
b'O' => self.can_push(&target, dir),
b'[' | b']' if *dir == Move::Left || *dir == Move::Right => self.can_push(&target, dir),
b']' => self.can_push(&target, dir) && self.can_push(&(&target + (-1, 0)), dir),
b'[' => self.can_push(&target, dir) && self.can_push(&(&target + (1, 0)), dir),
c => panic!("unexpected char {}", c),
}
}
fn embiggen(&mut self) {
let new_lines = (0..self.map.height())
.map(|r| self.map.row(r as i64).unwrap())
.map(|row| {
row.iter()
.flat_map(|c| match c {
b'#' => ['#', '#'],
b'O' => ['[', ']'],
b'.' => ['.', '.'],
b'@' => ['@', '.'],
c => panic!("unexpected character {}", c),
})
.collect::<String>()
})
.join("\n");
self.map = Grid::from(Cursor::new(new_lines.as_str()));
self.robot_pos = self.map.find(&b'@').unwrap().to_coord();
}
fn score(&self) -> i64 {
self.map
.data
.iter()
.enumerate()
.filter(|(_, v)| **v == b'O' || **v == b'[')
.map(|(i, _)| self.map.coord(i as i64).unwrap().y() * 100 + self.map.coord(i as i64).unwrap().x())
.sum()
}
}
#[derive(Debug, Eq, PartialEq)]
enum Move {
Left,
Right,
Up,
Down,
}
impl Display for Move {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Left => f.write_str("Left"),
Self::Right => f.write_str("Right"),
Self::Up => f.write_str("Up"),
Self::Down => f.write_str("Down"),
}
}
}
impl From<char> for Move {
fn from(c: char) -> Self {
match c {
'<' => Self::Left,
'>' => Self::Right,
'^' => Self::Up,
'v' => Self::Down,
c => panic!("invalid move {}", c),
}
}
}
impl Move {
fn ofs(&self) -> (i64, i64) {
match self {
Move::Left => (-1, 0),
Move::Right => (1, 0),
Move::Up => (0, -1),
Move::Down => (0, 1),
}
}
}
#[derive(Debug)]
struct MovePlan(Vec<Move>);
impl FromStr for MovePlan {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(MovePlan(s.chars().filter(|c| *c != '\n').map(Move::from).collect()))
}
}
fn parse(input: &str) -> (Warehouse, MovePlan) {
let lines = input.lines().collect_vec();
let parts = lines.split(|l| l.is_empty()).map(|ls| ls.join("\n")).collect_vec();
let map: Grid<u8> = parts[0].parse().unwrap();
let wh = Warehouse {
robot_pos: map.find(&b'@').unwrap().to_coord(),
map,
};
let moves = parts[1].parse().unwrap();
(wh, moves)
}
#[aoc(day15, part1)]
pub fn part1(input: &str) -> i64 {
let (mut wh, moves) = parse(input);
for m in moves.0 {
wh.step_robot(m);
}
wh.score()
}
#[aoc(day15, part2)]
pub fn part2(input: &str) -> i64 {
let (mut wh, moves) = parse(input);
wh.embiggen();
for m in moves.0 {
wh.step_robot(m);
}
wh.score()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE1: &str = "########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv<v>>v<<";
const EXAMPLE2: &str = "##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^";
const EXAMPLE3: &str = "#######
#...#.#
#.....#
#..OO@#
#..O..#
#.....#
#######
<vv<<^^<<^^";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE1), 2028);
assert_eq!(part1(EXAMPLE2), 10092);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE3), 618);
assert_eq!(part2(EXAMPLE2), 9021);
}
}

294
src/day16.rs Normal file
View File

@ -0,0 +1,294 @@
use aoc_runner_derive::aoc;
use grid::{Coord2d, Grid};
use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap},
str::FromStr,
};
type CoordType = i16;
type Coord = (CoordType, CoordType);
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
enum FacingDirection {
East,
South,
West,
North,
}
impl FacingDirection {
fn ofs(&self) -> (CoordType, CoordType) {
match self {
FacingDirection::East => (1, 0),
FacingDirection::South => (0, 1),
FacingDirection::West => (-1, 0),
FacingDirection::North => (0, -1),
}
}
fn reachable(&self) -> &[FacingDirection; 3] {
// Can move perpendicularly or the same direction, backwards would always increase path cost
match self {
FacingDirection::East => &[FacingDirection::East, FacingDirection::North, FacingDirection::South],
FacingDirection::West => &[FacingDirection::West, FacingDirection::North, FacingDirection::South],
FacingDirection::South => &[FacingDirection::South, FacingDirection::East, FacingDirection::West],
FacingDirection::North => &[FacingDirection::North, FacingDirection::East, FacingDirection::West],
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
struct State {
cost: usize,
position: Coord,
facing: FacingDirection,
}
impl Ord for State {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.cost
.cmp(&self.cost)
.then_with(|| self.position.cmp(&other.position))
.then_with(|| self.facing.cmp(&other.facing))
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
struct PathState {
state: State,
path: Vec<Coord>,
}
impl Ord for PathState {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.state.cmp(&other.state)
}
}
impl PartialOrd for PathState {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
struct Maze {
map: Grid<u8>,
}
impl FromStr for Maze {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let map: Grid<u8> = s.parse()?;
Ok(Self { map })
}
}
impl Maze {
fn valid_moves<'a>(&'a self, state: &'a State) -> impl Iterator<Item = State> + use<'a> {
let reachable = state.facing.reachable();
reachable
.iter()
.map(|dir| (dir, (state.position.0 + dir.ofs().0, state.position.1 + dir.ofs().1)))
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#'))
.map(|(dir, pos)| State {
facing: *dir,
position: pos,
cost: if *dir == state.facing {
state.cost + 1
} else {
state.cost + 1001
},
})
}
fn dijkstra(&self) -> usize {
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::new();
distances.insert((start, FacingDirection::East), 0);
queue.push(State {
cost: 0,
position: start,
facing: FacingDirection::East,
});
while let Some(state) = queue.pop() {
if state.position == finish {
return state.cost;
}
if distances
.get(&(state.position, state.facing))
.is_some_and(|v| state.cost > *v)
{
continue;
}
for new_state in self.valid_moves(&state) {
if distances
.get(&(new_state.position, new_state.facing))
.is_none_or(|best_cost| new_state.cost < *best_cost)
{
distances.insert((new_state.position, new_state.facing), new_state.cost);
queue.push(new_state);
}
}
}
usize::MAX
}
fn path_dijkstra(&mut self) -> (usize, Vec<Vec<Coord>>) {
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());
let mut best_paths = Vec::new();
let mut best_cost = usize::MAX;
distances.insert((start, FacingDirection::East), 0);
queue.push(PathState {
state: State {
cost: 0,
position: start,
facing: FacingDirection::East,
},
path: Vec::new(),
});
while let Some(PathState { state, mut path }) = queue.pop() {
if distances
.get(&(state.position, state.facing))
.is_some_and(|v| state.cost > *v)
{
continue;
}
if state.position == finish {
match state.cost.cmp(&best_cost) {
Ordering::Less => {
path.push(state.position);
best_paths.clear();
best_paths.push(path);
best_cost = state.cost
}
Ordering::Equal => {
path.push(state.position);
best_paths.push(path);
}
_ => {}
}
continue;
}
for new_state in self.valid_moves(&state) {
if distances
.get(&(new_state.position, new_state.facing))
.is_none_or(|best_cost| new_state.cost <= *best_cost)
{
let mut new_path = path.clone();
new_path.push(state.position);
distances.insert((new_state.position, new_state.facing), new_state.cost);
queue.push(PathState {
state: new_state,
path: new_path,
});
}
}
}
(best_cost, best_paths)
}
}
fn parse(input: &str) -> Maze {
input.parse().unwrap()
}
#[aoc(day16, part1)]
pub fn part1(input: &str) -> usize {
let maze = parse(input);
maze.dijkstra()
}
#[aoc(day16, part2)]
pub fn part2(input: &str) -> usize {
let mut maze = parse(input);
let best_paths = maze.path_dijkstra();
let mut path_map = maze.map.same_shape(false);
for tile in best_paths.1.iter().flatten() {
path_map.set(tile, true);
}
path_map.count(&true)
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE1: &str = "###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############";
const EXAMPLE2: &str = "#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################";
#[test]
fn part1_example1() {
assert_eq!(part1(EXAMPLE1), 7036);
}
#[test]
fn part1_example2() {
assert_eq!(part1(EXAMPLE2), 11048);
}
#[test]
fn part2_example1() {
assert_eq!(part2(EXAMPLE1), 45);
}
#[test]
fn part2_example2() {
assert_eq!(part2(EXAMPLE2), 64);
}
}

298
src/day17.rs Normal file
View File

@ -0,0 +1,298 @@
use aoc_runner_derive::aoc;
use itertools::Itertools;
use regex::Regex;
#[derive(Debug, Clone, Copy)]
enum Register {
A = 0,
B = 1,
C = 2,
}
impl From<usize> for Register {
fn from(value: usize) -> Self {
match value {
0 => Register::A,
1 => Register::B,
2 => Register::C,
v => panic!("invalid register address {}", v),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Opcode {
Adv = 0,
Bxl = 1,
Bst = 2,
Jnz = 3,
Bxc = 4,
Out = 5,
Bdv = 6,
Cdv = 7,
}
impl From<i64> for Opcode {
fn from(value: i64) -> Self {
match value {
0 => Opcode::Adv,
1 => Opcode::Bxl,
2 => Opcode::Bst,
3 => Opcode::Jnz,
4 => Opcode::Bxc,
5 => Opcode::Out,
6 => Opcode::Bdv,
7 => Opcode::Cdv,
v => panic!("invalid opcode {}", v),
}
}
}
impl Opcode {
fn interp_operand(&self, value: i64) -> Operand {
match self {
Self::Adv | Self::Bst | Self::Out | Self::Bdv | Self::Cdv => Operand::new_combo(value),
Self::Bxl | Self::Jnz => Operand::Literal(value),
Self::Bxc => Operand::Ignore,
}
}
}
#[derive(Debug, Clone, Copy)]
enum Operand {
Literal(i64),
Load(Register),
Ignore,
}
impl Operand {
fn new_combo(value: i64) -> Self {
match value {
0..=3 => Operand::Literal(value),
4 => Operand::Load(Register::A),
5 => Operand::Load(Register::B),
6 => Operand::Load(Register::C),
7 => panic!("reserved combo operand 7"),
i => panic!("invalid combo operand {}", i),
}
}
fn value(self, m: &Machine) -> i64 {
match self {
Self::Literal(i) => i,
Self::Load(reg) => *m.registers.load(reg),
Self::Ignore => panic!("can't read ignored operand"),
}
}
}
#[derive(Debug)]
struct RegisterFile<const SIZE: usize, T> {
file: [T; SIZE],
}
impl<T: Clone + From<i64>, const SIZE: usize> RegisterFile<SIZE, T> {
fn load(&self, reg: Register) -> &T {
&self.file[reg as usize]
}
fn store(&mut self, reg: Register, val: T) {
self.file[reg as usize] = val;
}
fn reset(&mut self) {
self.file.fill(0.into());
}
}
#[derive(Debug, Copy, Clone)]
struct Instruction {
opcode: Opcode,
operand: Operand,
}
impl Instruction {
fn exec(&self, m: &mut Machine) {
match self.opcode {
Opcode::Adv => self.adv(m),
Opcode::Bxl => self.bxl(m),
Opcode::Bst => self.bst(m),
Opcode::Jnz => self.jnz(m),
Opcode::Bxc => self.bxc(m),
Opcode::Out => self.out(m),
Opcode::Bdv => self.bdv(m),
Opcode::Cdv => self.cdv(m),
}
}
fn adv(&self, m: &mut Machine) {
let num = m.registers.load(Register::A);
let denom = 1 << self.operand.value(m);
m.registers.store(Register::A, num / denom);
m.advance();
}
fn bxl(&self, m: &mut Machine) {
let lhs = self.operand.value(m);
let rhs = m.registers.load(Register::B);
m.registers.store(Register::B, lhs ^ rhs);
m.advance();
}
fn bst(&self, m: &mut Machine) {
m.registers.store(Register::B, self.operand.value(m) % 8);
m.advance();
}
fn jnz(&self, m: &mut Machine) {
if *m.registers.load(Register::A) == 0 {
m.advance();
} else {
m.jump(self.operand.value(m) as usize);
}
}
fn bxc(&self, m: &mut Machine) {
let lhs = m.registers.load(Register::B);
let rhs = m.registers.load(Register::C);
m.registers.store(Register::B, lhs ^ rhs);
m.advance();
}
fn out(&self, m: &mut Machine) {
m.out_file.push(self.operand.value(m) % 8);
m.advance();
}
fn bdv(&self, m: &mut Machine) {
let num = m.registers.load(Register::A);
let denom = 1 << self.operand.value(m);
m.registers.store(Register::B, num / denom);
m.advance();
}
fn cdv(&self, m: &mut Machine) {
let num = m.registers.load(Register::A);
let denom = 1 << self.operand.value(m);
m.registers.store(Register::C, num / denom);
m.advance();
}
}
#[derive(Debug)]
pub struct Machine {
registers: RegisterFile<3, i64>,
program: Vec<Instruction>,
program_raw: Vec<i64>,
ip: usize,
out_file: Vec<i64>,
}
impl Machine {
fn run(&mut self) {
let program = self.program.clone();
while let Some(inst) = program.get(self.ip) {
inst.exec(self);
}
}
fn advance(&mut self) {
self.ip += 1;
}
fn jump(&mut self, addr: usize) {
self.ip = addr;
}
fn reset(&mut self) {
self.registers.reset();
self.ip = 0;
self.out_file.clear();
}
}
fn parse(input: &str) -> Machine {
let reg_re = Regex::new(r"Register ([ABC]): (\d+)").unwrap();
let prog_re = Regex::new(r"Program: ((\d+,)*\d+)").unwrap();
let mut registers: RegisterFile<3, i64> = RegisterFile { file: [0; 3] };
let mut program = Vec::new();
let mut program_raw = Vec::new();
for line in input.lines() {
if let Some(caps) = reg_re.captures(line) {
let address = (caps[1].as_bytes()[0] - b'A') as usize;
let value = caps[2].parse().unwrap();
registers.store(address.into(), value);
continue;
}
if let Some(caps) = prog_re.captures(line) {
let instructions = caps[1].split(',');
for (inst, operand) in instructions.tuples() {
let opcode = inst.parse::<i64>().unwrap();
let operand = operand.parse::<i64>().unwrap();
program_raw.push(opcode);
program_raw.push(operand);
let opcode: Opcode = opcode.into();
program.push(Instruction {
operand: opcode.interp_operand(operand),
opcode,
});
}
}
}
Machine {
registers,
program,
program_raw,
out_file: Vec::new(),
ip: 0,
}
}
#[aoc(day17, part1)]
pub fn part1(input: &str) -> String {
let mut machine = parse(input);
machine.run();
machine.out_file.iter().map(|n| n.to_string()).join(",")
}
// The program must be of the correct form to be solvable
pub fn solve(m: &mut Machine, guess: i64, i: usize) -> Option<i64> {
if i == m.program_raw.len() {
return Some(guess);
}
let program_pos = m.program_raw.len() - 1 - i;
let goal_digit = m.program_raw[program_pos];
for digit in 0..8 {
let local_guess = (digit << (program_pos * 3)) + guess;
m.reset();
m.registers.store(Register::A, local_guess);
m.run();
if m.out_file.len() == m.program_raw.len() && m.out_file[program_pos] == goal_digit {
if let Some(sol) = solve(m, local_guess, i + 1) {
return Some(sol);
}
}
}
None
}
#[aoc(day17, part2)]
pub fn part2(input: &str) -> i64 {
let mut machine = parse(input);
solve(&mut machine, 0, 0).expect("expected a solution")
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE1: &str = "Register A: 729
Register B: 0
Register C: 0
Program: 0,1,5,4,3,0";
const EXAMPLE2: &str = "Register A: 2024
Register B: 0
Register C: 0
Program: 0,3,5,4,3,0";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE1), "4,6,3,5,6,3,5,2,1,0");
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE2), 117440);
}
}

286
src/day18.rs Normal file
View File

@ -0,0 +1,286 @@
use aoc_runner_derive::aoc;
use grid::Grid;
use itertools::Itertools;
use std::{
cmp::Reverse,
collections::{BinaryHeap, VecDeque},
};
#[derive(Clone)]
struct MemoryMap {
map: Grid<bool>,
byte_stream: Vec<(i64, i64)>,
}
trait PathTrack {
const DOES_WORK: bool = true;
fn new() -> Self;
fn push(&mut self, pos: (i64, i64));
fn finalize(&mut self) {}
}
struct LengthPath(usize);
impl PathTrack for LengthPath {
fn new() -> Self {
LengthPath(0)
}
fn push(&mut self, _: (i64, i64)) {
self.0 += 1
}
}
impl PathTrack for Vec<(i64, i64)> {
fn new() -> Self {
Vec::new()
}
fn push(&mut self, pos: (i64, i64)) {
self.push(pos);
}
fn finalize(&mut self) {
self.reverse();
}
}
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);
let mut byte_stream = Vec::new();
for line in input.lines() {
if let Some((x, y)) = line.split_once(',') {
let pos: (i64, i64) = (x.parse().unwrap(), y.parse().unwrap());
byte_stream.push(pos);
}
}
Self { map, byte_stream }
}
fn place_byte(&mut self, i: usize) {
let pos = self.byte_stream[i];
self.map.set(&pos, false);
}
fn place_bytes(&mut self, start: usize, end: usize) {
for i in start..=end {
self.place_byte(i);
}
}
fn valid_moves<'a>(&'a self, pos: &'a (i64, i64)) -> impl Iterator<Item = (i64, i64)> + 'a {
([(0, 1), (1, 0), (0, -1), (-1, 0)])
.iter()
.filter(|ofs| self.map.get(&(pos.0 + ofs.0, pos.1 + ofs.1)).is_some_and(|v| *v))
.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);
let mut costs = self.map.same_shape(i64::MAX);
let mut prev = self.map.same_shape((i64::MAX, i64::MAX));
let mut queue = BinaryHeap::new();
costs.set(&start, 0);
queue.push((Reverse(0), start));
while let Some((cost, pos)) = queue.pop() {
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 costs.get(&pos).is_some_and(|v| cost.0 > *v) {
continue;
}
let moves = self.valid_moves(&pos);
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);
if T::DOES_WORK {
prev.set(&new_pos, pos);
}
queue.push((Reverse(cost.0 + 1), new_pos));
}
}
}
None
}
}
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(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)
}
// 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);
let mut path = input_map.bfs::<Vec<(i64, i64)>>((0, 0)).expect("no path found");
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.bfs::<Vec<(i64, i64)>>(path[obs_at - 1]) {
path = [before, &new_path].concat();
} else {
return input_map.byte_stream[byte];
}
}
}
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)
}
#[aoc(day18, part2)]
pub fn part2(input: &str) -> String {
let sol = part2_impl(input, 71, 71, 1024);
format!("{},{}", sol.0, sol.1)
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0";
#[test]
fn part1_example() {
assert_eq!(part1_impl(EXAMPLE, 7, 7, 12), 22);
}
#[test]
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);
}
}

105
src/day2.rs Normal file
View File

@ -0,0 +1,105 @@
use aoc_runner_derive::{aoc, aoc_generator};
use std::io::{BufRead, Lines};
#[aoc_generator(day2)]
pub fn get_input(input: &[u8]) -> Reports {
Reports::from(input.lines())
}
#[derive(Debug)]
pub struct Reports {
reports: Vec<Vec<u64>>,
}
impl<T: BufRead> From<Lines<T>> for Reports {
fn from(lines: Lines<T>) -> Self {
let mut reports = Vec::new();
for line in lines.map(|i| i.unwrap()) {
reports.push(
line.split_ascii_whitespace()
.map(|record| record.parse::<u64>().unwrap())
.collect(),
)
}
Reports { reports }
}
}
impl Reports {
fn is_safe(report: &[u64]) -> bool {
let mut ascending: bool = true;
let mut descending: bool = true;
for (a, b) in report.iter().zip(report.iter().skip(1)) {
if a > b {
ascending = false
}
if a < b {
descending = false;
}
let ad = a.abs_diff(*b);
if !(1..=3).contains(&ad) || (!ascending && !descending) {
return false;
};
}
true
}
fn count_safe(&self) -> u64 {
self.reports.iter().filter(|report| Self::is_safe(report)).count() as u64
}
fn is_dumb_dampened_safe(report: &[u64]) -> bool {
if Self::is_safe(report) {
return true;
}
for i in 0..report.len() {
let mut new_vec = report.to_owned();
new_vec.remove(i);
if Self::is_safe(&new_vec) {
return true;
}
}
false
}
fn dampened_count_safe(&self) -> u64 {
self.reports
.iter()
.filter(|report| Self::is_dumb_dampened_safe(report))
.count() as u64
}
}
// PROBLEM 1 solution
#[aoc(day2, part1)]
pub fn part1(input: &Reports) -> u64 {
input.count_safe()
}
// PROBLEM 2 solution
#[aoc(day2, part2)]
pub fn part2(input: &Reports) -> u64 {
input.dampened_count_safe()
}
#[cfg(test)]
mod tests {
use crate::day2::*;
const EXAMPLE: &[u8] = b"7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9";
#[test]
fn part1_example() {
let input = get_input(EXAMPLE);
println!("{:?}", input);
assert_eq!(part1(&input), 2);
}
#[test]
fn part2_example() {
let input = get_input(EXAMPLE);
assert_eq!(part2(&input), 4);
}
}

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() {}
}

64
src/day3.rs Normal file
View File

@ -0,0 +1,64 @@
use aoc_runner_derive::{aoc, aoc_generator};
use atoi::FromRadix10;
use regex::bytes::Regex;
use std::io::BufRead;
#[aoc_generator(day3)]
pub fn get_input(input: &[u8]) -> Vec<String> {
input.lines().map(|l| l.unwrap()).collect()
}
// PROBLEM 1 solution
#[aoc(day3, part1)]
pub fn part1(input: &[String]) -> u64 {
let re = Regex::new(r"(?-u)mul\((\d+),(\d+)\)").unwrap();
input
.iter()
.map(|line| {
re.captures_iter(line.as_bytes())
.map(|m| u64::from_radix_10(&m[1]).0 * u64::from_radix_10(&m[2]).0)
.sum::<u64>()
})
.sum()
}
// PROBLEM 2 solution
#[aoc(day3, part2)]
pub fn part2(input: &[String]) -> u64 {
let mut sum = 0u64;
let mut do_mul: u64 = 1;
let re = Regex::new(r"(?-u)(do\(\)|don't\(\)|mul\((\d+),(\d+)\))").unwrap();
for line in input {
let line = line.as_bytes();
for m in re.captures_iter(line) {
match &m[1] {
b"do()" => do_mul = 1,
b"don't()" => do_mul = 0,
_ => {
sum += u64::from_radix_10(&m[2]).0 * u64::from_radix_10(&m[3]).0 * do_mul;
}
}
}
}
sum
}
#[cfg(test)]
mod tests {
use crate::day3::*;
const EXAMPLE1: &[u8] = b"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
const EXAMPLE2: &[u8] = b"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))";
#[test]
fn part1_example() {
let input = get_input(EXAMPLE1);
assert_eq!(part1(&input), 161);
}
#[test]
fn part2_example() {
let input = get_input(EXAMPLE2);
assert_eq!(part2(&input), 48);
}
}

View File

@ -1,40 +1,12 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::time::{Duration, Instant};
use aoc_runner_derive::{aoc, aoc_generator};
use std::io::{BufRead, Lines};
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
pub fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
#[aoc_generator(day4)]
pub fn get_input(input: &[u8]) -> WordSearch {
WordSearch::from(input.lines())
}
fn duration_format(duration: Duration) -> String {
match duration.as_secs_f64() {
x if x > 1.0 => format!("{:.3}s", x),
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
x => format!("{:.3}us", x * 1e6),
}
}
fn main() {
let input = get_input();
let start = Instant::now();
let ans1 = problem1(input);
let duration1 = start.elapsed();
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
let input = get_input();
let start = Instant::now();
let ans2 = problem2(input);
let duration2 = start.elapsed();
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
println!("Total duration: {}", duration_format(duration1 + duration2));
}
struct WordSearch {
pub struct WordSearch {
rows: Vec<String>,
}
@ -81,22 +53,18 @@ impl WordSearch {
for x in 0..width {
for y in 0..height {
// check down-right
if x <= width - needle.len() && y <= height - needle.len() {
if (0..needle.len())
.into_iter()
.all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into())
{
count += 1
}
if x <= width - needle.len()
&& y <= height - needle.len()
&& (0..needle.len()).all(|i| self.get(x + i, y + i) == needle.as_bytes()[i].into())
{
count += 1
}
// check down-left
if x >= needle.len() - 1 && y <= height - needle.len() {
if (0..needle.len())
.into_iter()
.all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into())
{
count += 1
}
if x >= needle.len() - 1
&& y <= height - needle.len()
&& (0..needle.len()).all(|i| self.get(x - i, y + i) == needle.as_bytes()[i].into())
{
count += 1
}
}
}
@ -138,11 +106,10 @@ impl WordSearch {
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
#[aoc(day4, part1)]
pub fn part1(ws: &WordSearch) -> u64 {
let needle = "XMAS";
let needle_rev: String = needle.chars().rev().collect();
let ws = WordSearch::from(input);
ws.count_forward(needle)
+ ws.count_forward(&needle_rev)
+ ws.count_vertical(needle)
@ -152,17 +119,16 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
let ws = WordSearch::from(input);
#[aoc(day4, part2)]
pub fn part2(ws: &WordSearch) -> u64 {
ws.count_x_mas()
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
use crate::day4::*;
const EXAMPLE: &str = &"MMMSXXMASM
const EXAMPLE: &[u8] = b"MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
@ -174,14 +140,12 @@ MAMMMXMMMM
MXMXAXMASX";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 18);
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 18);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 9);
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 9);
}
}

148
src/day5.rs Normal file
View File

@ -0,0 +1,148 @@
use aoc_runner_derive::{aoc, aoc_generator};
use rustc_hash::FxHashMap;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::io::BufRead;
type HashMap<K, V> = FxHashMap<K, V>;
#[aoc_generator(day5)]
pub fn get_input(input: &[u8]) -> (OrderingRules, Vec<Vec<u64>>) {
let mut lines = input.lines();
let pairs = HashMap::from_iter(
lines
.by_ref()
.map_while(|l| match l {
Ok(line) if !line.is_empty() => {
let rule = BeforeRule::from(line);
Some(vec![
((rule.a, rule.b), Ordering::Less),
((rule.b, rule.a), Ordering::Greater),
])
}
_ => None,
})
.flatten(),
);
let updates: Vec<Vec<u64>> = lines
.by_ref()
.map(|l| l.unwrap().split(',').map(|n| n.parse::<u64>().unwrap()).collect())
.collect();
(OrderingRules { pairs }, updates)
}
#[derive(Debug)]
struct BeforeRule {
a: u64,
b: u64,
}
impl From<String> for BeforeRule {
fn from(line: String) -> BeforeRule {
let nums = line.split_once('|').unwrap();
BeforeRule {
a: nums.0.parse().unwrap(),
b: nums.1.parse().unwrap(),
}
}
}
#[derive(Debug)]
pub struct OrderingRules {
pairs: HashMap<(u64, u64), Ordering>,
}
impl OrderingRules {
fn check(&self, pages: &[u64]) -> bool {
pages.is_sorted_by(|a, b| self.is_sorted(*a, *b))
}
fn cmp(&self, a: u64, b: u64) -> Ordering {
if let Some(ord) = self.pairs.get(&(a, b)) {
*ord
} else {
Ordering::Equal
}
}
fn is_sorted(&self, a: u64, b: u64) -> bool {
matches!(self.pairs.get(&(a, b)), Some(Ordering::Less) | Some(Ordering::Equal))
}
}
// impl<'a, T: Iterator<Item = &'a str>> From<&mut T> for OrderingRules {
// fn from(input: &mut T) -> Self {
// let mut rules = Vec::new();
// for line in input {
// rules.push(line.into())
// }
// Self { rules }
// }
// }
// PROBLEM 1 solution
#[aoc(day5, part1)]
pub fn part1((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
updates
.iter()
.filter(|update| rules.check(update))
.map(|update| update[update.len() / 2])
.sum()
}
// PROBLEM 2 solution
#[aoc(day5, part2)]
pub fn part2((rules, updates): &(OrderingRules, Vec<Vec<u64>>)) -> u64 {
let mut updates = updates.clone();
updates
.iter_mut()
.filter(|update| !rules.check(update))
.map(|update| {
update.sort_by(|a, b| rules.cmp(*a, *b));
update[update.len() / 2]
})
.sum()
}
#[cfg(test)]
mod tests {
use crate::day5::*;
const EXAMPLE: &[u8] = b"47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 143);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 123);
}
}

215
src/day6.rs Normal file
View File

@ -0,0 +1,215 @@
use aoc_runner_derive::{aoc, aoc_generator};
use bitflags::bitflags;
use rayon::iter::ParallelIterator;
use rayon::slice::ParallelSlice;
use std::fmt;
use std::io::BufRead;
use std::ops::BitAnd;
use grid::Grid;
#[aoc_generator(day6)]
pub fn get_input(input: &[u8]) -> Map {
Map::from(input)
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum FacingDirection {
Up = 1,
Down = 2,
Left = 4,
Right = 8,
}
impl FacingDirection {
fn next(&self) -> FacingDirection {
match self {
FacingDirection::Up => FacingDirection::Right,
FacingDirection::Down => FacingDirection::Left,
FacingDirection::Left => FacingDirection::Up,
FacingDirection::Right => FacingDirection::Down,
}
}
fn pos_ofs(&self, pos: (i64, i64)) -> (i64, i64) {
match self {
FacingDirection::Up => (pos.0, pos.1 + -1),
FacingDirection::Down => (pos.0, pos.1 + 1),
FacingDirection::Left => (pos.0 + -1, pos.1),
FacingDirection::Right => (pos.0 + 1, pos.1),
}
}
}
enum StepOutcome {
LeftMap,
LoopFound,
Continue,
}
#[derive(Eq, PartialEq)]
enum RunOutcome {
LeftMap,
LoopFound,
}
bitflags! {
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct DirectionSet: u8 {
const Up = 1;
const Down = 2;
const Left = 4;
const Right = 8;
}
}
impl From<FacingDirection> for DirectionSet {
fn from(value: FacingDirection) -> Self {
match value {
FacingDirection::Up => DirectionSet::Up,
FacingDirection::Down => DirectionSet::Down,
FacingDirection::Left => DirectionSet::Left,
FacingDirection::Right => DirectionSet::Right,
}
}
}
impl fmt::Display for DirectionSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl BitAnd<FacingDirection> for DirectionSet {
type Output = DirectionSet;
fn bitand(self, rhs: FacingDirection) -> Self::Output {
self & DirectionSet::from(rhs)
}
}
#[derive(Clone)]
pub struct Map {
grid: Grid<u8>,
visited_from: Grid<DirectionSet>,
guard_facing: FacingDirection,
guard_pos: (i64, i64),
path: Vec<((i64, i64), FacingDirection)>,
}
impl<T: BufRead> From<T> for Map {
fn from(input: T) -> Self {
let grid = Grid::from(input);
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.into(),
guard_facing,
visited_from,
path: Vec::new(),
}
}
}
impl Map {
fn look(&self, dir: &FacingDirection) -> Option<&u8> {
self.grid.get(&dir.pos_ofs(self.guard_pos))
}
/// Move one step in the facing direction, return if we are still inside the bounds
fn step_guard<const RECORD_PATH: bool>(&mut self) -> StepOutcome {
let new_pos = self.guard_facing.pos_ofs(self.guard_pos);
if self
.visited_from
.get(&new_pos)
.is_some_and(|dirs| dirs.contains(self.guard_facing.into()))
{
StepOutcome::LoopFound
} else if self.grid.set(&new_pos, b'X').is_some() {
if RECORD_PATH {
self.path.push((new_pos, self.guard_facing));
}
self.visited_from.set(
&new_pos,
*self.visited_from.get(&new_pos).unwrap() | self.guard_facing.into(),
);
self.guard_pos = new_pos;
StepOutcome::Continue
} else {
StepOutcome::LeftMap
}
}
fn run_guard<const RECORD_PATH: bool>(&mut self) -> RunOutcome {
while let Some(val) = self.look(&self.guard_facing) {
match val {
b'#' => {
// obstacle, turn right
self.guard_facing = self.guard_facing.next();
}
_ => match self.step_guard::<RECORD_PATH>() {
StepOutcome::LeftMap => return RunOutcome::LeftMap,
StepOutcome::LoopFound => return RunOutcome::LoopFound,
StepOutcome::Continue => {}
},
}
}
RunOutcome::LeftMap
}
}
// PROBLEM 1 solution
#[aoc(day6, part1)]
pub fn part1(map: &Map) -> u64 {
let mut map = map.clone();
map.run_guard::<false>();
map.grid.count(&b'X') as u64
}
// PROBLEM 2 solution
#[aoc(day6, part2)]
pub fn part2(input_map: &Map) -> u64 {
// Use the solution from problem 1 to reduce the number of positions where obstacle placement will change the path
let mut path_map = input_map.clone();
path_map.run_guard::<true>();
path_map
.path
.par_windows(2)
.filter(|prev_cur| {
let last_posdir = prev_cur[0];
let mut test_map = input_map.clone();
test_map.grid.set(&prev_cur[1].0, b'#').unwrap();
test_map.guard_pos = last_posdir.0;
test_map.guard_facing = last_posdir.1;
test_map.run_guard::<false>() == RunOutcome::LoopFound
})
.count() as u64
}
#[cfg(test)]
mod tests {
use crate::day6::*;
const EXAMPLE: &[u8] = b"....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 41);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 6);
}
}

121
src/day7.rs Normal file
View File

@ -0,0 +1,121 @@
use aoc_runner_derive::{aoc, aoc_generator};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::io::{BufRead, Lines};
#[aoc_generator(day7)]
pub fn get_input(input: &[u8]) -> Calibrations {
Calibrations::from(input.lines())
}
#[derive(Debug, Clone)]
struct Calibration {
result: u64,
numbers: Vec<u64>,
}
impl From<&str> for Calibration {
fn from(value: &str) -> Self {
let (result, rest) = value.split_once(':').unwrap();
Self {
result: result.parse().unwrap(),
numbers: rest.split_ascii_whitespace().map(|s| s.parse().unwrap()).collect(),
}
}
}
#[derive(Debug)]
pub struct Calibrations {
cals: Vec<Calibration>,
}
#[derive(Debug, Copy, Clone)]
enum Operator {
Add,
Multiply,
Concatenate,
}
impl Operator {
fn exec(&self, a: u64, b: u64) -> u64 {
match self {
Operator::Add => a + b,
Operator::Multiply => a * b,
Operator::Concatenate => u64::pow(10, b.ilog10() + 1) * a + b,
}
}
}
impl<T: BufRead> From<Lines<T>> for Calibrations {
fn from(input: Lines<T>) -> Self {
let cals = input.map(|l| l.unwrap().as_str().into()).collect();
Self { cals }
}
}
impl Calibrations {
fn possible(&self, operators: &[Operator]) -> u64 {
self.cals
.par_iter()
.map(|cal| eval_calibration(operators, cal.result, cal.numbers[0], &cal.numbers[1..]))
.map(|result| result.unwrap_or(0))
.sum()
}
}
fn eval_calibration(operators: &[Operator], expect: u64, left: u64, right: &[u64]) -> Option<u64> {
if left > expect {
// all operations make the number larger, so this branch is hopeless, early exit
return None;
}
if right.is_empty() {
// base case - no further operations
if left == expect {
return Some(left);
} else {
return None;
}
}
operators
.iter()
.map(|oper| eval_calibration(operators, expect, oper.exec(left, right[0]), &right[1..]))
.find_map(|result| result)
}
// PROBLEM 1 solution
#[aoc(day7, part1)]
pub fn part1(cals: &Calibrations) -> u64 {
let operators = [Operator::Multiply, Operator::Add];
cals.possible(&operators)
}
// PROBLEM 2 solution
#[aoc(day7, part2)]
pub fn part2(cals: &Calibrations) -> u64 {
let operators = [Operator::Multiply, Operator::Add, Operator::Concatenate];
cals.possible(&operators)
}
#[cfg(test)]
mod tests {
use crate::day7::*;
const EXAMPLE: &[u8] = b"190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 3749);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 11387);
}
}

99
src/day8.rs Normal file
View File

@ -0,0 +1,99 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::Grid;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use std::io::BufRead;
type HashSet<T> = FxHashSet<T>;
#[aoc_generator(day8)]
pub fn get_input(input: &[u8]) -> AntennaMap {
AntennaMap::from(input)
}
pub struct AntennaMap {
map: Grid<u8>,
}
impl<T: BufRead> From<T> for AntennaMap {
fn from(input: T) -> Self {
Self { map: Grid::from(input) }
}
}
impl AntennaMap {
fn find_antinodes(&self, start: usize, reps: Option<usize>) -> Grid<bool> {
let mut antinodes = Grid::with_shape(self.map.width(), self.map.height(), false);
// find the unique frequencies in a dumb way
// NOTE: The dumb way is faster than the slightly-smarter ways I tried
let freq_set: HashSet<&u8> = HashSet::from_iter(self.map.data.iter().filter(|c| **c != b'.'));
// for each unique frequency, get all the pairs' positions
for freq in freq_set {
for pair in self
.map
.data
.iter()
.enumerate()
.filter(|(_, c)| *c == freq)
.map(|(i, _)| self.map.coord(i as i64).unwrap())
.permutations(2)
{
// 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.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.x + i * offset.0, a.y + i * offset.1);
if antinodes.set(&node_pos, true).is_none() {
// left the grid
break;
}
}
}
}
antinodes
}
}
// PROBLEM 1 solution
#[aoc(day8, part1)]
pub fn part1(map: &AntennaMap) -> u64 {
let antinodes = map.find_antinodes(1, Some(1));
antinodes.count(&true) as u64
}
// PROBLEM 2 solution
#[aoc(day8, part2)]
pub fn part2(map: &AntennaMap) -> u64 {
let antinodes = map.find_antinodes(0, None);
antinodes.count(&true) as u64
}
#[cfg(test)]
mod tests {
use crate::day8::*;
const EXAMPLE: &[u8] = b"............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 14);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 34);
}
}

166
src/day9.rs Normal file
View File

@ -0,0 +1,166 @@
use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools;
use std::fmt::{Display, Write};
use std::io::{BufRead, Lines};
#[aoc_generator(day9)]
pub fn get_input(input: &[u8]) -> DiskMap {
DiskMap::from(input.lines())
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
enum Unit {
File(usize),
Free,
}
#[derive(Clone)]
struct Inode {
id: usize,
pos: usize,
len: u8,
}
#[derive(Clone)]
pub struct DiskMap {
map: Vec<Unit>,
files: Vec<Inode>,
frees: Vec<Inode>,
}
impl<T: BufRead> From<Lines<T>> for DiskMap {
fn from(mut input: Lines<T>) -> Self {
let line_s = input.next().unwrap().unwrap();
let line = line_s.as_bytes();
let mut file_id = 0;
let mut map = Vec::new();
let mut files = Vec::new();
let mut frees = Vec::new();
for (i, c) in line.iter().enumerate() {
let len = c - b'0';
if i % 2 == 0 {
// file
files.push(Inode {
id: file_id,
pos: map.len(),
len,
});
for _ in 0..len {
map.push(Unit::File(file_id))
}
file_id += 1;
} else {
// free
frees.push(Inode {
id: 0,
pos: map.len(),
len,
});
for _ in 0..len {
map.push(Unit::Free)
}
}
}
Self { map, files, frees }
}
}
impl Display for DiskMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for unit in &self.map {
match unit {
Unit::File(i) => f.write_char((b'0' + (*i % 10) as u8) as char)?,
Unit::Free => f.write_char('.')?,
}
}
Ok(())
}
}
impl DiskMap {
fn checksum(&self) -> u64 {
self.map
.iter()
.enumerate()
.map(|(i, u)| match u {
Unit::File(id) => i * id,
Unit::Free => 0,
})
.sum::<usize>() as u64
}
}
// PROBLEM 1 solution
#[aoc(day9, part1)]
pub fn part1(map: &DiskMap) -> u64 {
let mut map = map.to_owned();
let mut last_free = 0;
for file in map.files.iter().rev() {
let frees = map
.map
.iter()
.enumerate()
.skip(last_free) // we greedy fill, so no need to check for free space before the last one we used
.take(file.pos + file.len as usize) // and we only need to search until the end of the current file
.filter(|(_i, u)| **u == Unit::Free || **u == Unit::File(file.id)) // look for free space or our existing space
.map(|(i, _u)| i)
.take(file.len as usize) // get the first file.len free blocks
.collect_vec();
// Note: no need to test for too small frees list here, since we are guaranteed at worst to find our current position
if frees[0] >= file.pos {
// if the first available free is > file.pos, it's fully packed, job done
break;
}
#[allow(clippy::needless_range_loop)]
for j in 0..file.len as usize {
map.map.swap(frees[j], file.pos + j);
}
last_free = frees[file.len as usize - 1]
}
map.checksum()
}
// PROBLEM 2 solution
#[aoc(day9, part2)]
pub fn part2(map: &DiskMap) -> u64 {
let mut map = map.to_owned();
for file in map.files.iter().rev() {
let free = map.frees.iter_mut().find(|inode| inode.len >= file.len); // find the first entry in the free space map large enough
if let Some(free) = free {
if free.pos >= file.pos {
// if it's past our position, continue, but can't break since there might be free space for future files
continue;
}
for j in 0..file.len {
map.map.swap(free.pos + j as usize, file.pos + j as usize);
}
// Note: It is slightly faster to keep these hanging around in the free map with size = 0 then to remove them from the vec
free.len -= file.len;
free.pos += file.len as usize;
map.frees.push(Inode {
id: 0,
pos: file.pos,
len: file.len,
});
}
}
map.checksum()
}
#[cfg(test)]
mod tests {
use crate::day9::*;
const EXAMPLE: &[u8] = b"2333133121414131402";
#[test]
fn part1_example() {
assert_eq!(part1(&get_input(EXAMPLE)), 1928);
}
#[test]
fn part2_example() {
assert_eq!(part2(&get_input(EXAMPLE)), 2858);
}
}

28
src/lib.rs Normal file
View File

@ -0,0 +1,28 @@
pub mod day1;
pub mod day10;
pub mod day11;
pub mod day12;
pub mod day13;
pub mod day14;
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;
pub mod day6;
pub mod day7;
pub mod day8;
pub mod day9;
use aoc_runner_derive::aoc_lib;
aoc_lib! { year = 2024 }

3
src/main.rs Normal file
View File

@ -0,0 +1,3 @@
use aoc_runner_derive::aoc_main;
aoc_main! { lib = aoc2024 }

View File

@ -3,5 +3,5 @@
version = 3
[[package]]
name = "day1"
name = "grid"
version = "0.1.0"

View File

@ -1,6 +1,9 @@
[package]
name = "day2"
name = "grid"
version = "0.1.0"
edition = "2021"
[dependencies]
[lib]
path = "lib.rs"

495
utils/grid/lib.rs Normal file
View File

@ -0,0 +1,495 @@
use std::{
fmt::{Debug, Display, Formatter, Write},
io::{BufRead, Cursor},
iter::repeat_n,
mem::swap,
ops::{Add, AddAssign, Sub},
str::FromStr,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Coord2d {
pub x: i64,
pub y: i64,
}
pub trait AsCoord2d {
fn to_coord(self) -> Coord2d;
fn x(&self) -> i64;
fn y(&self) -> i64;
}
impl<T: AsCoord2d> Sub<T> for &Coord2d {
type Output = Coord2d;
fn sub(self, rhs: T) -> Self::Output {
Coord2d {
x: self.x() - rhs.x(),
y: self.y() - rhs.y(),
}
}
}
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<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
}
fn x(&self) -> i64 {
self.x
}
fn y(&self) -> i64 {
self.y
}
}
impl AsCoord2d for &Coord2d {
fn to_coord(self) -> Coord2d {
self.to_owned()
}
fn x(&self) -> i64 {
self.x
}
fn y(&self) -> i64 {
self.y
}
}
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<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> {
/// 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::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_n(fill, width * height)),
width: width as i64,
}
}
pub fn width(&self) -> usize {
self.width as usize
}
pub fn height(&self) -> usize {
self.data.len() / self.width()
}
pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
c.y() * self.width + c.x()
}
pub fn coord(&self, pos: i64) -> Option<Coord2d> {
if pos < 0 || pos >= self.data.len() as i64 {
None
} else {
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;
}
if c.y() < 0 || c.y() as usize >= self.height() {
return None;
}
let pos = self.pos(c);
if pos < 0 || pos as usize >= self.data.len() {
return None;
}
self.pos(c).try_into().ok()
}
pub fn get<C: AsCoord2d>(&self, c: &C) -> Option<&T> {
match self.valid_pos(c) {
Some(pos) => Some(&self.data[pos]),
None => None,
}
}
pub fn get_mut<C: AsCoord2d>(&mut self, c: &C) -> Option<&mut T> {
match self.valid_pos(c) {
Some(pos) => Some(self.data.get_mut(pos).unwrap()),
None => None,
}
}
pub fn set<C: AsCoord2d>(&mut self, c: &C, mut val: T) -> Option<T> {
match self.valid_pos(c) {
Some(pos) => {
swap(&mut self.data[pos], &mut val);
Some(val)
}
None => None,
}
}
pub fn increment<'a, A, C: AsCoord2d>(&'a mut self, c: &C, i: A) -> Option<&'a T>
where
T: AddAssign<A>,
{
match self.valid_pos(c) {
Some(pos) => {
self.data[pos] += i;
Some(&self.data[pos])
}
None => None,
}
}
pub fn row(&self, y: i64) -> Option<&[T]> {
if y < self.height() as i64 && y >= 0 {
Some(&self.data[self.pos(&(0, y)) as usize..self.pos(&(self.width, y)) as usize])
} else {
None
}
}
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 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()
.enumerate()
.find_map(|(pos, val)| if val == haystack { Some(pos as i64) } else { None })
.unwrap_or(-1),
)
}
pub fn count(&self, haystack: &T) -> usize {
self.data.iter().filter(|item| *item == haystack).count()
}
pub fn forward_slice<C: AsCoord2d>(&self, start: &C, len: i64) -> Option<&[T]> {
let pos = (self.valid_pos(start), self.valid_pos(&(start.x() + len - 1, start.y())));
match pos {
(Some(pos1), Some(pos2)) => Some(&self.data[pos1..pos2 + 1]),
_ => None,
}
}
pub fn swap<A: AsCoord2d, B: AsCoord2d>(&mut self, a: A, b: B) {
if let (Some(a), Some(b)) = (self.valid_pos(&a), self.valid_pos(&b)) {
self.data.swap(a, b)
}
}
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
// if (self.width as usize) < needle.len() {
// return Vec::new();
// }
// let mut res = Vec::new();
// for y in 0..self.height() as i64 {
// let mut windows_tmp = self.row(y).unwrap().windows(needle.len());
// let windows = if REV {
// windows_tmp.rev()
// } else {
// windows_tmp
// };
// res.extend(
// windows
// .enumerate()
// .filter_map(|(x, w)| if w == needle { Some((x as i64, y)) } else { None }),
// );
// }
// res
// }
}
impl<T: BufRead> From<T> for Grid<u8> {
fn from(input: T) -> Grid<u8> {
let mut data = Vec::new();
let mut width = 0;
for line in input.split(b'\n').map(|i| i.unwrap()) {
if width == 0 {
width = line.len() as i64
} else if line.len() as i64 != width {
panic!("Grids must have fixed length rows")
}
data.extend_from_slice(&line);
}
Grid { data, width }
}
}
// Should be Grid<char>?
impl FromStr for Grid<u8> {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Cursor::new(s).into())
}
}
// impl<T: Copy + Eq + PartialEq + Display + Debug + Into<char>> Display for Grid<T> {
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// for y in 0..self.height() {
// for x in 0..self.width() {
// f.write_fmt(format_args!("{}",self.get(x as i64, y as i64).unwrap() as char))?;
// }
// f.write_char('\n')?;
// }
// f.write_char('\n')
// }
// }
impl Display for Grid<u8> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for y in 0..self.height() {
for x in 0..self.width() {
f.write_fmt(format_args!("{}", *self.get(&(x as i64, y as i64)).unwrap() as char))?;
}
f.write_char('\n')?;
}
f.write_char('\n')
}
}
impl Display for Grid<bool> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for y in 0..self.height() {
for x in 0..self.width() {
f.write_fmt(format_args!(
"{}",
match *self.get(&(x as i64, y as i64)).unwrap() {
true => '.',
false => '#',
}
))?;
}
f.write_char('\n')?;
}
f.write_char('\n')
}
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_VECTOR: &[u8] = b"ABCD
EFGH
IJKL
FBCG";
static TEST_VECTOR_S: &str = "ABCD
EFGH
IJKL
FBCG";
fn unchecked_load() -> Grid<u8> {
Grid::from(TEST_VECTOR)
}
#[test]
fn from_string() {
let grid = unchecked_load();
assert_eq!(grid.data, "ABCDEFGHIJKLFBCG".as_bytes());
assert_eq!(
TEST_VECTOR_S.parse::<Grid<u8>>().unwrap().data,
"ABCDEFGHIJKLFBCG".as_bytes()
);
}
#[test]
fn indexing() {
let grid = unchecked_load();
assert_eq!(grid.get(&(0, 0)), Some(b'A').as_ref());
assert_eq!(grid.get(&(3, 3)), Some(b'G').as_ref());
assert_eq!(grid.get(&(-1, 0)), None);
assert_eq!(grid.get(&(0, -1)), None);
assert_eq!(grid.get(&(5, 0)), None);
assert_eq!(grid.get(&(0, 5)), None);
}
#[test]
fn forward_slice() {
let grid = unchecked_load();
assert_eq!(grid.forward_slice(&(0, 0), 2), Some(b"AB".as_slice()));
assert_eq!(grid.forward_slice(&(2, 0), 2), Some(b"CD".as_slice()));
assert_eq!(grid.forward_slice(&(2, 0), 3), None);
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();
// assert_eq!(grid.window_compare(b"IJKL"), &[(0, 2)]);
// assert_eq!(grid.window_compare(b"BC"), &[(1, 0), (1, 3)]);
// assert_eq!(grid.window_compare(b"LF").len(), 0);
// }
}

25
utils/misc/Cargo.lock generated Normal file
View File

@ -0,0 +1,25 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "misc"
version = "0.1.0"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]

View File

@ -1,6 +1,7 @@
[package]
name = "day1"
name = "misc"
version = "0.1.0"
edition = "2021"
[dependencies]
num-traits = "0.2.19"

98
utils/misc/src/lib.rs Normal file
View File

@ -0,0 +1,98 @@
use num_traits::Signed;
use std::fmt::Display;
use std::ops::{Add, AddAssign};
/// Wrapped signed integer with custom upper bound with wrapping of 0s to the upper bound
#[derive(Eq, Clone, Copy)]
pub struct CustomWrapped<T: Signed + Copy> {
pub val: T,
pub bound: T,
}
impl<T: Signed + Copy> Add<T> for CustomWrapped<T> {
type Output = CustomWrapped<T>;
fn add(self, rhs: T) -> Self::Output {
Self {
val: ((self.val + rhs % self.bound) + self.bound) % self.bound,
bound: self.bound,
}
}
}
impl<T: Signed + Copy> Add<T> for &CustomWrapped<T> {
type Output = CustomWrapped<T>;
fn add(self, rhs: T) -> Self::Output {
CustomWrapped {
val: ((self.val + rhs % self.bound) + self.bound) % self.bound,
bound: self.bound,
}
}
}
impl<T: Signed + Copy> AddAssign<T> for CustomWrapped<T> {
fn add_assign(&mut self, rhs: T) {
self.val = ((self.val + rhs % self.bound) + self.bound) % self.bound
}
}
impl<T: Signed + Copy> CustomWrapped<T> {
pub fn new(val: T, bound: T) -> Self {
Self { val, bound }
}
}
impl<T: Signed + Copy + PartialEq> PartialEq for CustomWrapped<T> {
fn eq(&self, other: &Self) -> bool {
self.val.eq(&other.val)
}
}
impl<T: Signed + PartialOrd + Copy> PartialOrd for CustomWrapped<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.val.partial_cmp(&other.val)
}
}
impl<T: Signed + Ord + Copy> Ord for CustomWrapped<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.val.cmp(&other.val)
}
}
impl<T: Signed + PartialEq + Copy> PartialEq<T> for CustomWrapped<T> {
fn eq(&self, other: &T) -> bool {
self.val == *other
}
}
impl<T: Signed + PartialOrd + Copy> PartialOrd<T> for CustomWrapped<T> {
fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
self.val.partial_cmp(other)
}
}
impl<T: Display + Signed + Copy> Display for CustomWrapped<T>
where
T: Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.val.fmt(f)
}
}
// impl<T> Into<T> for CustomWrapped<T> {
// fn into(self) -> T {
// self.val
// }
// }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}