Compare commits

..

48 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
33 changed files with 2554 additions and 251 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

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

82
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ahash" name = "ahash"
@ -70,6 +70,8 @@ dependencies = [
"colored", "colored",
"grid", "grid",
"itertools", "itertools",
"misc",
"nom",
"rayon", "rayon",
"regex", "regex",
"rustc-hash", "rustc-hash",
@ -144,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"windows-sys", "windows-sys",
@ -154,9 +156,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
@ -173,9 +175,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.20" version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "darling" name = "darling"
@ -287,6 +289,29 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -549,22 +574,23 @@ dependencies = [
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
"windows_i686_gnu", "windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc", "windows_i686_msvc",
"windows_x86_64_gnu", "windows_x86_64_gnu",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm",
@ -573,45 +599,51 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"

View File

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

View File

@ -1,6 +1,6 @@
<!-- AOC TILES BEGIN --> <!-- AOC TILES BEGIN -->
<h1 align="center"> <h1 align="center">
2024 - 32 ⭐ - Rust 2024 - 48 ⭐ - Rust
</h1> </h1>
<a href="src/day1.rs"> <a href="src/day1.rs">
<img src=".aoc_tiles/tiles/2024/01.png" width="161px"> <img src=".aoc_tiles/tiles/2024/01.png" width="161px">
@ -50,4 +50,31 @@
<a href="src/day16.rs"> <a href="src/day16.rs">
<img src=".aoc_tiles/tiles/2024/16.png" width="161px"> <img src=".aoc_tiles/tiles/2024/16.png" width="161px">
</a> </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 --> <!-- AOC TILES END -->

View File

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

View File

@ -1,12 +1,26 @@
use aoc_runner_derive::aoc; use aoc_runner_derive::aoc;
use colored::Colorize; use colored::Colorize;
use grid::{AsCoord2d, Coord2d, Grid}; use grid::{AsCoord2d, Grid};
use regex::Regex; use misc::CustomWrapped;
use std::str::FromStr; 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 { struct Robot {
pos: Coord2d, pos: Coord,
vel: Coord2d, vel: (i64, i64),
}
struct Robots {
robots: Vec<Robot>,
width: i64,
height: i64,
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -17,52 +31,37 @@ enum Quadrant {
SE = 3, SE = 3,
} }
impl FromStr for Robot { fn nom_i64(input: &str) -> IResult<&str, i64> {
type Err = Box<dyn std::error::Error>; let (i, number) = map_res(recognize(preceded(opt(tag("-")), digit1)), i64::from_str)(input)?;
fn from_str(s: &str) -> Result<Self, Self::Err> { Ok((i, number))
let re = Regex::new(r"p=(\d+),(\d+) v=([+-]?\d+),([+-]?\d+)").unwrap(); }
match re.captures(s) { fn nom_i64_pair(input: &str) -> IResult<&str, (i64, i64)> {
Some(c) => Ok(Self { let (i, pair) = separated_pair(nom_i64, tag(","), nom_i64)(input)?;
pos: ( Ok((i, pair))
c.get(1).unwrap().as_str().parse::<i64>().unwrap(),
c.get(2).unwrap().as_str().parse().unwrap(),
)
.to_coord(),
vel: (
c.get(3).unwrap().as_str().parse::<i64>().unwrap(),
c.get(4).unwrap().as_str().parse().unwrap(),
)
.to_coord(),
}),
None => panic!(),
}
}
} }
impl Robot { impl Robot {
fn step(&mut self, bounds: (i64, i64)) { fn from_str(s: &str, bounds: (i64, i64)) -> Self {
let mut candidate_new_pos = ((self.pos.x() + self.vel.x()), (self.pos.y() + self.vel.y())); let (s, pos) = preceded(tag("p="), nom_i64_pair)(s).unwrap();
if candidate_new_pos.0 < 0 { let (_, vel) = preceded(tag(" v="), nom_i64_pair)(s).unwrap();
// if pos goes negative, add the upper bound Self {
candidate_new_pos.0 += bounds.0; pos: (CustomWrapped::new(pos.0, bounds.0), CustomWrapped::new(pos.1, bounds.1)),
vel,
} }
if candidate_new_pos.1 < 0 { }
candidate_new_pos.1 += bounds.1; fn step(&mut self, count: i64) {
} self.pos.0 += self.vel.x() * count;
candidate_new_pos.0 %= bounds.0; self.pos.1 += self.vel.y() * count;
candidate_new_pos.1 %= bounds.1;
self.pos = candidate_new_pos.to_coord();
} }
fn quad(&self, bounds: (i64, i64)) -> Option<Quadrant> { fn quad(&self, bounds: (i64, i64)) -> Option<Quadrant> {
let splits = (bounds.0 / 2, bounds.1 / 2); let splits = (bounds.0 / 2, bounds.1 / 2);
if self.pos.x() < splits.0 && self.pos.y() < splits.1 { if self.pos.0 < splits.0 && self.pos.1 < splits.1 {
Some(Quadrant::NW) Some(Quadrant::NW)
} else if self.pos.x() > splits.0 && self.pos.y() < splits.1 { } else if self.pos.0 > splits.0 && self.pos.1 < splits.1 {
Some(Quadrant::NE) Some(Quadrant::NE)
} else if self.pos.x() < splits.0 && self.pos.y() > splits.1 { } else if self.pos.0 < splits.0 && self.pos.1 > splits.1 {
Some(Quadrant::SW) Some(Quadrant::SW)
} else if self.pos.x() > splits.0 && self.pos.y() > splits.1 { } else if self.pos.0 > splits.0 && self.pos.1 > splits.1 {
Some(Quadrant::SE) Some(Quadrant::SE)
} else { } else {
None None
@ -70,49 +69,58 @@ impl Robot {
} }
} }
#[allow(dead_code)] impl Robots {
fn display(robots: &Vec<Robot>, bounds: (i64, i64)) { fn from_vec(robots: Vec<Robot>, width: i64, height: i64) -> Self {
let grid = as_grid(robots, bounds); Self { robots, width, height }
for row in 0..grid.height() { }
for col in 0..grid.width() { fn as_grid(&self) -> Grid<usize> {
print!( let mut grid = Grid::with_shape(self.width as usize, self.height as usize, 0usize);
"{}", for r in &self.robots {
if *grid.get(&(col, row)).unwrap() != 0 { grid.increment(&(r.pos.0.val, r.pos.1.val), 1usize);
"".green() }
} else { grid
" ".color(colored::Color::Black) }
} 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)
} }
println!();
} }
} }
fn as_grid(robots: &Vec<Robot>, bounds: (i64, i64)) -> Grid<usize> { impl Display for Robots {
let mut grid = Grid::with_shape(bounds.0 as usize, bounds.1 as usize, 0); fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for r in robots { let grid = self.as_grid();
grid.increment(&r.pos, 1usize); 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(())
} }
grid
} }
fn parse(input: &str) -> Vec<Robot> { fn parse(input: &str, width: i64, height: i64) -> Vec<Robot> {
input.lines().map(|l| l.parse().unwrap()).collect() input.lines().map(|l| Robot::from_str(l, (width, height))).collect()
} }
fn part1_impl(input: &str, width: i64, height: i64) -> u64 { fn part1_impl(input: &str, width: i64, height: i64) -> u64 {
let mut robots = parse(input); let mut robots = Robots::from_vec(parse(input, width, height), width, height);
for _ in 0..100 { robots.step(100);
for r in &mut robots { let counts = robots.count_quads();
r.step((width, height))
}
}
let mut counts = [0; 4];
for r in robots {
if let Some(q) = r.quad((width, height)) {
counts[q as usize] += 1
}
}
counts.iter().product() counts.iter().product()
} }
@ -125,19 +133,16 @@ pub fn part1(input: &str) -> u64 {
pub fn part2(input: &str) -> u64 { pub fn part2(input: &str) -> u64 {
let width = 101; let width = 101;
let height = 103; let height = 103;
let mut robots = parse(input); let mut robots = Robots::from_vec(parse(input, width, height), width, height);
for i in 1.. { for i in 1.. {
for r in &mut robots { robots.step(1);
r.step((width, height))
}
// collect into lines // collect into lines
let g = as_grid(&robots, (width, height)); let g = robots.as_grid();
if g.data if g.data
.chunk_by(|a, b| *a != 0 && *b != 0) .chunk_by(|a, b| *a != 0 && *b != 0)
.filter(|c| !c.is_empty() && c[0] != 0) .filter(|c| !c.is_empty() && c[0] != 0)
.any(|c| c.len() > width as usize / 10) .any(|c| c.len() > width as usize / 10)
{ {
display(&robots, (width, height));
return i; return i;
} }
} }

View File

@ -16,7 +16,7 @@ impl Display for Warehouse {
impl Warehouse { impl Warehouse {
fn step_robot(&mut self, dir: Move) { fn step_robot(&mut self, dir: Move) {
let start = self.robot_pos.clone(); let start = self.robot_pos;
if self.push(&start, &dir) { if self.push(&start, &dir) {
self.robot_pos = &self.robot_pos + dir.ofs(); self.robot_pos = &self.robot_pos + dir.ofs();
} }
@ -40,12 +40,12 @@ impl Warehouse {
// move both parts // move both parts
self.push(&target, dir); self.push(&target, dir);
self.push(&(&target + (-1, 0)), dir); self.push(&(&target + (-1, 0)), dir);
self.map.swap(&target, pos); self.map.swap(target, pos);
} }
b'[' => { b'[' => {
self.push(&target, dir); self.push(&target, dir);
self.push(&(&target + (1, 0)), dir); self.push(&(&target + (1, 0)), dir);
self.map.swap(&target, pos); self.map.swap(target, pos);
} }
c => panic!("unexpected char {}", c), c => panic!("unexpected char {}", c),
} }

View File

@ -1,13 +1,14 @@
use aoc_runner_derive::aoc; use aoc_runner_derive::aoc;
use grid::{AsCoord2d, Coord2d, Grid}; use grid::{Coord2d, Grid};
use itertools::Itertools;
use std::{ use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap}, collections::{BinaryHeap, HashMap},
i64,
str::FromStr, str::FromStr,
usize,
}; };
type CoordType = i16;
type Coord = (CoordType, CoordType);
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
enum FacingDirection { enum FacingDirection {
East, East,
@ -17,7 +18,7 @@ enum FacingDirection {
} }
impl FacingDirection { impl FacingDirection {
fn ofs(&self) -> (i64, i64) { fn ofs(&self) -> (CoordType, CoordType) {
match self { match self {
FacingDirection::East => (1, 0), FacingDirection::East => (1, 0),
FacingDirection::South => (0, 1), FacingDirection::South => (0, 1),
@ -25,11 +26,13 @@ impl FacingDirection {
FacingDirection::North => (0, -1), FacingDirection::North => (0, -1),
} }
} }
fn reachable(&self) -> [FacingDirection; 3] { fn reachable(&self) -> &[FacingDirection; 3] {
// Can move perpendicularly or the same direction, backwards would always increase path cost // Can move perpendicularly or the same direction, backwards would always increase path cost
match self { match self {
FacingDirection::East | FacingDirection::West => [*self, FacingDirection::North, FacingDirection::South], FacingDirection::East => &[FacingDirection::East, FacingDirection::North, FacingDirection::South],
FacingDirection::South | FacingDirection::North => [*self, FacingDirection::East, FacingDirection::West], FacingDirection::West => &[FacingDirection::West, FacingDirection::North, FacingDirection::South],
FacingDirection::South => &[FacingDirection::South, FacingDirection::East, FacingDirection::West],
FacingDirection::North => &[FacingDirection::North, FacingDirection::East, FacingDirection::West],
} }
} }
} }
@ -37,9 +40,8 @@ impl FacingDirection {
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
struct State { struct State {
cost: usize, cost: usize,
position: Coord2d, position: Coord,
facing: FacingDirection, facing: FacingDirection,
path: Vec<Coord2d>,
} }
impl Ord for State { impl Ord for State {
@ -58,94 +60,156 @@ impl PartialOrd for State {
} }
} }
#[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 { struct Maze {
map: Grid<u8>, map: Grid<u8>,
paths: HashMap<usize, Vec<Vec<Coord2d>>>,
} }
impl FromStr for Maze { impl FromStr for Maze {
type Err = Box<dyn std::error::Error>; type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let map: Grid<u8> = s.parse()?; let map: Grid<u8> = s.parse()?;
let paths = HashMap::new();
Ok(Self { map, paths }) Ok(Self { map })
} }
} }
impl Maze { impl Maze {
fn dijkstra<const RECORD_PATHS: bool>(&mut self) -> usize { fn valid_moves<'a>(&'a self, state: &'a State) -> impl Iterator<Item = State> + use<'a> {
let start = self.map.find(&b'S').expect("can't find start").to_coord(); let reachable = state.facing.reachable();
let finish = self.map.find(&b'E').expect("can't find finish").to_coord(); 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 distances = HashMap::new();
let mut queue = BinaryHeap::with_capacity(self.map.data.len()); let mut queue = BinaryHeap::new();
let mut best_cost = usize::MAX;
distances.insert((start, FacingDirection::East), 0); distances.insert((start, FacingDirection::East), 0);
queue.push(State { queue.push(State {
cost: 0, cost: 0,
position: start, position: start,
facing: FacingDirection::East, facing: FacingDirection::East,
path: Vec::new(),
}); });
while let Some(State { while let Some(state) = queue.pop() {
cost, if state.position == finish {
position, return state.cost;
facing,
path,
}) = queue.pop()
{
let mut new_path = path.clone();
new_path.push(position);
if position == finish {
if RECORD_PATHS {
if cost < best_cost {
best_cost = cost
}
if !self.paths.contains_key(&cost) {
self.paths.insert(cost, vec![new_path.clone()]);
} else {
self.paths.get_mut(&cost).unwrap().push(new_path.clone());
}
continue;
} else {
return cost;
}
} }
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) { if distances
.get(&(state.position, state.facing))
.is_some_and(|v| state.cost > *v)
{
continue; continue;
} }
for (new_dir, new_position, new_cost) in facing for new_state in self.valid_moves(&state) {
.reachable()
.iter()
.map(|dir| (dir, &position + dir.ofs()))
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#'))
.map(|(dir, pos)| (dir, pos, if *dir == facing { cost + 1 } else { cost + 1001 }))
{
if distances if distances
.get(&(new_position, *new_dir)) .get(&(new_state.position, new_state.facing))
.is_none_or(|best_cost| new_cost <= *best_cost) .is_none_or(|best_cost| new_state.cost < *best_cost)
{ {
queue.push(State { distances.insert((new_state.position, new_state.facing), new_state.cost);
cost: new_cost, queue.push(new_state);
position: new_position,
facing: *new_dir,
path: new_path.clone(),
});
distances.insert((new_position, *new_dir), new_cost);
} }
} }
} }
if best_cost == usize::MAX || !RECORD_PATHS { usize::MAX
panic!("no path found"); }
} else { fn path_dijkstra(&mut self) -> (usize, Vec<Vec<Coord>>) {
return best_cost; 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)
} }
} }
@ -155,22 +219,20 @@ fn parse(input: &str) -> Maze {
#[aoc(day16, part1)] #[aoc(day16, part1)]
pub fn part1(input: &str) -> usize { pub fn part1(input: &str) -> usize {
let mut maze = parse(input); let maze = parse(input);
maze.dijkstra::<false>() maze.dijkstra()
} }
#[aoc(day16, part2)] #[aoc(day16, part2)]
pub fn part2(input: &str) -> usize { pub fn part2(input: &str) -> usize {
let mut maze = parse(input); let mut maze = parse(input);
let best_cost = maze.dijkstra::<true>(); let best_paths = maze.path_dijkstra();
let best_paths = maze.paths.get(&best_cost).unwrap();
let best_path_tiles = best_paths.into_iter().flatten().collect_vec();
let mut path_map = maze.map.clone(); let mut path_map = maze.map.same_shape(false);
for tile in best_path_tiles { for tile in best_paths.1.iter().flatten() {
path_map.set(&tile, b'O'); path_map.set(tile, true);
} }
path_map.count(&b'O') path_map.count(&true)
} }
#[cfg(test)] #[cfg(test)]

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);
}
}

186
src/day20.rs Normal file
View File

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

240
src/day21.rs Normal file
View File

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

155
src/day22.rs Normal file
View File

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

194
src/day23.rs Normal file
View File

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

229
src/day24.rs Normal file
View File

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

143
src/day25.rs Normal file
View File

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

View File

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

View File

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

View File

@ -1,6 +1,3 @@
mod day16;
use aoc_runner_derive::aoc_lib;
pub mod day1; pub mod day1;
pub mod day10; pub mod day10;
pub mod day11; pub mod day11;
@ -8,7 +5,17 @@ pub mod day12;
pub mod day13; pub mod day13;
pub mod day14; pub mod day14;
pub mod day15; pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;
pub mod day2; 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 day3;
pub mod day4; pub mod day4;
pub mod day5; pub mod day5;
@ -17,4 +24,5 @@ pub mod day7;
pub mod day8; pub mod day8;
pub mod day9; pub mod day9;
use aoc_runner_derive::aoc_lib;
aoc_lib! { year = 2024 } aoc_lib! { year = 2024 }

View File

@ -1,7 +1,7 @@
use std::{ use std::{
fmt::{Debug, Display, Formatter, Write}, fmt::{Debug, Display, Formatter, Write},
io::{BufRead, Cursor}, io::{BufRead, Cursor},
iter::repeat, iter::repeat_n,
mem::swap, mem::swap,
ops::{Add, AddAssign, Sub}, ops::{Add, AddAssign, Sub},
str::FromStr, str::FromStr,
@ -39,6 +39,16 @@ impl<T: AsCoord2d> Add<T> for &Coord2d {
} }
} }
impl<T: AsCoord2d> Add<&T> for Coord2d {
type Output = Coord2d;
fn add(self, rhs: &T) -> Self::Output {
Coord2d {
x: self.x() + rhs.x(),
y: self.y() + rhs.y(),
}
}
}
impl AsCoord2d for Coord2d { impl AsCoord2d for Coord2d {
fn to_coord(self) -> Coord2d { fn to_coord(self) -> Coord2d {
self self
@ -63,86 +73,120 @@ impl AsCoord2d for &Coord2d {
} }
} }
impl AsCoord2d for (i32, i32) { impl<T> AsCoord2d for (T, T)
where
T: Copy + TryInto<i64>,
<T as TryInto<i64>>::Error: Debug,
{
fn to_coord(self) -> Coord2d { fn to_coord(self) -> Coord2d {
Coord2d { Coord2d {
x: self.0.into(), x: self.0.try_into().unwrap(),
y: self.1.into(), y: self.1.try_into().unwrap(),
} }
} }
fn x(&self) -> i64 { fn x(&self) -> i64 {
self.0.into() self.0.try_into().unwrap()
} }
fn y(&self) -> i64 { fn y(&self) -> i64 {
self.1.into() self.1.try_into().unwrap()
} }
} }
impl AsCoord2d for (i64, i64) { impl<T> AsCoord2d for &(T, T)
fn to_coord(self) -> Coord2d { where
Coord2d { x: self.0, y: self.1 } T: Copy + TryInto<i64>,
} <T as TryInto<i64>>::Error: Debug,
fn x(&self) -> i64 { {
self.0
}
fn y(&self) -> i64 {
self.1
}
}
impl AsCoord2d for (usize, usize) {
fn to_coord(self) -> Coord2d { fn to_coord(self) -> Coord2d {
Coord2d { Coord2d {
x: self.0 as i64, x: self.0.try_into().unwrap(),
y: self.1 as i64, y: self.1.try_into().unwrap(),
} }
} }
fn x(&self) -> i64 { fn x(&self) -> i64 {
self.0 as i64 self.0.try_into().unwrap()
} }
fn y(&self) -> i64 { fn y(&self) -> i64 {
self.1 as i64 self.1.try_into().unwrap()
} }
} }
impl AsCoord2d for (u64, u64) { impl From<Coord2d> for (i64, i64) {
fn to_coord(self) -> Coord2d { fn from(value: Coord2d) -> Self {
Coord2d { (value.x, value.y)
x: self.0 as i64, }
y: self.1 as i64, }
#[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,
} }
} }
fn x(&self) -> i64 { }
self.0 as i64
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 y(&self) -> i64 { fn size_hint(&self) -> (usize, Option<usize>) {
self.1 as i64 (self.grid.height() - 1, Some(self.grid.height() - 1))
} }
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq, Debug)]
pub struct Grid<T> { pub struct Grid<T> {
pub data: Vec<T>, pub data: Vec<T>,
width: i64, width: i64,
} }
impl<T: Clone + Eq + PartialEq + Debug> Grid<T> { impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
pub fn new(width: i64) -> Self {
Self {
data: Vec::new(),
width,
}
}
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill` /// 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> { pub fn same_shape<NT: Clone + Eq + PartialEq + Debug>(&self, fill: NT) -> Grid<NT> {
Grid { Grid::with_shape(self.width(), self.height(), fill)
data: Vec::from_iter(repeat(fill).take(self.width() * self.height())),
width: self.width,
}
} }
/// 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 { pub fn with_shape(width: usize, height: usize, fill: T) -> Self {
Self { Self {
data: Vec::from_iter(repeat(fill).take(width * height)), data: Vec::from_iter(repeat_n(fill, width * height)),
width: width as i64, width: width as i64,
} }
} }
@ -155,18 +199,33 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 { pub fn pos<C: AsCoord2d>(&self, c: &C) -> i64 {
c.y() * self.width + c.x() c.y() * self.width + c.x()
} }
pub fn coord(&self, pos: i64) -> Option<(i64, i64)> { pub fn coord(&self, pos: i64) -> Option<Coord2d> {
if pos < 0 || pos >= self.data.len() as i64 { if pos < 0 || pos >= self.data.len() as i64 {
None None
} else { } else {
Some((pos % self.width, pos / self.width)) Some(Coord2d {
x: pos % self.width,
y: pos / self.width,
})
} }
} }
// pub fn coord_iter(&self) -> CoordIter<_> {
// CoordIter { pos: 0, grid: self }
// }
pub fn is_valid<C: AsCoord2d>(&self, c: &C) -> bool {
if c.x() < 0 || c.x() >= self.width {
return false;
}
if c.y() < 0 || c.y() as usize >= self.height() {
return false;
}
return true;
}
fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> { fn valid_pos<C: AsCoord2d>(&self, c: &C) -> Option<usize> {
if c.x() < 0 || c.x() >= self.width { if c.x() < 0 || c.x() >= self.width {
return None; return None;
} }
if c.y() < 0 || c.y() >= self.data.len() as i64 / self.width { if c.y() < 0 || c.y() as usize >= self.height() {
return None; return None;
} }
let pos = self.pos(c); let pos = self.pos(c);
@ -216,15 +275,31 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
} }
} }
pub fn col(&self, x: i64) -> Option<Vec<&T>> { pub fn row_iter(&self, y: i64) -> Option<GridRowIter<T>> {
if x < self.width() as i64 && x >= 0 { if (y as usize) < self.height() {
Some((0..self.height()).map(|y| self.get(&(x, y as i64)).unwrap()).collect()) Some(GridRowIter::new(self, y))
} else { } else {
None None
} }
} }
pub fn find(&self, haystack: &T) -> Option<(i64, i64)> { pub fn col(&self, x: i64) -> Option<Vec<&T>> {
if let Some(iter) = self.col_iter(x) {
Some(iter.collect())
} else {
None
}
}
pub fn col_iter(&self, x: i64) -> Option<GridColIter<T>> {
if (x as usize) < self.width() {
Some(GridColIter::new(self, x))
} else {
None
}
}
pub fn find(&self, haystack: &T) -> Option<Coord2d> {
self.coord( self.coord(
self.data self.data
.iter() .iter()
@ -246,9 +321,8 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
} }
pub fn swap<A: AsCoord2d, B: AsCoord2d>(&mut self, a: A, b: B) { pub fn swap<A: AsCoord2d, B: AsCoord2d>(&mut self, a: A, b: B) {
match (self.valid_pos(&a), self.valid_pos(&b)) { if let (Some(a), Some(b)) = (self.valid_pos(&a), self.valid_pos(&b)) {
(Some(a), Some(b)) => self.data.swap(a, b), self.data.swap(a, b)
_ => {}
} }
} }
@ -323,6 +397,24 @@ impl Display for Grid<u8> {
} }
} }
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -371,6 +463,28 @@ FBCG";
assert_eq!(grid.forward_slice(&(0, 2), 4), Some(b"IJKL".as_slice())); 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] // #[test]
// fn window_compare() { // fn window_compare() {
// let grid = unchecked_load(); // let grid = unchecked_load();

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",
]

7
utils/misc/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
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);
}
}