Compare commits
48 Commits
4b85a90635
...
main
Author | SHA1 | Date | |
---|---|---|---|
77fba87ef6
|
|||
8b8ed2a323
|
|||
6efb9e0f83
|
|||
237ca36381
|
|||
d3bade1bdd
|
|||
50a197856a
|
|||
d3ce12693b
|
|||
d31f9725f5
|
|||
c5857ed449
|
|||
40d5e820bc
|
|||
5d518248a8
|
|||
cebdbd1007
|
|||
32937aaa0e
|
|||
c681727fb3
|
|||
02fc154547
|
|||
13f61e3a2b
|
|||
8958ba9361
|
|||
e60f6effa3
|
|||
c2e3422544
|
|||
6b6dededc2
|
|||
65d498f168
|
|||
cdb3a7261a
|
|||
dbee7d91b6
|
|||
0fee7c3594
|
|||
824d111b18
|
|||
a1dceb6ff1
|
|||
3712b32634
|
|||
2a11e17d92
|
|||
1c254fff93
|
|||
44108c4b35
|
|||
b588837624
|
|||
9a6ca66059
|
|||
2729799fa2
|
|||
8d2fbc0fcb
|
|||
4f48d839b2
|
|||
414569537e
|
|||
5036866663
|
|||
9be86e2cc2
|
|||
5bcead2691
|
|||
c99d8a400a
|
|||
5e8b974137
|
|||
28a88e1aa7
|
|||
33615b015f
|
|||
b7a1f05b1e
|
|||
de7ee8f0f6
|
|||
c31d653612
|
|||
20e6889572
|
|||
755fbbc53d
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 7.1 KiB |
7
.github/workflows/test.yaml
vendored
@ -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
@ -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"
|
||||||
|
@ -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
|
29
README.md
@ -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 -->
|
||||||
|
@ -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 {
|
||||||
|
165
src/day14.rs
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
|
208
src/day16.rs
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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() {}
|
||||||
|
}
|
@ -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(),
|
||||||
|
@ -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;
|
||||||
|
14
src/lib.rs
@ -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 }
|
||||||
|
@ -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
@ -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
@ -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
@ -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);
|
||||||
|
}
|
||||||
|
}
|