Compare commits
49 Commits
d1a30ea629
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5642d65bff | |||
| 745d4a8029 | |||
| a7d32abb93 | |||
| 98fcdfeaef | |||
| 7abd632567 | |||
| 62c866abf9 | |||
| ae143d4c57 | |||
| 5e6870d092 | |||
| a0fa5256e9 | |||
| 49368e7985 | |||
| 95e4095f32 | |||
| 0279edb249 | |||
| 47cb2fefa5 | |||
| ec5b46d793 | |||
| 9b42768fdb | |||
| 93f574463b | |||
| 63fecbd235 | |||
| f825de9426 | |||
| ab870a881e | |||
| 813c9f0612 | |||
| ff8870eb3e | |||
| 22d9f7c42a | |||
| 1e7eb12da9 | |||
| 06786817f3 | |||
| 46d409fa92 | |||
| 0d30968d3e | |||
| 62e9d17eff | |||
| 619c77aab7 | |||
| d05780e64a | |||
| 215e067d33 | |||
| 6d3686c946 | |||
| f10e8a4c04 | |||
| 57f4bc2732 | |||
| 36ceda33db | |||
| 3bdd5472aa | |||
| 876ebfcfe3 | |||
| 4f5d6d7fa0 | |||
| b7005f244d | |||
| 05f3d9411a | |||
| 71984d10d6 | |||
| 3f05695ea3 | |||
| 9a64352fc3 | |||
| 03a8692162 | |||
| 1156bc2e75 | |||
| bedf4a790a | |||
| a446a05c07 | |||
| e3c356b1ac | |||
| 0506dc670a | |||
| a4299a1fc5 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/target
|
||||
/input
|
||||
/.vscode
|
||||
|
||||
394
Cargo.lock
generated
394
Cargo.lock
generated
@ -15,7 +15,157 @@ dependencies = [
|
||||
name = "aoc22"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"num",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"trees",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -24,6 +174,127 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
dependencies = [
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
@ -40,3 +311,126 @@ name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trees"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de5f738ceab88e2491a94ddc33c3feeadfa95fedc60363ef110845df12f3878"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
||||
@ -7,6 +7,13 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
regex = "1"
|
||||
itertools = "^0.10"
|
||||
log = "^0.4"
|
||||
env_logger = "^0.10"
|
||||
num = "^0.1"
|
||||
lazy_static = "^1.4"
|
||||
once_cell = "^1.16"
|
||||
trees = "^0.4"
|
||||
|
||||
[[bin]]
|
||||
name = "d1p1"
|
||||
|
||||
16
src/bin/d10p1.rs
Normal file
16
src/bin/d10p1.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use aoc22::{day10, util};
|
||||
|
||||
pub fn main() {
|
||||
let instructions = day10::parse_instructions(&util::parse_input());
|
||||
let mut cpu = day10::CPU::new(instructions);
|
||||
|
||||
let mut sum = 0;
|
||||
for _ in 0..221 {
|
||||
if (cpu.cycle - (20 - 1)) % 40 == 0 {
|
||||
sum += cpu.signal_strength();
|
||||
}
|
||||
cpu.do_cycle().expect("No more instructions?");
|
||||
}
|
||||
|
||||
println!("Sum of 20 + 40n cycles: {}", sum);
|
||||
}
|
||||
14
src/bin/d10p2.rs
Normal file
14
src/bin/d10p2.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use aoc22::{day10, util};
|
||||
|
||||
pub fn main() {
|
||||
let instructions = day10::parse_instructions(&util::parse_input());
|
||||
let mut cpu = day10::CPU::new(instructions);
|
||||
let mut crt = day10::CRT::new();
|
||||
|
||||
for _ in 0..240 {
|
||||
crt.render(&cpu);
|
||||
cpu.do_cycle().expect("No more instructions?");
|
||||
}
|
||||
|
||||
crt.draw();
|
||||
}
|
||||
22
src/bin/d11p1.rs
Normal file
22
src/bin/d11p1.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use aoc22::{
|
||||
day11::{self, monkey_business},
|
||||
util,
|
||||
};
|
||||
|
||||
const N_ROUNDS: usize = 20;
|
||||
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let monkeys = day11::parse_monkeys(&util::parse_input());
|
||||
|
||||
for _ in 0..N_ROUNDS {
|
||||
day11::do_round(&monkeys, true);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Monkey business after {} rounds is {}",
|
||||
N_ROUNDS,
|
||||
monkey_business(&monkeys)
|
||||
);
|
||||
}
|
||||
22
src/bin/d11p2.rs
Normal file
22
src/bin/d11p2.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use aoc22::{
|
||||
day11::{self, monkey_business},
|
||||
util,
|
||||
};
|
||||
|
||||
const N_ROUNDS: usize = 10000;
|
||||
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let monkeys = day11::parse_monkeys(&util::parse_input());
|
||||
|
||||
for _ in 0..N_ROUNDS {
|
||||
day11::do_round(&monkeys, false);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Monkey business after {} rounds is {}",
|
||||
N_ROUNDS,
|
||||
monkey_business(&monkeys)
|
||||
);
|
||||
}
|
||||
9
src/bin/d12p1.rs
Normal file
9
src/bin/d12p1.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use aoc22::{day12, util};
|
||||
|
||||
pub fn main() {
|
||||
let heightmap = day12::parse_heightmap(&util::parse_input());
|
||||
|
||||
let dist = day12::path_steps(&heightmap, false).expect("No path found!?");
|
||||
|
||||
println!("Distance to target is {}", dist);
|
||||
}
|
||||
9
src/bin/d12p2.rs
Normal file
9
src/bin/d12p2.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use aoc22::{day12, util};
|
||||
|
||||
pub fn main() {
|
||||
let heightmap = day12::parse_heightmap(&util::parse_input());
|
||||
|
||||
let dist = day12::path_steps(&heightmap, true).expect("No path found!?");
|
||||
|
||||
println!("Distance to target is {}", dist);
|
||||
}
|
||||
14
src/bin/d13p1.rs
Normal file
14
src/bin/d13p1.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use aoc22::{day13, util};
|
||||
|
||||
pub fn main() {
|
||||
let pairs = day13::parse_pairs(&util::parse_input());
|
||||
|
||||
let mut sum = 0;
|
||||
for (i, pair) in pairs.iter().enumerate() {
|
||||
if pair.0 < pair.1 {
|
||||
sum += i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Sum of indices of pairs in right order: {}", sum);
|
||||
}
|
||||
24
src/bin/d13p2.rs
Normal file
24
src/bin/d13p2.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use aoc22::{
|
||||
day13::{self, Node},
|
||||
util,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn main() {
|
||||
let mut pairs = day13::parse_pairs(&util::parse_input());
|
||||
let mut packets = Vec::new();
|
||||
while let Some(pair) = pairs.pop() {
|
||||
packets.push(pair.0);
|
||||
packets.push(pair.1);
|
||||
}
|
||||
let div1 = Node::List(vec![Node::List(vec![Node::Num(2)])]);
|
||||
let div2 = Node::List(vec![Node::List(vec![Node::Num(6)])]);
|
||||
packets.push(div1.clone());
|
||||
packets.push(div2.clone());
|
||||
|
||||
packets.sort();
|
||||
let n1 = packets.iter().find_position(|p| **p == div1).unwrap().0 + 1;
|
||||
let n2 = packets.iter().find_position(|p| **p == div2).unwrap().0 + 1;
|
||||
|
||||
println!("Decoder key: {}", n1 * n2);
|
||||
}
|
||||
17
src/bin/d14p1.rs
Normal file
17
src/bin/d14p1.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use aoc22::{day14, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut cave = day14::parse_cave(&util::parse_input(), false);
|
||||
|
||||
println!("Initial cave:");
|
||||
cave.print();
|
||||
|
||||
let mut sand = 0;
|
||||
while cave.drop_sand() {
|
||||
sand += 1;
|
||||
}
|
||||
|
||||
println!("Final cave:");
|
||||
cave.print();
|
||||
println!("Total sand dropped: {}", sand);
|
||||
}
|
||||
17
src/bin/d14p2.rs
Normal file
17
src/bin/d14p2.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use aoc22::{day14, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut cave = day14::parse_cave(&util::parse_input(), true);
|
||||
|
||||
println!("Initial cave:");
|
||||
cave.print();
|
||||
|
||||
let mut sand = 0;
|
||||
while cave.drop_sand() {
|
||||
sand += 1;
|
||||
}
|
||||
|
||||
println!("Final cave:");
|
||||
cave.print();
|
||||
println!("Total sand dropped: {}", sand);
|
||||
}
|
||||
39
src/bin/d15p1.rs
Normal file
39
src/bin/d15p1.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use aoc22::{day15, util};
|
||||
use itertools::Itertools;
|
||||
|
||||
const ROW: isize = 2000000;
|
||||
|
||||
pub fn main() {
|
||||
let sensors = day15::parse_sensors(&util::parse_input());
|
||||
|
||||
let covered = sensors
|
||||
.iter()
|
||||
.map(|s| s.covered(ROW))
|
||||
.filter(|r| !r.is_empty())
|
||||
.sorted_by_key(|r| *r.start());
|
||||
|
||||
let mut max_x = isize::MIN;
|
||||
let mut total_covered = 0;
|
||||
for range in covered {
|
||||
let (min, max) = range.into_inner();
|
||||
if min > max_x {
|
||||
total_covered += max - min + 1;
|
||||
} else if max > max_x {
|
||||
total_covered += max - max_x;
|
||||
}
|
||||
max_x = max_x.max(max);
|
||||
}
|
||||
|
||||
let n_beacons = sensors
|
||||
.iter()
|
||||
.map(|s| s.loc_b)
|
||||
.filter(|b| b.0 == ROW)
|
||||
.unique()
|
||||
.count() as isize;
|
||||
|
||||
println!(
|
||||
"Impossible positions in row {}: {}",
|
||||
ROW,
|
||||
total_covered - n_beacons
|
||||
);
|
||||
}
|
||||
70
src/bin/d15p2.rs
Normal file
70
src/bin/d15p2.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use aoc22::{day15, util};
|
||||
use itertools::Itertools;
|
||||
|
||||
const COORD_MAX: isize = 4000000;
|
||||
const NUM_THREADS: usize = 16;
|
||||
|
||||
pub fn main() {
|
||||
let sensors = Arc::new(day15::parse_sensors(&util::parse_input()));
|
||||
|
||||
let mut handles = vec![];
|
||||
let next_row = Arc::new(Mutex::new(0));
|
||||
|
||||
for _ in 0..NUM_THREADS {
|
||||
let sensors = sensors.clone();
|
||||
let next_row = next_row.clone();
|
||||
let handle = thread::spawn(move || find_possible(&sensors, next_row));
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_possible(sensors: &Vec<day15::Sensor>, next_row: Arc<Mutex<isize>>) {
|
||||
loop {
|
||||
let row = {
|
||||
let mut handle = next_row.lock().unwrap();
|
||||
let result = *handle;
|
||||
if result > COORD_MAX {
|
||||
return;
|
||||
}
|
||||
*handle += 1;
|
||||
result
|
||||
};
|
||||
|
||||
let covered = sensors
|
||||
.iter()
|
||||
.map(|s| s.covered(row))
|
||||
.filter(|r| !r.is_empty())
|
||||
.sorted_by_key(|r| *r.start());
|
||||
|
||||
let mut max_x = isize::MIN;
|
||||
for range in covered {
|
||||
let (min, max) = range.into_inner();
|
||||
if min == max_x + 2 {
|
||||
let x = max_x + 1;
|
||||
println!(
|
||||
"x = {}, y = {}, tuning freq = {}",
|
||||
x,
|
||||
row,
|
||||
x * 4000000 + row
|
||||
)
|
||||
} else if max_x != isize::MIN && min > max_x + 2 {
|
||||
panic!(
|
||||
"More than one coordinate free in row {}: {}..{}",
|
||||
row,
|
||||
max_x + 1,
|
||||
min - 1
|
||||
);
|
||||
}
|
||||
max_x = max_x.max(max);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/bin/d16p1.rs
Normal file
19
src/bin/d16p1.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use aoc22::{
|
||||
day16,
|
||||
util::{self, BnBState},
|
||||
};
|
||||
|
||||
const TOTAL_TIME: usize = 30;
|
||||
|
||||
pub fn main() {
|
||||
let valves = day16::parse_valves(&util::parse_input());
|
||||
let paths = day16::calc_paths(&valves);
|
||||
let state = day16::State::new(&valves, TOTAL_TIME, 1);
|
||||
|
||||
let state = util::maximize(&state, &(&valves, &paths));
|
||||
|
||||
println!(
|
||||
"Most pressure released is {}",
|
||||
state.lower_bound(&(&valves, &paths))
|
||||
);
|
||||
}
|
||||
20
src/bin/d16p2.rs
Normal file
20
src/bin/d16p2.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use aoc22::{
|
||||
day16,
|
||||
util::{self, BnBState},
|
||||
};
|
||||
|
||||
const TOTAL_TIME: usize = 26;
|
||||
const N_AGENTS: usize = 2;
|
||||
|
||||
pub fn main() {
|
||||
let valves = day16::parse_valves(&util::parse_input());
|
||||
let paths = day16::calc_paths(&valves);
|
||||
let state = day16::State::new(&valves, TOTAL_TIME, N_AGENTS);
|
||||
|
||||
let state = util::maximize(&state, &(&valves, &paths));
|
||||
|
||||
println!(
|
||||
"Most pressure released is {}",
|
||||
state.lower_bound(&(&valves, &paths))
|
||||
);
|
||||
}
|
||||
13
src/bin/d17p1.rs
Normal file
13
src/bin/d17p1.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use aoc22::{day17, util};
|
||||
|
||||
const BLOCKS: usize = 2022;
|
||||
|
||||
pub fn main() {
|
||||
let jets = day17::parse_jets(&util::parse_input());
|
||||
|
||||
println!(
|
||||
"Height after {} blocks: {}",
|
||||
BLOCKS,
|
||||
day17::do_moves(&jets, BLOCKS)
|
||||
);
|
||||
}
|
||||
13
src/bin/d17p2.rs
Normal file
13
src/bin/d17p2.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use aoc22::{day17, util};
|
||||
|
||||
const BLOCKS: usize = 1_000_000_000_000;
|
||||
|
||||
pub fn main() {
|
||||
let jets = day17::parse_jets(&util::parse_input());
|
||||
|
||||
println!(
|
||||
"Height after {} blocks: {}",
|
||||
BLOCKS,
|
||||
day17::do_moves(&jets, BLOCKS)
|
||||
);
|
||||
}
|
||||
7
src/bin/d18p1.rs
Normal file
7
src/bin/d18p1.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use aoc22::{day18, util};
|
||||
|
||||
pub fn main() {
|
||||
let droplet = day18::parse_droplet(&util::parse_input());
|
||||
|
||||
println!("Surface area: {}", day18::surface_area(&droplet));
|
||||
}
|
||||
10
src/bin/d18p2.rs
Normal file
10
src/bin/d18p2.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use aoc22::{day18, util};
|
||||
|
||||
pub fn main() {
|
||||
let droplet = day18::parse_droplet(&util::parse_input());
|
||||
|
||||
println!(
|
||||
"Outer surface area: {}",
|
||||
day18::outer_surface_area(&droplet)
|
||||
);
|
||||
}
|
||||
26
src/bin/d19p1.rs
Normal file
26
src/bin/d19p1.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::thread;
|
||||
|
||||
use aoc22::{day19, util};
|
||||
|
||||
const MINUTES: usize = 24;
|
||||
|
||||
pub fn main() {
|
||||
let blueprints = day19::parse_blueprints(&util::parse_input());
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for (i, blueprint) in blueprints.iter().enumerate() {
|
||||
let blueprint = blueprint.clone();
|
||||
handles.push((
|
||||
i,
|
||||
thread::spawn(move || day19::max_geodes(MINUTES, &blueprint)),
|
||||
));
|
||||
}
|
||||
|
||||
let mut sum = 0;
|
||||
for (i, handle) in handles {
|
||||
let max = handle.join().unwrap();
|
||||
sum += (i + 1) * max;
|
||||
}
|
||||
|
||||
println!("Sum of quality scores: {}", sum);
|
||||
}
|
||||
27
src/bin/d19p2.rs
Normal file
27
src/bin/d19p2.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use std::thread;
|
||||
|
||||
use aoc22::{day19, util};
|
||||
|
||||
const MINUTES: usize = 32;
|
||||
const MAX_BLUEPRINTS: usize = 3;
|
||||
|
||||
pub fn main() {
|
||||
let blueprints = day19::parse_blueprints(&util::parse_input());
|
||||
assert!(blueprints.len() > 0);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for blueprint in blueprints.iter().take(MAX_BLUEPRINTS) {
|
||||
let blueprint = blueprint.clone();
|
||||
handles.push(thread::spawn(move || {
|
||||
day19::max_geodes(MINUTES, &blueprint)
|
||||
}));
|
||||
}
|
||||
|
||||
let mut prod = 1;
|
||||
for handle in handles {
|
||||
let max = handle.join().unwrap();
|
||||
prod *= max;
|
||||
}
|
||||
|
||||
println!("Product of geodes: {}", prod);
|
||||
}
|
||||
9
src/bin/d20p1.rs
Normal file
9
src/bin/d20p1.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use aoc22::{day20, util};
|
||||
|
||||
pub fn main() {
|
||||
let nodes = day20::parse_file(&util::parse_input(), 1);
|
||||
|
||||
day20::mix(&nodes);
|
||||
|
||||
println!("Sum of coordinates: {}", day20::calc_coordinates(&nodes));
|
||||
}
|
||||
16
src/bin/d20p2.rs
Normal file
16
src/bin/d20p2.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use aoc22::{day20, util};
|
||||
|
||||
const KEY: isize = 811589153;
|
||||
const ROUNDS: usize = 10;
|
||||
|
||||
pub fn main() {
|
||||
let nodes = day20::parse_file(&util::parse_input(), KEY);
|
||||
// day20::print_nodes(&nodes);
|
||||
|
||||
for _ in 0..ROUNDS {
|
||||
day20::mix(&nodes);
|
||||
// day20::print_nodes(&nodes);
|
||||
}
|
||||
|
||||
println!("Sum of coordinates: {}", day20::calc_coordinates(&nodes));
|
||||
}
|
||||
7
src/bin/d21p1.rs
Normal file
7
src/bin/d21p1.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use aoc22::{day21, util};
|
||||
|
||||
pub fn main() {
|
||||
let monkeys = day21::parse_monkeys(&util::parse_input());
|
||||
|
||||
println!("root yells: {}", day21::yell(monkeys.root(), false));
|
||||
}
|
||||
9
src/bin/d21p2.rs
Normal file
9
src/bin/d21p2.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use aoc22::{day21, util};
|
||||
|
||||
pub fn main() {
|
||||
let monkeys = day21::parse_monkeys(&util::parse_input());
|
||||
|
||||
let human = day21::find(&monkeys, "humn").unwrap();
|
||||
|
||||
println!("Human should yell: {}", day21::find_solution(human));
|
||||
}
|
||||
21
src/bin/d22p1.rs
Normal file
21
src/bin/d22p1.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use aoc22::{
|
||||
day22::{self, Navigable},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let (grid, instructions) = day22::parse_map_and_path(&util::parse_input());
|
||||
|
||||
// dbg!(&grid);
|
||||
// dbg!(&instructions);
|
||||
|
||||
let mut pose = grid.initial_pose();
|
||||
// dbg!(&pose);
|
||||
for inst in &instructions {
|
||||
pose = grid.exec_instruction(&pose, inst).0;
|
||||
// dbg!(&pose);
|
||||
}
|
||||
|
||||
let pass = 1000 * (pose.pos.0 + 1) + 4 * (pose.pos.1 + 1) + (pose.orientation as usize);
|
||||
println!("Passowrd: {}", pass);
|
||||
}
|
||||
40
src/bin/d22p2.rs
Normal file
40
src/bin/d22p2.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use aoc22::{
|
||||
day22::{self, CubeSide, Navigable},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let (grid, instructions) = day22::parse_map_and_path(&util::parse_input());
|
||||
|
||||
let mut pattern = HashMap::new();
|
||||
pattern.insert(CubeSide::Top, (0, 1));
|
||||
pattern.insert(CubeSide::Fore, (1, 1));
|
||||
pattern.insert(CubeSide::Left, (2, 0));
|
||||
pattern.insert(CubeSide::Back, (3, 0));
|
||||
pattern.insert(CubeSide::Bottom, (2, 1));
|
||||
pattern.insert(CubeSide::Right, (0, 2));
|
||||
|
||||
// Pattern for example input
|
||||
// WARNING: CubeGrid::wrap_around is hardcoded for the pattern above
|
||||
// pattern.insert(CubeSide::Top, (0, 2));
|
||||
// pattern.insert(CubeSide::Fore, (1, 2));
|
||||
// pattern.insert(CubeSide::Left, (1, 1));
|
||||
// pattern.insert(CubeSide::Back, (1, 0));
|
||||
// pattern.insert(CubeSide::Bottom, (2, 2));
|
||||
// pattern.insert(CubeSide::Right, (2, 3));
|
||||
|
||||
let grid = day22::CubeGrid::from(&grid, &pattern);
|
||||
let mut pose = grid.initial_pose();
|
||||
for inst in &instructions {
|
||||
pose = grid.exec_instruction(&pose, inst).0;
|
||||
}
|
||||
let pos = pose.1.pos;
|
||||
let row_add = pattern[&pose.0].0 * grid.side_height;
|
||||
let col_add = pattern[&pose.0].1 * grid.side_width;
|
||||
|
||||
let pass =
|
||||
1000 * (row_add + pos.0 + 1) + 4 * (col_add + pos.1 + 1) + (pose.1.orientation as usize);
|
||||
println!("Passowrd: {}", pass);
|
||||
}
|
||||
19
src/bin/d23p1.rs
Normal file
19
src/bin/d23p1.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use aoc22::{
|
||||
day23::{self, empty_ground},
|
||||
util,
|
||||
};
|
||||
|
||||
const N_ROUNDS: usize = 10;
|
||||
|
||||
pub fn main() {
|
||||
let mut elves = day23::parse_map(&util::parse_input());
|
||||
|
||||
for i in 0..N_ROUNDS {
|
||||
let elves_too_close = day23::do_round(&mut elves, i);
|
||||
if !elves_too_close {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Elves cover {} empty tiles", empty_ground(&elves));
|
||||
}
|
||||
20
src/bin/d23p2.rs
Normal file
20
src/bin/d23p2.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use aoc22::{
|
||||
day23::{self},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let mut elves = day23::parse_map(&util::parse_input());
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let elves_too_close = day23::do_round(&mut elves, i);
|
||||
|
||||
i += 1;
|
||||
if !elves_too_close {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("First idle round: {}", i);
|
||||
}
|
||||
13
src/bin/d24p1.rs
Normal file
13
src/bin/d24p1.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use aoc22::{
|
||||
day24::{self},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let (map, start, target) = day24::parse_map(&util::parse_input());
|
||||
|
||||
day24::print_map(&map, &start);
|
||||
|
||||
let (rounds, _) = day24::find_path(&map, &start, &target);
|
||||
println!("Goal is reachable in {} min", rounds);
|
||||
}
|
||||
19
src/bin/d24p2.rs
Normal file
19
src/bin/d24p2.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use aoc22::{
|
||||
day24::{self},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let (map, start, target) = day24::parse_map(&util::parse_input());
|
||||
|
||||
day24::print_map(&map, &start);
|
||||
|
||||
let (rounds_1, map_1) = day24::find_path(&map, &start, &target);
|
||||
println!("Goal is reachable in {} min", rounds_1);
|
||||
let (rounds_2, map_2) = day24::find_path(&map_1, &target, &start);
|
||||
println!("Start is reachable in {} min", rounds_2);
|
||||
let (rounds_3, _) = day24::find_path(&map_2, &start, &target);
|
||||
println!("Goal is reachable in {} min", rounds_2);
|
||||
|
||||
println!("Total time taken is {} min", rounds_1 + rounds_2 + rounds_3);
|
||||
}
|
||||
8
src/bin/d25p1.rs
Normal file
8
src/bin/d25p1.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use aoc22::{day25, util};
|
||||
|
||||
pub fn main() {
|
||||
let numbers = day25::parse_snafus(&util::parse_input());
|
||||
let sum: i64 = numbers.iter().sum();
|
||||
println!("Sum of numbers: {}", sum);
|
||||
println!("Sum of numbers, SNAFU'd: {}", day25::num_to_snafu(sum));
|
||||
}
|
||||
25
src/bin/d5p1.rs
Normal file
25
src/bin/d5p1.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use aoc22::{day5, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut arrangement = day5::parse_arrangement(&util::parse_input());
|
||||
|
||||
for inst in &arrangement.instructions {
|
||||
for _ in 0..inst.num {
|
||||
let box_ = arrangement
|
||||
.stacks
|
||||
.get_mut(inst.from)
|
||||
.unwrap()
|
||||
.pop_back()
|
||||
.unwrap();
|
||||
arrangement.stacks.get_mut(inst.to).unwrap().push_back(box_);
|
||||
}
|
||||
}
|
||||
|
||||
let top_boxes: String = arrangement
|
||||
.stacks
|
||||
.iter()
|
||||
.map(|s| s.iter().next_back().unwrap())
|
||||
.collect();
|
||||
|
||||
println!("The top boxes are {}", top_boxes);
|
||||
}
|
||||
34
src/bin/d5p2.rs
Normal file
34
src/bin/d5p2.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use aoc22::{day5, util};
|
||||
|
||||
pub fn main() {
|
||||
let mut arrangement = day5::parse_arrangement(&util::parse_input());
|
||||
|
||||
for inst in &arrangement.instructions {
|
||||
let mut boxes = VecDeque::new();
|
||||
for _ in 0..inst.num {
|
||||
let box_ = arrangement
|
||||
.stacks
|
||||
.get_mut(inst.from)
|
||||
.unwrap()
|
||||
.pop_back()
|
||||
.unwrap();
|
||||
boxes.push_front(box_);
|
||||
}
|
||||
|
||||
arrangement
|
||||
.stacks
|
||||
.get_mut(inst.to)
|
||||
.unwrap()
|
||||
.append(&mut boxes);
|
||||
}
|
||||
|
||||
let top_boxes: String = arrangement
|
||||
.stacks
|
||||
.iter()
|
||||
.map(|s| s.iter().next_back().unwrap())
|
||||
.collect();
|
||||
|
||||
println!("The top boxes are {}", top_boxes);
|
||||
}
|
||||
8
src/bin/d6p1.rs
Normal file
8
src/bin/d6p1.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use aoc22::{day6, util};
|
||||
|
||||
pub fn main() {
|
||||
let input = util::parse_input();
|
||||
let start = day6::parse_datastream(input.as_str(), 4);
|
||||
|
||||
println!("Packet starts after {} characters", start + 1);
|
||||
}
|
||||
8
src/bin/d6p2.rs
Normal file
8
src/bin/d6p2.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use aoc22::{day6, util};
|
||||
|
||||
pub fn main() {
|
||||
let input = util::parse_input();
|
||||
let start = day6::parse_datastream(input.as_str(), 14);
|
||||
|
||||
println!("Message starts after {} characters", start + 1);
|
||||
}
|
||||
20
src/bin/d7p1.rs
Normal file
20
src/bin/d7p1.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use aoc22::{day7, util};
|
||||
|
||||
const MAX_SIZE: usize = 100000;
|
||||
|
||||
pub fn main() {
|
||||
let root = day7::parse_cmdline(&util::parse_input());
|
||||
|
||||
let mut total_size = 0;
|
||||
for child in root.borrow().all_children() {
|
||||
let c = &*child.borrow();
|
||||
if let day7::Node::Dir { .. } = c {
|
||||
let size = c.size();
|
||||
if size <= MAX_SIZE {
|
||||
total_size += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Total size of small directories: {}", total_size);
|
||||
}
|
||||
26
src/bin/d7p2.rs
Normal file
26
src/bin/d7p2.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use aoc22::{day7, util};
|
||||
|
||||
const TOTAL_SPACE: usize = 70000000;
|
||||
const REQUIRED_SPACE: usize = 30000000;
|
||||
|
||||
pub fn main() {
|
||||
let root = day7::parse_cmdline(&util::parse_input());
|
||||
|
||||
let r = &*root.borrow();
|
||||
let free_space = TOTAL_SPACE - r.size();
|
||||
assert!(free_space < REQUIRED_SPACE);
|
||||
let required_deletion = REQUIRED_SPACE - free_space;
|
||||
|
||||
let mut min_sufficient = usize::MAX;
|
||||
for child in root.borrow().all_children() {
|
||||
let c = &*child.borrow();
|
||||
if let day7::Node::Dir { .. } = c {
|
||||
let size = c.size();
|
||||
if size >= required_deletion && size <= min_sufficient {
|
||||
min_sufficient = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Size of min sufficient dir is {}", min_sufficient);
|
||||
}
|
||||
10
src/bin/d8p1.rs
Normal file
10
src/bin/d8p1.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use aoc22::{
|
||||
day8::{self, find_visible},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let grid = day8::parse_grid(&util::parse_input());
|
||||
|
||||
println!("There are {} visible trees", find_visible(&grid).len());
|
||||
}
|
||||
22
src/bin/d8p2.rs
Normal file
22
src/bin/d8p2.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use aoc22::{
|
||||
day8::{self, calc_scenic_score},
|
||||
util,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let grid = day8::parse_grid(&util::parse_input());
|
||||
|
||||
let rows = grid.len();
|
||||
assert!(rows > 0);
|
||||
let cols = grid.get(0).unwrap().len();
|
||||
assert!(cols > 0);
|
||||
|
||||
let max_score = (0..rows)
|
||||
.map(move |r| (0..cols).map(move |c| (r, c)))
|
||||
.flatten()
|
||||
.map(|c| calc_scenic_score(&grid, c))
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
println!("Max scenic score is {}", max_score);
|
||||
}
|
||||
14
src/bin/d9p1.rs
Normal file
14
src/bin/d9p1.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use aoc22::{day9, util};
|
||||
|
||||
pub fn main() {
|
||||
let motions = day9::parse_motions(&util::parse_input());
|
||||
let mut state = day9::State::new(1);
|
||||
|
||||
for motion in &motions {
|
||||
day9::execute_motion(&mut state, &motion);
|
||||
}
|
||||
|
||||
println!("State after all motions:");
|
||||
state.print();
|
||||
println!("Visited fields: {}", state.visited.len());
|
||||
}
|
||||
14
src/bin/d9p2.rs
Normal file
14
src/bin/d9p2.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use aoc22::{day9, util};
|
||||
|
||||
pub fn main() {
|
||||
let motions = day9::parse_motions(&util::parse_input());
|
||||
let mut state = day9::State::new(9);
|
||||
|
||||
for motion in &motions {
|
||||
day9::execute_motion(&mut state, &motion);
|
||||
}
|
||||
|
||||
println!("State after all motions:");
|
||||
state.print();
|
||||
println!("Visited fields: {}", state.visited.len());
|
||||
}
|
||||
104
src/day10.rs
Normal file
104
src/day10.rs
Normal file
@ -0,0 +1,104 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Instruction {
|
||||
Noop,
|
||||
Addx(isize),
|
||||
}
|
||||
|
||||
pub fn parse_instructions(input: &String) -> Vec<Instruction> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
if line == "noop" {
|
||||
result.push(Instruction::Noop);
|
||||
} else {
|
||||
let (inst, v) = line.split_once(' ').unwrap();
|
||||
assert_eq!(inst, "addx");
|
||||
let v = v.parse().unwrap();
|
||||
result.push(Instruction::Addx(v));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CPU {
|
||||
pub x: isize,
|
||||
pub cycle: isize,
|
||||
instructions: Vec<Instruction>,
|
||||
pc: usize,
|
||||
inst_cycle: usize,
|
||||
}
|
||||
|
||||
impl CPU {
|
||||
pub fn new(instructions: Vec<Instruction>) -> CPU {
|
||||
CPU {
|
||||
x: 1,
|
||||
cycle: 0,
|
||||
instructions,
|
||||
pc: 0,
|
||||
inst_cycle: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_cycle(&mut self) -> Option<()> {
|
||||
if let Some(inst) = self.instructions.get(self.pc) {
|
||||
match inst {
|
||||
Instruction::Noop => {
|
||||
self.pc += 1;
|
||||
}
|
||||
Instruction::Addx(v) => {
|
||||
self.inst_cycle += 1;
|
||||
if self.inst_cycle == 2 {
|
||||
self.pc += 1;
|
||||
self.inst_cycle = 0;
|
||||
self.x += v;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.cycle += 1;
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signal_strength(&self) -> isize {
|
||||
return self.x * (self.cycle + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CRT {
|
||||
pub pixels: [[char; 40]; 6],
|
||||
}
|
||||
|
||||
impl CRT {
|
||||
pub fn new() -> CRT {
|
||||
CRT {
|
||||
pixels: [['.'; 40]; 6],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, cpu: &CPU) {
|
||||
let c = cpu.cycle;
|
||||
assert!(c >= 0 && c < 240);
|
||||
let x = c % 40;
|
||||
let y = c / 40;
|
||||
let sprite_pos = cpu.x - 1;
|
||||
let px = &mut self.pixels[y as usize][x as usize];
|
||||
if x >= sprite_pos && x < sprite_pos + 3 {
|
||||
*px = '#';
|
||||
} else {
|
||||
*px = '.';
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self) {
|
||||
for y in 0..6 {
|
||||
for x in 0..40 {
|
||||
print!("{}", self.pixels[y][x]);
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
207
src/day11.rs
Normal file
207
src/day11.rs
Normal file
@ -0,0 +1,207 @@
|
||||
use std::{cell::RefCell, collections::VecDeque};
|
||||
|
||||
use itertools::Itertools;
|
||||
use num::Integer;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpKind {
|
||||
Add,
|
||||
Mul,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpParam {
|
||||
Old,
|
||||
Num(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Operation {
|
||||
pub kind: OpKind,
|
||||
pub a: OpParam,
|
||||
pub b: OpParam,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Test {
|
||||
pub div: usize,
|
||||
pub target_true: usize,
|
||||
pub target_false: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Monkey {
|
||||
pub activity: usize,
|
||||
pub items: VecDeque<usize>,
|
||||
pub op: Operation,
|
||||
pub test: Test,
|
||||
}
|
||||
|
||||
pub fn parse_monkeys(input: &String) -> Vec<RefCell<Monkey>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for (n, monkey) in input.lines().chunks(7).into_iter().enumerate() {
|
||||
let (n_actual, monkey) = parse_monkey(&monkey.collect());
|
||||
assert_eq!(n, n_actual);
|
||||
result.push(RefCell::new(monkey));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_monkey(lines: &Vec<&str>) -> (usize, Monkey) {
|
||||
let re0 = Regex::new(r"^Monkey (\d)+:$").unwrap();
|
||||
let re1 = Regex::new(r"^ Starting items: ((\d+(?:, )?)+)$").unwrap();
|
||||
let re2 = Regex::new(r"^ Operation: new = (old|\d+) ([*+]) (old|\d+)$").unwrap();
|
||||
let re3 = Regex::new(r"^ Test: divisible by (\d+)$").unwrap();
|
||||
let re45 = Regex::new(r"^ If (true|false): throw to monkey (\d+)$").unwrap();
|
||||
|
||||
let n: usize = re0
|
||||
.captures(lines.get(0).unwrap())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let items = re1
|
||||
.captures(lines.get(1).unwrap())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(", ")
|
||||
.map(|i| i.parse().expect(&format!("Invalid item: {}", i)))
|
||||
.collect();
|
||||
|
||||
let op = re2.captures(lines.get(2).unwrap()).unwrap();
|
||||
let op_a = parse_op_param(op.get(1).unwrap().as_str());
|
||||
let op_kind = parse_op_kind(op.get(2).unwrap().as_str());
|
||||
let op_b = parse_op_param(op.get(3).unwrap().as_str());
|
||||
let op = Operation {
|
||||
kind: op_kind,
|
||||
a: op_a,
|
||||
b: op_b,
|
||||
};
|
||||
|
||||
let test_div = re3
|
||||
.captures(lines.get(3).unwrap())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let test_true = re45.captures(lines.get(4).unwrap()).unwrap();
|
||||
assert_eq!(test_true.get(1).unwrap().as_str(), "true");
|
||||
let test_true = test_true.get(2).unwrap().as_str().parse().unwrap();
|
||||
|
||||
let test_false = re45.captures(lines.get(5).unwrap()).unwrap();
|
||||
assert_eq!(test_false.get(1).unwrap().as_str(), "false");
|
||||
let test_false = test_false.get(2).unwrap().as_str().parse().unwrap();
|
||||
|
||||
let test = Test {
|
||||
div: test_div,
|
||||
target_true: test_true,
|
||||
target_false: test_false,
|
||||
};
|
||||
|
||||
assert!(lines.len() == 6 || lines.get(6).unwrap().is_empty());
|
||||
|
||||
(
|
||||
n,
|
||||
Monkey {
|
||||
activity: 0,
|
||||
items,
|
||||
op,
|
||||
test,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_op_param(s: &str) -> OpParam {
|
||||
if s == "old" {
|
||||
OpParam::Old
|
||||
} else {
|
||||
OpParam::Num(s.parse().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_op_kind(s: &str) -> OpKind {
|
||||
match s {
|
||||
"+" => OpKind::Add,
|
||||
"*" => OpKind::Mul,
|
||||
_ => panic!("Unknown OpKind {}", s),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_round(monkeys: &Vec<RefCell<Monkey>>, do_worry: bool) {
|
||||
let overall_mod = monkeys
|
||||
.iter()
|
||||
.map(|m| m.borrow().test.div)
|
||||
.reduce(|acc, div| acc.lcm(&div))
|
||||
.unwrap();
|
||||
for n in 0..monkeys.len() {
|
||||
let mut monkey = monkeys.get(n).unwrap().borrow_mut();
|
||||
log::debug!("Monkey {}", n);
|
||||
while !monkey.items.is_empty() {
|
||||
monkey.activity += 1;
|
||||
let mut item = monkey.items.pop_front().unwrap();
|
||||
log::debug!(" Monkey inspects an item with a worry level of {}.", item);
|
||||
item = run_op(&monkey.op, item) % overall_mod;
|
||||
log::debug!(" Worry level increases to {}.", item);
|
||||
if do_worry {
|
||||
item /= 3;
|
||||
log::debug!(
|
||||
" Monkey gets bored with item. Worry level is divided by 3 to {}.",
|
||||
item
|
||||
);
|
||||
}
|
||||
let target = if item % monkey.test.div == 0 {
|
||||
log::debug!("Current worry level is divisible by {}", monkey.test.div);
|
||||
monkey.test.target_true
|
||||
} else {
|
||||
log::debug!(
|
||||
"Current worry level is not divisible by {}",
|
||||
monkey.test.div
|
||||
);
|
||||
monkey.test.target_false
|
||||
};
|
||||
log::debug!(
|
||||
"Item with worry level {} is thrown to monkey {}.",
|
||||
item,
|
||||
target
|
||||
);
|
||||
let target = &mut monkeys.get(target).unwrap().borrow_mut().items;
|
||||
target.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_op(op: &Operation, item: usize) -> usize {
|
||||
let a = match op.a {
|
||||
OpParam::Old => item,
|
||||
OpParam::Num(n) => n,
|
||||
};
|
||||
let b = match op.b {
|
||||
OpParam::Old => item,
|
||||
OpParam::Num(n) => n,
|
||||
};
|
||||
match op.kind {
|
||||
OpKind::Add => a + b,
|
||||
OpKind::Mul => a * b,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn monkey_business(monkeys: &Vec<RefCell<Monkey>>) -> usize {
|
||||
monkeys
|
||||
.iter()
|
||||
.map(|m| m.borrow().activity)
|
||||
.sorted()
|
||||
.rev()
|
||||
.take(2)
|
||||
.product()
|
||||
}
|
||||
95
src/day12.rs
Normal file
95
src/day12.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
pub type Coord = (usize, usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Heightmap {
|
||||
pub map: Vec<Vec<usize>>,
|
||||
pub rows: usize,
|
||||
pub cols: usize,
|
||||
pub start: Coord,
|
||||
pub target: Coord,
|
||||
}
|
||||
|
||||
pub fn parse_heightmap(input: &String) -> Heightmap {
|
||||
let mut start = (usize::MAX, usize::MAX);
|
||||
let mut target = (usize::MAX, usize::MAX);
|
||||
let map = input
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(row, l)| {
|
||||
l.as_bytes()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(col, c)| {
|
||||
if *c == ('S' as u8) {
|
||||
start = (row, col);
|
||||
0
|
||||
} else if *c == ('E' as u8) {
|
||||
target = (row, col);
|
||||
25
|
||||
} else {
|
||||
(c - ('a' as u8)) as usize
|
||||
}
|
||||
})
|
||||
.collect_vec()
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
assert_ne!(start.0, usize::MAX);
|
||||
assert_ne!(target.0, usize::MAX);
|
||||
|
||||
let rows = map.len();
|
||||
let cols;
|
||||
if let itertools::MinMaxResult::MinMax(min, max) = map.iter().map(|r| r.len()).minmax() {
|
||||
cols = min;
|
||||
assert_eq!(min, max);
|
||||
} else {
|
||||
panic!("<2 rows?");
|
||||
}
|
||||
|
||||
Heightmap {
|
||||
map,
|
||||
cols,
|
||||
rows,
|
||||
start,
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_steps(map: &Heightmap, any_a: bool) -> Option<usize> {
|
||||
let mut dist = vec![vec![usize::MAX; map.cols]; map.rows];
|
||||
dist[map.target.0][map.target.1] = 0;
|
||||
let mut next = VecDeque::new();
|
||||
next.push_back(map.target);
|
||||
|
||||
let moves: Vec<(isize, isize)> = vec![(1, 0), (-1, 0), (0, 1), (0, -1)];
|
||||
while let Some(coord) = next.pop_front() {
|
||||
let curr_height = map.map[coord.0][coord.1];
|
||||
let curr_dist = dist[coord.0][coord.1];
|
||||
if curr_height == 0 && (any_a || coord == map.start) {
|
||||
return Some(curr_dist);
|
||||
}
|
||||
|
||||
for move_ in &moves {
|
||||
let y = (coord.0 as isize) + move_.0;
|
||||
if y < 0 || y >= map.rows as isize {
|
||||
continue;
|
||||
}
|
||||
let x = (coord.1 as isize) + move_.1;
|
||||
if x < 0 || x >= map.cols as isize {
|
||||
continue;
|
||||
}
|
||||
let y = y as usize;
|
||||
let x = x as usize;
|
||||
if map.map[y][x] + 1 >= curr_height && dist[y][x] > curr_dist + 1 {
|
||||
dist[y][x] = curr_dist + 1;
|
||||
next.push_back((y, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
120
src/day13.rs
Normal file
120
src/day13.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Node {
|
||||
List(Vec<Node>),
|
||||
Num(usize),
|
||||
}
|
||||
|
||||
impl Ord for Node {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match self {
|
||||
Node::List(l1) => match other {
|
||||
Node::Num(n2) => self.cmp(&Node::List(vec![Node::Num(*n2)])),
|
||||
Node::List(l2) => {
|
||||
for pair in l1.iter().zip_longest(l2.iter()) {
|
||||
match pair {
|
||||
EitherOrBoth::Both(n1, n2) => {
|
||||
if n1 < n2 {
|
||||
return Ordering::Less;
|
||||
} else if n2 < n1 {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
}
|
||||
EitherOrBoth::Left(_) => return Ordering::Greater,
|
||||
EitherOrBoth::Right(_) => return Ordering::Less,
|
||||
}
|
||||
}
|
||||
Ordering::Equal
|
||||
}
|
||||
},
|
||||
Node::Num(n1) => match other {
|
||||
Node::Num(n2) => n1.cmp(n2),
|
||||
Node::List(_) => Node::List(vec![Node::Num(*n1)]).cmp(other),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Node {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.partial_cmp(other) == Some(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Node {}
|
||||
|
||||
pub fn parse_pairs(input: &String) -> Vec<(Node, Node)> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for mut p in &input.lines().chunks(3) {
|
||||
let first = parse_list(p.next().unwrap()).0;
|
||||
let second = parse_list(p.next().unwrap()).0;
|
||||
assert!(p.next().unwrap_or("").is_empty());
|
||||
|
||||
result.push((first, second));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
enum ParseState {
|
||||
Idle,
|
||||
InNumber,
|
||||
}
|
||||
|
||||
fn parse_list(line: &str) -> (Node, usize) {
|
||||
assert_eq!(line.chars().next(), Some('['));
|
||||
|
||||
let mut state = ParseState::Idle;
|
||||
let mut i = 1;
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut num_start = 0;
|
||||
while i < line.len() {
|
||||
let c = line.chars().nth(i).unwrap();
|
||||
match state {
|
||||
ParseState::Idle => {
|
||||
if c == '[' {
|
||||
let (n, inc) = parse_list(&line[i..]);
|
||||
result.push(n);
|
||||
i += inc;
|
||||
} else if c == ']' {
|
||||
// End of list reached
|
||||
break;
|
||||
} else if c.is_numeric() {
|
||||
num_start = i;
|
||||
state = ParseState::InNumber;
|
||||
} else if c != ',' {
|
||||
panic!("Unknown char: {}", c);
|
||||
}
|
||||
}
|
||||
ParseState::InNumber => {
|
||||
if c == ',' {
|
||||
let slice = &line[num_start..i];
|
||||
result.push(Node::Num(slice.parse().unwrap()));
|
||||
state = ParseState::Idle;
|
||||
} else if c == ']' {
|
||||
// End of list reached
|
||||
let slice = &line[num_start..i];
|
||||
result.push(Node::Num(slice.parse().unwrap()));
|
||||
|
||||
break;
|
||||
} else if !c.is_numeric() {
|
||||
panic!("Unknown digit {}", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
(Node::List(result), i)
|
||||
}
|
||||
135
src/day14.rs
Normal file
135
src/day14.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::util::Coord;
|
||||
|
||||
pub const SAND_SOURCE: Coord = (0, 500);
|
||||
|
||||
pub struct Cave {
|
||||
pub rows: usize,
|
||||
pub cols: usize,
|
||||
pub blocked: Vec<Vec<bool>>,
|
||||
pub sand_source: Coord,
|
||||
}
|
||||
|
||||
impl Cave {
|
||||
pub fn print(&self) {
|
||||
for row in 0..self.rows {
|
||||
for col in 0..self.cols {
|
||||
if (row, col) == self.sand_source {
|
||||
print!("+")
|
||||
} else if self.blocked[row][col] {
|
||||
print!("#");
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
print!("\n")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_sand(&mut self) -> bool {
|
||||
let (y, x) = self.sand_source;
|
||||
let mut y = y as isize;
|
||||
let mut x = x as isize;
|
||||
|
||||
while y < self.rows as isize
|
||||
&& x > 0
|
||||
&& x < self.cols as isize
|
||||
&& !self.is_blocked(self.sand_source.0 as isize, self.sand_source.1 as isize)
|
||||
{
|
||||
if !self.is_blocked(y + 1, x) {
|
||||
y += 1;
|
||||
} else if !self.is_blocked(y + 1, x - 1) {
|
||||
y += 1;
|
||||
x -= 1;
|
||||
} else if !self.is_blocked(y + 1, x + 1) {
|
||||
y += 1;
|
||||
x += 1;
|
||||
} else {
|
||||
self.blocked[y as usize][x as usize] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_blocked(&self, y: isize, x: isize) -> bool {
|
||||
!(y < 0 || y >= self.rows as isize || x < 0 || x >= self.cols as isize)
|
||||
&& self.blocked[y as usize][x as usize]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_cave(input: &String, do_floor: bool) -> Cave {
|
||||
let mut paths = HashSet::new();
|
||||
|
||||
for line in input.lines() {
|
||||
let mut path = line.split("->").map(|n| parse_coord(n.trim()));
|
||||
let mut last = path.next().unwrap();
|
||||
paths.insert(last);
|
||||
for next in path {
|
||||
let y_min = next.0.min(last.0);
|
||||
let y_max = next.0.max(last.0);
|
||||
let x_min = next.1.min(last.1);
|
||||
let x_max = next.1.max(last.1);
|
||||
let horizontal = y_min == y_max;
|
||||
let vertical = x_min == x_max;
|
||||
|
||||
assert!(horizontal ^ vertical);
|
||||
if horizontal {
|
||||
for x in x_min..=x_max {
|
||||
paths.insert((y_min, x));
|
||||
}
|
||||
} else {
|
||||
for y in y_min..=y_max {
|
||||
paths.insert((y, x_max));
|
||||
}
|
||||
}
|
||||
|
||||
last = next;
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(paths.len(), 0);
|
||||
|
||||
let (mut y_min, mut y_max) = paths.iter().map(|c| c.0).minmax().into_option().unwrap();
|
||||
let (mut x_min, mut x_max) = paths.iter().map(|c| c.1).minmax().into_option().unwrap();
|
||||
|
||||
// Sand source is always at y = 0, x = 500
|
||||
y_min = y_min.min(SAND_SOURCE.0);
|
||||
x_min = x_min.min(SAND_SOURCE.1);
|
||||
x_max = x_max.max(SAND_SOURCE.1);
|
||||
|
||||
if do_floor {
|
||||
y_max += 2;
|
||||
let dy = y_max - y_min;
|
||||
x_min = x_min.min(SAND_SOURCE.1 - dy - 1);
|
||||
x_max = x_max.max(SAND_SOURCE.1 + dy + 1);
|
||||
for x in x_min..x_max {
|
||||
paths.insert((y_max, x));
|
||||
}
|
||||
}
|
||||
|
||||
let rows = y_max - y_min + 1;
|
||||
let cols = x_max - x_min + 1;
|
||||
|
||||
let mut blocked = vec![vec![false; cols]; rows];
|
||||
|
||||
for coord in paths {
|
||||
blocked[coord.0 - y_min][coord.1 - x_min] = true;
|
||||
}
|
||||
|
||||
Cave {
|
||||
rows,
|
||||
cols,
|
||||
blocked,
|
||||
sand_source: (SAND_SOURCE.0 - y_min, SAND_SOURCE.1 - x_min),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_coord(input: &str) -> Coord {
|
||||
let (x, y) = input.split_once(',').unwrap();
|
||||
(y.parse().unwrap(), x.parse().unwrap())
|
||||
}
|
||||
50
src/day15.rs
Normal file
50
src/day15.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::util::{Coordinate, SignedCoord};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sensor {
|
||||
pub loc_s: SignedCoord,
|
||||
pub loc_b: SignedCoord,
|
||||
pub range: usize,
|
||||
}
|
||||
|
||||
impl Sensor {
|
||||
pub fn covered(&self, y: isize) -> RangeInclusive<isize> {
|
||||
let dy = self.loc_s.0.abs_diff(y);
|
||||
if dy > self.range {
|
||||
1..=0
|
||||
} else {
|
||||
let range_x = (self.range - dy) as isize;
|
||||
(self.loc_s.1 - range_x)..=(self.loc_s.1 + range_x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sensors(input: &String) -> Vec<Sensor> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let re =
|
||||
Regex::new(r"^Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)$")
|
||||
.unwrap();
|
||||
for line in input.lines() {
|
||||
let captures = re.captures(line).unwrap();
|
||||
let loc_s: SignedCoord = (
|
||||
captures.get(2).unwrap().as_str().parse().unwrap(),
|
||||
captures.get(1).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
let loc_b: SignedCoord = (
|
||||
captures.get(4).unwrap().as_str().parse().unwrap(),
|
||||
captures.get(3).unwrap().as_str().parse().unwrap(),
|
||||
);
|
||||
result.push(Sensor {
|
||||
loc_s,
|
||||
loc_b,
|
||||
range: loc_s.manhattan(&loc_b),
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
379
src/day16.rs
Normal file
379
src/day16.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
hash::Hash,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::util;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Valve {
|
||||
pub flow_rate: usize,
|
||||
pub tunnels: Vec<usize>,
|
||||
}
|
||||
|
||||
pub fn parse_valves(input: &String) -> Vec<Valve> {
|
||||
let indices: HashMap<String, usize> = input
|
||||
.lines()
|
||||
.map(|l| l.split(' ').nth(1).unwrap())
|
||||
.sorted()
|
||||
.enumerate()
|
||||
.map(|(i, n)| (n.to_owned(), i))
|
||||
.collect();
|
||||
|
||||
let mut valves = HashMap::new();
|
||||
|
||||
let re =
|
||||
Regex::new(r"^Valve (\w+) has flow rate=(\d+); tunnels? leads? to valves? (.*)$").unwrap();
|
||||
for line in input.lines() {
|
||||
let captures = re
|
||||
.captures(line)
|
||||
.expect(&format!("No captures for {}", line));
|
||||
let name = captures.get(1).unwrap().as_str().to_owned();
|
||||
let flow_rate = captures.get(2).unwrap().as_str().parse().unwrap();
|
||||
let tunnels = captures
|
||||
.get(3)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(", ")
|
||||
.map(|s| indices[s])
|
||||
.collect();
|
||||
let valve = Valve { flow_rate, tunnels };
|
||||
valves.insert(name, valve);
|
||||
}
|
||||
|
||||
valves
|
||||
.iter()
|
||||
.map(|(name, v)| (indices[name], v))
|
||||
.sorted_by_key(|(i, _)| *i)
|
||||
.map(|(_, v)| v.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Path {
|
||||
pub dist: usize,
|
||||
pub path: Vec<usize>,
|
||||
}
|
||||
|
||||
pub fn calc_paths(valves: &Vec<Valve>) -> Vec<Vec<Path>> {
|
||||
let n = valves.len();
|
||||
|
||||
// result[from][to] = (dist, path)
|
||||
let mut result = vec![
|
||||
vec![
|
||||
Path {
|
||||
dist: usize::MAX,
|
||||
path: Vec::new()
|
||||
};
|
||||
n
|
||||
];
|
||||
n
|
||||
];
|
||||
|
||||
for i in 0..n {
|
||||
result[i][i].dist = 0;
|
||||
let mut next = VecDeque::new();
|
||||
next.push_back(i);
|
||||
while let Some(j) = next.pop_front() {
|
||||
let d_next = result[i][j].dist + 1;
|
||||
for tunnel in &valves[j].tunnels {
|
||||
let d_tunnel = &result[i][*tunnel];
|
||||
if d_next < d_tunnel.dist {
|
||||
let mut path = result[i][j].path.clone();
|
||||
path.push(*tunnel);
|
||||
result[i][*tunnel] = Path { dist: d_next, path };
|
||||
next.push_back(*tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq)]
|
||||
pub enum Goal {
|
||||
Idle,
|
||||
NothingReachable,
|
||||
Opening(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
pub opened: u64,
|
||||
pub locations: Vec<usize>,
|
||||
pub goals: Vec<Goal>,
|
||||
pub flow_rate: usize,
|
||||
pub time_remaining: usize,
|
||||
pub pressure_released: usize,
|
||||
pub history: Vec<Vec<usize>>,
|
||||
|
||||
interesting: HashSet<usize>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(valves: &Vec<Valve>, initial_time: usize, agents: usize) -> State {
|
||||
let interesting = valves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if v.flow_rate > 0 { Some(i) } else { None })
|
||||
.collect();
|
||||
State {
|
||||
opened: 0,
|
||||
locations: vec![0; agents],
|
||||
goals: vec![Goal::Idle; agents],
|
||||
flow_rate: 0,
|
||||
time_remaining: initial_time,
|
||||
pressure_released: 0,
|
||||
interesting,
|
||||
history: vec![vec![0]; agents],
|
||||
}
|
||||
}
|
||||
|
||||
fn find_goals(&self, (_, paths): &ExtraArgs) -> Vec<State> {
|
||||
let target_dists: HashMap<usize, usize> = self
|
||||
.goals
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, g)| {
|
||||
if let Goal::Opening(t) = g {
|
||||
Some((*t, paths[self.locations[i]][*t].dist))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let new_goals: HashMap<usize, Vec<usize>> = self
|
||||
.goals
|
||||
.iter()
|
||||
.positions(|g| matches!(g, Goal::Idle))
|
||||
.map(|i| {
|
||||
let loc = self.locations[i];
|
||||
(
|
||||
i,
|
||||
self.interesting
|
||||
.iter()
|
||||
.filter_map(|target| {
|
||||
let agent_dist = paths[loc][*target].dist;
|
||||
if agent_dist + 2 >= self.time_remaining {
|
||||
// Can't reach + open the valve as well as
|
||||
// letting anything flow through
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(dist) = target_dists.get(target) {
|
||||
if *dist > agent_dist {
|
||||
// Someone else is targeting this, but they're slower
|
||||
Some(*target)
|
||||
} else {
|
||||
// Someone else is targeting this, and they're faster
|
||||
None
|
||||
}
|
||||
} else {
|
||||
// Noone else is targeting this
|
||||
Some(*target)
|
||||
}
|
||||
})
|
||||
.collect_vec(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if new_goals.len() == 1 {
|
||||
let (i, targets) = new_goals.iter().next().unwrap();
|
||||
if targets.is_empty() {
|
||||
let mut state = self.clone();
|
||||
state.goals[*i] = Goal::NothingReachable;
|
||||
return vec![state];
|
||||
}
|
||||
|
||||
targets
|
||||
.iter()
|
||||
.map(|t| {
|
||||
let mut state = self.clone();
|
||||
state.goals[*i] = Goal::Opening(*t);
|
||||
state
|
||||
})
|
||||
.collect()
|
||||
} else if new_goals.len() == 2 {
|
||||
let mut it = new_goals.iter();
|
||||
let (i1, targets1) = it.next().unwrap();
|
||||
let (i2, targets2) = it.next().unwrap();
|
||||
if targets1.len() == 0 {
|
||||
if targets2.len() == 0 {
|
||||
let mut state = self.clone();
|
||||
state.goals[*i1] = Goal::NothingReachable;
|
||||
state.goals[*i2] = Goal::NothingReachable;
|
||||
return vec![state];
|
||||
}
|
||||
|
||||
return targets2
|
||||
.iter()
|
||||
.map(|t| {
|
||||
let mut state = self.clone();
|
||||
state.goals[*i1] = Goal::NothingReachable;
|
||||
state.goals[*i2] = Goal::Opening(*t);
|
||||
state
|
||||
})
|
||||
.collect();
|
||||
} else if targets2.len() == 0 {
|
||||
return targets1
|
||||
.iter()
|
||||
.map(|t| {
|
||||
let mut state = self.clone();
|
||||
state.goals[*i1] = Goal::Opening(*t);
|
||||
state.goals[*i2] = Goal::NothingReachable;
|
||||
state
|
||||
})
|
||||
.collect();
|
||||
} else if targets1.len() == 1 && targets1 == targets2 {
|
||||
// Both agents can only reach one and the same goal
|
||||
let mut state = self.clone();
|
||||
let target = targets1[0];
|
||||
if paths[self.locations[*i1]][target].dist < paths[self.locations[*i2]][target].dist
|
||||
{
|
||||
state.goals[*i1] = Goal::Opening(target);
|
||||
state.goals[*i2] = Goal::NothingReachable;
|
||||
} else {
|
||||
state.goals[*i1] = Goal::NothingReachable;
|
||||
state.goals[*i2] = Goal::Opening(target);
|
||||
}
|
||||
return vec![state];
|
||||
}
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
for target1 in targets1 {
|
||||
for target2 in targets2 {
|
||||
if target1 == target2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut state = self.clone();
|
||||
state.goals[*i1] = Goal::Opening(*target1);
|
||||
state.goals[*i2] = Goal::Opening(*target2);
|
||||
result.push(state);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
} else {
|
||||
unimplemented!("Can't find goals for >2 agents at the same time");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ExtraArgs<'a> = (&'a Vec<Valve>, &'a Vec<Vec<Path>>);
|
||||
impl util::BnBState<ExtraArgs<'_>> for State {
|
||||
fn possible_actions(&self, (valves, paths): &ExtraArgs) -> Vec<State> {
|
||||
assert!(!self.finished());
|
||||
|
||||
let goalless = self
|
||||
.goals
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, g)| matches!(g, Goal::Idle))
|
||||
.collect_vec();
|
||||
if goalless.len() > 0 {
|
||||
return self.find_goals(&(valves, paths));
|
||||
}
|
||||
|
||||
let min_goal_dist = self
|
||||
.goals
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, g)| {
|
||||
if let Goal::Opening(target) = g {
|
||||
Some(paths[self.locations[i]][*target].dist)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.min()
|
||||
.unwrap();
|
||||
|
||||
let time_spent = min_goal_dist + 1;
|
||||
let mut completed = self.clone();
|
||||
completed.pressure_released += completed.flow_rate * time_spent;
|
||||
completed.time_remaining -= time_spent;
|
||||
for (i, goal) in self.goals.iter().enumerate() {
|
||||
if let Goal::Opening(goal) = goal {
|
||||
let path = &paths[self.locations[i]][*goal];
|
||||
if path.dist == min_goal_dist {
|
||||
completed.locations[i] = *goal;
|
||||
completed.opened |= 1 << goal;
|
||||
completed.goals[i] = Goal::Idle;
|
||||
if completed.interesting.remove(goal) {
|
||||
// Only increase flow rate if no other agent already
|
||||
// opened this valve
|
||||
completed.flow_rate += valves[*goal].flow_rate;
|
||||
}
|
||||
completed.history[i].push(*goal);
|
||||
} else {
|
||||
completed.locations[i] = path.path[time_spent - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec![completed];
|
||||
}
|
||||
|
||||
fn finished(&self) -> bool {
|
||||
self.time_remaining == 0
|
||||
|| self
|
||||
.goals
|
||||
.iter()
|
||||
.all(|g| matches!(g, Goal::NothingReachable)) //self.interesting.is_empty()
|
||||
}
|
||||
|
||||
fn lower_bound(&self, _: &ExtraArgs) -> usize {
|
||||
self.pressure_released + self.flow_rate * self.time_remaining
|
||||
}
|
||||
|
||||
fn upper_bound(&self, (valves, paths): &ExtraArgs) -> usize {
|
||||
let mut additional_flow = 0;
|
||||
for i in &self.interesting {
|
||||
let i = i.clone();
|
||||
if (self.opened & (1 << i)) != 0 {
|
||||
continue;
|
||||
}
|
||||
let dist = self
|
||||
.locations
|
||||
.iter()
|
||||
.map(|loc| paths[*loc][i].dist)
|
||||
.min()
|
||||
.unwrap();
|
||||
if dist < self.time_remaining {
|
||||
let flow = valves[i].flow_rate;
|
||||
additional_flow += flow * (self.time_remaining - dist - 1);
|
||||
}
|
||||
}
|
||||
|
||||
self.lower_bound(&(valves, paths)) + additional_flow
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for State {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.opened.hash(state);
|
||||
self.locations.hash(state);
|
||||
self.goals.hash(state);
|
||||
self.time_remaining.hash(state);
|
||||
self.pressure_released.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for State {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.opened == other.opened
|
||||
&& self.locations == other.locations
|
||||
&& self.goals == other.goals
|
||||
&& self.time_remaining == other.time_remaining
|
||||
&& self.pressure_released == other.pressure_released
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for State {}
|
||||
317
src/day17.rs
Normal file
317
src/day17.rs
Normal file
@ -0,0 +1,317 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::util;
|
||||
|
||||
const VENT_WIDTH: usize = 7;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Jet {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Jet {
|
||||
pub fn shift(&self) -> isize {
|
||||
match &self {
|
||||
Jet::Left => 1,
|
||||
Jet::Right => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_jets(input: &String) -> Vec<Jet> {
|
||||
assert_eq!(input.lines().count(), 1);
|
||||
input
|
||||
.chars()
|
||||
.filter_map(|c| match c {
|
||||
'<' => Some(Jet::Left),
|
||||
'>' => Some(Jet::Right),
|
||||
'\n' => None,
|
||||
_ => panic!("Unknown jet {}", c),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BlockKind {
|
||||
Dash = 0,
|
||||
Plus,
|
||||
L,
|
||||
Pipe,
|
||||
Square,
|
||||
}
|
||||
|
||||
impl BlockKind {
|
||||
pub fn from_num(n: usize) -> BlockKind {
|
||||
match n {
|
||||
0 => BlockKind::Dash,
|
||||
1 => BlockKind::Plus,
|
||||
2 => BlockKind::L,
|
||||
3 => BlockKind::Pipe,
|
||||
4 => BlockKind::Square,
|
||||
_ => panic!("Unknown block kind {}", n),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
match self {
|
||||
BlockKind::Dash => 1,
|
||||
BlockKind::Plus => 3,
|
||||
BlockKind::L => 3,
|
||||
BlockKind::Pipe => 4,
|
||||
BlockKind::Square => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
BlockKind::Dash => 4,
|
||||
BlockKind::Plus => 3,
|
||||
BlockKind::L => 3,
|
||||
BlockKind::Pipe => 1,
|
||||
BlockKind::Square => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vent_rows(&self) -> &'static Vec<VentRow> {
|
||||
static ROWS: OnceCell<Vec<Vec<VentRow>>> = OnceCell::new();
|
||||
let rows = ROWS.get_or_init(|| {
|
||||
vec![
|
||||
vec![0b1111], // Dash
|
||||
vec![0b010, 0b111, 0b010], // Plus
|
||||
vec![0b001, 0b001, 0b111], // L
|
||||
vec![0b1; 4], // Pipe
|
||||
vec![0b11, 0b11], // Square
|
||||
]
|
||||
});
|
||||
&rows
|
||||
.get(*self as usize)
|
||||
.unwrap_or_else(|| panic!("Unknown BlockKind: {:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
const N_BLOCK_TYPES: usize = 5;
|
||||
|
||||
pub struct Block {
|
||||
kind: BlockKind,
|
||||
pos: util::Coord,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn spawn(&self, vent: &mut Vec<VentRow>) {
|
||||
for (i, row) in self.kind.vent_rows().iter().rev().enumerate() {
|
||||
vent[self.pos.0 + i] |= *row << self.shift_dist();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_range(&self) -> std::ops::Range<usize> {
|
||||
let row = self.pos.0;
|
||||
row..(row + self.kind.height())
|
||||
}
|
||||
|
||||
pub fn can_push(&self, vent: &Vec<VentRow>, jet: Jet) -> bool {
|
||||
if (jet == Jet::Left && self.pos.1 == 0)
|
||||
|| (jet == Jet::Right && self.pos.1 + self.kind.width() >= VENT_WIDTH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let shift = self.shift_dist() as isize + jet.shift();
|
||||
for (i, row) in self.row_range().rev().enumerate() {
|
||||
let bitmask = self.kind.vent_rows()[i] << shift;
|
||||
if vent[row] & bitmask != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn can_fall(&self, vent: &Vec<VentRow>) -> bool {
|
||||
if self.pos.0 == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i, row) in self.row_range().rev().enumerate() {
|
||||
let bitmask = self.kind.vent_rows()[i] << self.shift_dist();
|
||||
if vent[row - 1] & bitmask != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn shift_dist(&self) -> usize {
|
||||
VENT_WIDTH - self.pos.1 - self.kind.width()
|
||||
}
|
||||
}
|
||||
|
||||
type VentRow = u8;
|
||||
|
||||
const CYCLE_SIZE: usize = 5;
|
||||
|
||||
pub fn do_moves(jets: &Vec<Jet>, n_blocks: usize) -> usize {
|
||||
let mut block_i = 0;
|
||||
let mut current_block: Option<Block> = None;
|
||||
let mut vent = vec![0; 4];
|
||||
|
||||
// {jet_i: {round: (height, block_i)}}
|
||||
let mut block_cycles: HashMap<usize, HashMap<usize, (usize, usize)>> = HashMap::new();
|
||||
let mut skipped_height: Option<usize> = None;
|
||||
|
||||
let mut round = 0;
|
||||
while block_i <= n_blocks {
|
||||
let jet_i = round % jets.len();
|
||||
|
||||
if current_block.is_none() {
|
||||
if block_i % N_BLOCK_TYPES == 0 {
|
||||
if skipped_height.is_none() {
|
||||
let entry = block_cycles.entry(jet_i).or_insert_with(HashMap::new);
|
||||
entry.insert(round, (vent.len() - empty_rows(&vent), block_i));
|
||||
if let Some((cycle_height, cycle_blocks)) = find_cycle(entry, CYCLE_SIZE) {
|
||||
println!(
|
||||
"Found cycle ({} height per {} blocks) after {} rounds!",
|
||||
cycle_height, cycle_blocks, round
|
||||
);
|
||||
|
||||
let remaining_blocks = n_blocks - block_i;
|
||||
let cycles = remaining_blocks / cycle_blocks;
|
||||
block_i += cycles * cycle_blocks;
|
||||
skipped_height = Some(cycles * cycle_height);
|
||||
|
||||
println!(
|
||||
"Skipping {} blocks, {} height",
|
||||
cycles * cycle_blocks,
|
||||
cycles * cycle_height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
current_block = Some(spawn_block(&mut vent, block_i % N_BLOCK_TYPES));
|
||||
// println!("Vent as block {} begins falling:", block_i);
|
||||
// print_vent(&vent, current_block.as_ref());
|
||||
// println!("");
|
||||
block_i += 1;
|
||||
}
|
||||
let block = current_block.as_mut().unwrap();
|
||||
|
||||
// Push left/right
|
||||
let jet = jets[jet_i];
|
||||
if block.can_push(&vent, jet) {
|
||||
let (_, ref mut col) = block.pos;
|
||||
*col = ((*col as isize) - jet.shift()) as usize;
|
||||
}
|
||||
|
||||
// Fall
|
||||
if block.can_fall(&vent) {
|
||||
let (ref mut row, _) = block.pos;
|
||||
*row -= 1;
|
||||
} else {
|
||||
block.spawn(&mut vent);
|
||||
current_block = None;
|
||||
}
|
||||
round += 1;
|
||||
}
|
||||
|
||||
vent.len() - empty_rows(&vent) + skipped_height.unwrap_or(0)
|
||||
}
|
||||
|
||||
const SPAWN_OFFSET_X: usize = 2;
|
||||
const SPAWN_OFFSET_Y: usize = 3;
|
||||
|
||||
fn spawn_block(vent: &mut Vec<VentRow>, block: usize) -> Block {
|
||||
let block = BlockKind::from_num(block);
|
||||
|
||||
let mut empty_rows = empty_rows(vent);
|
||||
let diff = (block.height() as isize + 3) - (empty_rows as isize);
|
||||
if diff > 0 {
|
||||
vent.append(&mut vec![0; diff as usize]);
|
||||
empty_rows += diff as usize;
|
||||
}
|
||||
|
||||
let row = vent.len() - empty_rows + SPAWN_OFFSET_Y;
|
||||
let col = SPAWN_OFFSET_X;
|
||||
|
||||
assert!(row + block.height() - 1 < vent.len());
|
||||
|
||||
let block = Block {
|
||||
kind: block,
|
||||
pos: (row, col),
|
||||
};
|
||||
|
||||
block
|
||||
}
|
||||
|
||||
fn empty_rows(vent: &Vec<VentRow>) -> usize {
|
||||
vent.iter()
|
||||
.rev()
|
||||
.position(|r| *r != 0)
|
||||
.unwrap_or_else(|| vent.len())
|
||||
}
|
||||
|
||||
pub fn print_vent(vent: &Vec<VentRow>, block: Option<&Block>) {
|
||||
let block_rows = block.map(|b| b.kind.vent_rows());
|
||||
let block_range = block.map(|b| b.row_range());
|
||||
let block_range = block_range.as_ref();
|
||||
for (r, vent_row) in vent.iter().enumerate().rev() {
|
||||
print!("|");
|
||||
let block_collision = if block.is_some() && block_range.unwrap().contains(&r) {
|
||||
block_rows.unwrap()[block_range.unwrap().end - r - 1] << block.unwrap().shift_dist()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
for i in (0..VENT_WIDTH).rev() {
|
||||
let col = 1 << i;
|
||||
let c = if (block_collision & col) != 0 {
|
||||
'@'
|
||||
} else if (vent_row & col) == 0 {
|
||||
'.'
|
||||
} else {
|
||||
'#'
|
||||
};
|
||||
print!("{}", c);
|
||||
}
|
||||
print!("|\n");
|
||||
}
|
||||
println!("+-------+");
|
||||
}
|
||||
|
||||
fn find_cycle(
|
||||
jet_heights: &HashMap<usize, (usize, usize)>,
|
||||
n_cycles: usize,
|
||||
) -> Option<(usize, usize)> {
|
||||
if jet_heights.len() < n_cycles {
|
||||
return None;
|
||||
}
|
||||
|
||||
for (init_round, (init_height, init_blocks)) in jet_heights {
|
||||
'next_loop: for (next_round, (next_height, next_blocks)) in jet_heights {
|
||||
if init_round == next_round || *next_height < *init_height {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cycle = next_round - init_round;
|
||||
let cycle_height = next_height - init_height;
|
||||
let cycle_blocks = next_blocks - init_blocks;
|
||||
for n in 2..=n_cycles {
|
||||
let round = init_round + n * cycle;
|
||||
let entry = jet_heights.get(&round);
|
||||
if let Some((height, blocks)) = entry {
|
||||
let expected_height = init_height + n * cycle_height;
|
||||
let expected_blocks = init_blocks + n * cycle_blocks;
|
||||
if *height != expected_height || *blocks != expected_blocks {
|
||||
continue 'next_loop;
|
||||
}
|
||||
} else {
|
||||
continue 'next_loop;
|
||||
}
|
||||
}
|
||||
|
||||
return Some((cycle_height, cycle_blocks));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
240
src/day18.rs
Normal file
240
src/day18.rs
Normal file
@ -0,0 +1,240 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Material {
|
||||
Air,
|
||||
Lava,
|
||||
Steam,
|
||||
}
|
||||
|
||||
pub fn parse_droplet(input: &String) -> Vec<Vec<Vec<Material>>> {
|
||||
let mut cubes: Vec<(usize, usize, usize)> = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut sp = line.split(',');
|
||||
cubes.push((
|
||||
sp.next().unwrap().parse().unwrap(),
|
||||
sp.next().unwrap().parse().unwrap(),
|
||||
sp.next().unwrap().parse().unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
let (x_min, x_max) = cubes.iter().map(|c| c.0).minmax().into_option().unwrap();
|
||||
let (y_min, y_max) = cubes.iter().map(|c| c.1).minmax().into_option().unwrap();
|
||||
let (z_min, z_max) = cubes.iter().map(|c| c.2).minmax().into_option().unwrap();
|
||||
let x_len = x_max - x_min + 3;
|
||||
let y_len = y_max - y_min + 3;
|
||||
let z_len = z_max - z_min + 3;
|
||||
|
||||
let mut result = vec![vec![vec![Material::Air; z_len]; y_len]; x_len];
|
||||
for (x, y, z) in cubes {
|
||||
result[x - x_min + 1][y - y_min + 1][z - z_min + 1] = Material::Lava;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn surface_area(droplet: &Vec<Vec<Vec<Material>>>) -> usize {
|
||||
let x_len = droplet.len();
|
||||
assert!(x_len > 0);
|
||||
let y_len = droplet[0].len();
|
||||
assert!(y_len > 0);
|
||||
let z_len = droplet[0][0].len();
|
||||
assert!(z_len > 0);
|
||||
|
||||
let up = (0..x_len)
|
||||
.flat_map(|x| {
|
||||
(0..y_len)
|
||||
.map(move |y| (x, y, (0..z_len).collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
let down = (0..x_len)
|
||||
.flat_map(|x| {
|
||||
(0..y_len)
|
||||
.map(move |y| (x, y, (0..z_len).rev().collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
let left = (0..x_len)
|
||||
.flat_map(|x| {
|
||||
(0..z_len)
|
||||
.map(move |z| (x, z, (0..y_len).collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
let right = (0..x_len)
|
||||
.flat_map(|x| {
|
||||
(0..z_len)
|
||||
.map(move |z| (x, z, (0..y_len).rev().collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
let fore = (0..y_len)
|
||||
.flat_map(|y| {
|
||||
(0..z_len)
|
||||
.map(move |z| (y, z, (0..x_len).collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
let back = (0..y_len)
|
||||
.flat_map(|y| {
|
||||
(0..z_len)
|
||||
.map(move |z| (y, z, (0..x_len).rev().collect()))
|
||||
.collect_vec()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut result = 0;
|
||||
result += surface_area_dir(droplet, &up, ArgOrder::XYZ);
|
||||
result += surface_area_dir(droplet, &down, ArgOrder::XYZ);
|
||||
result += surface_area_dir(droplet, &left, ArgOrder::XZY);
|
||||
result += surface_area_dir(droplet, &right, ArgOrder::XZY);
|
||||
result += surface_area_dir(droplet, &fore, ArgOrder::YZX);
|
||||
result += surface_area_dir(droplet, &back, ArgOrder::YZX);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
enum ArgOrder {
|
||||
XYZ,
|
||||
XZY,
|
||||
YZX,
|
||||
}
|
||||
|
||||
fn surface_area_dir(
|
||||
droplet: &Vec<Vec<Vec<Material>>>,
|
||||
lines: &Vec<(usize, usize, Vec<usize>)>,
|
||||
order: ArgOrder,
|
||||
) -> usize {
|
||||
let mut result = 0;
|
||||
for (a, b, c_range) in lines {
|
||||
let mut last_empty = true;
|
||||
for c in c_range {
|
||||
let occupied = match order {
|
||||
ArgOrder::XYZ => droplet[*a][*b][*c],
|
||||
ArgOrder::XZY => droplet[*a][*c][*b],
|
||||
ArgOrder::YZX => droplet[*c][*a][*b],
|
||||
};
|
||||
if last_empty && matches!(occupied, Material::Lava) {
|
||||
result += 1;
|
||||
}
|
||||
last_empty = matches!(occupied, Material::Air);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn outer_surface_area(droplet: &Vec<Vec<Vec<Material>>>) -> usize {
|
||||
let x_len = droplet.len();
|
||||
assert!(x_len > 0);
|
||||
let y_len = droplet[0].len();
|
||||
assert!(y_len > 0);
|
||||
let z_len = droplet[0][0].len();
|
||||
assert!(z_len > 0);
|
||||
|
||||
let mut next = Vec::new();
|
||||
|
||||
// top/bottom
|
||||
for x in 0..x_len {
|
||||
for y in 0..y_len {
|
||||
next.push((x, y, 0));
|
||||
next.push((x, y, z_len - 1));
|
||||
}
|
||||
}
|
||||
// left/right
|
||||
for x in 0..x_len {
|
||||
for z in 0..z_len {
|
||||
next.push((x, 0, z));
|
||||
next.push((x, y_len - 1, z));
|
||||
}
|
||||
}
|
||||
// fore/back
|
||||
for y in 0..y_len {
|
||||
for z in 0..z_len {
|
||||
next.push((0, y, z));
|
||||
next.push((x_len - 1, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
let mut visited: HashSet<_> = next.iter().map(|c| c.clone()).collect();
|
||||
let generators: [Box<dyn Fn(&Coord) -> Option<Coord>>; 6] = [
|
||||
Box::new(|c| go_fore(c)),
|
||||
Box::new(|c| go_back(c, x_len)),
|
||||
Box::new(|c| go_left(c)),
|
||||
Box::new(|c| go_right(c, y_len)),
|
||||
Box::new(|c| go_up(c)),
|
||||
Box::new(|c| go_down(c, z_len)),
|
||||
];
|
||||
|
||||
let mut result = 0;
|
||||
while let Some(n) = next.pop().clone() {
|
||||
for gen in &generators {
|
||||
if let Some(c) = gen(&n) {
|
||||
let occupied = droplet[c.0][c.1][c.2];
|
||||
if matches!(occupied, Material::Air) && visited.insert(c) {
|
||||
next.push(c);
|
||||
} else if matches!(occupied, Material::Lava) {
|
||||
result += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
type Coord = (usize, usize, usize);
|
||||
|
||||
fn go_fore(c: &Coord) -> Option<Coord> {
|
||||
if c.0 > 0 {
|
||||
Some((c.0 - 1, c.1, c.2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn go_back(c: &Coord, x_len: usize) -> Option<Coord> {
|
||||
if c.0 < x_len - 1 {
|
||||
Some((c.0 + 1, c.1, c.2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn go_left(c: &Coord) -> Option<Coord> {
|
||||
if c.1 > 0 {
|
||||
Some((c.0, c.1 - 1, c.2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn go_right(c: &Coord, y_len: usize) -> Option<Coord> {
|
||||
if c.1 < y_len - 1 {
|
||||
Some((c.0, c.1 + 1, c.2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn go_up(c: &Coord) -> Option<Coord> {
|
||||
if c.2 > 0 {
|
||||
Some((c.0, c.1, c.2 - 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn go_down(c: &Coord, z_len: usize) -> Option<Coord> {
|
||||
if c.2 < z_len - 1 {
|
||||
Some((c.0, c.1, c.2 + 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
210
src/day19.rs
Normal file
210
src/day19.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use regex::Regex;
|
||||
|
||||
use crate::util::{self, BnBState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Blueprint {
|
||||
pub ore_ore_cost: usize,
|
||||
pub clay_ore_cost: usize,
|
||||
pub obsidian_ore_cost: usize,
|
||||
pub obsidian_clay_cost: usize,
|
||||
pub geode_ore_cost: usize,
|
||||
pub geode_obsidian_cost: usize,
|
||||
}
|
||||
|
||||
pub fn parse_blueprints(input: &String) -> Vec<Blueprint> {
|
||||
let re = Regex::new(
|
||||
r"Blueprint (\d+): Each ore robot costs (\d+) ore. Each clay robot costs (\d+) ore. Each obsidian robot costs (\d+) ore and (\d+) clay. Each geode robot costs (\d+) ore and (\d+) obsidian.",
|
||||
).unwrap();
|
||||
input
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(i, line)| {
|
||||
let captures = re
|
||||
.captures(line)
|
||||
.unwrap_or_else(|| panic!("Invalid blueprint formatting:\n{}", line));
|
||||
assert_eq!(
|
||||
captures.get(1).unwrap().as_str().parse::<usize>().unwrap(),
|
||||
i + 1
|
||||
);
|
||||
|
||||
let ore_ore_cost = captures.get(2).unwrap().as_str().parse().unwrap();
|
||||
let clay_ore_cost = captures.get(3).unwrap().as_str().parse().unwrap();
|
||||
let obsidian_ore_cost = captures.get(4).unwrap().as_str().parse().unwrap();
|
||||
let obsidian_clay_cost = captures.get(5).unwrap().as_str().parse().unwrap();
|
||||
let geode_ore_cost = captures.get(6).unwrap().as_str().parse().unwrap();
|
||||
let geode_obsidian_cost = captures.get(7).unwrap().as_str().parse().unwrap();
|
||||
|
||||
Blueprint {
|
||||
ore_ore_cost,
|
||||
clay_ore_cost,
|
||||
obsidian_ore_cost,
|
||||
obsidian_clay_cost,
|
||||
geode_ore_cost,
|
||||
geode_obsidian_cost,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct State {
|
||||
pub time_remaining: usize,
|
||||
|
||||
pub ore: usize,
|
||||
pub clay: usize,
|
||||
pub obsidian: usize,
|
||||
pub geodes: usize,
|
||||
|
||||
pub ore_robots: usize,
|
||||
pub clay_robots: usize,
|
||||
pub obsidian_robots: usize,
|
||||
pub geode_robots: usize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(initial_time: usize) -> State {
|
||||
State {
|
||||
time_remaining: initial_time,
|
||||
ore: 0,
|
||||
clay: 0,
|
||||
obsidian: 0,
|
||||
geodes: 0,
|
||||
ore_robots: 1,
|
||||
clay_robots: 0,
|
||||
obsidian_robots: 0,
|
||||
geode_robots: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn produce<F: Fn(&mut State)>(&self, time_for_ore_prod: isize, produce: F) -> Option<State> {
|
||||
let time_until_robot_ready = (time_for_ore_prod.max(0) as usize) + 1;
|
||||
// For this to make sense, we also need at least one minute
|
||||
// remaining after producing the robot
|
||||
if time_until_robot_ready + 1 > self.time_remaining {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut produced = self.clone();
|
||||
produced.run_steps(time_until_robot_ready);
|
||||
produce(&mut produced);
|
||||
Some(produced)
|
||||
}
|
||||
|
||||
fn produce_ore_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||
if self.ore_robots == 0 {
|
||||
return None;
|
||||
}
|
||||
let time_required = num::Integer::div_ceil(
|
||||
&((blueprint.ore_ore_cost as isize) - (self.ore as isize)),
|
||||
&(self.ore_robots as isize),
|
||||
);
|
||||
self.produce(time_required, |mut s| {
|
||||
s.ore_robots += 1;
|
||||
s.ore -= blueprint.ore_ore_cost;
|
||||
})
|
||||
}
|
||||
|
||||
fn produce_clay_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||
if self.ore_robots == 0 {
|
||||
return None;
|
||||
}
|
||||
let time_required = num::Integer::div_ceil(
|
||||
&((blueprint.clay_ore_cost as isize) - (self.ore as isize)),
|
||||
&(self.ore_robots as isize),
|
||||
);
|
||||
self.produce(time_required, |mut s| {
|
||||
s.clay_robots += 1;
|
||||
s.ore -= blueprint.clay_ore_cost;
|
||||
})
|
||||
}
|
||||
|
||||
fn produce_obsidian_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||
if self.ore_robots == 0 || self.clay_robots == 0 {
|
||||
return None;
|
||||
}
|
||||
let time_required_ore = num::Integer::div_ceil(
|
||||
&((blueprint.obsidian_ore_cost as isize) - (self.ore as isize)),
|
||||
&(self.ore_robots as isize),
|
||||
);
|
||||
let time_required_clay = num::Integer::div_ceil(
|
||||
&((blueprint.obsidian_clay_cost as isize) - (self.clay as isize)),
|
||||
&(self.clay_robots as isize),
|
||||
);
|
||||
self.produce(time_required_ore.max(time_required_clay), |mut s| {
|
||||
s.obsidian_robots += 1;
|
||||
s.ore -= blueprint.obsidian_ore_cost;
|
||||
s.clay -= blueprint.obsidian_clay_cost;
|
||||
})
|
||||
}
|
||||
|
||||
fn produce_geode_next(&self, blueprint: &Blueprint) -> Option<State> {
|
||||
if self.ore_robots == 0 || self.obsidian_robots == 0 {
|
||||
return None;
|
||||
}
|
||||
let time_required_ore = num::Integer::div_ceil(
|
||||
&((blueprint.geode_ore_cost as isize) - (self.ore as isize)),
|
||||
&(self.ore_robots as isize),
|
||||
);
|
||||
let time_required_obsidian = num::Integer::div_ceil(
|
||||
&((blueprint.geode_obsidian_cost as isize) - (self.obsidian as isize)),
|
||||
&(self.obsidian_robots as isize),
|
||||
);
|
||||
self.produce(time_required_ore.max(time_required_obsidian), |mut s| {
|
||||
s.geode_robots += 1;
|
||||
s.ore -= blueprint.geode_ore_cost;
|
||||
s.obsidian -= blueprint.geode_obsidian_cost;
|
||||
})
|
||||
}
|
||||
|
||||
fn run_steps(&mut self, n: usize) {
|
||||
assert!(self.time_remaining >= n);
|
||||
|
||||
self.time_remaining -= n;
|
||||
self.ore += self.ore_robots * n;
|
||||
self.clay += self.clay_robots * n;
|
||||
self.obsidian += self.obsidian_robots * n;
|
||||
self.geodes += self.geode_robots * n;
|
||||
}
|
||||
}
|
||||
|
||||
impl util::BnBState<Blueprint> for State {
|
||||
fn finished(&self) -> bool {
|
||||
self.time_remaining == 0
|
||||
}
|
||||
|
||||
fn possible_actions(&self, blueprint: &Blueprint) -> Vec<State> {
|
||||
assert!(!self.finished());
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
if self.time_remaining > 1 {
|
||||
self.produce_ore_next(blueprint).map(|a| result.push(a));
|
||||
self.produce_clay_next(blueprint).map(|a| result.push(a));
|
||||
self.produce_obsidian_next(blueprint)
|
||||
.map(|a| result.push(a));
|
||||
self.produce_geode_next(blueprint).map(|a| result.push(a));
|
||||
}
|
||||
let mut do_nothing = self.clone();
|
||||
do_nothing.run_steps(self.time_remaining);
|
||||
result.push(do_nothing);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn upper_bound(&self, b: &Blueprint) -> usize {
|
||||
// Build one geode robot each remaining turn
|
||||
// \sum_{k=1}^n {k - 1} = \sum_{k=0}^{n-1} {k} = 1/2 (n-1) n
|
||||
self.lower_bound(b) + ((self.time_remaining - 1) * self.time_remaining) / 2
|
||||
}
|
||||
|
||||
fn lower_bound(&self, _: &Blueprint) -> usize {
|
||||
self.geodes + self.geode_robots * self.time_remaining
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_geodes(minutes: usize, blueprint: &Blueprint) -> usize {
|
||||
let initial = State::new(minutes);
|
||||
|
||||
util::maximize(&initial, blueprint).lower_bound(blueprint)
|
||||
}
|
||||
122
src/day20.rs
Normal file
122
src/day20.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::{cell::RefCell, fmt::Debug, ops::Deref, rc::Rc};
|
||||
|
||||
pub struct Node {
|
||||
pub val: isize,
|
||||
pub next: Option<Rc<RefCell<Node>>>,
|
||||
pub prev: Option<Rc<RefCell<Node>>>,
|
||||
}
|
||||
|
||||
// Default Debug implementation will loop indefinitely, since we have a ring buffer
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Node")
|
||||
.field("val", &self.val)
|
||||
.field("next", &self.next.as_ref().map(|n| n.borrow().val))
|
||||
.field("prev", &self.prev.as_ref().map(|n| n.borrow().val))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_next(node: &Rc<RefCell<Node>>, n: usize) -> Rc<RefCell<Node>> {
|
||||
let mut curr = node.clone();
|
||||
for _ in 0..n {
|
||||
let next = curr.borrow().next.as_ref().unwrap().clone();
|
||||
curr = next;
|
||||
}
|
||||
curr
|
||||
}
|
||||
|
||||
pub fn get_prev(node: &Rc<RefCell<Node>>, n: usize) -> Rc<RefCell<Node>> {
|
||||
let mut curr = node.clone();
|
||||
for _ in 0..n {
|
||||
let prev = curr.borrow().prev.as_ref().unwrap().clone();
|
||||
curr = prev;
|
||||
}
|
||||
curr
|
||||
}
|
||||
|
||||
pub fn parse_file(input: &String, key: isize) -> Vec<Rc<RefCell<Node>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut last = None;
|
||||
for line in input.lines() {
|
||||
let val: isize = line.parse().unwrap();
|
||||
let node = Rc::new(RefCell::new(Node {
|
||||
val: val * key,
|
||||
next: None,
|
||||
prev: last.clone(),
|
||||
}));
|
||||
if let Some(l) = last {
|
||||
l.as_ref().borrow_mut().next = Some(node.clone());
|
||||
}
|
||||
result.push(node.clone());
|
||||
last = Some(node);
|
||||
}
|
||||
|
||||
assert!(result.len() > 0);
|
||||
|
||||
let last_refcell = last.as_ref().unwrap().deref();
|
||||
last_refcell.borrow_mut().next = Some(result[0].clone());
|
||||
let first_refcell = &*result[0];
|
||||
first_refcell.borrow_mut().prev = Some(last.as_ref().unwrap().clone());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn mix(nodes: &Vec<Rc<RefCell<Node>>>) {
|
||||
let div = nodes.len() - 1;
|
||||
|
||||
for node in nodes {
|
||||
let n = node.borrow().val;
|
||||
if n == 0 || (n > 0 && (n as usize) % div == 0) || (n < 0 && (-n as usize) % div == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove from current position
|
||||
let prev = get_prev(node, 1);
|
||||
let next = get_next(node, 1);
|
||||
prev.borrow_mut().next = Some(next.clone());
|
||||
next.borrow_mut().prev = Some(prev.clone());
|
||||
|
||||
let prev = if n > 0 {
|
||||
let n = (n as usize) % div;
|
||||
assert!(n < div);
|
||||
get_next(node, n)
|
||||
} else {
|
||||
let n = ((-n as usize) % div) + 1;
|
||||
assert!(n < div);
|
||||
get_prev(node, n)
|
||||
};
|
||||
let next = get_next(&prev, 1);
|
||||
prev.deref().borrow_mut().next = Some(node.clone());
|
||||
next.deref().borrow_mut().prev = Some(node.clone());
|
||||
|
||||
let mut n_mut = node.deref().borrow_mut();
|
||||
n_mut.prev = Some(prev.clone());
|
||||
n_mut.next = Some(next.clone());
|
||||
drop(n_mut);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_nodes(nodes: &Vec<Rc<RefCell<Node>>>) {
|
||||
let mut curr = nodes.iter().find(|n| n.borrow().val == 0).unwrap().clone();
|
||||
loop {
|
||||
print!("{}, ", curr.borrow().val);
|
||||
|
||||
let next = curr.borrow().next.as_ref().unwrap().clone();
|
||||
curr = next;
|
||||
if curr.borrow().val == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
|
||||
pub fn calc_coordinates(nodes: &Vec<Rc<RefCell<Node>>>) -> isize {
|
||||
let zero = nodes.iter().find(|n| n.borrow().val == 0).unwrap();
|
||||
let first = get_next(zero, 1000).borrow().val;
|
||||
let second = get_next(zero, 2000).borrow().val;
|
||||
let third = get_next(zero, 3000).borrow().val;
|
||||
|
||||
first + second + third
|
||||
}
|
||||
149
src/day21.rs
Normal file
149
src/day21.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use regex::Regex;
|
||||
use trees::{tr, Node, Tree};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Operation {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn from(s: &str) -> Operation {
|
||||
match s {
|
||||
"+" => Operation::Add,
|
||||
"-" => Operation::Sub,
|
||||
"*" => Operation::Mul,
|
||||
"/" => Operation::Div,
|
||||
_ => panic!("Unknown operation {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum MonkeyKind {
|
||||
Number(isize),
|
||||
Calculate(String, String, Operation),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Monkey {
|
||||
pub name: String,
|
||||
pub kind: MonkeyKind,
|
||||
}
|
||||
|
||||
pub fn parse_monkeys(input: &String) -> Tree<Monkey> {
|
||||
let mut monkeys = HashMap::new();
|
||||
|
||||
let re = Regex::new(r"^(\w+): (?:(\d+)|(\w+) ([+\-*/]) (\w+))$").unwrap();
|
||||
for line in input.lines() {
|
||||
let captures = re.captures(line).unwrap();
|
||||
let name = captures.get(1).unwrap().as_str();
|
||||
let kind = if let Some(num) = captures.get(2) {
|
||||
MonkeyKind::Number(num.as_str().parse().unwrap())
|
||||
} else {
|
||||
MonkeyKind::Calculate(
|
||||
captures.get(3).unwrap().as_str().to_owned(),
|
||||
captures.get(5).unwrap().as_str().to_owned(),
|
||||
Operation::from(captures.get(4).unwrap().as_str()),
|
||||
)
|
||||
};
|
||||
monkeys.insert(
|
||||
name.to_owned(),
|
||||
Monkey {
|
||||
name: name.to_owned(),
|
||||
kind,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let tree = to_tree(&monkeys, "root");
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
fn to_tree(monkeys: &HashMap<String, Monkey>, monkey: &str) -> Tree<Monkey> {
|
||||
let monkey = &monkeys[monkey];
|
||||
let mut tree = tr(monkey.clone());
|
||||
|
||||
if let MonkeyKind::Calculate(a, b, _) = &monkey.kind {
|
||||
let forest = -to_tree(monkeys, a) - to_tree(monkeys, b);
|
||||
tree.append(forest);
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
pub fn yell(monkey: &Node<Monkey>, no_human: bool) -> isize {
|
||||
if no_human {
|
||||
assert!(monkey.data().name != "humn");
|
||||
}
|
||||
|
||||
match &monkey.data().kind {
|
||||
MonkeyKind::Number(n) => *n,
|
||||
MonkeyKind::Calculate(_, _, op) => {
|
||||
let mut children = monkey.iter();
|
||||
let a = children.next().unwrap();
|
||||
let b = children.next().unwrap();
|
||||
match op {
|
||||
Operation::Add => yell(a, no_human) + yell(b, no_human),
|
||||
Operation::Sub => yell(a, no_human) - yell(b, no_human),
|
||||
Operation::Mul => yell(a, no_human) * yell(b, no_human),
|
||||
Operation::Div => yell(a, no_human) / yell(b, no_human),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_solution(target: &Node<Monkey>) -> isize {
|
||||
let parent = target.parent().unwrap();
|
||||
let other_pos = parent.iter().position(|n| n != target).unwrap();
|
||||
assert!(other_pos < 2);
|
||||
let sibling = parent.iter().nth(other_pos).unwrap();
|
||||
let other = yell(sibling, true);
|
||||
|
||||
if parent.parent().is_none() {
|
||||
other
|
||||
} else {
|
||||
let parent_target = find_solution(parent);
|
||||
if let MonkeyKind::Calculate(_, _, op) = parent.data().kind {
|
||||
match op {
|
||||
Operation::Add => parent_target - other,
|
||||
Operation::Sub => {
|
||||
if other_pos == 0 {
|
||||
other - parent_target
|
||||
} else {
|
||||
other + parent_target
|
||||
}
|
||||
}
|
||||
Operation::Mul => parent_target / other,
|
||||
Operation::Div => {
|
||||
if other_pos == 0 {
|
||||
other / parent_target
|
||||
} else {
|
||||
parent_target * other
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Parent does not calculate!?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find<'a>(monkeys: &'a Tree<Monkey>, name: &str) -> Option<&'a Node<Monkey>> {
|
||||
let mut next = vec![monkeys.root()];
|
||||
|
||||
while let Some(n) = next.pop() {
|
||||
let data = n.data();
|
||||
if data.name == name {
|
||||
return Some(n);
|
||||
}
|
||||
next.extend(n.iter());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
611
src/day22.rs
Normal file
611
src/day22.rs
Normal file
@ -0,0 +1,611 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::util::Dir;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TurnDir {
|
||||
CW,
|
||||
CCW,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PassInstr {
|
||||
Move(usize),
|
||||
Turn(TurnDir),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pose {
|
||||
pub pos: (usize, usize),
|
||||
pub orientation: Dir,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MoveResult {
|
||||
Blocked,
|
||||
BlockedByWrapping,
|
||||
WrappedAround,
|
||||
Success,
|
||||
}
|
||||
|
||||
impl MoveResult {
|
||||
pub fn was_blocked(&self) -> bool {
|
||||
matches!(self, MoveResult::Blocked | MoveResult::BlockedByWrapping)
|
||||
}
|
||||
|
||||
pub fn wrapped_around(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
MoveResult::WrappedAround | MoveResult::BlockedByWrapping
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Navigable {
|
||||
type Pose;
|
||||
type NavigationInstruction;
|
||||
|
||||
fn forward(&self, from: &Self::Pose, steps: usize) -> (Self::Pose, MoveResult);
|
||||
fn exec_instruction(
|
||||
&self,
|
||||
from: &Self::Pose,
|
||||
inst: &Self::NavigationInstruction,
|
||||
) -> (Self::Pose, MoveResult);
|
||||
fn initial_pose(&self) -> Self::Pose;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SparseGrid {
|
||||
pub grid: Vec<Vec<Option<bool>>>,
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
pub row_bounds: Vec<(usize, usize)>,
|
||||
pub col_bounds: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl SparseGrid {
|
||||
fn print(&self, pose: Option<&Pose>) {
|
||||
let check_pos = |y, x| {
|
||||
if let Some(pose) = pose {
|
||||
pose.pos.0 == y && pose.pos.1 == x
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
for (y, row) in self.grid.iter().enumerate() {
|
||||
for (x, col) in row.iter().enumerate() {
|
||||
let c = if check_pos(y, x) {
|
||||
assert!(*col == Some(false));
|
||||
match pose.unwrap().orientation {
|
||||
Dir::Right => '>',
|
||||
Dir::Down => 'v',
|
||||
Dir::Left => '<',
|
||||
Dir::Up => '^',
|
||||
}
|
||||
} else if col.is_none() {
|
||||
' '
|
||||
} else if *col == Some(true) {
|
||||
'#'
|
||||
} else {
|
||||
'.'
|
||||
};
|
||||
print!("{}", c);
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Navigable for SparseGrid {
|
||||
type Pose = Pose;
|
||||
type NavigationInstruction = PassInstr;
|
||||
|
||||
fn forward(&self, from: &Pose, steps: usize) -> (Pose, MoveResult) {
|
||||
let mut pose = from.clone();
|
||||
let pos = &mut pose.pos;
|
||||
let mut status = MoveResult::Success;
|
||||
for _ in 0..steps {
|
||||
let next_pos = match from.orientation {
|
||||
Dir::Left => {
|
||||
let bounds = &self.row_bounds[pos.0];
|
||||
let next_col = if pos.1 == 0 || pos.1 == bounds.0 {
|
||||
status = MoveResult::WrappedAround;
|
||||
bounds.1
|
||||
} else {
|
||||
pos.1 - 1
|
||||
};
|
||||
(pos.0, next_col)
|
||||
}
|
||||
Dir::Up => {
|
||||
let bounds = &self.col_bounds[pos.1];
|
||||
let next_row = if pos.0 == 0 || pos.0 == bounds.0 {
|
||||
status = MoveResult::WrappedAround;
|
||||
bounds.1
|
||||
} else {
|
||||
pos.0 - 1
|
||||
};
|
||||
(next_row, pos.1)
|
||||
}
|
||||
Dir::Right => {
|
||||
let bounds = &self.row_bounds[pos.0];
|
||||
let next_col = if pos.1 == bounds.1 {
|
||||
status = MoveResult::WrappedAround;
|
||||
bounds.0
|
||||
} else {
|
||||
pos.1 + 1
|
||||
};
|
||||
(pos.0, next_col)
|
||||
}
|
||||
Dir::Down => {
|
||||
let bounds = &self.col_bounds[pos.1];
|
||||
let next_row = if pos.0 == bounds.1 {
|
||||
status = MoveResult::WrappedAround;
|
||||
bounds.0
|
||||
} else {
|
||||
pos.0 + 1
|
||||
};
|
||||
(next_row, pos.1)
|
||||
}
|
||||
};
|
||||
|
||||
if self.grid[next_pos.0][next_pos.1].unwrap() {
|
||||
// We're about to hit a wall
|
||||
if status.wrapped_around() {
|
||||
return (pose, MoveResult::BlockedByWrapping);
|
||||
} else {
|
||||
return (pose, MoveResult::Blocked);
|
||||
}
|
||||
}
|
||||
*pos = next_pos;
|
||||
}
|
||||
|
||||
(pose, status)
|
||||
}
|
||||
|
||||
fn exec_instruction(
|
||||
&self,
|
||||
from: &Self::Pose,
|
||||
inst: &Self::NavigationInstruction,
|
||||
) -> (Self::Pose, MoveResult) {
|
||||
match inst {
|
||||
PassInstr::Move(n) => self.forward(from, *n),
|
||||
PassInstr::Turn(dir) => (
|
||||
Pose {
|
||||
pos: from.pos,
|
||||
orientation: match dir {
|
||||
TurnDir::CW => from.orientation.cw(),
|
||||
TurnDir::CCW => from.orientation.ccw(),
|
||||
},
|
||||
},
|
||||
MoveResult::Success,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_pose(&self) -> Self::Pose {
|
||||
let col = self.grid[0]
|
||||
.iter()
|
||||
.position(|t| if let Some(occ) = t { !occ } else { false })
|
||||
.unwrap();
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum CubeSide {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
Fore,
|
||||
Back,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CubeGrid {
|
||||
// The side grids don't have to be sparse, but we don't have a dense
|
||||
// implementation...
|
||||
pub sides: HashMap<CubeSide, SparseGrid>,
|
||||
|
||||
pub side_height: usize,
|
||||
pub side_width: usize,
|
||||
}
|
||||
|
||||
impl CubeGrid {
|
||||
pub fn from(sparse: &SparseGrid, pattern: &HashMap<CubeSide, (usize, usize)>) -> CubeGrid {
|
||||
let fold_width = pattern.values().map(|(_, col)| col).max().unwrap() + 1;
|
||||
let fold_height = pattern.values().map(|(row, _)| row).max().unwrap() + 1;
|
||||
let side_width = sparse.width / fold_width;
|
||||
assert!(sparse.width % fold_width == 0);
|
||||
let side_height = sparse.height / fold_height;
|
||||
assert!(sparse.height % fold_height == 0);
|
||||
|
||||
let mut sides = HashMap::new();
|
||||
for (side, (fold_row, fold_col)) in pattern {
|
||||
let mut grid = Vec::new();
|
||||
for r in 0..side_height {
|
||||
let mut row = Vec::new();
|
||||
for c in 0..side_width {
|
||||
let tile = sparse.grid[fold_row * side_height + r][fold_col * side_width + c];
|
||||
assert!(tile.is_some());
|
||||
row.push(tile);
|
||||
}
|
||||
grid.push(row);
|
||||
}
|
||||
let grid = SparseGrid {
|
||||
grid,
|
||||
height: side_height,
|
||||
width: side_width,
|
||||
row_bounds: vec![(0, side_width - 1); side_height],
|
||||
col_bounds: vec![(0, side_height - 1); side_width],
|
||||
};
|
||||
assert!(sides.insert(*side, grid).is_none());
|
||||
}
|
||||
|
||||
CubeGrid {
|
||||
sides,
|
||||
side_height,
|
||||
side_width,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(&self, pose: &(CubeSide, Pose)) {
|
||||
for (side, grid) in &self.sides {
|
||||
if pose.0 != *side {
|
||||
continue;
|
||||
}
|
||||
println!("{:?}:", side);
|
||||
grid.print(Some(&pose.1));
|
||||
println!("");
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_around(&self, side: CubeSide, pose: &Pose) -> (CubeSide, Pose) {
|
||||
// FIXME: This is hardcoded for the folding pattern of the actual input.
|
||||
// It won't work for the example input.
|
||||
assert_eq!(self.side_height, self.side_width);
|
||||
let dir = pose.orientation;
|
||||
let pos = pose.pos;
|
||||
let row = pos.0;
|
||||
let last = self.side_height - 1;
|
||||
let row_inv = last - row;
|
||||
let col = pos.1;
|
||||
match side {
|
||||
CubeSide::Top => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Right,
|
||||
Pose {
|
||||
pos: (row, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Fore,
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Left,
|
||||
Pose {
|
||||
pos: (row_inv, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Back,
|
||||
Pose {
|
||||
pos: (col, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
},
|
||||
CubeSide::Bottom => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Right,
|
||||
Pose {
|
||||
pos: (row_inv, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Back,
|
||||
Pose {
|
||||
pos: (col, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Left,
|
||||
Pose {
|
||||
pos: (row, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Fore,
|
||||
Pose {
|
||||
pos: (last, col),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
},
|
||||
CubeSide::Left => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Bottom,
|
||||
Pose {
|
||||
pos: (row, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Back,
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Top,
|
||||
Pose {
|
||||
pos: (row_inv, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Fore,
|
||||
Pose {
|
||||
pos: (col, 0),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
),
|
||||
},
|
||||
CubeSide::Right => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Bottom,
|
||||
Pose {
|
||||
pos: (row_inv, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Fore,
|
||||
Pose {
|
||||
pos: (col, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Top,
|
||||
Pose {
|
||||
pos: (row, last),
|
||||
orientation: Dir::Left,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Back,
|
||||
Pose {
|
||||
pos: (last, col),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
},
|
||||
CubeSide::Fore => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Right,
|
||||
Pose {
|
||||
pos: (last, row),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Bottom,
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Left,
|
||||
Pose {
|
||||
pos: (0, row),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Top,
|
||||
Pose {
|
||||
pos: (last, col),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
},
|
||||
CubeSide::Back => match dir {
|
||||
Dir::Right => (
|
||||
CubeSide::Bottom,
|
||||
Pose {
|
||||
pos: (last, row),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
Dir::Down => (
|
||||
CubeSide::Right,
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Left => (
|
||||
CubeSide::Top,
|
||||
Pose {
|
||||
pos: (0, row),
|
||||
orientation: Dir::Down,
|
||||
},
|
||||
),
|
||||
Dir::Up => (
|
||||
CubeSide::Left,
|
||||
Pose {
|
||||
pos: (last, col),
|
||||
orientation: Dir::Up,
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Navigable for CubeGrid {
|
||||
type Pose = (CubeSide, Pose);
|
||||
type NavigationInstruction = PassInstr;
|
||||
|
||||
fn forward(
|
||||
&self,
|
||||
(from_side, from_pose): &Self::Pose,
|
||||
steps: usize,
|
||||
) -> (Self::Pose, MoveResult) {
|
||||
let mut side = from_side.clone();
|
||||
let mut pose = from_pose.clone();
|
||||
let mut status = MoveResult::Success;
|
||||
for _ in 0..steps {
|
||||
let (next_pose, res) = self.sides[&side].forward(&pose, 1);
|
||||
if res.wrapped_around() {
|
||||
let (next_side, next_pose) = self.wrap_around(side, &pose);
|
||||
if self.sides[&next_side].grid[next_pose.pos.0][next_pose.pos.1] == Some(true) {
|
||||
// Can't wrap around, we're blocked
|
||||
return ((side, pose), MoveResult::BlockedByWrapping);
|
||||
}
|
||||
side = next_side;
|
||||
pose = next_pose;
|
||||
status = MoveResult::WrappedAround;
|
||||
} else if res.was_blocked() {
|
||||
return ((side, pose), MoveResult::Blocked);
|
||||
} else {
|
||||
pose = next_pose;
|
||||
}
|
||||
}
|
||||
|
||||
((side, pose), status)
|
||||
}
|
||||
|
||||
fn exec_instruction(
|
||||
&self,
|
||||
from: &Self::Pose,
|
||||
inst: &Self::NavigationInstruction,
|
||||
) -> (Self::Pose, MoveResult) {
|
||||
match inst {
|
||||
PassInstr::Move(n) => self.forward(from, *n),
|
||||
PassInstr::Turn(dir) => (
|
||||
(
|
||||
from.0,
|
||||
Pose {
|
||||
pos: from.1.pos,
|
||||
orientation: match dir {
|
||||
TurnDir::CW => from.1.orientation.cw(),
|
||||
TurnDir::CCW => from.1.orientation.ccw(),
|
||||
},
|
||||
},
|
||||
),
|
||||
MoveResult::Success,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_pose(&self) -> Self::Pose {
|
||||
let col = self.sides[&CubeSide::Top].grid[0]
|
||||
.iter()
|
||||
.position(|t| *t == Some(false))
|
||||
.unwrap();
|
||||
(
|
||||
CubeSide::Top,
|
||||
Pose {
|
||||
pos: (0, col),
|
||||
orientation: Dir::Right,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_map_and_path(input: &str) -> (SparseGrid, Vec<PassInstr>) {
|
||||
let lines = input.lines().collect_vec();
|
||||
|
||||
let mut grid: Vec<Vec<Option<bool>>> = Vec::new();
|
||||
let mut width = 0;
|
||||
for line in &lines[..lines.len() - 2] {
|
||||
width = width.max(line.len());
|
||||
let grid_line = line
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
' ' => None,
|
||||
'.' => Some(false),
|
||||
'#' => Some(true),
|
||||
_ => panic!("Unknown grid character {}", c),
|
||||
})
|
||||
.collect();
|
||||
grid.push(grid_line);
|
||||
}
|
||||
|
||||
let height = grid.len();
|
||||
let mut row_bounds = vec![(0, 0); height];
|
||||
for row in 0..height {
|
||||
let row_width = grid[row].len();
|
||||
if row_width < width {
|
||||
grid[row].extend((row_width..width).map(|_| None));
|
||||
}
|
||||
let lower = grid[row].iter().position(Option::is_some).unwrap();
|
||||
let upper = width - 1 - grid[row].iter().rev().position(Option::is_some).unwrap();
|
||||
row_bounds[row] = (lower, upper);
|
||||
}
|
||||
|
||||
let mut col_bounds = vec![(0, 0); width];
|
||||
for col in 0..width {
|
||||
let col_it = (0..height).map(|row| grid[row][col]);
|
||||
let lower = col_it.clone().position(|t| t.is_some()).unwrap();
|
||||
let upper = height - 1 - col_it.rev().position(|t| t.is_some()).unwrap();
|
||||
col_bounds[col] = (lower, upper);
|
||||
}
|
||||
|
||||
let grid = SparseGrid {
|
||||
grid,
|
||||
height,
|
||||
width,
|
||||
row_bounds,
|
||||
col_bounds,
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let mut inst_begin = 0;
|
||||
let inst_line = lines[lines.len() - 1].as_bytes();
|
||||
while inst_begin < inst_line.len() {
|
||||
let mut i = inst_begin;
|
||||
let c = inst_line[i];
|
||||
if c.is_ascii_digit() {
|
||||
while i < inst_line.len() && inst_line[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
let dist = std::str::from_utf8(&inst_line[inst_begin..i])
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
instructions.push(PassInstr::Move(dist));
|
||||
inst_begin = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = match c {
|
||||
b'L' => TurnDir::CCW,
|
||||
b'R' => TurnDir::CW,
|
||||
_ => panic!("Unknown turn direction {}", c),
|
||||
};
|
||||
instructions.push(PassInstr::Turn(dir));
|
||||
inst_begin += 1;
|
||||
}
|
||||
|
||||
(grid, instructions)
|
||||
}
|
||||
112
src/day23.rs
Normal file
112
src/day23.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::util::{self, SignedCoord};
|
||||
|
||||
pub fn parse_map(input: &str) -> HashSet<SignedCoord> {
|
||||
let mut result = HashSet::new();
|
||||
for (y, row) in input.lines().enumerate() {
|
||||
for (x, c) in row.chars().enumerate() {
|
||||
if c == '#' {
|
||||
result.insert((y as isize, x as isize));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
const NUM_DIRS: usize = 4;
|
||||
|
||||
pub fn do_round(elves: &mut HashSet<SignedCoord>, it: usize) -> bool {
|
||||
let mut proposals: HashMap<SignedCoord, Vec<SignedCoord>> = HashMap::new();
|
||||
|
||||
let mut elves_too_close = false;
|
||||
for elf in elves.iter() {
|
||||
if noone_adjacent(elves, elf) {
|
||||
continue;
|
||||
}
|
||||
elves_too_close = true;
|
||||
for offset in 0..NUM_DIRS {
|
||||
let dir = (it + offset) % NUM_DIRS;
|
||||
if let Some(new_loc) = propose(elves, elf, dir) {
|
||||
proposals.entry(new_loc).or_insert_with(Vec::new).push(*elf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (target, interested) in proposals {
|
||||
if interested.len() == 1 {
|
||||
elves.remove(&interested[0]);
|
||||
elves.insert(target);
|
||||
}
|
||||
}
|
||||
|
||||
elves_too_close
|
||||
}
|
||||
|
||||
fn noone_adjacent(elves: &HashSet<SignedCoord>, elf: &SignedCoord) -> bool {
|
||||
!(elves.contains(&(elf.0, elf.1 + 1))
|
||||
|| elves.contains(&(elf.0, elf.1 - 1))
|
||||
|| elves.contains(&(elf.0 + 1, elf.1))
|
||||
|| elves.contains(&(elf.0 + 1, elf.1 + 1))
|
||||
|| elves.contains(&(elf.0 + 1, elf.1 - 1))
|
||||
|| elves.contains(&(elf.0 - 1, elf.1))
|
||||
|| elves.contains(&(elf.0 - 1, elf.1 + 1))
|
||||
|| elves.contains(&(elf.0 - 1, elf.1 - 1)))
|
||||
}
|
||||
|
||||
fn propose(elves: &HashSet<SignedCoord>, elf: &SignedCoord, dir: usize) -> Option<SignedCoord> {
|
||||
let (moved, other) = match dir {
|
||||
0 => (
|
||||
(elf.0 - 1, elf.1),
|
||||
((elf.0 - 1, elf.1 - 1), (elf.0 - 1, elf.1 + 1)),
|
||||
), // N
|
||||
1 => (
|
||||
(elf.0 + 1, elf.1),
|
||||
((elf.0 + 1, elf.1 - 1), (elf.0 + 1, elf.1 + 1)),
|
||||
), // S
|
||||
2 => (
|
||||
(elf.0, elf.1 - 1),
|
||||
((elf.0 - 1, elf.1 - 1), (elf.0 + 1, elf.1 - 1)),
|
||||
), // E
|
||||
3 => (
|
||||
(elf.0, elf.1 + 1),
|
||||
((elf.0 - 1, elf.1 + 1), (elf.0 + 1, elf.1 + 1)),
|
||||
), // W
|
||||
_ => panic!("Unknown direction {}", dir),
|
||||
};
|
||||
if elves.contains(&moved) || elves.contains(&other.0) || elves.contains(&other.1) {
|
||||
None
|
||||
} else {
|
||||
Some(moved)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_map(elves: &HashSet<SignedCoord>) {
|
||||
let (y_min, y_max) = util::minmax_unwrap(&elves.iter().map(|c| c.0).minmax());
|
||||
let (x_min, x_max) = util::minmax_unwrap(&elves.iter().map(|c| c.1).minmax());
|
||||
|
||||
println!("({}, {})", y_min, x_min);
|
||||
for y in y_min..(y_max + 1) {
|
||||
for x in x_min..(x_max + 1) {
|
||||
if elves.contains(&(y, x)) {
|
||||
print!("#");
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
|
||||
pub fn empty_ground(elves: &HashSet<SignedCoord>) -> usize {
|
||||
let (y_min, y_max) = util::minmax_unwrap(&elves.iter().map(|c| c.0).minmax());
|
||||
let (x_min, x_max) = util::minmax_unwrap(&elves.iter().map(|c| c.1).minmax());
|
||||
let y_dim = (y_max + 1 - y_min) as usize;
|
||||
let x_dim = (x_max + 1 - x_min) as usize;
|
||||
|
||||
y_dim * x_dim - elves.len()
|
||||
}
|
||||
188
src/day24.rs
Normal file
188
src/day24.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::util::{Coord, Dir};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Tile {
|
||||
Empty,
|
||||
Wall,
|
||||
Blizzards(Vec<Dir>),
|
||||
}
|
||||
|
||||
/// Returns (map, start, end)
|
||||
pub fn parse_map(input: &str) -> (Vec<Vec<Tile>>, Coord, Coord) {
|
||||
let mut map = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
let mut row = Vec::new();
|
||||
|
||||
for c in line.chars() {
|
||||
row.push(match c {
|
||||
'.' => Tile::Empty,
|
||||
'#' => Tile::Wall,
|
||||
_ => Tile::Blizzards(vec![Dir::from_char(c).unwrap()]),
|
||||
});
|
||||
}
|
||||
|
||||
map.push(row);
|
||||
}
|
||||
|
||||
assert!(map.len() >= 2);
|
||||
let last_row = map.len() - 1;
|
||||
|
||||
let start = map[0]
|
||||
.iter()
|
||||
.position(|t| matches!(t, Tile::Empty))
|
||||
.unwrap();
|
||||
let end = map[last_row]
|
||||
.iter()
|
||||
.position(|t| matches!(t, Tile::Empty))
|
||||
.unwrap();
|
||||
|
||||
(map, (0, start), (last_row, end))
|
||||
}
|
||||
|
||||
pub fn print_map(map: &Vec<Vec<Tile>>, expedition: &Coord) {
|
||||
for (y, row) in map.iter().enumerate() {
|
||||
for (x, tile) in row.iter().enumerate() {
|
||||
if *expedition == (y, x) {
|
||||
print!("E");
|
||||
} else {
|
||||
match tile {
|
||||
Tile::Empty => print!("."),
|
||||
Tile::Wall => print!("#"),
|
||||
Tile::Blizzards(blizzards) => {
|
||||
if blizzards.len() > 1 {
|
||||
print!("{}", blizzards.len());
|
||||
} else {
|
||||
print!("{}", blizzards[0].to_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_map(map: &Vec<Vec<Tile>>) -> Vec<Vec<Tile>> {
|
||||
assert!(map.len() > 0);
|
||||
// Outermost rows/columns are walls
|
||||
let min_y = 1;
|
||||
let max_y = map.len() - 2;
|
||||
let min_x = 1;
|
||||
let max_x = map[0].len() - 2;
|
||||
|
||||
// Start with a map without blizzards
|
||||
let mut result: Vec<Vec<Tile>> = map
|
||||
.iter()
|
||||
.map(|r| {
|
||||
r.iter()
|
||||
.map(|t| {
|
||||
if matches!(t, Tile::Blizzards(_)) {
|
||||
Tile::Empty
|
||||
} else {
|
||||
t.clone()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
for y in 0..map.len() {
|
||||
for x in 0..map[0].len() {
|
||||
if let Tile::Blizzards(blizzards) = &map[y][x] {
|
||||
for blizzard_dir in blizzards {
|
||||
let new_tile = match blizzard_dir {
|
||||
Dir::Right => {
|
||||
if x == max_x {
|
||||
(y, min_x)
|
||||
} else {
|
||||
(y, x + 1)
|
||||
}
|
||||
}
|
||||
Dir::Down => {
|
||||
if y == max_y {
|
||||
(min_y, x)
|
||||
} else {
|
||||
(y + 1, x)
|
||||
}
|
||||
}
|
||||
Dir::Left => {
|
||||
if x == min_x {
|
||||
(y, max_x)
|
||||
} else {
|
||||
(y, x - 1)
|
||||
}
|
||||
}
|
||||
Dir::Up => {
|
||||
if y == min_y {
|
||||
(max_y, x)
|
||||
} else {
|
||||
(y - 1, x)
|
||||
}
|
||||
}
|
||||
};
|
||||
let new_tile = &mut result[new_tile.0][new_tile.1];
|
||||
if let Tile::Blizzards(blizzards) = new_tile {
|
||||
blizzards.push(*blizzard_dir);
|
||||
} else {
|
||||
assert!(matches!(new_tile, Tile::Empty));
|
||||
*new_tile = Tile::Blizzards(vec![*blizzard_dir]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns length of the shortest path
|
||||
pub fn find_path(
|
||||
initial: &Vec<Vec<Tile>>,
|
||||
start: &Coord,
|
||||
target: &Coord,
|
||||
) -> (usize, Vec<Vec<Tile>>) {
|
||||
let height = initial.len();
|
||||
assert!(height > 0);
|
||||
let width = initial[0].len();
|
||||
|
||||
let mut map = initial.clone();
|
||||
let mut to_check = HashSet::new();
|
||||
to_check.insert(*start);
|
||||
let mut rounds = 0;
|
||||
|
||||
loop {
|
||||
map = next_map(&map);
|
||||
rounds += 1;
|
||||
let mut check_next = HashSet::new();
|
||||
|
||||
for pos in to_check {
|
||||
let mut reachable = vec![pos];
|
||||
if pos.0 != 0 {
|
||||
reachable.push((pos.0 - 1, pos.1));
|
||||
}
|
||||
if pos.0 != height - 1 {
|
||||
reachable.push((pos.0 + 1, pos.1));
|
||||
}
|
||||
if pos.1 != 0 {
|
||||
reachable.push((pos.0, pos.1 - 1));
|
||||
}
|
||||
if pos.1 != width - 1 {
|
||||
reachable.push((pos.0, pos.1 + 1));
|
||||
}
|
||||
|
||||
for p in reachable {
|
||||
if p == *target {
|
||||
return (rounds, map);
|
||||
}
|
||||
if matches!(map[p.0][p.1], Tile::Empty) {
|
||||
check_next.insert(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to_check = check_next;
|
||||
}
|
||||
}
|
||||
83
src/day25.rs
Normal file
83
src/day25.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub fn parse_snafus(input: &str) -> Vec<i64> {
|
||||
input.lines().map(snafu_to_num).collect()
|
||||
}
|
||||
|
||||
pub fn snafu_to_num(snafu: &str) -> i64 {
|
||||
let mut pow = 1;
|
||||
let mut result = 0;
|
||||
for c in snafu.chars().rev() {
|
||||
let digit = match c {
|
||||
'=' => -2,
|
||||
'-' => -1,
|
||||
'0' => 0,
|
||||
'1' => 1,
|
||||
'2' => 2,
|
||||
_ => panic!("Unknown digit {}", c),
|
||||
};
|
||||
result += pow * digit;
|
||||
pow *= 5;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn num_to_snafu(num: i64) -> String {
|
||||
assert!(num > 0);
|
||||
|
||||
let exp = (num as f64).log(5.0).floor() as u32 + 1;
|
||||
let mut result = VecDeque::with_capacity((exp as usize) + 1);
|
||||
let mut pow = 5_i64.pow(exp);
|
||||
let mut rem = num;
|
||||
|
||||
while pow >= 1 {
|
||||
let digit = rem / pow;
|
||||
rem %= pow;
|
||||
result.push_back(digit);
|
||||
pow /= 5;
|
||||
}
|
||||
|
||||
for i in (0..result.len()).rev() {
|
||||
if result[i] > 2 {
|
||||
result[i] -= 5;
|
||||
result[i - 1] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
.iter()
|
||||
.skip_while(|d| **d == 0)
|
||||
.map(|d| match d {
|
||||
-2 => '=',
|
||||
-1 => '-',
|
||||
0 => '0',
|
||||
1 => '1',
|
||||
2 => '2',
|
||||
_ => panic!("Unknown digit {}", d),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn num_to_snafu_examples() {
|
||||
use crate::day25::num_to_snafu;
|
||||
assert_eq!(num_to_snafu(1), "1");
|
||||
assert_eq!(num_to_snafu(2), "2");
|
||||
assert_eq!(num_to_snafu(3), "1=");
|
||||
assert_eq!(num_to_snafu(4), "1-");
|
||||
assert_eq!(num_to_snafu(5), "10");
|
||||
assert_eq!(num_to_snafu(6), "11");
|
||||
assert_eq!(num_to_snafu(7), "12");
|
||||
assert_eq!(num_to_snafu(8), "2=");
|
||||
assert_eq!(num_to_snafu(9), "2-");
|
||||
assert_eq!(num_to_snafu(10), "20");
|
||||
assert_eq!(num_to_snafu(15), "1=0");
|
||||
assert_eq!(num_to_snafu(20), "1-0");
|
||||
assert_eq!(num_to_snafu(2022), "1=11-2");
|
||||
assert_eq!(num_to_snafu(12345), "1-0---0");
|
||||
assert_eq!(num_to_snafu(314159265), "1121-1110-1=0");
|
||||
assert_eq!(num_to_snafu(4890), "2=-1=0");
|
||||
}
|
||||
}
|
||||
97
src/day5.rs
Normal file
97
src/day5.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Instruction {
|
||||
pub num: usize,
|
||||
pub from: usize,
|
||||
pub to: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Arrangement {
|
||||
pub stacks: Vec<VecDeque<char>>,
|
||||
pub instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
fn parse_stack(line: &str) -> impl Iterator<Item = Option<char>> + '_ {
|
||||
line.as_bytes().chunks(4).map(|b| -> Option<char> {
|
||||
if *b.get(0).unwrap() as char == '[' {
|
||||
Some(*b.get(1).unwrap() as char)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_instruction(line: &str) -> Instruction {
|
||||
let re = Regex::new(r"move (\d+) from (\d+) to (\d+)").unwrap();
|
||||
let captures = re.captures(line).unwrap();
|
||||
Instruction {
|
||||
num: captures.get(1).unwrap().as_str().parse().unwrap(),
|
||||
from: captures.get(2).unwrap().as_str().parse::<usize>().unwrap() - 1,
|
||||
to: captures.get(3).unwrap().as_str().parse::<usize>().unwrap() - 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum ParseState {
|
||||
Stacks,
|
||||
StackNumbers,
|
||||
Instructions,
|
||||
}
|
||||
|
||||
pub fn parse_arrangement(input: &String) -> Arrangement {
|
||||
let lines: Vec<_> = input.lines().collect();
|
||||
let first_line = lines.get(0).unwrap();
|
||||
let length = first_line.len();
|
||||
// Each stack is 3 characters long, all but the last have a space following
|
||||
// them.
|
||||
assert!(length % 4 == 3);
|
||||
let n_stacks = (length - 3) / 4 + 1;
|
||||
let mut stacks: Vec<VecDeque<char>> = Vec::with_capacity(n_stacks);
|
||||
for _ in 0..n_stacks {
|
||||
stacks.push(VecDeque::new());
|
||||
}
|
||||
let mut instructions = Vec::new();
|
||||
|
||||
let mut state = ParseState::Stacks;
|
||||
|
||||
for line in lines.iter() {
|
||||
state = match state {
|
||||
ParseState::Stacks => {
|
||||
if line.contains('[') {
|
||||
ParseState::Stacks
|
||||
} else {
|
||||
ParseState::StackNumbers
|
||||
}
|
||||
}
|
||||
ParseState::StackNumbers => {
|
||||
if line.starts_with("move") {
|
||||
ParseState::Instructions
|
||||
} else {
|
||||
ParseState::StackNumbers
|
||||
}
|
||||
}
|
||||
ParseState::Instructions => ParseState::Instructions,
|
||||
};
|
||||
|
||||
match state {
|
||||
ParseState::Stacks => {
|
||||
for (i, box_) in parse_stack(line).enumerate() {
|
||||
if let Some(c) = box_ {
|
||||
stacks.get_mut(i).unwrap().push_front(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseState::StackNumbers => { /* Nothing to do here */ }
|
||||
ParseState::Instructions => instructions.push(parse_instruction(line)),
|
||||
}
|
||||
}
|
||||
|
||||
Arrangement {
|
||||
stacks,
|
||||
instructions,
|
||||
}
|
||||
}
|
||||
19
src/day6.rs
Normal file
19
src/day6.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
|
||||
pub fn parse_datastream(stream: &str, n_distinct: usize) -> usize {
|
||||
let mut chars: VecDeque<char> = VecDeque::new();
|
||||
for (i, char) in stream.chars().enumerate() {
|
||||
if i >= n_distinct {
|
||||
chars.pop_front();
|
||||
}
|
||||
chars.push_back(char);
|
||||
if i >= n_distinct - 1 {
|
||||
let char_set: HashSet<char> = chars.iter().map(char::clone).collect();
|
||||
if char_set.len() == n_distinct {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Couldn't find a start marker!")
|
||||
}
|
||||
158
src/day7.rs
Normal file
158
src/day7.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, vec};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Node {
|
||||
File {
|
||||
name: String,
|
||||
size: usize,
|
||||
},
|
||||
Dir {
|
||||
name: String,
|
||||
children: HashMap<String, Rc<RefCell<Node>>>,
|
||||
parent: Option<Rc<RefCell<Node>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn print(&self, level: usize) {
|
||||
match self {
|
||||
Node::File { name, size } => println!("{0:1$}| {2}\t{3}", "", level * 2, name, size),
|
||||
Node::Dir { name, children, .. } => {
|
||||
println!("{0:1$}| {2}", "", level * 2, name);
|
||||
println!("{0:1$} \\", "", level * 2);
|
||||
for (_, child) in children {
|
||||
(**child).borrow().print(level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Node::File { size, .. } => *size,
|
||||
Node::Dir { children, .. } => children.iter().map(|(_, c)| (**c).borrow().size()).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_children(&self) -> Vec<Rc<RefCell<Node>>> {
|
||||
match self {
|
||||
Node::File { .. } => vec![],
|
||||
Node::Dir { children, .. } => {
|
||||
let mut result: Vec<Rc<RefCell<Node>>> = children.values().map(Rc::clone).collect();
|
||||
for (_, child) in children {
|
||||
result.append(&mut (**child).borrow().all_children());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ParseState {
|
||||
Command,
|
||||
ListOutput,
|
||||
}
|
||||
|
||||
pub fn parse_cmdline(input: &String) -> Rc<RefCell<Node>> {
|
||||
let lines = input.lines();
|
||||
let mut state = ParseState::Command;
|
||||
let root = Rc::new(RefCell::new(Node::Dir {
|
||||
name: "/".to_owned(),
|
||||
children: HashMap::new(),
|
||||
parent: None,
|
||||
}));
|
||||
let mut current_node = root.clone();
|
||||
|
||||
for line in lines {
|
||||
if line.starts_with("$") {
|
||||
state = ParseState::Command;
|
||||
}
|
||||
|
||||
match state {
|
||||
ParseState::Command => {
|
||||
let cmd = parse_command(line);
|
||||
match cmd {
|
||||
Command::ChangeDirRoot => current_node = root.clone(),
|
||||
Command::ChangeDirParent => {
|
||||
let next_node = if let Node::Dir { parent, .. } = &*(*current_node).borrow()
|
||||
{
|
||||
match parent {
|
||||
Some(p) => p.clone(),
|
||||
None => root.clone(),
|
||||
}
|
||||
// parent.unwrap_or(root.clone()).clone()
|
||||
} else {
|
||||
panic!("Current node is a file!?");
|
||||
};
|
||||
current_node = next_node;
|
||||
}
|
||||
Command::ChangeDirChild(name) => {
|
||||
let next_node =
|
||||
if let Node::Dir { children, .. } = &*(*current_node).borrow() {
|
||||
children.get(name.as_str()).unwrap().clone()
|
||||
} else {
|
||||
panic!("Current node is a file!?");
|
||||
};
|
||||
current_node = next_node;
|
||||
}
|
||||
Command::List => state = ParseState::ListOutput,
|
||||
}
|
||||
}
|
||||
ParseState::ListOutput => {
|
||||
let node = parse_list_output(line, ¤t_node);
|
||||
if let Node::Dir { children, .. } = &mut *(*current_node).borrow_mut() {
|
||||
let n = match &node {
|
||||
Node::File { name, .. } => name,
|
||||
Node::Dir { name, .. } => name,
|
||||
};
|
||||
children.insert(n.clone(), Rc::new(RefCell::new(node)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
enum Command {
|
||||
ChangeDirRoot,
|
||||
ChangeDirParent,
|
||||
ChangeDirChild(String),
|
||||
List,
|
||||
}
|
||||
|
||||
fn parse_command(line: &str) -> Command {
|
||||
let re = Regex::new(r"^\$ (cd|ls)(?: (.*))?$").unwrap();
|
||||
let captures = re.captures(line).unwrap();
|
||||
if captures.get(1).unwrap().as_str() == "ls" {
|
||||
assert!(captures.get(2) == None);
|
||||
Command::List
|
||||
} else {
|
||||
let dir = captures.get(2).unwrap().as_str();
|
||||
if dir == "/" {
|
||||
Command::ChangeDirRoot
|
||||
} else if dir == ".." {
|
||||
Command::ChangeDirParent
|
||||
} else {
|
||||
Command::ChangeDirChild(dir.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_list_output(line: &str, parent: &Rc<RefCell<Node>>) -> Node {
|
||||
let (first, second) = line.split_once(' ').unwrap();
|
||||
if first == "dir" {
|
||||
Node::Dir {
|
||||
name: second.to_owned(),
|
||||
children: HashMap::new(),
|
||||
parent: Some(parent.clone()),
|
||||
}
|
||||
} else {
|
||||
Node::File {
|
||||
name: second.to_owned(),
|
||||
size: first.parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/day8.rs
Normal file
92
src/day8.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub type Grid = Vec<Vec<u32>>;
|
||||
|
||||
pub fn parse_grid(input: &String) -> Grid {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
result.push(line.chars().map(|c| c.to_digit(10).unwrap()).collect());
|
||||
}
|
||||
|
||||
let row_lens: Vec<usize> = result.iter().map(Vec::len).collect();
|
||||
assert_eq!(row_lens.iter().max(), row_lens.iter().min());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub type Coordinate = (usize, usize);
|
||||
|
||||
pub fn find_visible(grid: &Grid) -> HashSet<Coordinate> {
|
||||
let mut result = HashSet::new();
|
||||
|
||||
let rows = grid.len();
|
||||
assert!(rows > 0);
|
||||
let cols = grid.get(0).unwrap().len();
|
||||
assert!(cols > 0);
|
||||
|
||||
for row in 0..rows {
|
||||
let mut left_indices = (0..cols).map(|c| (row, c));
|
||||
let mut right_indices = left_indices.clone().rev();
|
||||
result.extend(check_indices(&grid, &mut left_indices));
|
||||
result.extend(check_indices(&grid, &mut right_indices));
|
||||
}
|
||||
for col in 0..cols {
|
||||
let mut top_indices = (0..rows).map(|r| (r, col));
|
||||
let mut bottom_indices = top_indices.clone().rev();
|
||||
result.extend(check_indices(&grid, &mut top_indices));
|
||||
result.extend(check_indices(&grid, &mut bottom_indices));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn check_indices(grid: &Grid, coords: &mut dyn Iterator<Item = Coordinate>) -> Vec<Coordinate> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut max_height: i64 = -1;
|
||||
for (row, col) in coords {
|
||||
let height = *grid.get(row).unwrap().get(col).unwrap() as i64;
|
||||
if height > max_height {
|
||||
result.push((row, col));
|
||||
max_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn calc_scenic_score(grid: &Grid, (row, col): Coordinate) -> usize {
|
||||
let rows = grid.len();
|
||||
assert!(rows > 0);
|
||||
let cols = grid.get(0).unwrap().len();
|
||||
assert!(cols > 0);
|
||||
|
||||
let height = *grid.get(row).unwrap().get(col).unwrap();
|
||||
let mut left = (0..col).map(|c| (row, c)).rev();
|
||||
let mut right = (col + 1..cols).map(|c| (row, c));
|
||||
let mut up = (0..row).map(|r| (r, col)).rev();
|
||||
let mut down = (row + 1..rows).map(|r| (r, col));
|
||||
let mut directions: Vec<&mut dyn Iterator<Item = Coordinate>> =
|
||||
vec![&mut left, &mut right, &mut up, &mut down];
|
||||
directions
|
||||
.iter_mut()
|
||||
.map(|d| calc_viewing_distance(grid, height, *d))
|
||||
.product()
|
||||
}
|
||||
|
||||
fn calc_viewing_distance(
|
||||
grid: &Grid,
|
||||
start_height: u32,
|
||||
coords: &mut dyn Iterator<Item = Coordinate>,
|
||||
) -> usize {
|
||||
let mut result = 0;
|
||||
for (row, col) in coords {
|
||||
result += 1;
|
||||
let height = *grid.get(row).unwrap().get(col).unwrap();
|
||||
if height >= start_height {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
129
src/day9.rs
Normal file
129
src/day9.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::util::Dir;
|
||||
|
||||
pub type Motion = (Dir, usize);
|
||||
|
||||
pub fn parse_motions(input: &String) -> Vec<Motion> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for line in input.lines() {
|
||||
let (d, n) = line.split_once(' ').unwrap();
|
||||
let dir = match d {
|
||||
"L" => Dir::Left,
|
||||
"R" => Dir::Right,
|
||||
"U" => Dir::Up,
|
||||
"D" => Dir::Down,
|
||||
_ => panic!("Unknown direction {}", d),
|
||||
};
|
||||
let n = n.parse().unwrap();
|
||||
result.push((dir, n));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub type Coord = (i64, i64);
|
||||
|
||||
pub struct State {
|
||||
pub head: Coord,
|
||||
pub knots: Vec<Coord>,
|
||||
pub visited: HashSet<Coord>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(n_knots: usize) -> State {
|
||||
let mut s = State {
|
||||
head: (0, 0),
|
||||
knots: vec![(0, 0); n_knots],
|
||||
visited: HashSet::new(),
|
||||
};
|
||||
s.visited.insert((0, 0));
|
||||
s
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
let knots_x: Vec<i64> = self.knots.iter().map(|v| v.0).collect();
|
||||
let visited_x: Vec<i64> = self.visited.iter().map(|v| v.0).collect();
|
||||
let knots_y: Vec<i64> = self.knots.iter().map(|v| v.1).collect();
|
||||
let visited_y: Vec<i64> = self.visited.iter().map(|v| v.1).collect();
|
||||
let min_x = 0
|
||||
.min(self.head.0)
|
||||
.min(*knots_x.iter().min().unwrap())
|
||||
.min(*visited_x.iter().min().unwrap());
|
||||
let max_x = 0
|
||||
.max(self.head.0)
|
||||
.max(*knots_x.iter().max().unwrap())
|
||||
.max(*visited_x.iter().max().unwrap());
|
||||
let min_y = 0
|
||||
.min(self.head.1)
|
||||
.min(*knots_y.iter().min().unwrap())
|
||||
.min(*visited_y.iter().min().unwrap());
|
||||
let max_y = 0
|
||||
.max(self.head.1)
|
||||
.max(*knots_y.iter().max().unwrap())
|
||||
.max(*visited_y.iter().max().unwrap());
|
||||
|
||||
for y in min_y..max_y + 1 {
|
||||
'x: for x in min_x..max_x + 1 {
|
||||
let c = (x, y);
|
||||
if self.head == c {
|
||||
print!("H");
|
||||
} else {
|
||||
for (i, knot) in self.knots.iter().enumerate() {
|
||||
if c == *knot {
|
||||
print!("{}", i + 1);
|
||||
continue 'x;
|
||||
}
|
||||
}
|
||||
if self.visited.contains(&c) {
|
||||
print!("#");
|
||||
} else if c == (0, 0) {
|
||||
print!("s");
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_motion(state: &mut State, motion: &Motion) {
|
||||
for _ in 0..motion.1 {
|
||||
let head = &mut state.head;
|
||||
*head = match motion.0 {
|
||||
Dir::Left => (head.0 - 1, head.1),
|
||||
Dir::Right => (head.0 + 1, head.1),
|
||||
Dir::Up => (head.0, head.1 - 1),
|
||||
Dir::Down => (head.0, head.1 + 1),
|
||||
};
|
||||
tail_catchup(state);
|
||||
state.visited.insert(*state.knots.last().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tail_catchup(state: &mut State) {
|
||||
let mut prev = state.head.clone();
|
||||
let knots = &mut state.knots;
|
||||
for i in 0..knots.len() {
|
||||
let knot = knots.get_mut(i).unwrap();
|
||||
let dx = prev.0 - knot.0;
|
||||
let dy = prev.1 - knot.1;
|
||||
if dx > 1 {
|
||||
knot.0 += 1;
|
||||
knot.1 += dy / dy.abs().max(1);
|
||||
} else if dx < -1 {
|
||||
knot.0 -= 1;
|
||||
knot.1 += dy / dy.abs().max(1);
|
||||
} else if dy > 1 {
|
||||
knot.1 += 1;
|
||||
knot.0 += dx / dx.abs().max(1);
|
||||
} else if dy < -1 {
|
||||
knot.1 -= 1;
|
||||
knot.0 += dx / dx.abs().max(1);
|
||||
}
|
||||
prev = knot.clone();
|
||||
}
|
||||
}
|
||||
21
src/lib.rs
21
src/lib.rs
@ -1,5 +1,26 @@
|
||||
pub mod day1;
|
||||
pub mod day10;
|
||||
pub mod day11;
|
||||
pub mod day12;
|
||||
pub mod day13;
|
||||
pub mod day14;
|
||||
pub mod day15;
|
||||
pub mod day16;
|
||||
pub mod day17;
|
||||
pub mod day18;
|
||||
pub mod day19;
|
||||
pub mod day2;
|
||||
pub mod day20;
|
||||
pub mod day21;
|
||||
pub mod day22;
|
||||
pub mod day23;
|
||||
pub mod day24;
|
||||
pub mod day25;
|
||||
pub mod day3;
|
||||
pub mod day4;
|
||||
pub mod day5;
|
||||
pub mod day6;
|
||||
pub mod day7;
|
||||
pub mod day8;
|
||||
pub mod day9;
|
||||
pub mod util;
|
||||
|
||||
147
src/util.rs
147
src/util.rs
@ -1,5 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
|
||||
use itertools::MinMaxResult;
|
||||
|
||||
pub fn parse_input() -> String {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
@ -35,3 +39,146 @@ pub fn max_n<T: Ord + Copy>(slice: &[T], n: usize) -> Result<Vec<T>, ()> {
|
||||
|
||||
Ok(max_vals)
|
||||
}
|
||||
|
||||
pub type Coord = (usize, usize);
|
||||
|
||||
pub trait Coordinate {
|
||||
fn manhattan(&self, other: &Self) -> usize;
|
||||
fn to_signed(&self) -> SignedCoord;
|
||||
fn to_unsigned(&self) -> Coord;
|
||||
}
|
||||
|
||||
impl Coordinate for Coord {
|
||||
fn manhattan(&self, other: &Self) -> usize {
|
||||
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
|
||||
}
|
||||
|
||||
fn to_signed(&self) -> SignedCoord {
|
||||
(self.0 as isize, self.1 as isize)
|
||||
}
|
||||
|
||||
fn to_unsigned(&self) -> Coord {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub type SignedCoord = (isize, isize);
|
||||
|
||||
impl Coordinate for SignedCoord {
|
||||
fn manhattan(&self, other: &Self) -> usize {
|
||||
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
|
||||
}
|
||||
|
||||
fn to_signed(&self) -> SignedCoord {
|
||||
*self
|
||||
}
|
||||
|
||||
fn to_unsigned(&self) -> Coord {
|
||||
(self.0 as usize, self.1 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BnBState<T> {
|
||||
fn finished(&self) -> bool;
|
||||
fn lower_bound(&self, extra: &T) -> usize;
|
||||
fn upper_bound(&self, extra: &T) -> usize;
|
||||
fn possible_actions(&self, extra: &T) -> Vec<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub fn maximize<E, S>(initial_state: &S, extra: &E) -> S
|
||||
where
|
||||
S: BnBState<E> + Clone + Hash + Eq,
|
||||
{
|
||||
let mut lower_bound = initial_state.lower_bound(extra);
|
||||
let mut best = initial_state.clone();
|
||||
|
||||
let mut next = vec![(initial_state.clone(), initial_state.upper_bound(extra))];
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(initial_state.clone());
|
||||
|
||||
while let Some(n) = next.pop() {
|
||||
if n.1 < lower_bound {
|
||||
// Between pushing this state and popping it, we've found a better solution
|
||||
continue;
|
||||
}
|
||||
|
||||
let state = n.0;
|
||||
for action in state.possible_actions(extra) {
|
||||
if action.finished() {
|
||||
let action_lower = action.lower_bound(extra);
|
||||
if action_lower > lower_bound {
|
||||
lower_bound = action_lower;
|
||||
best = action;
|
||||
}
|
||||
} else {
|
||||
let action_upper = action.upper_bound(extra);
|
||||
if action_upper > lower_bound && !visited.contains(&action) {
|
||||
next.push((action.clone(), action_upper));
|
||||
visited.insert(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Dir {
|
||||
Right = 0,
|
||||
Down,
|
||||
Left,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn from_usize(n: usize) -> Option<Dir> {
|
||||
match n {
|
||||
0 => Some(Dir::Right),
|
||||
1 => Some(Dir::Down),
|
||||
2 => Some(Dir::Left),
|
||||
3 => Some(Dir::Up),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(c: char) -> Option<Dir> {
|
||||
match c {
|
||||
'^' => Some(Dir::Up),
|
||||
'>' => Some(Dir::Right),
|
||||
'v' => Some(Dir::Down),
|
||||
'<' => Some(Dir::Left),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Dir::Right => ">",
|
||||
Dir::Down => "v",
|
||||
Dir::Left => "<",
|
||||
Dir::Up => "^",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cw(&self) -> Dir {
|
||||
let d = *self as usize;
|
||||
Self::from_usize((d + 1) % 4).unwrap()
|
||||
}
|
||||
|
||||
pub fn ccw(&self) -> Dir {
|
||||
let d = *self as usize;
|
||||
// Add 3 instead of subtracting one to avoid negative numbers
|
||||
Self::from_usize((d + 3) % 4).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn minmax_unwrap<T: Clone>(mm: &MinMaxResult<T>) -> (T, T) {
|
||||
match mm {
|
||||
MinMaxResult::NoElements => panic!("No elements"),
|
||||
MinMaxResult::OneElement(a) => (a.clone(), a.clone()),
|
||||
MinMaxResult::MinMax(a, b) => (a.clone(), b.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user