Compare commits

..

39 Commits

Author SHA1 Message Date
5642d65bff Day 25, part 1 2022-12-25 16:23:03 +01:00
745d4a8029 Day 24, part 2 2022-12-24 18:45:53 +01:00
a7d32abb93 Day 24, part 1 2022-12-24 18:40:37 +01:00
98fcdfeaef Day 23, part 2 2022-12-24 14:08:01 +01:00
7abd632567 Day 23, part 1 2022-12-23 12:28:17 +01:00
62c866abf9 Day 22, part 2 2022-12-22 17:21:06 +01:00
ae143d4c57 Navigable trait 2022-12-22 15:00:15 +01:00
5e6870d092 Day 22, part 1 2022-12-22 12:57:38 +01:00
a0fa5256e9 Day 16, part 2 2022-12-21 21:44:12 +01:00
49368e7985 Track paths during distance calculation 2022-12-21 19:54:27 +01:00
95e4095f32 Day 16, part 1 solved 2022-12-21 19:33:17 +01:00
0279edb249 Generalized branch-and-bound implementation 2022-12-21 19:32:31 +01:00
47cb2fefa5 Day 21, part 2 2022-12-21 18:45:28 +01:00
ec5b46d793 Use trees crate 2022-12-21 18:00:23 +01:00
9b42768fdb Day 21, part 1 2022-12-21 17:32:59 +01:00
93f574463b Day 20, part 2 2022-12-21 15:41:35 +01:00
63fecbd235 Day 20, part 1 2022-12-21 15:18:35 +01:00
f825de9426 Day 19, part 2 2022-12-21 13:44:58 +01:00
ab870a881e Day 19, part 1 2022-12-21 13:39:43 +01:00
813c9f0612 Day 18, part 2 2022-12-20 17:20:18 +01:00
ff8870eb3e Day 18, part 1 2022-12-20 16:44:34 +01:00
22d9f7c42a Day 17, part 2 2022-12-20 15:59:53 +01:00
1e7eb12da9 Day 17, part 1 2022-12-20 14:35:12 +01:00
06786817f3 More tries at day 16 2022-12-20 00:45:14 +01:00
46d409fa92 First attempt at day 16 2022-12-20 00:44:56 +01:00
0d30968d3e Multithread it 2022-12-16 11:07:36 +01:00
62e9d17eff Day 15, puzzle 2 2022-12-16 10:54:48 +01:00
619c77aab7 Day 15, puzzle 1 2022-12-16 10:48:51 +01:00
d05780e64a Day 14, puzzle 2 2022-12-16 09:52:48 +01:00
215e067d33 Day 14, puzzle 1 2022-12-16 09:46:19 +01:00
6d3686c946 Fix warning 2022-12-16 08:43:14 +01:00
f10e8a4c04 Day 13, puzzle 2 2022-12-16 01:13:35 +01:00
57f4bc2732 Day 13, puzzle 1 2022-12-16 00:58:34 +01:00
36ceda33db Day 12, puzzle 2 2022-12-15 22:47:17 +01:00
3bdd5472aa Day 12, puzzle 1 2022-12-15 22:41:50 +01:00
876ebfcfe3 Day 11, puzzle 2 2022-12-15 21:34:16 +01:00
4f5d6d7fa0 Day 11, puzzle 1 2022-12-15 21:04:47 +01:00
b7005f244d Day 10, puzzle 2 2022-12-15 16:26:34 +01:00
05f3d9411a Day 10, puzzle 1 2022-12-15 15:44:37 +01:00
52 changed files with 4276 additions and 7 deletions

394
Cargo.lock generated
View File

@ -15,7 +15,157 @@ dependencies = [
name = "aoc22" name = "aoc22"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"env_logger",
"itertools",
"lazy_static",
"log",
"num",
"once_cell",
"regex", "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]] [[package]]
@ -24,6 +174,127 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 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]] [[package]]
name = "regex" name = "regex"
version = "1.7.0" version = "1.7.0"
@ -40,3 +311,126 @@ name = "regex-syntax"
version = "0.6.28" version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 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"

View File

@ -7,6 +7,13 @@ edition = "2021"
[dependencies] [dependencies]
regex = "1" 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]] [[bin]]
name = "d1p1" name = "d1p1"

16
src/bin/d10p1.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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));
}

104
src/day10.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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");
}
}

View File

@ -1,12 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Debug)] use crate::util::Dir;
pub enum Dir {
Left,
Right,
Up,
Down,
}
pub type Motion = (Dir, usize); pub type Motion = (Dir, usize);

View File

@ -1,5 +1,21 @@
pub mod day1; 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 day2;
pub mod day20;
pub mod day21;
pub mod day22;
pub mod day23;
pub mod day24;
pub mod day25;
pub mod day3; pub mod day3;
pub mod day4; pub mod day4;
pub mod day5; pub mod day5;

View File

@ -1,5 +1,9 @@
use std::collections::HashSet;
use std::env; use std::env;
use std::fs; use std::fs;
use std::hash::Hash;
use itertools::MinMaxResult;
pub fn parse_input() -> String { pub fn parse_input() -> String {
let args: Vec<String> = env::args().collect(); 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) 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()),
}
}