Compare commits

...

49 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
71984d10d6 Ignore .vscode folder 2022-12-15 11:35:55 +01:00
3f05695ea3 Day 9, both puzzles 2022-12-15 11:35:33 +01:00
9a64352fc3 Day 8, puzzle 2 2022-12-14 16:25:10 +01:00
03a8692162 Day 8, puzzle 1 2022-12-14 16:17:38 +01:00
1156bc2e75 Day 7, puzzle 2 2022-12-14 13:32:05 +01:00
bedf4a790a Day 7, puzzle 1 2022-12-14 13:26:20 +01:00
a446a05c07 Day 6, puzzle 2 2022-12-13 16:56:25 +01:00
e3c356b1ac Day 6, puzzle 1 2022-12-13 16:54:19 +01:00
0506dc670a Day 5, puzzle 2 2022-12-13 16:41:53 +01:00
a4299a1fc5 Day 5, puzzle 1 2022-12-13 16:38:31 +01:00
67 changed files with 4957 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
/input
/.vscode

394
Cargo.lock generated
View File

@ -15,7 +15,157 @@ dependencies = [
name = "aoc22"
version = "0.1.0"
dependencies = [
"env_logger",
"itertools",
"lazy_static",
"log",
"num",
"once_cell",
"regex",
"trees",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "io-lifetimes"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
@ -24,6 +174,127 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
dependencies = [
"num-integer",
"num-traits",
"rand",
"rustc-serialize",
]
[[package]]
name = "num-complex"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
dependencies = [
"num-traits",
"rustc-serialize",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
"rustc-serialize",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.7.0"
@ -40,3 +311,126 @@ name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustix"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "trees"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de5f738ceab88e2491a94ddc33c3feeadfa95fedc60363ef110845df12f3878"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"

View File

@ -7,6 +7,13 @@ edition = "2021"
[dependencies]
regex = "1"
itertools = "^0.10"
log = "^0.4"
env_logger = "^0.10"
num = "^0.1"
lazy_static = "^1.4"
once_cell = "^1.16"
trees = "^0.4"
[[bin]]
name = "d1p1"

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

25
src/bin/d5p1.rs Normal file
View File

@ -0,0 +1,25 @@
use aoc22::{day5, util};
pub fn main() {
let mut arrangement = day5::parse_arrangement(&util::parse_input());
for inst in &arrangement.instructions {
for _ in 0..inst.num {
let box_ = arrangement
.stacks
.get_mut(inst.from)
.unwrap()
.pop_back()
.unwrap();
arrangement.stacks.get_mut(inst.to).unwrap().push_back(box_);
}
}
let top_boxes: String = arrangement
.stacks
.iter()
.map(|s| s.iter().next_back().unwrap())
.collect();
println!("The top boxes are {}", top_boxes);
}

34
src/bin/d5p2.rs Normal file
View File

@ -0,0 +1,34 @@
use std::collections::VecDeque;
use aoc22::{day5, util};
pub fn main() {
let mut arrangement = day5::parse_arrangement(&util::parse_input());
for inst in &arrangement.instructions {
let mut boxes = VecDeque::new();
for _ in 0..inst.num {
let box_ = arrangement
.stacks
.get_mut(inst.from)
.unwrap()
.pop_back()
.unwrap();
boxes.push_front(box_);
}
arrangement
.stacks
.get_mut(inst.to)
.unwrap()
.append(&mut boxes);
}
let top_boxes: String = arrangement
.stacks
.iter()
.map(|s| s.iter().next_back().unwrap())
.collect();
println!("The top boxes are {}", top_boxes);
}

8
src/bin/d6p1.rs Normal file
View File

@ -0,0 +1,8 @@
use aoc22::{day6, util};
pub fn main() {
let input = util::parse_input();
let start = day6::parse_datastream(input.as_str(), 4);
println!("Packet starts after {} characters", start + 1);
}

8
src/bin/d6p2.rs Normal file
View File

@ -0,0 +1,8 @@
use aoc22::{day6, util};
pub fn main() {
let input = util::parse_input();
let start = day6::parse_datastream(input.as_str(), 14);
println!("Message starts after {} characters", start + 1);
}

20
src/bin/d7p1.rs Normal file
View File

@ -0,0 +1,20 @@
use aoc22::{day7, util};
const MAX_SIZE: usize = 100000;
pub fn main() {
let root = day7::parse_cmdline(&util::parse_input());
let mut total_size = 0;
for child in root.borrow().all_children() {
let c = &*child.borrow();
if let day7::Node::Dir { .. } = c {
let size = c.size();
if size <= MAX_SIZE {
total_size += size;
}
}
}
println!("Total size of small directories: {}", total_size);
}

26
src/bin/d7p2.rs Normal file
View File

@ -0,0 +1,26 @@
use aoc22::{day7, util};
const TOTAL_SPACE: usize = 70000000;
const REQUIRED_SPACE: usize = 30000000;
pub fn main() {
let root = day7::parse_cmdline(&util::parse_input());
let r = &*root.borrow();
let free_space = TOTAL_SPACE - r.size();
assert!(free_space < REQUIRED_SPACE);
let required_deletion = REQUIRED_SPACE - free_space;
let mut min_sufficient = usize::MAX;
for child in root.borrow().all_children() {
let c = &*child.borrow();
if let day7::Node::Dir { .. } = c {
let size = c.size();
if size >= required_deletion && size <= min_sufficient {
min_sufficient = size;
}
}
}
println!("Size of min sufficient dir is {}", min_sufficient);
}

10
src/bin/d8p1.rs Normal file
View File

@ -0,0 +1,10 @@
use aoc22::{
day8::{self, find_visible},
util,
};
pub fn main() {
let grid = day8::parse_grid(&util::parse_input());
println!("There are {} visible trees", find_visible(&grid).len());
}

22
src/bin/d8p2.rs Normal file
View File

@ -0,0 +1,22 @@
use aoc22::{
day8::{self, calc_scenic_score},
util,
};
pub fn main() {
let grid = day8::parse_grid(&util::parse_input());
let rows = grid.len();
assert!(rows > 0);
let cols = grid.get(0).unwrap().len();
assert!(cols > 0);
let max_score = (0..rows)
.map(move |r| (0..cols).map(move |c| (r, c)))
.flatten()
.map(|c| calc_scenic_score(&grid, c))
.max()
.unwrap();
println!("Max scenic score is {}", max_score);
}

14
src/bin/d9p1.rs Normal file
View File

@ -0,0 +1,14 @@
use aoc22::{day9, util};
pub fn main() {
let motions = day9::parse_motions(&util::parse_input());
let mut state = day9::State::new(1);
for motion in &motions {
day9::execute_motion(&mut state, &motion);
}
println!("State after all motions:");
state.print();
println!("Visited fields: {}", state.visited.len());
}

14
src/bin/d9p2.rs Normal file
View File

@ -0,0 +1,14 @@
use aoc22::{day9, util};
pub fn main() {
let motions = day9::parse_motions(&util::parse_input());
let mut state = day9::State::new(9);
for motion in &motions {
day9::execute_motion(&mut state, &motion);
}
println!("State after all motions:");
state.print();
println!("Visited fields: {}", state.visited.len());
}

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

97
src/day5.rs Normal file
View File

@ -0,0 +1,97 @@
use std::collections::VecDeque;
use regex::Regex;
#[derive(Debug)]
pub struct Instruction {
pub num: usize,
pub from: usize,
pub to: usize,
}
#[derive(Debug)]
pub struct Arrangement {
pub stacks: Vec<VecDeque<char>>,
pub instructions: Vec<Instruction>,
}
fn parse_stack(line: &str) -> impl Iterator<Item = Option<char>> + '_ {
line.as_bytes().chunks(4).map(|b| -> Option<char> {
if *b.get(0).unwrap() as char == '[' {
Some(*b.get(1).unwrap() as char)
} else {
None
}
})
}
fn parse_instruction(line: &str) -> Instruction {
let re = Regex::new(r"move (\d+) from (\d+) to (\d+)").unwrap();
let captures = re.captures(line).unwrap();
Instruction {
num: captures.get(1).unwrap().as_str().parse().unwrap(),
from: captures.get(2).unwrap().as_str().parse::<usize>().unwrap() - 1,
to: captures.get(3).unwrap().as_str().parse::<usize>().unwrap() - 1,
}
}
#[derive(Debug, Clone, Copy)]
enum ParseState {
Stacks,
StackNumbers,
Instructions,
}
pub fn parse_arrangement(input: &String) -> Arrangement {
let lines: Vec<_> = input.lines().collect();
let first_line = lines.get(0).unwrap();
let length = first_line.len();
// Each stack is 3 characters long, all but the last have a space following
// them.
assert!(length % 4 == 3);
let n_stacks = (length - 3) / 4 + 1;
let mut stacks: Vec<VecDeque<char>> = Vec::with_capacity(n_stacks);
for _ in 0..n_stacks {
stacks.push(VecDeque::new());
}
let mut instructions = Vec::new();
let mut state = ParseState::Stacks;
for line in lines.iter() {
state = match state {
ParseState::Stacks => {
if line.contains('[') {
ParseState::Stacks
} else {
ParseState::StackNumbers
}
}
ParseState::StackNumbers => {
if line.starts_with("move") {
ParseState::Instructions
} else {
ParseState::StackNumbers
}
}
ParseState::Instructions => ParseState::Instructions,
};
match state {
ParseState::Stacks => {
for (i, box_) in parse_stack(line).enumerate() {
if let Some(c) = box_ {
stacks.get_mut(i).unwrap().push_front(c);
}
}
}
ParseState::StackNumbers => { /* Nothing to do here */ }
ParseState::Instructions => instructions.push(parse_instruction(line)),
}
}
Arrangement {
stacks,
instructions,
}
}

19
src/day6.rs Normal file
View File

@ -0,0 +1,19 @@
use std::collections::{HashSet, VecDeque};
pub fn parse_datastream(stream: &str, n_distinct: usize) -> usize {
let mut chars: VecDeque<char> = VecDeque::new();
for (i, char) in stream.chars().enumerate() {
if i >= n_distinct {
chars.pop_front();
}
chars.push_back(char);
if i >= n_distinct - 1 {
let char_set: HashSet<char> = chars.iter().map(char::clone).collect();
if char_set.len() == n_distinct {
return i;
}
}
}
panic!("Couldn't find a start marker!")
}

158
src/day7.rs Normal file
View File

@ -0,0 +1,158 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc, vec};
use regex::Regex;
#[derive(Debug)]
pub enum Node {
File {
name: String,
size: usize,
},
Dir {
name: String,
children: HashMap<String, Rc<RefCell<Node>>>,
parent: Option<Rc<RefCell<Node>>>,
},
}
impl Node {
pub fn print(&self, level: usize) {
match self {
Node::File { name, size } => println!("{0:1$}| {2}\t{3}", "", level * 2, name, size),
Node::Dir { name, children, .. } => {
println!("{0:1$}| {2}", "", level * 2, name);
println!("{0:1$} \\", "", level * 2);
for (_, child) in children {
(**child).borrow().print(level + 1);
}
}
}
}
pub fn size(&self) -> usize {
match self {
Node::File { size, .. } => *size,
Node::Dir { children, .. } => children.iter().map(|(_, c)| (**c).borrow().size()).sum(),
}
}
pub fn all_children(&self) -> Vec<Rc<RefCell<Node>>> {
match self {
Node::File { .. } => vec![],
Node::Dir { children, .. } => {
let mut result: Vec<Rc<RefCell<Node>>> = children.values().map(Rc::clone).collect();
for (_, child) in children {
result.append(&mut (**child).borrow().all_children());
}
result
}
}
}
}
enum ParseState {
Command,
ListOutput,
}
pub fn parse_cmdline(input: &String) -> Rc<RefCell<Node>> {
let lines = input.lines();
let mut state = ParseState::Command;
let root = Rc::new(RefCell::new(Node::Dir {
name: "/".to_owned(),
children: HashMap::new(),
parent: None,
}));
let mut current_node = root.clone();
for line in lines {
if line.starts_with("$") {
state = ParseState::Command;
}
match state {
ParseState::Command => {
let cmd = parse_command(line);
match cmd {
Command::ChangeDirRoot => current_node = root.clone(),
Command::ChangeDirParent => {
let next_node = if let Node::Dir { parent, .. } = &*(*current_node).borrow()
{
match parent {
Some(p) => p.clone(),
None => root.clone(),
}
// parent.unwrap_or(root.clone()).clone()
} else {
panic!("Current node is a file!?");
};
current_node = next_node;
}
Command::ChangeDirChild(name) => {
let next_node =
if let Node::Dir { children, .. } = &*(*current_node).borrow() {
children.get(name.as_str()).unwrap().clone()
} else {
panic!("Current node is a file!?");
};
current_node = next_node;
}
Command::List => state = ParseState::ListOutput,
}
}
ParseState::ListOutput => {
let node = parse_list_output(line, &current_node);
if let Node::Dir { children, .. } = &mut *(*current_node).borrow_mut() {
let n = match &node {
Node::File { name, .. } => name,
Node::Dir { name, .. } => name,
};
children.insert(n.clone(), Rc::new(RefCell::new(node)));
}
}
}
}
root
}
enum Command {
ChangeDirRoot,
ChangeDirParent,
ChangeDirChild(String),
List,
}
fn parse_command(line: &str) -> Command {
let re = Regex::new(r"^\$ (cd|ls)(?: (.*))?$").unwrap();
let captures = re.captures(line).unwrap();
if captures.get(1).unwrap().as_str() == "ls" {
assert!(captures.get(2) == None);
Command::List
} else {
let dir = captures.get(2).unwrap().as_str();
if dir == "/" {
Command::ChangeDirRoot
} else if dir == ".." {
Command::ChangeDirParent
} else {
Command::ChangeDirChild(dir.to_owned())
}
}
}
fn parse_list_output(line: &str, parent: &Rc<RefCell<Node>>) -> Node {
let (first, second) = line.split_once(' ').unwrap();
if first == "dir" {
Node::Dir {
name: second.to_owned(),
children: HashMap::new(),
parent: Some(parent.clone()),
}
} else {
Node::File {
name: second.to_owned(),
size: first.parse().unwrap(),
}
}
}

92
src/day8.rs Normal file
View File

@ -0,0 +1,92 @@
use std::collections::HashSet;
pub type Grid = Vec<Vec<u32>>;
pub fn parse_grid(input: &String) -> Grid {
let mut result = Vec::new();
for line in input.lines() {
result.push(line.chars().map(|c| c.to_digit(10).unwrap()).collect());
}
let row_lens: Vec<usize> = result.iter().map(Vec::len).collect();
assert_eq!(row_lens.iter().max(), row_lens.iter().min());
result
}
pub type Coordinate = (usize, usize);
pub fn find_visible(grid: &Grid) -> HashSet<Coordinate> {
let mut result = HashSet::new();
let rows = grid.len();
assert!(rows > 0);
let cols = grid.get(0).unwrap().len();
assert!(cols > 0);
for row in 0..rows {
let mut left_indices = (0..cols).map(|c| (row, c));
let mut right_indices = left_indices.clone().rev();
result.extend(check_indices(&grid, &mut left_indices));
result.extend(check_indices(&grid, &mut right_indices));
}
for col in 0..cols {
let mut top_indices = (0..rows).map(|r| (r, col));
let mut bottom_indices = top_indices.clone().rev();
result.extend(check_indices(&grid, &mut top_indices));
result.extend(check_indices(&grid, &mut bottom_indices));
}
result
}
fn check_indices(grid: &Grid, coords: &mut dyn Iterator<Item = Coordinate>) -> Vec<Coordinate> {
let mut result = Vec::new();
let mut max_height: i64 = -1;
for (row, col) in coords {
let height = *grid.get(row).unwrap().get(col).unwrap() as i64;
if height > max_height {
result.push((row, col));
max_height = height;
}
}
result
}
pub fn calc_scenic_score(grid: &Grid, (row, col): Coordinate) -> usize {
let rows = grid.len();
assert!(rows > 0);
let cols = grid.get(0).unwrap().len();
assert!(cols > 0);
let height = *grid.get(row).unwrap().get(col).unwrap();
let mut left = (0..col).map(|c| (row, c)).rev();
let mut right = (col + 1..cols).map(|c| (row, c));
let mut up = (0..row).map(|r| (r, col)).rev();
let mut down = (row + 1..rows).map(|r| (r, col));
let mut directions: Vec<&mut dyn Iterator<Item = Coordinate>> =
vec![&mut left, &mut right, &mut up, &mut down];
directions
.iter_mut()
.map(|d| calc_viewing_distance(grid, height, *d))
.product()
}
fn calc_viewing_distance(
grid: &Grid,
start_height: u32,
coords: &mut dyn Iterator<Item = Coordinate>,
) -> usize {
let mut result = 0;
for (row, col) in coords {
result += 1;
let height = *grid.get(row).unwrap().get(col).unwrap();
if height >= start_height {
break;
}
}
result
}

129
src/day9.rs Normal file
View File

@ -0,0 +1,129 @@
use std::collections::HashSet;
use crate::util::Dir;
pub type Motion = (Dir, usize);
pub fn parse_motions(input: &String) -> Vec<Motion> {
let mut result = Vec::new();
for line in input.lines() {
let (d, n) = line.split_once(' ').unwrap();
let dir = match d {
"L" => Dir::Left,
"R" => Dir::Right,
"U" => Dir::Up,
"D" => Dir::Down,
_ => panic!("Unknown direction {}", d),
};
let n = n.parse().unwrap();
result.push((dir, n));
}
result
}
pub type Coord = (i64, i64);
pub struct State {
pub head: Coord,
pub knots: Vec<Coord>,
pub visited: HashSet<Coord>,
}
impl State {
pub fn new(n_knots: usize) -> State {
let mut s = State {
head: (0, 0),
knots: vec![(0, 0); n_knots],
visited: HashSet::new(),
};
s.visited.insert((0, 0));
s
}
pub fn print(&self) {
let knots_x: Vec<i64> = self.knots.iter().map(|v| v.0).collect();
let visited_x: Vec<i64> = self.visited.iter().map(|v| v.0).collect();
let knots_y: Vec<i64> = self.knots.iter().map(|v| v.1).collect();
let visited_y: Vec<i64> = self.visited.iter().map(|v| v.1).collect();
let min_x = 0
.min(self.head.0)
.min(*knots_x.iter().min().unwrap())
.min(*visited_x.iter().min().unwrap());
let max_x = 0
.max(self.head.0)
.max(*knots_x.iter().max().unwrap())
.max(*visited_x.iter().max().unwrap());
let min_y = 0
.min(self.head.1)
.min(*knots_y.iter().min().unwrap())
.min(*visited_y.iter().min().unwrap());
let max_y = 0
.max(self.head.1)
.max(*knots_y.iter().max().unwrap())
.max(*visited_y.iter().max().unwrap());
for y in min_y..max_y + 1 {
'x: for x in min_x..max_x + 1 {
let c = (x, y);
if self.head == c {
print!("H");
} else {
for (i, knot) in self.knots.iter().enumerate() {
if c == *knot {
print!("{}", i + 1);
continue 'x;
}
}
if self.visited.contains(&c) {
print!("#");
} else if c == (0, 0) {
print!("s");
} else {
print!(".");
}
}
}
print!("\n");
}
}
}
pub fn execute_motion(state: &mut State, motion: &Motion) {
for _ in 0..motion.1 {
let head = &mut state.head;
*head = match motion.0 {
Dir::Left => (head.0 - 1, head.1),
Dir::Right => (head.0 + 1, head.1),
Dir::Up => (head.0, head.1 - 1),
Dir::Down => (head.0, head.1 + 1),
};
tail_catchup(state);
state.visited.insert(*state.knots.last().unwrap());
}
}
pub fn tail_catchup(state: &mut State) {
let mut prev = state.head.clone();
let knots = &mut state.knots;
for i in 0..knots.len() {
let knot = knots.get_mut(i).unwrap();
let dx = prev.0 - knot.0;
let dy = prev.1 - knot.1;
if dx > 1 {
knot.0 += 1;
knot.1 += dy / dy.abs().max(1);
} else if dx < -1 {
knot.0 -= 1;
knot.1 += dy / dy.abs().max(1);
} else if dy > 1 {
knot.1 += 1;
knot.0 += dx / dx.abs().max(1);
} else if dy < -1 {
knot.1 -= 1;
knot.0 += dx / dx.abs().max(1);
}
prev = knot.clone();
}
}

View File

@ -1,5 +1,26 @@
pub mod day1;
pub mod day10;
pub mod day11;
pub mod day12;
pub mod day13;
pub mod day14;
pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;
pub mod day2;
pub mod day20;
pub mod day21;
pub mod day22;
pub mod day23;
pub mod day24;
pub mod day25;
pub mod day3;
pub mod day4;
pub mod day5;
pub mod day6;
pub mod day7;
pub mod day8;
pub mod day9;
pub mod util;

View File

@ -1,5 +1,9 @@
use std::collections::HashSet;
use std::env;
use std::fs;
use std::hash::Hash;
use itertools::MinMaxResult;
pub fn parse_input() -> String {
let args: Vec<String> = env::args().collect();
@ -35,3 +39,146 @@ pub fn max_n<T: Ord + Copy>(slice: &[T], n: usize) -> Result<Vec<T>, ()> {
Ok(max_vals)
}
pub type Coord = (usize, usize);
pub trait Coordinate {
fn manhattan(&self, other: &Self) -> usize;
fn to_signed(&self) -> SignedCoord;
fn to_unsigned(&self) -> Coord;
}
impl Coordinate for Coord {
fn manhattan(&self, other: &Self) -> usize {
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
}
fn to_signed(&self) -> SignedCoord {
(self.0 as isize, self.1 as isize)
}
fn to_unsigned(&self) -> Coord {
*self
}
}
pub type SignedCoord = (isize, isize);
impl Coordinate for SignedCoord {
fn manhattan(&self, other: &Self) -> usize {
return self.0.abs_diff(other.0) + self.1.abs_diff(other.1);
}
fn to_signed(&self) -> SignedCoord {
*self
}
fn to_unsigned(&self) -> Coord {
(self.0 as usize, self.1 as usize)
}
}
pub trait BnBState<T> {
fn finished(&self) -> bool;
fn lower_bound(&self, extra: &T) -> usize;
fn upper_bound(&self, extra: &T) -> usize;
fn possible_actions(&self, extra: &T) -> Vec<Self>
where
Self: Sized;
}
pub fn maximize<E, S>(initial_state: &S, extra: &E) -> S
where
S: BnBState<E> + Clone + Hash + Eq,
{
let mut lower_bound = initial_state.lower_bound(extra);
let mut best = initial_state.clone();
let mut next = vec![(initial_state.clone(), initial_state.upper_bound(extra))];
let mut visited = HashSet::new();
visited.insert(initial_state.clone());
while let Some(n) = next.pop() {
if n.1 < lower_bound {
// Between pushing this state and popping it, we've found a better solution
continue;
}
let state = n.0;
for action in state.possible_actions(extra) {
if action.finished() {
let action_lower = action.lower_bound(extra);
if action_lower > lower_bound {
lower_bound = action_lower;
best = action;
}
} else {
let action_upper = action.upper_bound(extra);
if action_upper > lower_bound && !visited.contains(&action) {
next.push((action.clone(), action_upper));
visited.insert(action);
}
}
}
}
best
}
#[derive(Debug, Clone, Copy)]
pub enum Dir {
Right = 0,
Down,
Left,
Up,
}
impl Dir {
pub fn from_usize(n: usize) -> Option<Dir> {
match n {
0 => Some(Dir::Right),
1 => Some(Dir::Down),
2 => Some(Dir::Left),
3 => Some(Dir::Up),
_ => None,
}
}
pub fn from_char(c: char) -> Option<Dir> {
match c {
'^' => Some(Dir::Up),
'>' => Some(Dir::Right),
'v' => Some(Dir::Down),
'<' => Some(Dir::Left),
_ => None,
}
}
pub fn to_str(&self) -> &'static str {
match self {
Dir::Right => ">",
Dir::Down => "v",
Dir::Left => "<",
Dir::Up => "^",
}
}
pub fn cw(&self) -> Dir {
let d = *self as usize;
Self::from_usize((d + 1) % 4).unwrap()
}
pub fn ccw(&self) -> Dir {
let d = *self as usize;
// Add 3 instead of subtracting one to avoid negative numbers
Self::from_usize((d + 3) % 4).unwrap()
}
}
pub fn minmax_unwrap<T: Clone>(mm: &MinMaxResult<T>) -> (T, T) {
match mm {
MinMaxResult::NoElements => panic!("No elements"),
MinMaxResult::OneElement(a) => (a.clone(), a.clone()),
MinMaxResult::MinMax(a, b) => (a.clone(), b.clone()),
}
}