Compare commits
59 Commits
804d6b6b92
...
master
Author | SHA1 | Date | |
---|---|---|---|
a7cda04308 | |||
ec3b438f40 | |||
e1496aa25a | |||
8b265bc3ec | |||
1f6d987c53 | |||
9ef31dde55 | |||
|
9236e70abe | ||
|
2e2d668269 | ||
|
6abb28f96f | ||
393db1d3bf | |||
|
a998e68b71 | ||
166008ebb9 | |||
3b9a3eed0c | |||
|
a10ff091da | ||
19d28ea991 | |||
3bc97158bc | |||
|
86eb94514e | ||
|
c7c444bce9 | ||
8c6ffc2eb9 | |||
|
61374b8655 | ||
54053779aa | |||
|
63818ee2ee | ||
|
5abd82efff | ||
6492fd5c03 | |||
5633d37773 | |||
33e9af89be | |||
0f6f5cfc76 | |||
50db6dafd0 | |||
dffb8a8673 | |||
df55d78dec | |||
584cad1690 | |||
|
bf72a26b2b | ||
8991100b59 | |||
|
6cc7cb8752 | ||
5bcc40b030 | |||
24b88815f2 | |||
|
3dc38678ff | ||
|
754b296abf | ||
|
aca62e1c44 | ||
|
2133290565 | ||
|
19e219bcc3 | ||
d77c4ed0c9 | |||
2269a7e086 | |||
|
444a7f6e47 | ||
|
2fbc98de3d | ||
|
ca360f7baa | ||
fb8ec81683 | |||
|
6577bae991 | ||
|
c55a721067 | ||
340ef2d4d3 | |||
|
b1a8ab8bcd | ||
|
b519e0bad2 | ||
|
335b7a25cf | ||
|
7681bbc760 | ||
|
cf1fb4c792 | ||
|
161e5f7a5f | ||
|
cde23745ef | ||
|
6f60951b94 | ||
|
7b4cb884c1 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,5 @@
|
|||||||
*/data/*
|
*/data/*
|
||||||
|
*/target/*
|
||||||
**.exe
|
**.exe
|
||||||
|
|
||||||
|
*.out
|
||||||
|
351
2021/Cargo.lock
generated
Normal file
351
2021/Cargo.lock
generated
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "advent-2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"color-eyre",
|
||||||
|
"num",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.72"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-eyre"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"color-spantrace",
|
||||||
|
"eyre",
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-spantrace"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eyre"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
|
||||||
|
dependencies = [
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indenter"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
|
[[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.108"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-error"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
|
||||||
|
dependencies = [
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
|
||||||
|
dependencies = [
|
||||||
|
"sharded-slab",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
11
2021/Cargo.toml
Normal file
11
2021/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "advent-2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Joseph Montanaro <jfmontanaro@modg.org>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
color-eyre = "^0.5.11"
|
||||||
|
num = "^0.4.0"
|
29
2021/src/day1.rs
Normal file
29
2021/src/day1.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::ParseLines;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(i32, i32)> {
|
||||||
|
let mut single_increases = 0;
|
||||||
|
let mut window_increases = 0;
|
||||||
|
let mut prev = (None, None, None);
|
||||||
|
for res in data.parse_lines::<i32>() {
|
||||||
|
let n = res?;
|
||||||
|
if let Some(p) = prev.2 {
|
||||||
|
// apparently we can't combine "if let" and regular boolean conditions
|
||||||
|
if n > p {
|
||||||
|
single_increases += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(first), Some(second), Some(third)) = prev {
|
||||||
|
if (n + second + third) > (first + second + third) {
|
||||||
|
window_increases += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// would have forgotten this if not for the warning triggered by "let mut" without mutation
|
||||||
|
prev = (prev.1, prev.2, Some(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((single_increases, window_increases))
|
||||||
|
}
|
108
2021/src/day10.rs
Normal file
108
2021/src/day10.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
enum BracketKind {
|
||||||
|
Paren,
|
||||||
|
Square,
|
||||||
|
Angle,
|
||||||
|
Curly,
|
||||||
|
}
|
||||||
|
use BracketKind::*;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
enum GroupDelimiter {
|
||||||
|
Start(BracketKind),
|
||||||
|
End(BracketKind),
|
||||||
|
}
|
||||||
|
use GroupDelimiter::*;
|
||||||
|
|
||||||
|
|
||||||
|
enum LineStatus {
|
||||||
|
Valid,
|
||||||
|
Incomplete(Vec<BracketKind>),
|
||||||
|
Corrupted(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn score_corrupted(bk: BracketKind) -> usize {
|
||||||
|
match bk {
|
||||||
|
Paren => 3,
|
||||||
|
Square => 57,
|
||||||
|
Curly => 1197,
|
||||||
|
Angle => 25137,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn score_incomplete(brackets: &[BracketKind]) -> usize {
|
||||||
|
let mut total = 0;
|
||||||
|
for bk in brackets.iter().rev() {
|
||||||
|
let additional = match bk {
|
||||||
|
Paren => 1,
|
||||||
|
Square => 2,
|
||||||
|
Curly => 3,
|
||||||
|
Angle => 4,
|
||||||
|
};
|
||||||
|
total = total * 5 + additional;
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_bracket(b: char) -> eyre::Result<GroupDelimiter> {
|
||||||
|
match b {
|
||||||
|
'(' => Ok(Start(Paren)),
|
||||||
|
')' => Ok(End(Paren)),
|
||||||
|
'[' => Ok(Start(Square)),
|
||||||
|
']' => Ok(End(Square)),
|
||||||
|
'<' => Ok(Start(Angle)),
|
||||||
|
'>' => Ok(End(Angle)),
|
||||||
|
'{' => Ok(Start(Curly)),
|
||||||
|
'}' => Ok(End(Curly)),
|
||||||
|
_ => Err(eyre!("Invalid value for bracket: {}", b)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_line(line: &str) -> eyre::Result<LineStatus> {
|
||||||
|
let mut state = Vec::new();
|
||||||
|
for c in line.chars() {
|
||||||
|
match parse_bracket(c)? {
|
||||||
|
Start(current) => state.push(current),
|
||||||
|
End(current) => {
|
||||||
|
match state.last() {
|
||||||
|
Some(&prev) if prev == current => {state.pop();},
|
||||||
|
_ => return Ok(LineStatus::Corrupted(score_corrupted(current))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.len() > 0 {
|
||||||
|
Ok(LineStatus::Incomplete(state))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(LineStatus::Valid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut total_corrupted = 0;
|
||||||
|
let mut completions = Vec::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
match parse_line(line)? {
|
||||||
|
LineStatus::Corrupted(s) => total_corrupted += s,
|
||||||
|
LineStatus::Incomplete(state) => completions.push(score_incomplete(&state)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completions.sort();
|
||||||
|
let middle = completions.len() / 2 + 1; // given an odd length, this is the middle value
|
||||||
|
|
||||||
|
Ok((total_corrupted, completions[middle]))
|
||||||
|
}
|
76
2021/src/day11.rs
Normal file
76
2021/src/day11.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::Vec2;
|
||||||
|
|
||||||
|
|
||||||
|
struct Octopi {
|
||||||
|
grid: Vec2<u8>,
|
||||||
|
flashed: HashSet<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Octopi {
|
||||||
|
fn step(&mut self) -> usize {
|
||||||
|
for row in 0..self.grid.row_count() {
|
||||||
|
for col in 0..self.grid.col_count() {
|
||||||
|
self.inc(row, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let count = self.flashed.len();
|
||||||
|
self.flashed.clear();
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc(&mut self, row: usize, col: usize) {
|
||||||
|
if self.flashed.contains(&(row, col)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let energy = self.grid.get_mut(row, col);
|
||||||
|
*energy += 1;
|
||||||
|
if *energy > 9 {
|
||||||
|
self.flashed.insert((row, col));
|
||||||
|
*energy = 0;
|
||||||
|
for (n_row, n_col) in self.grid.neighbor_coords(row, col) {
|
||||||
|
self.inc(n_row, n_col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<Octopi> {
|
||||||
|
let mut grid = Vec2::new(10);
|
||||||
|
for line in data.lines() {
|
||||||
|
let mut row = Vec::with_capacity(10); // kinda inefficient but whatever, it only happens once
|
||||||
|
for c in line.chars() {
|
||||||
|
match c.to_digit(10) {
|
||||||
|
Some(d) => row.push(d as u8),
|
||||||
|
None => return Err(eyre!("Invalid digit: {}", c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid.push(row);
|
||||||
|
}
|
||||||
|
Ok(Octopi {grid, flashed: HashSet::new()})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut octopi = load(data)?;
|
||||||
|
|
||||||
|
let mut num_flashes = 0;
|
||||||
|
let mut two = None;
|
||||||
|
for i in 0.. {
|
||||||
|
let n = octopi.step();
|
||||||
|
if i < 100 {
|
||||||
|
num_flashes += n;
|
||||||
|
}
|
||||||
|
if n == 100 {
|
||||||
|
two = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((num_flashes, two.unwrap() + 1))
|
||||||
|
}
|
174
2021/src/day12.rs
Normal file
174
2021/src/day12.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::IterExt;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
enum Size {
|
||||||
|
Big,
|
||||||
|
Small,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this is a terrible way to do it, because it's completely tied to
|
||||||
|
// the dataset and needs to be redone in order to e.g. use a sample
|
||||||
|
// dataset. A better way would be to use [char; 2] to represent each
|
||||||
|
// cave and supply a method that computes size from letter case.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
enum Tag {
|
||||||
|
start,
|
||||||
|
de,
|
||||||
|
DN,
|
||||||
|
jv,
|
||||||
|
MU,
|
||||||
|
oa,
|
||||||
|
OC,
|
||||||
|
rj,
|
||||||
|
vd,
|
||||||
|
VP,
|
||||||
|
WK,
|
||||||
|
yb,
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
struct Cave {
|
||||||
|
tag: Tag,
|
||||||
|
size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromStr for Cave {
|
||||||
|
type Err = eyre::Report;
|
||||||
|
fn from_str(s: &str) -> eyre::Result<Self> {
|
||||||
|
let cave = match s {
|
||||||
|
"start" => Cave {tag: Tag::start, size: Size::Small},
|
||||||
|
"de" => Cave {tag: Tag::de, size: Size::Small},
|
||||||
|
"DN" => Cave {tag: Tag::DN, size: Size::Big},
|
||||||
|
"jv" => Cave {tag: Tag::jv, size: Size::Small},
|
||||||
|
"MU" => Cave {tag: Tag::MU, size: Size::Big},
|
||||||
|
"oa" => Cave {tag: Tag::oa, size: Size::Small},
|
||||||
|
"OC" => Cave {tag: Tag::OC, size: Size::Big},
|
||||||
|
"rj" => Cave {tag: Tag::rj, size: Size::Small},
|
||||||
|
"vd" => Cave {tag: Tag::vd, size: Size::Small},
|
||||||
|
"VP" => Cave {tag: Tag::VP, size: Size::Big},
|
||||||
|
"WK" => Cave {tag: Tag::WK, size: Size::Big},
|
||||||
|
"yb" => Cave {tag: Tag::yb, size: Size::Small},
|
||||||
|
"end" => Cave {tag: Tag::end, size: Size::Small},
|
||||||
|
_ => return Err(eyre!("Invalid cave tag: {}", s)),
|
||||||
|
};
|
||||||
|
Ok(cave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type CaveMap = HashMap<Tag, Vec<Cave>>;
|
||||||
|
|
||||||
|
|
||||||
|
struct Path {
|
||||||
|
order: Vec<Cave>,
|
||||||
|
visited: HashSet<Cave>,
|
||||||
|
doubled: Option<Cave>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
fn new() -> Self {
|
||||||
|
Path {
|
||||||
|
order: vec![Cave {tag: Tag::start, size: Size::Small}],
|
||||||
|
visited: HashSet::new(),
|
||||||
|
doubled: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, cave: Cave) {
|
||||||
|
self.order.push(cave);
|
||||||
|
if cave.size == Size::Small {
|
||||||
|
let inserted = self.visited.insert(cave);
|
||||||
|
if !inserted { // value was already present in set
|
||||||
|
self.doubled = Some(cave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop(&mut self) {
|
||||||
|
let cave = self.order.pop().unwrap();
|
||||||
|
if cave.size == Size::Small {
|
||||||
|
match self.doubled {
|
||||||
|
Some(d) if d == cave => {self.doubled = None},
|
||||||
|
_ => {self.visited.remove(&cave);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(&self) -> Option<&Cave> {
|
||||||
|
self.order.last()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<CaveMap> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
let (a, b) = line.split('-').take_pair()?;
|
||||||
|
let cave_a = a.parse::<Cave>()?;
|
||||||
|
let cave_b = b.parse::<Cave>()?;
|
||||||
|
|
||||||
|
let neighbors_a = map.entry(cave_a.tag).or_insert_with(|| vec![]);
|
||||||
|
neighbors_a.push(cave_b);
|
||||||
|
|
||||||
|
let neighbors_b = map.entry(cave_b.tag).or_insert_with(|| vec![]);
|
||||||
|
neighbors_b.push(cave_a);
|
||||||
|
}
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn can_visit_part1(path: &Path, cave: &Cave) -> bool {
|
||||||
|
if cave.size == Size::Big {true}
|
||||||
|
else if cave.tag == Tag::start {false}
|
||||||
|
else if path.visited.contains(cave) {false}
|
||||||
|
else {true}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_part2(path: &Path, cave: &Cave) -> bool {
|
||||||
|
if cave.size == Size::Big {true}
|
||||||
|
else if cave.tag == Tag::start {false}
|
||||||
|
else if path.visited.contains(cave) && path.doubled.is_some() {false}
|
||||||
|
else {true}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn search<F>(map: &CaveMap, path: &mut Path, can_visit: F) -> Vec<Vec<Cave>>
|
||||||
|
where F: Fn(&Path, &Cave) -> bool + Copy
|
||||||
|
{
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for neighbor in &map[&path.last().unwrap().tag] {
|
||||||
|
if neighbor.tag == Tag::end {
|
||||||
|
path.push(*neighbor);
|
||||||
|
result.push(path.order.clone());
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
else if can_visit(path, neighbor) {
|
||||||
|
path.push(*neighbor);
|
||||||
|
let child_paths = search(map, path, can_visit);
|
||||||
|
result.extend(child_paths);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let map = load(data)?;
|
||||||
|
let one = search(&map, &mut Path::new(), can_visit_part1);
|
||||||
|
let two = search(&map, &mut Path::new(), can_visit_part2);
|
||||||
|
|
||||||
|
Ok((one.len(), two.len()))
|
||||||
|
}
|
108
2021/src/day13.rs
Normal file
108
2021/src/day13.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::{Vec2, IterExt};
|
||||||
|
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Line {
|
||||||
|
value: usize,
|
||||||
|
direction: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<(Vec2<bool>, Vec<Line>)> {
|
||||||
|
// populate a 1500x1500 grid
|
||||||
|
let mut grid: Vec2<bool> = Vec2::with_capacity(1500, 1500);
|
||||||
|
grid.fill_to_cap(false);
|
||||||
|
|
||||||
|
let mut lines = data.lines();
|
||||||
|
for line in &mut lines {
|
||||||
|
if line == "" {break}
|
||||||
|
|
||||||
|
let (col, row) = line.split(',').map(|s| s.parse::<usize>()).take_pair()?;
|
||||||
|
grid.set(row?, col?, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// having consumed the coordinates, we can move on to the fold lines
|
||||||
|
let mut folds = Vec::new();
|
||||||
|
for line in lines {
|
||||||
|
let (axis, intercept) = line
|
||||||
|
.split(' ')
|
||||||
|
.nth(2)
|
||||||
|
.ok_or_else(|| eyre!("Invalid fold line statement: {}", line))?
|
||||||
|
.split('=')
|
||||||
|
.take_pair()?;
|
||||||
|
|
||||||
|
let direction = match axis {
|
||||||
|
"x" => Direction::Vertical,
|
||||||
|
"y" => Direction::Horizontal,
|
||||||
|
_ => return Err(eyre!("Invalid axis: {}", axis)),
|
||||||
|
};
|
||||||
|
let value = intercept.parse::<usize>()?;
|
||||||
|
|
||||||
|
folds.push(Line {value, direction});
|
||||||
|
}
|
||||||
|
Ok((grid, folds))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn fold(grid: &Vec2<bool>, line: &Line) -> Vec2<bool> {
|
||||||
|
let mut new_rows = grid.row_count();
|
||||||
|
let mut new_cols = grid.col_count();
|
||||||
|
match line.direction {
|
||||||
|
Direction::Horizontal => new_rows = line.value,
|
||||||
|
Direction::Vertical => new_cols = line.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_grid = Vec2::with_capacity(new_rows, new_cols);
|
||||||
|
new_grid.fill_to_cap(false);
|
||||||
|
for row in 0..new_rows {
|
||||||
|
for col in 0..new_cols {
|
||||||
|
if *grid.get(row, col) {
|
||||||
|
new_grid.set(row, col, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mirror_row = row;
|
||||||
|
let mut mirror_col = col;
|
||||||
|
match line.direction {
|
||||||
|
Direction::Horizontal => mirror_row = 2 * new_rows - row,
|
||||||
|
Direction::Vertical => mirror_col = 2 * new_cols - col,
|
||||||
|
}
|
||||||
|
if mirror_row < grid.row_count() && mirror_col < grid.col_count()
|
||||||
|
&& *grid.get(mirror_row, mirror_col)
|
||||||
|
{
|
||||||
|
new_grid.set(row, col, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_grid
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn show(grid: &Vec2<bool>) {
|
||||||
|
for row in grid.rows() {
|
||||||
|
let line = row.iter().map(|&v| if v {'#'} else {' '}).collect::<String>();
|
||||||
|
println!("[{}]", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let (grid, folds) = load(data)?;
|
||||||
|
|
||||||
|
let mut folded = fold(&grid, &folds[0]);
|
||||||
|
let one = folded.values().filter(|v| **v).count();
|
||||||
|
|
||||||
|
for line in &folds[1..] {
|
||||||
|
folded = fold(&folded, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
show(&folded);
|
||||||
|
|
||||||
|
Ok((one, 0))
|
||||||
|
}
|
83
2021/src/day14.rs
Normal file
83
2021/src/day14.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::{IterExt, Counter};
|
||||||
|
|
||||||
|
|
||||||
|
type Pairs = Counter<(char, char)>;
|
||||||
|
type Rules = HashMap<(char, char), char>;
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<(Pairs, Rules)> {
|
||||||
|
let mut lines = data.lines();
|
||||||
|
let template = lines.next().unwrap().to_string();
|
||||||
|
let mut pairs = Counter::new();
|
||||||
|
for i in 1..template.len() {
|
||||||
|
let a = template.as_bytes()[i - 1] as char;
|
||||||
|
let b = template.as_bytes()[i] as char;
|
||||||
|
pairs.inc((a, b));
|
||||||
|
}
|
||||||
|
lines.next();
|
||||||
|
|
||||||
|
let mut rules = HashMap::new();
|
||||||
|
for line in &mut lines {
|
||||||
|
let (pair_s, tgt_s) = line.split(" -> ").take_pair()?;
|
||||||
|
let pair = pair_s.chars().take_pair()?;
|
||||||
|
let tgt = tgt_s.chars().next().unwrap();
|
||||||
|
rules.insert(pair, tgt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((pairs, rules))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn expand(src: &Pairs, rules: &Rules) -> Pairs {
|
||||||
|
let mut dst = Counter::new();
|
||||||
|
for (&(a, b), count) in src.iter() {
|
||||||
|
if let Some(&tween) = rules.get(&(a, b)) {
|
||||||
|
dst.inc_by((a, tween), count);
|
||||||
|
dst.inc_by((tween, b), count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn score(pairs: &Pairs) -> usize {
|
||||||
|
// "score" is defined as the number of occurences of the most common
|
||||||
|
// character minus the number of occurrences of the least common
|
||||||
|
let mut firsts = Counter::new();
|
||||||
|
let mut lasts = Counter::new();
|
||||||
|
for (pair, count) in pairs.iter() {
|
||||||
|
let (a, b) = *pair;
|
||||||
|
firsts.inc_by(a, count);
|
||||||
|
lasts.inc_by(b, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the count of a given character is the number of times it appears
|
||||||
|
// in either first or last place of a pair, whichever is greater
|
||||||
|
let mut counts = Counter::new();
|
||||||
|
for (c, count) in firsts.iter().chain(lasts.iter()) {
|
||||||
|
if count > counts.get(c) {
|
||||||
|
counts.set(*c, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let max = counts.iter().map(|(_c, n)| n).max().unwrap();
|
||||||
|
let min = counts.iter().map(|(_c, n)| n).min().unwrap();
|
||||||
|
max - min
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let (mut pairs, rules) = load(data)?;
|
||||||
|
|
||||||
|
for _i in 0..10 {pairs = expand(&pairs, &rules);}
|
||||||
|
let one = score(&pairs);
|
||||||
|
|
||||||
|
for _i in 0..30 {pairs = expand(&pairs, &rules);}
|
||||||
|
let two = score(&pairs);
|
||||||
|
|
||||||
|
Ok((one, two))
|
||||||
|
}
|
105
2021/src/day15.rs
Normal file
105
2021/src/day15.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
use std::collections::{HashMap, BinaryHeap};
|
||||||
|
use std::cmp::{Ord, PartialOrd, Ordering};
|
||||||
|
|
||||||
|
use crate::lib::Vec2;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
struct Node {
|
||||||
|
row: usize,
|
||||||
|
col: usize,
|
||||||
|
cost: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Node {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
other.cost.cmp(&self.cost)
|
||||||
|
.then_with(|| self.row.cmp(&other.row))
|
||||||
|
.then_with(|| self.col.cmp(&other.col))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Node {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn search(graph: &Vec2<usize>) -> usize {
|
||||||
|
// lifted shamelessly from https://doc.rust-lang.org/std/collections/binary_heap/index.html
|
||||||
|
let mut q = BinaryHeap::new();
|
||||||
|
let start = Node {row: 0, col: 0, cost: 0};
|
||||||
|
q.push(start);
|
||||||
|
let mut costs = HashMap::new();
|
||||||
|
|
||||||
|
while let Some(node) = q.pop() {
|
||||||
|
if node.row == graph.row_count() - 1 && node.col == graph.col_count() - 1 {
|
||||||
|
return node.cost;
|
||||||
|
}
|
||||||
|
if &node.cost > costs.get(&(node.row, node.col)).unwrap_or(&usize::MAX) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (row, col) in graph.rect_neighbors(node.row, node.col) {
|
||||||
|
let hop_cost = graph.get(row, col);
|
||||||
|
let neighbor = Node {row, col, cost: node.cost + hop_cost};
|
||||||
|
if &neighbor.cost < costs.get(&(row, col)).unwrap_or(&usize::MAX) {
|
||||||
|
q.push(neighbor);
|
||||||
|
costs.insert((row, col), neighbor.cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Could not find a path");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<Vec2<usize>> {
|
||||||
|
let width = data.lines().next().unwrap().len();
|
||||||
|
let mut grid = Vec2::new(width);
|
||||||
|
for line in data.lines() {
|
||||||
|
let chars = line.chars().map(|c| c.to_digit(10).unwrap() as usize);
|
||||||
|
grid.push(chars);
|
||||||
|
}
|
||||||
|
Ok(grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn tile(grid: &Vec2<usize>) -> Vec2<usize> {
|
||||||
|
let old_cols = grid.col_count();
|
||||||
|
let new_cols = old_cols * 5;
|
||||||
|
let mut result = Vec2::new(new_cols);
|
||||||
|
for row in grid.rows() {
|
||||||
|
let values = (0..new_cols).map(|i| {
|
||||||
|
let v = row[i % old_cols] + i / old_cols;
|
||||||
|
(v - 1) % 9 + 1
|
||||||
|
});
|
||||||
|
result.push(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_rows = grid.row_count();
|
||||||
|
let new_rows = old_rows * 5;
|
||||||
|
for row_n in old_rows..new_rows {
|
||||||
|
let values = (0..new_cols).map(|c| {
|
||||||
|
let r = row_n % old_rows;
|
||||||
|
let v = result.get(r, c) + row_n / old_rows;
|
||||||
|
(v - 1) % 9 + 1
|
||||||
|
})
|
||||||
|
.collect::<Vec<usize>>();
|
||||||
|
result.push(values.into_iter());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let grid = load(data)?;
|
||||||
|
let grid2 = tile(&grid);
|
||||||
|
|
||||||
|
let one = search(&grid);
|
||||||
|
let two = search(&grid2);
|
||||||
|
|
||||||
|
Ok((one, two))
|
||||||
|
}
|
127
2021/src/day16.rs
Normal file
127
2021/src/day16.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::{BitVec, BitSlice};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Packet {
|
||||||
|
version: u8,
|
||||||
|
type_id: u8,
|
||||||
|
content: PacketContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PacketContent {
|
||||||
|
Literal(u64),
|
||||||
|
Operator(Vec<Packet>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser {
|
||||||
|
data: BitVec,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
fn from_hex(hex: &str) -> eyre::Result<Self> {
|
||||||
|
let mut data = BitVec::with_capacity(hex.len() * 4);
|
||||||
|
for i in 0 .. (hex.len() / 2) { // we are assuming valid hex
|
||||||
|
let chunk = &hex[i * 2 .. (i + 1) * 2];
|
||||||
|
let byte = u8::from_str_radix(chunk, 16)?;
|
||||||
|
data.push_byte(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Parser {data, idx: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self, n: usize) -> BitSlice {
|
||||||
|
let range = self.idx .. self.idx + n;
|
||||||
|
self.idx += n;
|
||||||
|
self.data.slice(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_value(&mut self) -> u64 {
|
||||||
|
let mut value = 0;
|
||||||
|
loop {
|
||||||
|
let group = self.next(5);
|
||||||
|
value = (value << 4) | group.slice(1..5).as_u64();
|
||||||
|
if !group[0] {break;}
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_children(&mut self) -> Vec<Packet> {
|
||||||
|
let mut children = Vec::new();
|
||||||
|
if self.next(1).as_bool() {
|
||||||
|
// count refers to number of sub-packets
|
||||||
|
let count = self.next(11).as_u16();
|
||||||
|
children = Vec::with_capacity(count as usize);
|
||||||
|
for _i in 0..count {
|
||||||
|
children.push(self.parse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// len refers to content length in bits
|
||||||
|
let len = self.next(15).as_u16();
|
||||||
|
let end = self.idx + (len as usize);
|
||||||
|
while self.idx < end {
|
||||||
|
children.push(self.parse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self) -> Packet {
|
||||||
|
let version = self.next(3).as_u8();
|
||||||
|
let type_id = self.next(3).as_u8();
|
||||||
|
let content = match type_id {
|
||||||
|
4 => PacketContent::Literal(self.parse_value()),
|
||||||
|
_ => PacketContent::Operator(self.parse_children()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Packet {version, type_id, content}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn sum_versions(root: &Packet) -> usize {
|
||||||
|
let mut total = root.version as usize;
|
||||||
|
if let PacketContent::Operator(children) = &root.content {
|
||||||
|
for child in children {
|
||||||
|
total += sum_versions(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn evaluate(packet: &Packet) -> usize {
|
||||||
|
let children = match &packet.content {
|
||||||
|
PacketContent::Literal(value) => {return *value as usize;}, // early exit here
|
||||||
|
PacketContent::Operator(children) => children,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut values = children.iter().map(|p| evaluate(p));
|
||||||
|
match packet.type_id {
|
||||||
|
0 => values.sum(),
|
||||||
|
1 => values.product(),
|
||||||
|
2 => values.min().unwrap(),
|
||||||
|
3 => values.max().unwrap(),
|
||||||
|
// it's guaranteed that comparisons will have exactly two children
|
||||||
|
5 => if values.next().unwrap() > values.next().unwrap() {1} else {0},
|
||||||
|
6 => if values.next().unwrap() < values.next().unwrap() {1} else {0},
|
||||||
|
7 => if values.next().unwrap() == values.next().unwrap() {1} else {0},
|
||||||
|
// the version number was a 3-bit value, so:
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut parser = Parser::from_hex(data)?;
|
||||||
|
let root = parser.parse();
|
||||||
|
|
||||||
|
let one = sum_versions(&root);
|
||||||
|
let two = evaluate(&root);
|
||||||
|
|
||||||
|
Ok((one, two))
|
||||||
|
}
|
81
2021/src/day17.rs
Normal file
81
2021/src/day17.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct Pair {
|
||||||
|
x: i64,
|
||||||
|
y: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProbeState {
|
||||||
|
pos: Pair,
|
||||||
|
vel: Pair,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Zone {
|
||||||
|
x: Range<i64>,
|
||||||
|
y: Range<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ProbeState {
|
||||||
|
fn new(vx: i64, vy: i64) -> Self {
|
||||||
|
let vel = Pair {x: vx, y: vy};
|
||||||
|
ProbeState {pos: Pair::default(), vel}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self) {
|
||||||
|
self.pos.x += self.vel.x;
|
||||||
|
self.pos.y += self.vel.y;
|
||||||
|
self.vel.y -= 1;
|
||||||
|
if self.vel.x > 0 {
|
||||||
|
self.vel.x -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_in(&self, zone: Zone) -> bool {
|
||||||
|
zone.x.contains(&self.pos.x) && zone.y.contains(&self.pos.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_past(&self, zone: Zone) -> bool {
|
||||||
|
self.pos.x >= zone.x.end || self.pos.y <= zone.y.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const VX_MIN: i64 = 23;
|
||||||
|
const VX_MAX: i64 = 293;
|
||||||
|
const VY_MIN: i64 = -69;
|
||||||
|
const VY_MAX: i64 = 68;
|
||||||
|
const TARGET: Zone = Zone {x: 269..293, y: -68..-43};
|
||||||
|
|
||||||
|
|
||||||
|
//sample data
|
||||||
|
// const VX_MIN: i64 = 6;
|
||||||
|
// const VX_MAX: i64 = 31;
|
||||||
|
// const VY_MIN: i64 = -11;
|
||||||
|
// const VY_MAX: i64 = 10;
|
||||||
|
// const TARGET: Zone = Zone {x: 20..31, y: -10..-4};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(_data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut total = 0;
|
||||||
|
// just brute force it, there are only ~35k possibilities
|
||||||
|
for vx in VX_MIN..VX_MAX {
|
||||||
|
for vy in VY_MIN..VY_MAX {
|
||||||
|
let mut state = ProbeState::new(vx, vy);
|
||||||
|
while !state.is_past(TARGET) {
|
||||||
|
state.step();
|
||||||
|
if state.is_in(TARGET) {
|
||||||
|
total += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((2278, total))
|
||||||
|
}
|
188
2021/src/day18.rs
Normal file
188
2021/src/day18.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Node {
|
||||||
|
Leaf(u8),
|
||||||
|
Branch {
|
||||||
|
left: Box<Node>,
|
||||||
|
right: Box<Node>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use Node::*;
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
fn unwrap_branch(&self) -> (&Node, &Node) {
|
||||||
|
match self {
|
||||||
|
Node::Leaf(_) => panic!("Attempted to unwrap branches from a leaf node"),
|
||||||
|
Node::Branch {left, right} => (left, right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leftmost_value_mut(&mut self) -> &mut u8 {
|
||||||
|
match self {
|
||||||
|
Node::Leaf(val) => val,
|
||||||
|
Node::Branch {left, ..} => left.leftmost_value_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rightmost_value_mut(&mut self) -> &mut u8 {
|
||||||
|
match self {
|
||||||
|
Node::Leaf(val) => val,
|
||||||
|
Node::Branch {right, ..} => right.rightmost_value_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn magnitude(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Node::Leaf(v) => *v as usize,
|
||||||
|
Node::Branch {left, right} => 3 * left.magnitude() + 2 * right.magnitude(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Parser<'a> {
|
||||||
|
chars: std::str::Chars<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn from(src: &'a str) -> Parser<'a> {
|
||||||
|
let mut chars = src.chars();
|
||||||
|
chars.next(); // throw away the opening bracket to avoid nesting too deeply
|
||||||
|
Parser {chars}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self) -> Node {
|
||||||
|
let mut left = None;
|
||||||
|
loop {
|
||||||
|
let node = match self.chars.next() {
|
||||||
|
Some('[') => self.parse(),
|
||||||
|
Some(','|']') => {continue;},
|
||||||
|
Some(c) => Node::Leaf(c.to_digit(10).unwrap() as u8),
|
||||||
|
None => {dbg!(left); panic!("Ran out of characters while parsing");},
|
||||||
|
};
|
||||||
|
|
||||||
|
if left.is_none() {
|
||||||
|
left = Some(node);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Node::Branch {
|
||||||
|
left: Box::new(left.unwrap()),
|
||||||
|
right: Box::new(node),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(data: &str) -> Vec<Node> {
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
let mut parser = Parser::from(line);
|
||||||
|
nodes.push(parser.parse());
|
||||||
|
}
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn explode(node: &mut Node, depth: usize) -> Option<(u8, u8)> {
|
||||||
|
// only branch nodes can explode
|
||||||
|
if let Leaf(_) = node {return None;}
|
||||||
|
|
||||||
|
if depth >= 4 {
|
||||||
|
if let (Leaf(v1), Leaf(v2)) = node.unwrap_branch() {
|
||||||
|
let result = (*v1, *v2);
|
||||||
|
*node = Leaf(0);
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Branch {left, right} = node {
|
||||||
|
if let Some((v1, v2)) = explode(left, depth + 1) {
|
||||||
|
if v2 > 0 {
|
||||||
|
*right.leftmost_value_mut() += v2;
|
||||||
|
return Some((v1, 0));
|
||||||
|
}
|
||||||
|
return Some((v1, v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((v1, v2)) = explode(right, depth + 1) {
|
||||||
|
if v1 > 0 {
|
||||||
|
*left.rightmost_value_mut() += v1;
|
||||||
|
return Some((0, v2));
|
||||||
|
}
|
||||||
|
return Some((v1, v2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn split(node: &mut Node) -> bool {
|
||||||
|
match node {
|
||||||
|
Leaf(v) if *v > 9 => {
|
||||||
|
*node = Branch {
|
||||||
|
left: Box::new(Leaf(*v / 2)),
|
||||||
|
right: Box::new(Leaf((*v + 1) / 2)),
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Leaf(_) => return false,
|
||||||
|
Branch {left, right} => {
|
||||||
|
if split(left) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return split(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn reduce(node: &mut Node) {
|
||||||
|
loop {
|
||||||
|
if let Some(..) = explode(node, 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if split(node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn add(left: Node, right: Node) -> Node {
|
||||||
|
let mut sum = Node::Branch {
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
};
|
||||||
|
reduce(&mut sum);
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let nodes = load(data);
|
||||||
|
let one = nodes.into_iter().reduce(|sum, next| add(sum, next)).unwrap();
|
||||||
|
|
||||||
|
// I'm sure there's a more efficient way to do this, but
|
||||||
|
let mut max = 0;
|
||||||
|
let nodes2 = load(data);
|
||||||
|
for i in 0..nodes2.len() {
|
||||||
|
for j in 0..nodes2.len() {
|
||||||
|
if i == j {continue;}
|
||||||
|
let a = nodes2[i].clone();
|
||||||
|
let b = nodes2[j].clone();
|
||||||
|
let magnitude = add(a, b).magnitude();
|
||||||
|
if magnitude > max {
|
||||||
|
max = magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((one.magnitude(), max))
|
||||||
|
}
|
84
2021/src/day2.rs
Normal file
84
2021/src/day2.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::{eyre, ensure};
|
||||||
|
|
||||||
|
use crate::lib::ParseLines;
|
||||||
|
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
Forward,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct MoveCmd {
|
||||||
|
dir: Direction,
|
||||||
|
value: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromStr for MoveCmd {
|
||||||
|
type Err = eyre::Report;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parts: Vec<&str> = s.split_whitespace().collect();
|
||||||
|
ensure!(parts.len() == 2, "Could not parse: \"{}\" (Wrong number of words)", s);
|
||||||
|
|
||||||
|
let dir = match parts[0] {
|
||||||
|
"forward" => Direction::Forward,
|
||||||
|
"up" => Direction::Up,
|
||||||
|
"down" => Direction::Down,
|
||||||
|
_ => return Err(eyre!("Could not parse: \"{}\" (invalid axis)", s)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = parts[1].parse::<i32>()?;
|
||||||
|
|
||||||
|
Ok(MoveCmd{dir, value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part1(commands: &Vec<MoveCmd>) -> i32 {
|
||||||
|
let mut horiz = 0; // how many units forward
|
||||||
|
let mut vert = 0; // how many units DOWN (so flipped)
|
||||||
|
for cmd in commands {
|
||||||
|
match cmd.dir {
|
||||||
|
Direction::Forward => horiz += cmd.value,
|
||||||
|
Direction::Up => vert -= cmd.value,
|
||||||
|
Direction::Down => vert += cmd.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horiz * vert
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part2(commands: &Vec<MoveCmd>) -> i32 {
|
||||||
|
let mut horiz = 0;
|
||||||
|
let mut vert = 0;
|
||||||
|
let mut aim = 0;
|
||||||
|
for cmd in commands {
|
||||||
|
match cmd.dir {
|
||||||
|
Direction::Down => aim += cmd.value, // flipped again
|
||||||
|
Direction::Up => aim -= cmd.value,
|
||||||
|
Direction::Forward => {
|
||||||
|
horiz += cmd.value;
|
||||||
|
vert += cmd.value * aim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horiz * vert
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(i32, i32)> {
|
||||||
|
let mut commands = Vec::new();
|
||||||
|
for res in data.parse_lines::<MoveCmd>() {
|
||||||
|
commands.push(res?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((part1(&commands), part2(&commands)))
|
||||||
|
}
|
67
2021/src/day3.rs
Normal file
67
2021/src/day3.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
fn most_common_bit(numbers: &Vec<u16>, pos: u16) -> u16 {
|
||||||
|
// number of times the bit in question is 1,
|
||||||
|
// minus the number of times it's 0
|
||||||
|
let mask: u16 = 1 << pos;
|
||||||
|
let total: isize = numbers
|
||||||
|
.iter()
|
||||||
|
.map(|n| if n & mask > 0 {1} else {-1})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// if the total is positive, then 1 is more frequent than 0
|
||||||
|
if total >= 0 {1} else {0}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part1(numbers: &Vec<u16>) -> u64 {
|
||||||
|
let gamma = (0..12).reduce(|g, i|
|
||||||
|
g | most_common_bit(numbers, i) << i
|
||||||
|
).unwrap(); // unwrap is safe here since this iterator will always have elements
|
||||||
|
|
||||||
|
// epsilon is just the bit inverse of gamma, but since these are really 12-bit numbers
|
||||||
|
// we have to clear the 4 spurious 1's that come from inverting the original
|
||||||
|
let epsilon = !gamma & (65535 >> 4);
|
||||||
|
epsilon as u64 * gamma as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn filter_bit_criteria(numbers: Vec<u16>, bit_pos: u16, bit_value: u16) -> Vec<u16> {
|
||||||
|
numbers
|
||||||
|
.into_iter()
|
||||||
|
.filter(|n| (n >> bit_pos) & 1 == bit_value)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part2(numbers: &Vec<u16>) -> u64 {
|
||||||
|
let mut oxy = numbers.clone();
|
||||||
|
let mut co2 = numbers.clone();
|
||||||
|
for pos in (0..12).rev() {
|
||||||
|
if oxy.len() > 1 {
|
||||||
|
let oxy_bit = most_common_bit(&oxy, pos);
|
||||||
|
oxy = filter_bit_criteria(oxy, pos, oxy_bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if co2.len() > 1 {
|
||||||
|
let co2_bit = most_common_bit(&co2, pos) ^ 1; // flip the rightmost bit, giving us the least common instead
|
||||||
|
co2 = filter_bit_criteria(co2, pos, co2_bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume both vecs have len == 1 now, since otherwise it means
|
||||||
|
// AoC gave us an invalid problem input
|
||||||
|
oxy[0] as u64 * co2[0] as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(u64, u64)> {
|
||||||
|
let mut nums = Vec::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
nums.push(u16::from_str_radix(line, 2)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((part1(&nums), part2(&nums)))
|
||||||
|
}
|
211
2021/src/day4.rs
Normal file
211
2021/src/day4.rs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
struct Ratchet<T, F>
|
||||||
|
where T: Copy,
|
||||||
|
F: Fn(T) -> u8,
|
||||||
|
{
|
||||||
|
value: Option<T>,
|
||||||
|
getter: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F> Ratchet<T, F>
|
||||||
|
where T: Copy,
|
||||||
|
F: Fn(T) -> u8
|
||||||
|
{
|
||||||
|
fn new(getter: F) -> Self {
|
||||||
|
Ratchet {
|
||||||
|
value: None,
|
||||||
|
getter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc(&mut self, other: T) {
|
||||||
|
match self.value {
|
||||||
|
Some(v) if (self.getter)(other) <= (self.getter)(v) => (),
|
||||||
|
_ => self.value = Some(other),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec(&mut self, other: T) {
|
||||||
|
match self.value {
|
||||||
|
Some(v) if (self.getter)(other) >= (self.getter)(v) => (),
|
||||||
|
_ => self.value = Some(other),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
|
struct Square {
|
||||||
|
value: u8,
|
||||||
|
draw_time: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
|
struct Board {
|
||||||
|
squares: [[Square; 5]; 5],
|
||||||
|
bingo_time: Option<u8>,
|
||||||
|
bingo_value: Option<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
fn from(squares: &[Square]) -> eyre::Result<Self> {
|
||||||
|
let mut it = squares.iter();
|
||||||
|
let mut b = Self::default();
|
||||||
|
for row in 0..5 {
|
||||||
|
for col in 0..5 {
|
||||||
|
match it.next() {
|
||||||
|
Some(sq) => b.squares[row][col] = *sq,
|
||||||
|
None => return Err(eyre!("Not enough values to populate board")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bingo(&mut self) {
|
||||||
|
let mut board_bingo = Ratchet::new(|sq: Square| sq.draw_time.unwrap()); // these unwraps are safe because we will only call inc() or dec() after ensuring there is a value there
|
||||||
|
let mut cols_bingo: [bool; 5] = [true; 5];
|
||||||
|
let mut cols_last_drawn: Vec<Ratchet<Square, _>> = (0..5)
|
||||||
|
.map(|_i| Ratchet::new(|sq: Square| sq.draw_time.unwrap()))
|
||||||
|
.collect();
|
||||||
|
// let mut cols_last_drawn: [Ratchet<Square, _>; 5] = [Ratchet::new(|sq: Square| sq.draw_time.unwrap()); 5];
|
||||||
|
for row in self.squares {
|
||||||
|
let mut row_bingo = true;
|
||||||
|
let mut row_last_drawn = Ratchet::new(|sq: Square| sq.draw_time.unwrap());
|
||||||
|
for (col, sq) in row.iter().enumerate() {
|
||||||
|
match sq.draw_time {
|
||||||
|
Some(_) => {
|
||||||
|
row_last_drawn.inc(*sq);
|
||||||
|
cols_last_drawn[col].inc(*sq);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
row_bingo = false;
|
||||||
|
cols_bingo[col] = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if row_bingo {
|
||||||
|
let bingo_sq = row_last_drawn.value.unwrap(); // we know there will be a value here if row_bingo is true
|
||||||
|
board_bingo.dec(bingo_sq); //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for col in 0..5 {
|
||||||
|
if cols_bingo[col] {
|
||||||
|
let bingo_sq = cols_last_drawn[col].value.unwrap(); // similar to above
|
||||||
|
board_bingo.dec(bingo_sq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sq) = board_bingo.value {
|
||||||
|
self.bingo_time = sq.draw_time;
|
||||||
|
self.bingo_value = Some(sq.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn score(&self) -> eyre::Result<isize> {
|
||||||
|
let bingo_time = match self.bingo_time {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return Err(eyre!("Cannot find the score of a non-winning board")),
|
||||||
|
};
|
||||||
|
let mut total = 0;
|
||||||
|
for row in self.squares {
|
||||||
|
for sq in row {
|
||||||
|
if sq.draw_time.is_some() && sq.draw_time.unwrap() > bingo_time {
|
||||||
|
total += sq.value as isize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let multiplier = self.bingo_value.unwrap() as isize;
|
||||||
|
Ok(total * multiplier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Board {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
for row in self.squares {
|
||||||
|
write!(f, "|")?;
|
||||||
|
for (i, sq) in row.iter().enumerate() {
|
||||||
|
let dt = sq.draw_time.map(|dt| format!("{}", dt)).unwrap_or(String::from("__"));
|
||||||
|
write!(f, "({}: {})", sq.value, dt)?;
|
||||||
|
if i < 4 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "|\n")?;
|
||||||
|
}
|
||||||
|
if self.bingo_time.is_some() {
|
||||||
|
write!(f, "Bingo: ({}: {})", self.bingo_value.unwrap(), self.bingo_time.unwrap())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
write!(f, "Bingo: (__: __)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<Vec<Board>> {
|
||||||
|
let mut lines = data.lines().peekable();
|
||||||
|
let mut draws: [Option<u8>; 100] = [None; 100]; // indices are the number drawn, values are the position in which it was drawn
|
||||||
|
for (i, draw) in lines.next().unwrap().split(',').enumerate() {
|
||||||
|
let num = draw.parse::<usize>()?;
|
||||||
|
draws[num] = Some(i.try_into().unwrap()); // will panic if there are more than 256 items in this iterator, but there aren't
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut boards = Vec::new();
|
||||||
|
let mut squares = Vec::new();
|
||||||
|
for line in lines {
|
||||||
|
for s in line.split_whitespace() {
|
||||||
|
let value = s.parse::<u8>()?; // shadows the loop variable I guess?
|
||||||
|
let sq = Square {value, draw_time: draws[value as usize]};
|
||||||
|
squares.push(sq);
|
||||||
|
}
|
||||||
|
if squares.len() == 25 {
|
||||||
|
boards.push(Board::from(&squares)?);
|
||||||
|
boards.last_mut().unwrap().set_bingo();
|
||||||
|
squares.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(boards)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part1(boards: &[Board]) -> eyre::Result<Board> {
|
||||||
|
let winner = boards.iter()
|
||||||
|
.filter(|b| b.bingo_time.is_some())
|
||||||
|
.min_by_key(|b| b.bingo_time.unwrap());
|
||||||
|
|
||||||
|
match winner {
|
||||||
|
Some(w) => Ok(*w),
|
||||||
|
None => Err(eyre!("Could not find a winning board")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part2(boards: &[Board]) -> eyre::Result<Board> {
|
||||||
|
let winner = boards.iter()
|
||||||
|
.filter(|b| b.bingo_time.is_some())
|
||||||
|
.max_by_key(|b| b.bingo_time.unwrap());
|
||||||
|
|
||||||
|
match winner {
|
||||||
|
Some(w) => Ok(*w),
|
||||||
|
None => Err(eyre!("Could not find a winning board")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(isize, isize)> {
|
||||||
|
let boards = load(data)?;
|
||||||
|
let winner = part1(&boards)?;
|
||||||
|
let loser = part2(&boards)?;
|
||||||
|
Ok((winner.score()?, loser.score()?))
|
||||||
|
}
|
150
2021/src/day5.rs
Normal file
150
2021/src/day5.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use num::integer::gcd;
|
||||||
|
|
||||||
|
use crate::lib::{ParseLines, IterExt};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct Point {
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct Slope {
|
||||||
|
rise: isize,
|
||||||
|
run: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slope {
|
||||||
|
fn apply_to(&self, pt: Point) -> Point {
|
||||||
|
Point {
|
||||||
|
x: pt.x + self.run,
|
||||||
|
y: pt.y + self.rise,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct Line {
|
||||||
|
start: Point,
|
||||||
|
end: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Line {
|
||||||
|
fn slope(&self) -> Slope {
|
||||||
|
let dx = self.end.y - self.start.y;
|
||||||
|
let dy = self.end.x - self.start.x;
|
||||||
|
|
||||||
|
let divisor;
|
||||||
|
if dx == 0 || dy == 0 {
|
||||||
|
divisor = (dx + dy).abs();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
divisor = gcd(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Slope {
|
||||||
|
rise: dx / divisor,
|
||||||
|
run: dy / divisor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn points(&self) -> PointsIter {
|
||||||
|
PointsIter {
|
||||||
|
line: self,
|
||||||
|
pos: self.start,
|
||||||
|
slope: self.slope(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PointsIter<'a> {
|
||||||
|
line: &'a Line,
|
||||||
|
pos: Point,
|
||||||
|
slope: Slope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for PointsIter<'_> {
|
||||||
|
type Item = Point;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// note, this approach will break if total dX and dY are not multiples of rise/run
|
||||||
|
if self.pos == self.slope.apply_to(self.line.end) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let orig = self.pos;
|
||||||
|
self.pos = self.slope.apply_to(self.pos);
|
||||||
|
Some(orig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromStr for Line {
|
||||||
|
type Err = eyre::Report;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (p1, p2) = s.split(" -> ").take_pair()?;
|
||||||
|
let (x1, y1) = p1.split(',').take_pair()?;
|
||||||
|
let (x2, y2) = p2.split(',').take_pair()?;
|
||||||
|
|
||||||
|
let start = Point {
|
||||||
|
x: x1.parse::<isize>()?,
|
||||||
|
y: y1.parse::<isize>()?,
|
||||||
|
};
|
||||||
|
let end = Point {
|
||||||
|
x: x2.parse::<isize>()?,
|
||||||
|
y: y2.parse::<isize>()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Line {start, end})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part1(lines: &[Line]) -> usize {
|
||||||
|
let mut grid = vec![[0; 1000]; 1000];
|
||||||
|
|
||||||
|
let points = lines.iter()
|
||||||
|
.filter(|l| l.start.x == l.end.x || l.start.y == l.end.y)
|
||||||
|
.flat_map(|l| l.points());
|
||||||
|
|
||||||
|
for pt in points {
|
||||||
|
grid[pt.y as usize][pt.x as usize] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.into_iter()
|
||||||
|
.flat_map(|row| row.into_iter())
|
||||||
|
.filter(|n| *n > 1)
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part2(lines: &[Line]) -> usize {
|
||||||
|
let mut grid = vec![[0; 1000]; 1000];
|
||||||
|
|
||||||
|
for pt in lines.iter().flat_map(|l| l.points()) {
|
||||||
|
grid[pt.y as usize][pt.x as usize] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.into_iter()
|
||||||
|
.flat_map(|row| row.into_iter())
|
||||||
|
.filter(|n| *n > 1)
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
for line in data.parse_lines::<Line>() {
|
||||||
|
lines.push(line?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((part1(&lines), part2(&lines)))
|
||||||
|
}
|
56
2021/src/day6.rs
Normal file
56
2021/src/day6.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
struct Ocean {
|
||||||
|
fish: [usize; 9],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ocean {
|
||||||
|
fn from(ages: &[usize]) -> Self {
|
||||||
|
let mut ocean = Ocean {fish: [0; 9]};
|
||||||
|
for n in ages {
|
||||||
|
ocean.fish[*n] += 1;
|
||||||
|
}
|
||||||
|
ocean
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self) {
|
||||||
|
let mut new = [0; 9];
|
||||||
|
|
||||||
|
// 0 is a special case
|
||||||
|
new[8] += self.fish[0]; // newly-created fish
|
||||||
|
new[6] += self.fish[0]; // existing fish with timers reset
|
||||||
|
for i in 1..9 {
|
||||||
|
new[i - 1] += self.fish[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fish = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sum(&self) -> usize {
|
||||||
|
self.fish.iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_by(&mut self, n: usize) {
|
||||||
|
for _ in 0..n {
|
||||||
|
self.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut ages = Vec::new();
|
||||||
|
for a in data.trim().split(',') {
|
||||||
|
ages.push(a.parse::<usize>()?);
|
||||||
|
}
|
||||||
|
let mut ocean = Ocean::from(&ages);
|
||||||
|
|
||||||
|
ocean.step_by(80);
|
||||||
|
let one = ocean.sum();
|
||||||
|
|
||||||
|
ocean.step_by(256 - 80);
|
||||||
|
let two = ocean.sum();
|
||||||
|
|
||||||
|
Ok((one, two))
|
||||||
|
}
|
56
2021/src/day7.rs
Normal file
56
2021/src/day7.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
fn distance_between(a: usize, b: usize) -> usize {
|
||||||
|
// can't do the fancypants (a - b).abs() thing here because these are unsigned
|
||||||
|
if a > b {
|
||||||
|
a - b
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b - a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn cost_simple(counts: &[usize], pos: usize) -> usize {
|
||||||
|
counts.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(0, |total, (i, count)| {
|
||||||
|
total + (distance_between(pos, i) * count) // yes, these parens are unnecessary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn cost_complex(counts: &[usize], pos: usize) -> usize {
|
||||||
|
counts.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(0, |total, (i, count)| {
|
||||||
|
let dx = distance_between(pos, i);
|
||||||
|
let cost_single = dx * (dx + 1) / 2; // Gauss' method for summing numbers from 1 to n
|
||||||
|
total + (cost_single * count)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let mut counts = Vec::new();
|
||||||
|
for s in data.trim().split(',') {
|
||||||
|
let n = s.parse::<usize>()?;
|
||||||
|
if n >= counts.len() {
|
||||||
|
counts.resize(n + 1, 0);
|
||||||
|
}
|
||||||
|
counts[n] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min_simple = usize::MAX;
|
||||||
|
let mut min_complex = usize::MAX;
|
||||||
|
for i in 0..counts.len() {
|
||||||
|
let simple = cost_simple(&counts, i);
|
||||||
|
if simple < min_simple {min_simple = simple;}
|
||||||
|
|
||||||
|
let complex = cost_complex(&counts, i);
|
||||||
|
if complex < min_complex {min_complex = complex;}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((min_simple, min_complex))
|
||||||
|
}
|
280
2021/src/day8.rs
Normal file
280
2021/src/day8.rs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::{ParseLines, IterExt};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
enum Segment {
|
||||||
|
A = 1,
|
||||||
|
B = 2,
|
||||||
|
C = 4,
|
||||||
|
D = 8,
|
||||||
|
E = 16,
|
||||||
|
F = 32,
|
||||||
|
G = 64, // wheee!
|
||||||
|
}
|
||||||
|
use Segment::*;
|
||||||
|
|
||||||
|
impl Segment {
|
||||||
|
fn from_char(c: char) -> eyre::Result<Self> {
|
||||||
|
match c {
|
||||||
|
'a' => Ok(A),
|
||||||
|
'b' => Ok(B),
|
||||||
|
'c' => Ok(C),
|
||||||
|
'd' => Ok(D),
|
||||||
|
'e' => Ok(E),
|
||||||
|
'f' => Ok(F),
|
||||||
|
'g' => Ok(G),
|
||||||
|
_ => Err(eyre!("Invalid character for segment: {}", c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_ord(&self) -> usize {
|
||||||
|
// kinda gross but whatever
|
||||||
|
match self {
|
||||||
|
A => 0, B => 1, C => 2, D => 3,
|
||||||
|
E => 4, F => 5, G => 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_ord(n: u8) -> eyre::Result<Self> {
|
||||||
|
match n {
|
||||||
|
0 => Ok(A),
|
||||||
|
1 => Ok(B),
|
||||||
|
2 => Ok(C),
|
||||||
|
3 => Ok(D),
|
||||||
|
4 => Ok(E),
|
||||||
|
5 => Ok(F),
|
||||||
|
6 => Ok(G),
|
||||||
|
_ => Err(eyre!("Invalid ordinal for segment: {}", n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_value(n: u8) -> eyre::Result<Self> {
|
||||||
|
match n {
|
||||||
|
1 => Ok(A),
|
||||||
|
2 => Ok(B),
|
||||||
|
4 => Ok(C),
|
||||||
|
8 => Ok(D),
|
||||||
|
16 => Ok(E),
|
||||||
|
32 => Ok(F),
|
||||||
|
64 => Ok(G),
|
||||||
|
_ => Err(eyre!("Invalid value for segment: {}", n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
|
struct SegmentSet {
|
||||||
|
data: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentSet {
|
||||||
|
fn contains(&self, segment: Segment) -> bool {
|
||||||
|
self.data & (segment as u8) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, segment: Segment) {
|
||||||
|
self.data |= segment as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> u8 {
|
||||||
|
(0..7).fold(0, |total, i| total + ((self.data >> i) & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exclusion(&self, other: &Self) -> Self {
|
||||||
|
SegmentSet {data: self.data ^ other.data}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&self) -> SegmentSetIter {
|
||||||
|
SegmentSetIter {ss: self, pos: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SegmentSet {
|
||||||
|
type Err = eyre::Report;
|
||||||
|
fn from_str(s: &str) -> eyre::Result<Self> {
|
||||||
|
let mut set = Self::default();
|
||||||
|
for c in s.chars() {
|
||||||
|
set.insert(Segment::from_char(c)?);
|
||||||
|
}
|
||||||
|
Ok(set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SegmentSetIter<'a> {
|
||||||
|
ss: &'a SegmentSet,
|
||||||
|
pos: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for SegmentSetIter<'_> {
|
||||||
|
type Item = Segment;
|
||||||
|
fn next(&mut self) -> Option<Segment> {
|
||||||
|
while self.pos < 7 {
|
||||||
|
let seg_value = self.ss.data & (1 << self.pos);
|
||||||
|
self.pos += 1;
|
||||||
|
if seg_value > 0 {
|
||||||
|
return Some(Segment::from_value(seg_value).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct Screen {
|
||||||
|
patterns: [SegmentSet; 10],
|
||||||
|
output: [SegmentSet; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Screen {
|
||||||
|
type Err = eyre::Report;
|
||||||
|
fn from_str(s: &str) -> eyre::Result<Self> {
|
||||||
|
let mut screen = Screen::default();
|
||||||
|
let (patterns, output) = s.split(" | ").take_pair()?;
|
||||||
|
|
||||||
|
for (i, patt) in patterns.split(' ').enumerate() {
|
||||||
|
screen.patterns[i] = patt.parse::<SegmentSet>()?;
|
||||||
|
}
|
||||||
|
for (i, out) in output.split(' ').enumerate() {
|
||||||
|
screen.output[i] = out.parse::<SegmentSet>()?;
|
||||||
|
}
|
||||||
|
Ok(screen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn segment_counts(screen: &Screen) -> [usize; 7] {
|
||||||
|
let mut counts = [0; 7];
|
||||||
|
for i in 0..7 {
|
||||||
|
let segment = Segment::from_ord(i).unwrap();
|
||||||
|
counts[i as usize] = screen.patterns.iter()
|
||||||
|
.filter(|p| p.contains(segment))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn find_a(screen: &Screen) -> Segment {
|
||||||
|
// figure out which segment is currently attached to the "A" wire
|
||||||
|
let mut seven = None;
|
||||||
|
let mut one = None;
|
||||||
|
for p in screen.patterns {
|
||||||
|
if p.len() == 2 {
|
||||||
|
one = Some(p); // the only digit that uses 2 segments is 1
|
||||||
|
}
|
||||||
|
else if p.len() == 3 {
|
||||||
|
seven = Some(p); // similarly for 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = seven.unwrap().exclusion(&one.unwrap());
|
||||||
|
if result.len() != 1 {
|
||||||
|
panic!("This should never happen");
|
||||||
|
}
|
||||||
|
result.iter().next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn solve(screen: &Screen) -> [Segment; 7] {
|
||||||
|
let mut result = [A; 7]; // A is just a placeholder value
|
||||||
|
let four_pattern = screen.patterns.iter().find(|p| p.len() == 4).unwrap();
|
||||||
|
for (i, count) in segment_counts(screen).iter().enumerate() {
|
||||||
|
let possible = match count {
|
||||||
|
4 => Some(E),
|
||||||
|
6 => Some(B),
|
||||||
|
7|8 => None,
|
||||||
|
9 => Some(F),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(segment) = possible {
|
||||||
|
result[i] = segment;
|
||||||
|
}
|
||||||
|
else if *count == 7 { // 7 appearances means either D or G
|
||||||
|
let segment = Segment::from_ord(i as u8).unwrap();
|
||||||
|
if four_pattern.contains(segment) {
|
||||||
|
result[i] = D;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[i] = G;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if *count == 8 { // similarly 8 should be either A or C
|
||||||
|
let segment = Segment::from_ord(i as u8).unwrap();
|
||||||
|
if segment == find_a(screen) {
|
||||||
|
result[i] = A;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[i] = C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn decode(output: [SegmentSet; 4], map: [Segment; 7]) -> usize {
|
||||||
|
let mut result = 0;
|
||||||
|
for (i, segments_enc) in output.iter().enumerate() {
|
||||||
|
let mut segments_dec = SegmentSet::default();
|
||||||
|
for segment_enc in segments_enc.iter() {
|
||||||
|
let segment_dec = map[segment_enc.to_ord()];
|
||||||
|
segments_dec.insert(segment_dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
let digit = match segments_dec.data {
|
||||||
|
0b01110111 => 0,
|
||||||
|
0b00100100 => 1,
|
||||||
|
0b01011101 => 2,
|
||||||
|
0b01101101 => 3,
|
||||||
|
0b00101110 => 4,
|
||||||
|
0b01101011 => 5,
|
||||||
|
0b01111011 => 6,
|
||||||
|
0b00100101 => 7,
|
||||||
|
0b01111111 => 8,
|
||||||
|
0b01101111 => 9,
|
||||||
|
_ => {
|
||||||
|
dbg!(segments_dec);
|
||||||
|
panic!("Invalid segment sequence");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
result += digit * 10usize.pow(3 - (i as u32));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part2(screens: &[Screen]) -> usize {
|
||||||
|
let mut result = 0;
|
||||||
|
for screen in screens {
|
||||||
|
let map = solve(screen);
|
||||||
|
result += decode(screen.output, map);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn part1(screens: &[Screen]) -> usize {
|
||||||
|
screens.iter()
|
||||||
|
.flat_map(|d| d.output.iter())
|
||||||
|
.filter(|s| matches!(s.len(), 2|3|4|7))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
|
||||||
|
let screens = data
|
||||||
|
.parse_lines::<Screen>()
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok((part1(&screens), part2(&screens)))
|
||||||
|
}
|
143
2021/src/day9.rs
Normal file
143
2021/src/day9.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
use crate::lib::{IterExt, Vec2};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Cell {
|
||||||
|
height: u32,
|
||||||
|
min: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NeighborsIter {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
state: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for NeighborsIter {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let neighbor = match self.state {
|
||||||
|
0 if self.x > 0 => Some((self.y, self.x - 1)),
|
||||||
|
1 if self.y > 0 => Some((self.y - 1, self.x)),
|
||||||
|
2 if self.x < self.cols - 1 => Some((self.y, self.x + 1)),
|
||||||
|
3 if self.y < self.rows - 1 => Some((self.y + 1, self.x)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
self.state += 1;
|
||||||
|
|
||||||
|
if self.state > 4 {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
neighbor.or_else(|| self.next())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load(data: &str) -> eyre::Result<Vec2<Cell>> {
|
||||||
|
let num_columns = data.lines().next().unwrap().len();
|
||||||
|
let mut heights = Vec2::new(num_columns);
|
||||||
|
for line in data.lines() {
|
||||||
|
let mut row = Vec::new();
|
||||||
|
for c in line.chars() {
|
||||||
|
match c.to_digit(10) {
|
||||||
|
Some(d) => row.push(Cell {height: d, min: false}),
|
||||||
|
None => return Err(eyre!("Invalid character (not a digit): {}", c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
heights.push(row);
|
||||||
|
}
|
||||||
|
Ok(heights)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn scan(map: &mut Vec2<Cell>) {
|
||||||
|
for row in 0..map.row_count() {
|
||||||
|
for col in 0..map.col_count() {
|
||||||
|
let mut lt_left = true;
|
||||||
|
if col > 0 {
|
||||||
|
if map[row][col].height < map[row][col - 1].height {
|
||||||
|
map[row][col - 1].min = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lt_left = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lt_top = true;
|
||||||
|
if row > 0 {
|
||||||
|
if map[row][col].height < map[row - 1][col].height {
|
||||||
|
map[row - 1][col].min = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lt_top = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lt_left && lt_top {
|
||||||
|
map[row][col].min = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn low_points(map: &Vec2<Cell>) -> impl Iterator<Item=(usize, &Cell)> {
|
||||||
|
map.values().enumerate().filter(|(_, c)| c.min)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn neighbors(map: &Vec2<Cell>, coords: (usize, usize)) -> NeighborsIter {
|
||||||
|
NeighborsIter {
|
||||||
|
x: coords.1,
|
||||||
|
y: coords.0,
|
||||||
|
rows: map.row_count(),
|
||||||
|
cols: map.col_count(),
|
||||||
|
state: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn basin_size (map: &Vec2<Cell>, start: (usize, usize), visited: &mut HashSet<(usize, usize)>) -> u32 {
|
||||||
|
let mut total = 1;
|
||||||
|
for coords in neighbors(map, start) {
|
||||||
|
if map[coords.0][coords.1].height < 9 && !visited.contains(&coords) {
|
||||||
|
visited.insert(coords);
|
||||||
|
total += basin_size(map, coords, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn basins(map: &Vec2<Cell>) -> u32 {
|
||||||
|
low_points(map)
|
||||||
|
.map(|(i, _)| {
|
||||||
|
let start = map.coords(i);
|
||||||
|
basin_size(map, start, &mut HashSet::from([start]))
|
||||||
|
})
|
||||||
|
.max_n(3)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.product()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run(data: &str) -> eyre::Result<(u32, u32)> {
|
||||||
|
// let sample = "2199943210\n3987894921\n9856789892\n8767896789\n9899965678";
|
||||||
|
let mut map = load(data)?;
|
||||||
|
scan(&mut map);
|
||||||
|
|
||||||
|
let one = low_points(&map).fold(0, |acc, (_, c)| acc + c.height + 1);
|
||||||
|
let two = basins(&map);
|
||||||
|
|
||||||
|
Ok((one, two))
|
||||||
|
}
|
468
2021/src/lib.rs
Normal file
468
2021/src/lib.rs
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::ops::{Index, IndexMut, Range};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use color_eyre::{eyre};
|
||||||
|
use eyre::eyre;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn load(filename: &str) -> eyre::Result<String> {
|
||||||
|
let mut file = File::open(filename)?;
|
||||||
|
let mut data = String::new();
|
||||||
|
file.read_to_string(&mut data)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct LineParser<'a, T: FromStr> {
|
||||||
|
it: std::str::Lines<'a>,
|
||||||
|
convert: fn(&str) -> Result<T, <T as FromStr>::Err>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<'a, T: FromStr> Iterator for LineParser<'a, T> {
|
||||||
|
type Item = Result<T, <T as FromStr>::Err>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.it.next().map(|s|
|
||||||
|
(self.convert)(s)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// define a trait so that we can attach it to String
|
||||||
|
pub trait ParseLines {
|
||||||
|
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ParseLines for str {
|
||||||
|
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T> {
|
||||||
|
LineParser {
|
||||||
|
it: self.lines(),
|
||||||
|
convert: str::parse::<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ParseLines for String {
|
||||||
|
fn parse_lines<T: FromStr>(&self) -> LineParser<'_, T> {
|
||||||
|
self[..].parse_lines::<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait IterExt: Iterator {
|
||||||
|
fn take_pair(&mut self) -> eyre::Result<(Self::Item, Self::Item)> {
|
||||||
|
let a = match self.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(eyre!("Not enough values to unpack")),
|
||||||
|
};
|
||||||
|
let b = match self.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(eyre!("Not enough values to unpack")),
|
||||||
|
};
|
||||||
|
Ok((a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_n(&mut self, n: usize) -> eyre::Result<VecDeque<Self::Item>>
|
||||||
|
where Self: Sized, Self::Item: Ord,
|
||||||
|
{
|
||||||
|
let mut items = VecDeque::with_capacity(n);
|
||||||
|
while let Some(item) = self.next() {
|
||||||
|
for i in 0..=items.len() {
|
||||||
|
if i < items.len() && item > items[i] {
|
||||||
|
if items.len() == n {
|
||||||
|
items.pop_back();
|
||||||
|
}
|
||||||
|
items.insert(i, item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if i == items.len() && i < n {
|
||||||
|
items.push_back(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() < n {
|
||||||
|
Err(eyre!("Not enough values to take the maximum {}", n))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator> IterExt for I {}
|
||||||
|
|
||||||
|
|
||||||
|
// 2d container, functions kind of like a "vec of vecs" but all data is stored in a single vec for memory contiguity
|
||||||
|
pub struct Vec2<T> {
|
||||||
|
data: Vec<T>,
|
||||||
|
columns: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct NeighborCoords {
|
||||||
|
num_rows: usize,
|
||||||
|
num_cols: usize,
|
||||||
|
start_row: usize,
|
||||||
|
start_col: usize,
|
||||||
|
i: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for NeighborCoords {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.i > 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rel_row, rel_col) = (self.i / 3, self.i % 3);
|
||||||
|
self.i += 1;
|
||||||
|
if rel_row == 1 && rel_col == 1 {
|
||||||
|
return self.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
let row_offset = self.start_row + rel_row;
|
||||||
|
let col_offset = self.start_col + rel_col;
|
||||||
|
// the "neighbor window" is off by 1, so we make the comparisons off by 1 as well
|
||||||
|
if row_offset > 0 && row_offset <= self.num_rows
|
||||||
|
&& col_offset > 0 && col_offset <= self.num_cols
|
||||||
|
{
|
||||||
|
Some((row_offset - 1, col_offset - 1))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct RectNeighbors {
|
||||||
|
nc: NeighborCoords,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for RectNeighbors {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some((row, col)) = self.nc.next() {
|
||||||
|
if row == self.nc.start_row || col == self.nc.start_col {
|
||||||
|
return Some((row, col));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return self.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl<T> Vec2<T> {
|
||||||
|
pub fn new(columns: usize) -> Vec2<T> {
|
||||||
|
Vec2 {data: Vec::<T>::new(), columns}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity(rows: usize, columns: usize) -> Vec2<T> {
|
||||||
|
Vec2 {data: Vec::<T>::with_capacity(rows * columns), columns}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push<S: IntoIterator<Item=T>>(&mut self, row: S) {
|
||||||
|
self.data.extend(row.into_iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_count(&self) -> usize {
|
||||||
|
self.data.len() / self.columns
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_count(&self) -> usize {
|
||||||
|
self.columns
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn coords(&self, i: usize) -> (usize, usize) {
|
||||||
|
// convert underlying index into 2d coordinates
|
||||||
|
(i / self.columns, i % self.columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(&self, row: usize, col: usize) -> usize {
|
||||||
|
// and the reverse
|
||||||
|
self.columns * row + col
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rows(&self) -> impl Iterator<Item=&[T]> {
|
||||||
|
self.data.chunks_exact(self.columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rows_mut(&mut self) -> impl Iterator<Item=&mut [T]> {
|
||||||
|
self.data.chunks_exact_mut(self.columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(&self) -> impl Iterator<Item=&T> {
|
||||||
|
self.data.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values_mut(&mut self) -> impl Iterator<Item=&mut T> {
|
||||||
|
self.data.iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn neighbor_coords(&self, row: usize, col: usize) -> NeighborCoords {
|
||||||
|
NeighborCoords {
|
||||||
|
num_rows: self.row_count(),
|
||||||
|
num_cols: self.col_count(),
|
||||||
|
start_row: row,
|
||||||
|
start_col: col,
|
||||||
|
i: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect_neighbors(&self, row: usize, col: usize) -> RectNeighbors {
|
||||||
|
RectNeighbors {nc: self.neighbor_coords(row, col)}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, row: usize, col: usize) -> &T {
|
||||||
|
&self.data[self.offset(row, col)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, row: usize, col: usize) -> &mut T {
|
||||||
|
self.data.index_mut(self.offset(row, col))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, row: usize, col: usize, val: T) {
|
||||||
|
let existing = self.data.index_mut(self.offset(row, col));
|
||||||
|
*existing = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl<T: Copy> Vec2<T> {
|
||||||
|
pub fn fill(&mut self, value: T) {
|
||||||
|
for i in 0..self.data.len() {
|
||||||
|
self.data[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_to_cap(&mut self, value: T) {
|
||||||
|
self.fill(value);
|
||||||
|
for _i in self.data.len()..self.data.capacity() {
|
||||||
|
self.data.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> Index<usize> for Vec2<T> {
|
||||||
|
type Output = [T];
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
let start = self.columns * index;
|
||||||
|
&self.data[start..(start + self.columns)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> IndexMut<usize> for Vec2<T> {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
let start = self.columns * index;
|
||||||
|
&mut self.data[start..(start + self.columns)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T: Display> Display for Vec2<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||||
|
for row in self.rows() {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, v) in row.iter().enumerate() {
|
||||||
|
if i > 0 {write!(f, "")?;}
|
||||||
|
write!(f, "{}", v)?;
|
||||||
|
}
|
||||||
|
write!(f, "]\n")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Counter<K> {
|
||||||
|
data: HashMap<K, usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl<K> Counter<K>
|
||||||
|
where K: Eq + Hash,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Counter {data: HashMap::<K, usize>::new()}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc(&mut self, key: K) {
|
||||||
|
let v = self.data.entry(key).or_insert(0);
|
||||||
|
*v += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_by(&mut self, key: K, increment: usize) {
|
||||||
|
let v = self.data.entry(key).or_insert(0);
|
||||||
|
*v += increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec(&mut self, key: K) {
|
||||||
|
let v = self.data.entry(key).or_insert(0);
|
||||||
|
if *v > 0 {
|
||||||
|
*v -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dec_by(&mut self, key: K, decrement: usize) {
|
||||||
|
let v = self.data.entry(key).or_insert(0);
|
||||||
|
if *v >= decrement {
|
||||||
|
*v -= decrement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, key: &K) -> usize {
|
||||||
|
*self.data.get(key).unwrap_or(&0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, key: K, value: usize) {
|
||||||
|
self.data.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item =(&'_ K, usize)> {
|
||||||
|
self.data.iter().map(|(k, v)| (k, *v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BitVec {
|
||||||
|
data: Vec<u8>,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl BitVec {
|
||||||
|
fn new() -> Self {
|
||||||
|
BitVec {data: vec![], len: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
|
let true_cap = (cap + 8 - 1) / 8; // ceiling division
|
||||||
|
BitVec {
|
||||||
|
data: Vec::with_capacity(true_cap),
|
||||||
|
len: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, b: bool) {
|
||||||
|
let offset = self.len % 8;
|
||||||
|
let v = (b as u8) << (7 - offset);
|
||||||
|
match offset {
|
||||||
|
0 => self.data.push(v),
|
||||||
|
_ => *self.data.last_mut().unwrap() |= v,
|
||||||
|
}
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_byte(&mut self, byte: u8) {
|
||||||
|
if self.len() % 8 != 0 {
|
||||||
|
panic!("Attempted to push full byte across byte boundary");
|
||||||
|
}
|
||||||
|
self.data.push(byte);
|
||||||
|
self.len += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(&self, range: Range<usize>) -> BitSlice {
|
||||||
|
if range.end > self.len {
|
||||||
|
panic!("Slice out of bounds: {}..{}", range.start, range.end);
|
||||||
|
}
|
||||||
|
BitSlice {
|
||||||
|
src: self,
|
||||||
|
start: range.start,
|
||||||
|
end: range.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for BitVec {
|
||||||
|
type Output = bool;
|
||||||
|
fn index(&self, index: usize) -> &bool {
|
||||||
|
let (idx, offset) = (index / 8, index % 8);
|
||||||
|
let bit = self.data[idx] & (128 >> offset);
|
||||||
|
if bit == 0 {&false} else {&true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct BitSlice<'a> {
|
||||||
|
src: &'a BitVec,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl BitSlice<'_> {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u64(&self) -> u64 {
|
||||||
|
if self.len() > 64 {panic!("Slice too large for u64");}
|
||||||
|
|
||||||
|
let mut value = 0;
|
||||||
|
for i in self.start..self.end {
|
||||||
|
let offset = self.end - 1 - i;
|
||||||
|
value |= (self.src[i] as u64) << offset;
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u16(&self) -> u16 {
|
||||||
|
if self.len() > 16 {panic!("Slice too large for u16");}
|
||||||
|
self.as_u64() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u8(&self) -> u8 {
|
||||||
|
if self.len() > 8 {panic!("Slice too large for u8");}
|
||||||
|
self.as_u64() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bool(&self) -> bool {
|
||||||
|
if self.len() != 1 {
|
||||||
|
panic!("Only slices of length 1 can be converted to bool");
|
||||||
|
}
|
||||||
|
self.as_u64() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(&self, range: Range<usize>) -> BitSlice {
|
||||||
|
if range.end > self.len() {
|
||||||
|
panic!("Slice out of bounds: {}..{}", range.start, range.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
BitSlice {
|
||||||
|
src: &self.src,
|
||||||
|
start: self.start + range.start,
|
||||||
|
end: self.start + range.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for BitSlice<'_> {
|
||||||
|
type Output = bool;
|
||||||
|
fn index(&self, index: usize) -> &bool {
|
||||||
|
self.src.index(self.start + index)
|
||||||
|
}
|
||||||
|
}
|
36
2021/src/main.rs
Normal file
36
2021/src/main.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use std::time::{Instant};
|
||||||
|
|
||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
mod lib;
|
||||||
|
use lib::load;
|
||||||
|
mod day18;
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> eyre::Result<()> {
|
||||||
|
let data = load("data/18.txt")?;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let (one, two) = day18::run(&data)?;
|
||||||
|
let (dur, unit) = format_ns(start.elapsed().as_nanos());
|
||||||
|
|
||||||
|
let precision = 2.0 - dur.log10().floor();
|
||||||
|
println!(
|
||||||
|
"One: {0}\nTwo: {1}\nCompleted in {2:.4$}{3}",
|
||||||
|
one, two, dur, unit, precision as usize
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn format_ns(ns: u128) -> (f64, &'static str) {
|
||||||
|
const UNITS: [&str; 4] = ["ns", "us", "ms", "s"];
|
||||||
|
let mut display_n = ns as f64; //
|
||||||
|
let mut unit_idx = 0;
|
||||||
|
while display_n > 1000.0 && unit_idx < 4 {
|
||||||
|
display_n /= 1000.0;
|
||||||
|
unit_idx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
(display_n, UNITS[unit_idx])
|
||||||
|
}
|
49
2022/Cargo.lock
generated
Normal file
49
2022/Cargo.lock
generated
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "advent-2022"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
50
2022/Cargo.toml
Normal file
50
2022/Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
[package]
|
||||||
|
name = "advent-2022"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "advent_2022"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day1"
|
||||||
|
path = "src/day1.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day2"
|
||||||
|
path = "src/day2.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day3"
|
||||||
|
path = "src/day3.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day4"
|
||||||
|
path = "src/day4.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day5"
|
||||||
|
path = "src/day5.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day6"
|
||||||
|
path = "src/day6.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day7"
|
||||||
|
path = "src/day7.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day8"
|
||||||
|
path = "src/day8.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day9"
|
||||||
|
path = "src/day9.rs"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex = "1.7.0"
|
||||||
|
lazy_static = "1.4.0"
|
28
2022/src/day1.rs
Normal file
28
2022/src/day1.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const DATA: &'static str = include_str!("../data/day1.txt");
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut counts = DATA.trim()
|
||||||
|
.split("\n\n")
|
||||||
|
.map(|lines| {
|
||||||
|
lines.split("\n")
|
||||||
|
.map(|c| c.parse::<u64>().unwrap())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
counts.sort_by_key(|values| values.iter().sum::<u64>());
|
||||||
|
|
||||||
|
let max_total_cals: u64 = counts.last()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let top3_total_cals: &u64 = &counts[(counts.len() - 3)..]
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
println!("Part 1: {max_total_cals}");
|
||||||
|
println!("Part 2: {top3_total_cals}");
|
||||||
|
}
|
108
2022/src/day2.rs
Normal file
108
2022/src/day2.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
const DATA: &'static str = include_str!("../data/day2.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// A Y
|
||||||
|
// B X
|
||||||
|
// C Z
|
||||||
|
// ";
|
||||||
|
|
||||||
|
|
||||||
|
enum RPS {
|
||||||
|
Rock = 1,
|
||||||
|
Paper = 2,
|
||||||
|
Scissors = 3,
|
||||||
|
}
|
||||||
|
impl FromStr for RPS {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"A" | "X" => Ok(Rock),
|
||||||
|
"B" | "Y" => Ok(Paper),
|
||||||
|
"C" | "Z" => Ok(Scissors),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum Outcome {
|
||||||
|
Win = 6,
|
||||||
|
Loss = 0,
|
||||||
|
Draw = 3,
|
||||||
|
}
|
||||||
|
impl FromStr for Outcome {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, ()> {
|
||||||
|
match s {
|
||||||
|
"X" => Ok(Loss),
|
||||||
|
"Y" => Ok(Draw),
|
||||||
|
"Z" => Ok(Win),
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use RPS::*;
|
||||||
|
use Outcome::*;
|
||||||
|
|
||||||
|
|
||||||
|
fn compare(me: &RPS, them: &RPS) -> Outcome {
|
||||||
|
match (me, them) {
|
||||||
|
(Rock, Rock) => Draw,
|
||||||
|
(Rock, Paper) => Loss,
|
||||||
|
(Rock, Scissors) => Win,
|
||||||
|
(Paper, Rock) => Win,
|
||||||
|
(Paper, Paper) => Draw,
|
||||||
|
(Paper, Scissors) => Loss,
|
||||||
|
(Scissors, Rock) => Loss,
|
||||||
|
(Scissors, Paper) => Win,
|
||||||
|
(Scissors, Scissors) => Draw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_response(opponent: &RPS, outcome: &Outcome) -> RPS {
|
||||||
|
match (opponent, outcome) {
|
||||||
|
(Rock, Win) => Paper,
|
||||||
|
(Rock, Loss) => Scissors,
|
||||||
|
(Rock, Draw) => Rock,
|
||||||
|
(Paper, Win) => Scissors,
|
||||||
|
(Paper, Loss) => Rock,
|
||||||
|
(Paper, Draw) => Paper,
|
||||||
|
(Scissors, Win) => Rock,
|
||||||
|
(Scissors, Loss) => Paper,
|
||||||
|
(Scissors, Draw) => Scissors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn score(me: RPS, them: RPS) -> u64 {
|
||||||
|
let outcome = compare(&me, &them);
|
||||||
|
(me as u64) + (outcome as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut total_1 = 0;
|
||||||
|
for pair in DATA.trim().split("\n") {
|
||||||
|
let mut it = pair.split(" ")
|
||||||
|
.map(|v| v.parse::<RPS>().unwrap());
|
||||||
|
let (them, me) = (it.next().unwrap(), it.next().unwrap());
|
||||||
|
total_1 += score(me, them);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Part 1: {total_1}");
|
||||||
|
|
||||||
|
let mut total_2 = 0;
|
||||||
|
for pair in DATA.trim().split("\n") {
|
||||||
|
let mut it = pair.split(" ");
|
||||||
|
let them = it.next().unwrap().parse::<RPS>().unwrap();
|
||||||
|
let outcome = it.next().unwrap().parse::<Outcome>().unwrap();
|
||||||
|
let me = get_response(&them, &outcome);
|
||||||
|
total_2 += me as u64;
|
||||||
|
total_2 += outcome as u64
|
||||||
|
}
|
||||||
|
println!("Part 2: {total_2}");
|
||||||
|
}
|
103
2022/src/day3.rs
Normal file
103
2022/src/day3.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
const DATA: &'static str = include_str!("../data/day3.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// vJrwpWtwJgWrhcsFMMfFFhFp
|
||||||
|
// jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
|
||||||
|
// PmmdzqPrVvPwwTWBwg
|
||||||
|
// wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
|
||||||
|
// ttgJtRGJQctTZtZT
|
||||||
|
// CrZsJsPPZsGzwwsLwLmpwMDw
|
||||||
|
// ";
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DataError {
|
||||||
|
Imbalanced,
|
||||||
|
UnknownValue,
|
||||||
|
NoDuplicate,
|
||||||
|
NoTriplicate,
|
||||||
|
ShortGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Rucksack {
|
||||||
|
left: Vec<u8>,
|
||||||
|
right: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rucksack {
|
||||||
|
fn find_duplicate(&self) -> Option<u8> {
|
||||||
|
// pretty sure this is faster than hash set, given the sizes involved
|
||||||
|
self.left.iter()
|
||||||
|
.find(|c| self.right.contains(c))
|
||||||
|
.map(|c| *c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_all(&self) -> impl Iterator<Item = &u8> {
|
||||||
|
self.left.iter().chain(self.right.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, v: u8) -> bool {
|
||||||
|
self.left.contains(&v) || self.right.contains(&v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Rucksack {
|
||||||
|
type Err = DataError;
|
||||||
|
|
||||||
|
fn from_str(line: &str) -> Result<Rucksack, DataError> {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.len() % 2 != 0 {
|
||||||
|
return Err(DataError::Imbalanced);
|
||||||
|
}
|
||||||
|
let (a, b) = line.as_bytes().split_at(line.len() / 2);
|
||||||
|
Ok(Rucksack {
|
||||||
|
left: a.into(),
|
||||||
|
right: b.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn priority(c: u8) -> Result<u64, DataError> {
|
||||||
|
match c {
|
||||||
|
// lowercase
|
||||||
|
97..=122 => Ok(c as u64 - 96),
|
||||||
|
// uppercase
|
||||||
|
65..=90 => Ok(c as u64 - 38),
|
||||||
|
_ => Err(DataError::UnknownValue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), DataError> {
|
||||||
|
let mut dup_total = 0;
|
||||||
|
let rucks = DATA.trim()
|
||||||
|
.split("\n")
|
||||||
|
.map(|line| Rucksack::from_str(line))
|
||||||
|
.collect::<Result<Vec<Rucksack>, DataError>>()?;
|
||||||
|
|
||||||
|
for ruck in rucks.iter() {
|
||||||
|
let dup = ruck.find_duplicate()
|
||||||
|
.ok_or(DataError::NoDuplicate)?;
|
||||||
|
dup_total += priority(dup)?;
|
||||||
|
}
|
||||||
|
println!("Part 1: {dup_total}");
|
||||||
|
|
||||||
|
let mut groups_total = 0;
|
||||||
|
for (idx, ruck) in rucks.iter().enumerate().step_by(3) {
|
||||||
|
if idx > rucks.len() - 3 {
|
||||||
|
return Err(DataError::ShortGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
let badge = *ruck.iter_all()
|
||||||
|
.find(|&v| rucks[idx + 1].contains(*v) && rucks[idx + 2].contains(*v))
|
||||||
|
.ok_or(DataError::NoTriplicate)?;
|
||||||
|
groups_total += priority(badge)?;
|
||||||
|
}
|
||||||
|
println!("Part 2: {groups_total}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
89
2022/src/day4.rs
Normal file
89
2022/src/day4.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
const DATA: &'static str = include_str!("../data/day4.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// 2-4,6-8
|
||||||
|
// 2-3,4-5
|
||||||
|
// 5-7,7-9
|
||||||
|
// 2-8,3-7
|
||||||
|
// 6-6,4-6
|
||||||
|
// 2-6,4-8
|
||||||
|
// ";
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DataError {
|
||||||
|
NotANumber,
|
||||||
|
NotAPair,
|
||||||
|
NotARange,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Range {
|
||||||
|
start: u32,
|
||||||
|
end: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
fn contains(&self, other: &Range) -> bool {
|
||||||
|
self.start <= other.start && self.end >= other.end
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlaps(&self, other: &Range) -> bool {
|
||||||
|
// what if self.start == other.start or self.end == other.end?
|
||||||
|
if self.start <= other.start && self.end >= other.start {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.start <= other.end && self.end >= other.end {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Range {
|
||||||
|
type Err = DataError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Range, DataError> {
|
||||||
|
let mut bounds = s.split('-');
|
||||||
|
|
||||||
|
let start = bounds.next()
|
||||||
|
.ok_or(DataError::NotARange)?
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|_e| DataError::NotANumber)?;
|
||||||
|
let end = bounds.next()
|
||||||
|
.ok_or(DataError::NotARange)?
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|_e| DataError::NotANumber)?;
|
||||||
|
|
||||||
|
Ok(Range {start, end})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), DataError> {
|
||||||
|
let ranges = DATA.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let mut pairs = line.split(',');
|
||||||
|
let left: Range = pairs.next()
|
||||||
|
.ok_or(DataError::NotAPair)?
|
||||||
|
.parse()?;
|
||||||
|
let right: Range = pairs.next()
|
||||||
|
.ok_or(DataError::NotAPair)?
|
||||||
|
.parse()?;
|
||||||
|
Ok((left, right))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<(Range, Range)>, DataError>>()?;
|
||||||
|
|
||||||
|
let total_supersets = ranges.iter()
|
||||||
|
.filter(|(a, b)| a.contains(b) || b.contains(a))
|
||||||
|
.count();
|
||||||
|
println!("Part 1: {total_supersets}");
|
||||||
|
|
||||||
|
let total_overlaps = ranges.iter()
|
||||||
|
.filter(|(a, b)| a.overlaps(b) || b.contains(a) || a.contains(b))
|
||||||
|
.count();
|
||||||
|
println!("Part 2: {total_overlaps}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
113
2022/src/day5.rs
Normal file
113
2022/src/day5.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use core::num::ParseIntError;
|
||||||
|
|
||||||
|
|
||||||
|
const DATA: &'static str = include_str!("../data/day5.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// [D]
|
||||||
|
// [N] [C]
|
||||||
|
// [Z] [M] [P]
|
||||||
|
// 1 2 3
|
||||||
|
|
||||||
|
// move 1 from 2 to 1
|
||||||
|
// move 3 from 1 to 3
|
||||||
|
// move 2 from 2 to 1
|
||||||
|
// move 1 from 1 to 2
|
||||||
|
// ";
|
||||||
|
|
||||||
|
|
||||||
|
enum BadLine {
|
||||||
|
NotEnoughWords,
|
||||||
|
ParseError(ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct State {
|
||||||
|
stacks: [Vec<char>; 9],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn process(&mut self, op: &Instruction) {
|
||||||
|
for _ in 0..(op.count) {
|
||||||
|
let item = self.stacks[op.src as usize - 1].pop().unwrap();
|
||||||
|
self.stacks[op.dst as usize - 1].push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_inorder(&mut self, op: &Instruction) {
|
||||||
|
let src_stack = &mut self.stacks[op.src as usize - 1];
|
||||||
|
let split_idx = src_stack.len() - op.count as usize;
|
||||||
|
let mut substack = src_stack.split_off(split_idx);
|
||||||
|
self.stacks[op.dst as usize - 1].append(&mut substack);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tops(&self) -> String {
|
||||||
|
let mut res = String::with_capacity(9);
|
||||||
|
for stack in &self.stacks {
|
||||||
|
res.push(*stack.last().unwrap());
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Instruction {
|
||||||
|
count: u32,
|
||||||
|
src: u32,
|
||||||
|
dst: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_map(map: &str) -> State {
|
||||||
|
let rows: Vec<&str> = map.lines().collect();
|
||||||
|
let mut stacks: [Vec<char>; 9] = Default::default();
|
||||||
|
// skip the first row, because it's just the stack numbers
|
||||||
|
for row in rows.iter().rev().skip(1) {
|
||||||
|
for (col_num, c) in row.chars().enumerate() {
|
||||||
|
if c.is_ascii_uppercase() {
|
||||||
|
let stack_idx = (col_num - 1) / 4;
|
||||||
|
stacks[stack_idx].push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
State { stacks }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_instruction(data: &str) -> Result<Instruction, BadLine> {
|
||||||
|
let mut words = data.split(' ');
|
||||||
|
Ok(Instruction {
|
||||||
|
count: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
|
||||||
|
src: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
|
||||||
|
dst: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let mut splat = DATA.trim_end().split("\n\n");
|
||||||
|
let map = splat.next().unwrap();
|
||||||
|
let instruction_lines = splat.next().unwrap();
|
||||||
|
let mut state = parse_map(map);
|
||||||
|
|
||||||
|
let instructions: Vec<Instruction> = instruction_lines.lines()
|
||||||
|
.map(|line| parse_instruction(line).map_err(|_e| format!("Bad line: {line}")))
|
||||||
|
.collect::<Result<Vec<Instruction>, String>>()?;
|
||||||
|
|
||||||
|
for i in &instructions {
|
||||||
|
state.process(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Part 1: {}", state.get_tops());
|
||||||
|
|
||||||
|
let mut state2 = parse_map(map);
|
||||||
|
for i in &instructions {
|
||||||
|
state2.process_inorder(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Part 2: {}", state2.get_tops());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
58
2022/src/day6.rs
Normal file
58
2022/src/day6.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// const DATA: &'static str = "zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw";
|
||||||
|
const DATA: &'static str = include_str!("../data/day6.txt");
|
||||||
|
|
||||||
|
|
||||||
|
struct CharCounts {
|
||||||
|
chars: [u8; 26],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharCounts {
|
||||||
|
fn new() -> Self {
|
||||||
|
CharCounts { chars: [0; 26]}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idx(c: u8) -> usize {
|
||||||
|
(c as usize) - (b'a' as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc(&mut self, c: u8) {
|
||||||
|
self.chars[Self::idx(c)] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec(&mut self, c: u8) {
|
||||||
|
self.chars[Self::idx(c)] -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(&self) -> u8 {
|
||||||
|
// max only returns None if the iterator is empty
|
||||||
|
*self.chars.iter().max().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn find_marker(data: &[u8], window_size: usize) -> Option<usize> {
|
||||||
|
let mut window = CharCounts::new();
|
||||||
|
for (i, v) in data.iter().enumerate() {
|
||||||
|
window.inc(*v);
|
||||||
|
if i > (window_size - 1) {
|
||||||
|
let dropout = data[i - window_size];
|
||||||
|
window.dec(dropout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > (window_size - 1) && window.max() == 1 {
|
||||||
|
return Some(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let bytes = DATA.trim().as_bytes();
|
||||||
|
|
||||||
|
let m1 = find_marker(&bytes, 4).unwrap();
|
||||||
|
println!("Part 1: {m1}");
|
||||||
|
|
||||||
|
let m2 = find_marker(&bytes, 14).unwrap();
|
||||||
|
println!("Part 2: {m2}");
|
||||||
|
}
|
229
2022/src/day7.rs
Normal file
229
2022/src/day7.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
||||||
|
struct Directory {
|
||||||
|
name: String,
|
||||||
|
children: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
name: String,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum Node {
|
||||||
|
Branch(Directory),
|
||||||
|
Leaf(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CMD_CD: Regex = Regex::new(r"^\$ cd (\S+)$").unwrap();
|
||||||
|
static ref CMD_LS: Regex = Regex::new(r"^\$ ls$").unwrap();
|
||||||
|
static ref DIR_LISTING: Regex = Regex::new(r"^dir (\S+)$").unwrap();
|
||||||
|
static ref FILE_LISTING: Regex = Regex::new(r"^(\d+) (\S+)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct DirListing {
|
||||||
|
name: String,
|
||||||
|
children: Vec<usize>,
|
||||||
|
parent: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NodeListing {
|
||||||
|
Branch(DirListing),
|
||||||
|
Leaf(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn build_map(data: &str) -> Result<HashMap<usize, NodeListing>, String> {
|
||||||
|
let root = DirListing {
|
||||||
|
name: "".to_string(),
|
||||||
|
children: vec![],
|
||||||
|
parent: None,
|
||||||
|
};
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(0, NodeListing::Branch(root));
|
||||||
|
|
||||||
|
let mut stack: Vec<usize> = vec![];
|
||||||
|
stack.push(0);
|
||||||
|
|
||||||
|
'lines: for line in data.trim().lines() {
|
||||||
|
if CMD_LS.is_match(line) {continue;}
|
||||||
|
|
||||||
|
if let Some(c) = CMD_CD.captures(line) {
|
||||||
|
let name = c.get(1).unwrap().as_str();
|
||||||
|
if name == "/" {
|
||||||
|
stack.truncate(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == ".." {
|
||||||
|
stack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_dir = match map.get(&stack.last().unwrap()).unwrap() {
|
||||||
|
NodeListing::Branch(dir) => dir,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
for id in ¤t_dir.children {
|
||||||
|
let child_name = match map.get(&id).unwrap() {
|
||||||
|
NodeListing::Branch(dir) => &dir.name,
|
||||||
|
NodeListing::Leaf(file) => &file.name,
|
||||||
|
};
|
||||||
|
if child_name == name {
|
||||||
|
stack.push(*id);
|
||||||
|
continue 'lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(format!("Attempted to cd into directory before ls-ing it: {}", line.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = DIR_LISTING.captures(line) {
|
||||||
|
let parent_id = stack.last().unwrap();
|
||||||
|
let child_id = map.len();
|
||||||
|
let parent = match map.get_mut(parent_id).unwrap() {
|
||||||
|
NodeListing::Branch(dir) => dir,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
parent.children.push(child_id);
|
||||||
|
|
||||||
|
let dir = DirListing {
|
||||||
|
name: c.get(1).unwrap().as_str().to_string(),
|
||||||
|
children: vec![],
|
||||||
|
parent: Some(*parent_id),
|
||||||
|
};
|
||||||
|
map.insert(child_id, NodeListing::Branch(dir));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = FILE_LISTING.captures(line) {
|
||||||
|
let name = c.get(2).unwrap().as_str().to_string();
|
||||||
|
let size = c.get(1).unwrap().as_str().parse::<usize>()
|
||||||
|
.map_err(|_e| format!("Invalid file size: {}", line.to_string()))?;
|
||||||
|
|
||||||
|
let file_id = map.len();
|
||||||
|
let file = File {name, size};
|
||||||
|
map.insert(file_id, NodeListing::Leaf(file));
|
||||||
|
|
||||||
|
let parent = match map.get_mut(stack.last().unwrap()).unwrap() {
|
||||||
|
NodeListing::Branch(dir) => dir,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
parent.children.push(file_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(format!("Could not interpret line: {line}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn build_tree(map: &mut HashMap<usize, NodeListing>, root_listing: &DirListing) -> Directory {
|
||||||
|
let mut root_children = Vec::new();
|
||||||
|
for child_id in &root_listing.children {
|
||||||
|
match map.remove(&child_id).unwrap() {
|
||||||
|
NodeListing::Leaf(file) => {
|
||||||
|
root_children.push(Node::Leaf(file));
|
||||||
|
},
|
||||||
|
NodeListing::Branch(dir_listing) => {
|
||||||
|
let dir = build_tree(map, &dir_listing);
|
||||||
|
root_children.push(Node::Branch(dir))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory {
|
||||||
|
name: root_listing.name.clone(),
|
||||||
|
children: root_children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_sizes(root: &Directory, mut sizes: Vec<usize>) -> (usize, Vec<usize>) {
|
||||||
|
let mut dir_size = 0;
|
||||||
|
|
||||||
|
for child in &root.children {
|
||||||
|
match child {
|
||||||
|
Node::Leaf(file) => dir_size += file.size,
|
||||||
|
Node::Branch(dir) => {
|
||||||
|
let (child_size, rs) = get_sizes(dir, sizes);
|
||||||
|
sizes = rs;
|
||||||
|
dir_size += child_size;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes.push(dir_size);
|
||||||
|
(dir_size, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let mut map = build_map(DATA)?;
|
||||||
|
let root = match map.remove(&0).unwrap() {
|
||||||
|
NodeListing::Branch(dir) => dir,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tree = build_tree(&mut map, &root);
|
||||||
|
let (total_size, dir_sizes) = get_sizes(&tree, vec![]);
|
||||||
|
|
||||||
|
let part1_sizes: usize = dir_sizes.iter()
|
||||||
|
.filter(|&&s| s <= 100000)
|
||||||
|
.sum();
|
||||||
|
println!("Part 1: {part1_sizes}");
|
||||||
|
|
||||||
|
let available = 70_000_000 - total_size;
|
||||||
|
if available >= 30_000_000 {
|
||||||
|
return Err("Weird, there's already enough space available".to_string());
|
||||||
|
}
|
||||||
|
let target_size = 30_000_000 - available;
|
||||||
|
|
||||||
|
let delete_size = dir_sizes.iter()
|
||||||
|
.filter(|&&s| s >= target_size)
|
||||||
|
.min()
|
||||||
|
.unwrap();
|
||||||
|
println!("Part 2: {delete_size}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DATA: &'static str = include_str!("../data/day7.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// $ cd /
|
||||||
|
// $ ls
|
||||||
|
// dir a
|
||||||
|
// 14848514 b.txt
|
||||||
|
// 8504156 c.dat
|
||||||
|
// dir d
|
||||||
|
// $ cd a
|
||||||
|
// $ ls
|
||||||
|
// dir e
|
||||||
|
// 29116 f
|
||||||
|
// 2557 g
|
||||||
|
// 62596 h.lst
|
||||||
|
// $ cd e
|
||||||
|
// $ ls
|
||||||
|
// 584 i
|
||||||
|
// $ cd ..
|
||||||
|
// $ cd ..
|
||||||
|
// $ cd d
|
||||||
|
// $ ls
|
||||||
|
// 4060174 j
|
||||||
|
// 8033020 d.log
|
||||||
|
// 5626152 d.ext
|
||||||
|
// 7214296 k
|
||||||
|
// ";
|
93
2022/src/day8.rs
Normal file
93
2022/src/day8.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
const DATA: &'static str = include_str!("../data/day8.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// 30373
|
||||||
|
// 25512
|
||||||
|
// 65332
|
||||||
|
// 33549
|
||||||
|
// 35390
|
||||||
|
// ";
|
||||||
|
|
||||||
|
|
||||||
|
// O(N * (2 * (sqrt(N) - 1))), so roughly N^1.5? Where N is number of elements in the grid
|
||||||
|
fn is_visible(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> bool {
|
||||||
|
let height = grid[y][x];
|
||||||
|
let rows = grid.len();
|
||||||
|
let cols = grid[x].len();
|
||||||
|
// search horizontal
|
||||||
|
let vis_left = (0..x).all(|cmp_x| grid[y][cmp_x] < height);
|
||||||
|
let vis_right = ((x + 1)..cols).all(|cmp_x| grid[y][cmp_x] < height);
|
||||||
|
|
||||||
|
let vis_top = (0..y).all(|cmp_y| grid[cmp_y][x] < height);
|
||||||
|
let vis_bottom = ((y + 1)..rows).all(|cmp_y| grid[cmp_y][x] < height);
|
||||||
|
|
||||||
|
vis_left || vis_right || vis_top || vis_bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn scenic_score(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> usize {
|
||||||
|
let height = grid[y][x];
|
||||||
|
let rows = grid.len();
|
||||||
|
let cols = grid[x].len();
|
||||||
|
|
||||||
|
let sc_left = (0..x).rev()
|
||||||
|
.position(|cmp_x| grid[y][cmp_x] >= height) // 0-indexed position of the first blocking tree
|
||||||
|
.map(|p| p + 1) // +1 since we include the blocking tree in the count
|
||||||
|
.unwrap_or(x); // if no blocking trees, score is how many are to the left of current
|
||||||
|
|
||||||
|
let sc_right = ((x + 1)..cols)
|
||||||
|
.position(|cmp_x| grid[y][cmp_x] >= height)
|
||||||
|
.map(|p| p + 1)
|
||||||
|
.unwrap_or(cols - x - 1); // e.g. if x is 4 and there are 5 cols, this is rightmost, so 5 - 4 - 1 = 0
|
||||||
|
|
||||||
|
let sc_top = (0..y).rev()
|
||||||
|
.position(|cmp_y| grid[cmp_y][x] >= height)
|
||||||
|
.map(|p| p + 1)
|
||||||
|
.unwrap_or(y);
|
||||||
|
|
||||||
|
let sc_bottom = ((y + 1)..rows)
|
||||||
|
.position(|cmp_y| grid[cmp_y][x] >= height)
|
||||||
|
.map(|p| p + 1)
|
||||||
|
.unwrap_or(rows - y - 1);
|
||||||
|
|
||||||
|
sc_left * sc_right * sc_top * sc_bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn build_grid(data: &str) -> Result<Vec<Vec<usize>>, String> {
|
||||||
|
data.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
line.chars()
|
||||||
|
.map(|c| c.to_digit(10).map(|c| c as usize).ok_or_else(|| format!("Failed to convert to integer: {c}") ))
|
||||||
|
.collect::<Result<Vec<usize>, String>>()
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Vec<usize>>, String>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let grid = build_grid(DATA)?;
|
||||||
|
|
||||||
|
let mut visible_trees = 0;
|
||||||
|
for y in 0..grid.len() {
|
||||||
|
for x in 0..grid[y].len() {
|
||||||
|
if is_visible(&grid, x, y) {
|
||||||
|
visible_trees += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Part 1: {visible_trees}");
|
||||||
|
|
||||||
|
let mut max_score = 0;
|
||||||
|
for y in 0..grid.len() {
|
||||||
|
for x in 0..grid[y].len() {
|
||||||
|
let score = scenic_score(&grid, x, y);
|
||||||
|
if score > max_score {
|
||||||
|
max_score = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Part 2: {max_score}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
184
2022/src/day9.rs
Normal file
184
2022/src/day9.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum Direction {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Direction {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, String> {
|
||||||
|
match s {
|
||||||
|
"L" => Ok(Direction::Left),
|
||||||
|
"R" => Ok(Direction::Right),
|
||||||
|
"U" => Ok(Direction::Up),
|
||||||
|
"D" => Ok(Direction::Down),
|
||||||
|
_ => Err(format!("Not a valid direction: {s}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
struct Pos {
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Rope<const L: usize> {
|
||||||
|
knots: [Pos; L],
|
||||||
|
tail_visited: HashSet<Pos>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const L: usize> Rope<L> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Rope {
|
||||||
|
knots: [Pos {x: 0, y: 0}; L],
|
||||||
|
tail_visited: HashSet::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_move(&mut self, dir: Direction, count: isize) {
|
||||||
|
for _ in 0..count {
|
||||||
|
let head = &mut self.knots[0];
|
||||||
|
match dir {
|
||||||
|
Direction::Left => head.x -= 1,
|
||||||
|
Direction::Right => head.x += 1,
|
||||||
|
Direction::Up => head.y += 1,
|
||||||
|
Direction::Down => head.y -= 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 1..(self.knots.len()) {
|
||||||
|
self.move_follower(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tail_visited.insert(*self.knots.last().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_follower(&mut self, i: usize) {
|
||||||
|
let leader = self.knots[i - 1];
|
||||||
|
let follower = &mut self.knots[i];
|
||||||
|
|
||||||
|
let dx = leader.x - follower.x;
|
||||||
|
let dy = leader.y - follower.y;
|
||||||
|
|
||||||
|
// check to make sure that we didn't do anything wrong previously
|
||||||
|
if dx.abs() + dy.abs() > 4
|
||||||
|
|| dx.abs() == 0 && dy.abs() > 2
|
||||||
|
|| dx.abs() > 2 && dy.abs() == 0
|
||||||
|
{
|
||||||
|
panic!("Invalid head and tail positions: {dx}, {dy}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// head and tail are in line vertically
|
||||||
|
if dx == 0 && dy.abs() == 2 {
|
||||||
|
follower.y += dy / 2; // use integer division to keep the sign of dy
|
||||||
|
}
|
||||||
|
// in line horizontally
|
||||||
|
if dy == 0 && dx.abs() == 2 {
|
||||||
|
follower.x += dx / 2; // similarly
|
||||||
|
}
|
||||||
|
|
||||||
|
// head and tail are offset by 1 horizontaly and 2 vertically
|
||||||
|
if dx.abs() == 1 && dy.abs() == 2 {
|
||||||
|
follower.x += dx;
|
||||||
|
follower.y += dy / 2;
|
||||||
|
}
|
||||||
|
// offset by 1 vertically and 2 horizontally
|
||||||
|
if dy.abs() == 1 && dx.abs() == 2 {
|
||||||
|
follower.y += dy;
|
||||||
|
follower.x += dx / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset by 2 in both dimensions
|
||||||
|
if dx.abs() == 2 && dy.abs() == 2 {
|
||||||
|
follower.x += dx / 2;
|
||||||
|
follower.y += dy / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
impl<const L: usize> Display for Rope<L> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||||
|
let min_x = self.knots.iter().map(|pos| pos.x).min().unwrap();
|
||||||
|
let max_x = self.knots.iter().map(|pos| pos.x).max().unwrap();
|
||||||
|
let min_y = self.knots.iter().map(|pos| pos.y).min().unwrap();
|
||||||
|
let max_y = self.knots.iter().map(|pos| pos.y).max().unwrap();
|
||||||
|
|
||||||
|
let n_rows = usize::try_from(max_y - min_y + 1).unwrap();
|
||||||
|
let n_cols = usize::try_from(max_x - min_x + 1).unwrap();
|
||||||
|
let mut grid = vec![vec!['.'; n_cols]; n_rows];
|
||||||
|
|
||||||
|
for (i, kt) in self.knots.iter().enumerate() {
|
||||||
|
let rel_x = usize::try_from(kt.x - min_x).unwrap();
|
||||||
|
let rel_y = usize::try_from(max_y - kt.y).unwrap();
|
||||||
|
let tgt = &mut grid[rel_y][rel_x];
|
||||||
|
if *tgt == '.' {
|
||||||
|
*tgt = char::from_digit(i as u32, 10).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in grid {
|
||||||
|
write!(f, "{}\n", row.iter().collect::<String>())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let instructions = DATA.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let mut splat = line.split(' ');
|
||||||
|
match (splat.next(), splat.next()) {
|
||||||
|
(Some(dir), Some(dist)) => {
|
||||||
|
let dist = dist.parse().map_err(|_e| format!("Not an integer: {dist}"));
|
||||||
|
Ok((dir.parse()?, dist?))
|
||||||
|
},
|
||||||
|
_ => Err(format!("Could not parse move instruction from line: {line}"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<(Direction, isize)>, String>>()?;
|
||||||
|
|
||||||
|
let mut rope1 = Rope::<2>::new();
|
||||||
|
for &(dir, dist) in &instructions {
|
||||||
|
rope1.do_move(dir, dist);
|
||||||
|
}
|
||||||
|
println!("Part 1: {}", rope1.tail_visited.len());
|
||||||
|
|
||||||
|
let mut rope2 = Rope::<10>::new();
|
||||||
|
for &(dir, dist) in &instructions {
|
||||||
|
rope2.do_move(dir, dist);
|
||||||
|
}
|
||||||
|
println!("Part 2: {}", rope2.tail_visited.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DATA: &'static str = include_str!("../data/day9.txt");
|
||||||
|
// const DATA: &'static str = "
|
||||||
|
// R 5
|
||||||
|
// U 8
|
||||||
|
// L 8
|
||||||
|
// D 3
|
||||||
|
// R 17
|
||||||
|
// D 10
|
||||||
|
// L 25
|
||||||
|
// U 20
|
||||||
|
// ";
|
67
2022/src/lib.rs
Normal file
67
2022/src/lib.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
|
||||||
|
struct Grid<T> {
|
||||||
|
rows: Vec<Vec<T>>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> Grid<T> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Grid { rows: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_default_values<D>(width: usize, height: usize) -> Self
|
||||||
|
where T: Default + Clone
|
||||||
|
{
|
||||||
|
let rows = (0..height)
|
||||||
|
.map(|_i| vec![Default::default(); width])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Grid { rows }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_rows<I>(rows: I) -> Self
|
||||||
|
where I: Iterator<Item = Vec<T>>
|
||||||
|
{
|
||||||
|
Grid { rows: rows.collect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_chars<F, E>(src: &str, conv: F) -> Result<Self, E>
|
||||||
|
where F: Fn(char) -> Result<T, E>
|
||||||
|
{
|
||||||
|
let rows: Result<Vec<Vec<T>>, E> = src.lines()
|
||||||
|
.map(|line| {
|
||||||
|
line.chars()
|
||||||
|
.map(|c| conv(c))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Grid { rows: rows? })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_rows(&self) -> impl Iterator<Item = &Vec<T>> {
|
||||||
|
self.rows.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> Index<[usize; 2]> for Grid<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn index(&self, idx: [usize; 2]) -> &T {
|
||||||
|
let [x, y] = idx;
|
||||||
|
&self.rows[y][x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> IndexMut<[usize; 2]> for Grid<T> {
|
||||||
|
fn index_mut(&mut self, idx: [usize; 2]) -> &mut T {
|
||||||
|
let [x, y] = idx;
|
||||||
|
&mut self.rows[y][x]
|
||||||
|
}
|
||||||
|
}
|
79
2024/01.c
Normal file
79
2024/01.c
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
|
||||||
|
int cmp(const void *a, const void *b) {
|
||||||
|
int *_a = (int *)a;
|
||||||
|
int *_b = (int *)b;
|
||||||
|
return *_a - *_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int count_occurrences(int num, int arr[], int len) {
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (arr[i] == num) {
|
||||||
|
total++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int part1(int nums_l[], int nums_r[], int len) {
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int diff = nums_l[i] - nums_r[i];
|
||||||
|
if (diff < 0) {
|
||||||
|
diff = diff * -1;
|
||||||
|
}
|
||||||
|
sum += diff;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int part2(int nums_l[], int nums_r[], int len) {
|
||||||
|
int score = 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
score += nums_l[i] * count_occurrences(nums_l[i], nums_r, len);
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
FILE *file = fopen("data/01.txt", "r");
|
||||||
|
char *data = (char *)malloc(16384);
|
||||||
|
size_t data_len = fread(data, 1, 16384, file);
|
||||||
|
|
||||||
|
int nums_l[1000];
|
||||||
|
int nums_r[1000];
|
||||||
|
|
||||||
|
int nlines = 0;
|
||||||
|
while (1) {
|
||||||
|
char* line = strsep(&data, "\n");
|
||||||
|
int left = strtol(line, &line, 10);
|
||||||
|
int right = strtol(line, NULL, 10);
|
||||||
|
|
||||||
|
// if `strtol` fails, it apparently just returns 0, how helpful
|
||||||
|
if (left == 0 && right == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nums_l[nlines] = left;
|
||||||
|
nums_r[nlines] = right;
|
||||||
|
|
||||||
|
nlines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort(nums_l, nlines, 4, cmp);
|
||||||
|
qsort(nums_r, nlines, 4, cmp);
|
||||||
|
|
||||||
|
int solution_1 = part1(nums_l, nums_r, nlines);
|
||||||
|
printf("Part 1: %i\n", solution_1);
|
||||||
|
|
||||||
|
int solution_2 = part2(nums_l, nums_r, nlines);
|
||||||
|
printf("Part 2: %i\n", solution_2);
|
||||||
|
}
|
77
2024/02.cpp
Normal file
77
2024/02.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
vector<int> parse_line(string line) {
|
||||||
|
int start = 0;
|
||||||
|
vector<int> result;
|
||||||
|
while (start < line.length()) {
|
||||||
|
int end = line.find(" ", start);
|
||||||
|
|
||||||
|
string word = line.substr(start, end - start);
|
||||||
|
int n = stoi(word);
|
||||||
|
result.push_back(n);
|
||||||
|
|
||||||
|
start = end + 1;
|
||||||
|
if (end == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool is_valid(vector<int> report) {
|
||||||
|
int prev_diff = 0;
|
||||||
|
for (int i = 1; i < report.size(); i++) {
|
||||||
|
int diff = report[i] - report[i - 1];
|
||||||
|
if (diff < -3 || diff == 0 || diff > 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// on the first iteration, we can't compare to the previous difference
|
||||||
|
if (i == 1) {
|
||||||
|
prev_diff = diff;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((diff > 0 && prev_diff < 0) || (diff < 0 && prev_diff > 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
prev_diff = diff;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
ifstream file("data/02.txt");
|
||||||
|
string line;
|
||||||
|
int count_part1 = 0;
|
||||||
|
int count_part2 = 0;
|
||||||
|
while (getline(file, line)) {
|
||||||
|
auto report = parse_line(line);
|
||||||
|
if (is_valid(report)) {
|
||||||
|
count_part1++;
|
||||||
|
count_part2++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < report.size(); i++) {
|
||||||
|
int n = report[i];
|
||||||
|
report.erase(report.begin() + i);
|
||||||
|
if (is_valid(report)) {
|
||||||
|
count_part2++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
report.insert(report.begin() + i, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cout << "Part 1: " << count_part1 << "\n";
|
||||||
|
cout << "Part 2: " << count_part2 << "\n";
|
||||||
|
}
|
87
2024/03.fs
Normal file
87
2024/03.fs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
: load-data ( addr1 u1 -- addr2 u2 )
|
||||||
|
20000 allocate drop \ allocate a 20k buffer, drop the status code
|
||||||
|
dup 2swap \ duplicate the buffer addr and stick both copies under the addr/length of filename
|
||||||
|
r/o open-file drop \ open file, drop the status code
|
||||||
|
20000 swap \ stick the number of bytes to be read in between the buffer addr and file id
|
||||||
|
read-file drop \ read the file, drop the status code
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
: chop-prefix ( addr u u2 -- addr2 u2 )
|
||||||
|
\ chop the first `u2` bytes off the beginning of the string at `addr u`
|
||||||
|
tuck \ duplicate `u2` and store it "under" the length of the string
|
||||||
|
- \ subtract `u2` from the length of the string
|
||||||
|
-rot \ stick the new string length underneath the start pointer
|
||||||
|
+ \ increment the start pointer by `u2`
|
||||||
|
swap \ put them back in the right order
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
require regexp.fs
|
||||||
|
|
||||||
|
: mul-instr ( addr u -- flag )
|
||||||
|
\ match a string of the form `mul(x,y)` where x and y are integers and capture those integers
|
||||||
|
(( =" mul(" \( {++ \d ++} \) ` , \( {++ \d ++} \) ` ) )) ;
|
||||||
|
|
||||||
|
|
||||||
|
: get-product ( addr u -- u2 )
|
||||||
|
mul-instr \ match the string from `addr u` against the above regex
|
||||||
|
if \ if the regex matches, then:
|
||||||
|
\1 s>number drop \ convert the first capture from string to number, drop the status code (we already know it will succeed)
|
||||||
|
\2 s>number drop \ convert the second capture from string to number, drop the status code
|
||||||
|
* \ multiply, and leave the answer on the stack
|
||||||
|
else
|
||||||
|
0 \ otherwise, leave 0 on the stack
|
||||||
|
then
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
variable result \ initialize `result` with 0
|
||||||
|
0 result !
|
||||||
|
|
||||||
|
variable enabled
|
||||||
|
-1 enabled ! \ idiomatically -1 is "true" (really anything other than 0 is true)
|
||||||
|
|
||||||
|
: handle-mul ( addr u -- )
|
||||||
|
get-product \ pass those to get-product above
|
||||||
|
result @ + \ load `result` and add to product
|
||||||
|
result ! \ store this new value back in `result`
|
||||||
|
;
|
||||||
|
|
||||||
|
: sum-mul-instrs ( addr u -- u2 )
|
||||||
|
\ we want to loop from addr to (addr + u - 8), because 8 is the min length of a valid mul(x,y) instruction
|
||||||
|
\ we also want to have addr + u on the top of the stack when we enter the loop,
|
||||||
|
\ so that we can use that to compute the remaining length of the string from our current address
|
||||||
|
|
||||||
|
over + \ copy addr to top of stack and add to length
|
||||||
|
dup 8 - \ duplicate, then subtract 8 from the top value
|
||||||
|
rot \ move original addr to top of stack
|
||||||
|
( stack at this point: [ addr + u, addr + u - 8, addr ] )
|
||||||
|
( i.e. [ end-of-string, loop-limit, loop-start ] )
|
||||||
|
|
||||||
|
do \ start looping
|
||||||
|
I 4 s" do()" str= \ compare the length-4 substring starting at I to the string "do()"
|
||||||
|
if \ if valid do() instruction,
|
||||||
|
-1 enabled ! \ set enabled=true
|
||||||
|
then
|
||||||
|
|
||||||
|
I 7 s" don't()" str= \ compare length-7 substring to "don't()"
|
||||||
|
if \ if valid don't() instruction,
|
||||||
|
0 enabled ! \ set enabled=false
|
||||||
|
then
|
||||||
|
|
||||||
|
I 4 s" mul(" str= \ compare length-4 substring to "mul("
|
||||||
|
enabled @ and \ combine with current value of `enabled`
|
||||||
|
if \ if a candidate for `mul(x,y)` instruction, and enabled=true, then
|
||||||
|
dup I - \ subtract current string pointer from end-of-string pointer to get length of remaining string
|
||||||
|
I swap handle-mul \ put current pointer onto stack again, swap so stack is ( addr len), and handle
|
||||||
|
then
|
||||||
|
loop
|
||||||
|
|
||||||
|
drop \ get rid of end-of-string pointer
|
||||||
|
result @ \ return value of result
|
||||||
|
;
|
||||||
|
|
||||||
|
s" data/03.txt" load-data
|
||||||
|
sum-mul-instrs .
|
||||||
|
bye
|
171
2024/04.f90
Normal file
171
2024/04.f90
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
program advent04
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
character, dimension(146, 146) :: grid
|
||||||
|
integer :: row, col, part1, part2
|
||||||
|
real :: start, end
|
||||||
|
|
||||||
|
call cpu_time(start)
|
||||||
|
grid = load("data/04.txt", 140, 140)
|
||||||
|
part1 = 0
|
||||||
|
part2 = 0
|
||||||
|
|
||||||
|
|
||||||
|
do col = 4, 143
|
||||||
|
do row = 4, 143
|
||||||
|
if (grid(row, col) == 'X') then
|
||||||
|
part1 = part1 + count_xmas(row, col)
|
||||||
|
end if
|
||||||
|
|
||||||
|
if (grid(row, col) == 'A') then
|
||||||
|
part2 = part2 + count_mas(row, col)
|
||||||
|
end if
|
||||||
|
end do
|
||||||
|
end do
|
||||||
|
|
||||||
|
call cpu_time(end)
|
||||||
|
|
||||||
|
print *, "Part 1:", part1
|
||||||
|
print *, "Part 2:", part2
|
||||||
|
print *, "Execution time:", end - start
|
||||||
|
|
||||||
|
contains
|
||||||
|
|
||||||
|
function load(path, n_rows, n_cols) result(grid)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
character(*), intent(in) :: path
|
||||||
|
integer, intent(in) :: n_rows, n_cols
|
||||||
|
integer :: handle, row, col
|
||||||
|
character, dimension(:, :), allocatable :: grid
|
||||||
|
character(n_cols) :: line
|
||||||
|
|
||||||
|
allocate(grid(n_rows + 6, n_cols + 6))
|
||||||
|
grid = '.'
|
||||||
|
|
||||||
|
open(newunit=handle, file=path, status="old", action="read")
|
||||||
|
do row = 4, n_rows + 3
|
||||||
|
read(handle, *) line
|
||||||
|
do col = 1, n_cols
|
||||||
|
grid(row, col + 3) = line(col:col)
|
||||||
|
end do
|
||||||
|
end do
|
||||||
|
|
||||||
|
close(handle)
|
||||||
|
end function load
|
||||||
|
|
||||||
|
integer function count_xmas(row, col) result(count)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
integer, intent(in) :: row, col
|
||||||
|
integer :: i
|
||||||
|
integer(8) :: prod
|
||||||
|
integer(8), dimension(8) :: primes
|
||||||
|
character, dimension(7, 7) :: test_grid, window
|
||||||
|
integer(8), dimension(7, 7) :: prime_mask, matches, matches_prime
|
||||||
|
|
||||||
|
test_grid = reshape( &
|
||||||
|
[&
|
||||||
|
'S', '.', '.', 'S', '.', '.', 'S', &
|
||||||
|
'.', 'A', '.', 'A', '.', 'A', '.', &
|
||||||
|
'.', '.', 'M', 'M', 'M', '.', '.', &
|
||||||
|
'S', 'A', 'M', 'X', 'M', 'A', 'S', &
|
||||||
|
'.', '.', 'M', 'M', 'M', '.', '.', &
|
||||||
|
'.', 'A', '.', 'A', '.', 'A', '.', &
|
||||||
|
'S', '.', '.', 'S', '.', '.', 'S' &
|
||||||
|
], &
|
||||||
|
shape(test_grid) &
|
||||||
|
)
|
||||||
|
|
||||||
|
primes = [2, 3, 5, 7, 11, 13, 17, 19]
|
||||||
|
|
||||||
|
prime_mask = reshape( &
|
||||||
|
[ &
|
||||||
|
2, 1, 1, 3, 1, 1, 5, &
|
||||||
|
1, 2, 1, 3, 1, 5, 1, &
|
||||||
|
1, 1, 2, 3, 5, 1, 1, &
|
||||||
|
19, 19, 19, 1, 7, 7, 7, &
|
||||||
|
1, 1, 17, 13, 11, 1, 1, &
|
||||||
|
1, 17, 1, 13, 1, 11, 1, &
|
||||||
|
17, 1, 1, 13, 1, 1, 11 &
|
||||||
|
], &
|
||||||
|
shape(prime_mask) &
|
||||||
|
)
|
||||||
|
|
||||||
|
window = grid(row - 3:row + 3, col - 3:col + 3)
|
||||||
|
matches = logical_to_int64(window == test_grid)
|
||||||
|
matches_prime = matches * prime_mask
|
||||||
|
prod = product(zero_to_one(matches_prime))
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
do i = 1, 8
|
||||||
|
if (mod(prod, primes(i) ** 3) == 0) then
|
||||||
|
count = count + 1
|
||||||
|
end if
|
||||||
|
end do
|
||||||
|
end function count_xmas
|
||||||
|
|
||||||
|
integer function count_mas(row, col) result(count)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
integer, intent(in) :: row, col
|
||||||
|
integer :: i
|
||||||
|
character, dimension(3, 3) :: window, t1, t2, t3, t4
|
||||||
|
|
||||||
|
t1 = reshape( &
|
||||||
|
[ &
|
||||||
|
'M', '.', 'S', &
|
||||||
|
'.', 'A', '.', &
|
||||||
|
'M', '.', 'S' &
|
||||||
|
], &
|
||||||
|
shape(t1) &
|
||||||
|
)
|
||||||
|
t2 = t1(3:1:-1, :) ! flip t1 top-to-bottom
|
||||||
|
t3 = transpose(t1) ! swap t1 rows for columns
|
||||||
|
t4 = t3(:, 3:1:-1) ! flip t3 lef-to-right
|
||||||
|
|
||||||
|
window = grid(row - 1:row + 1, col - 1:col + 1)
|
||||||
|
if ( &
|
||||||
|
count_matches(window, t1) == 5 &
|
||||||
|
.or. count_matches(window, t2) == 5 &
|
||||||
|
.or. count_matches(window, t3) == 5 &
|
||||||
|
.or. count_matches(window, t4) == 5 &
|
||||||
|
) then
|
||||||
|
count = 1
|
||||||
|
else
|
||||||
|
count = 0
|
||||||
|
end if
|
||||||
|
end function count_mas
|
||||||
|
|
||||||
|
integer function count_matches(a1, a2) result(matches)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
character, dimension(:, :) :: a1, a2
|
||||||
|
|
||||||
|
matches = count(a1 == a2)
|
||||||
|
end function count_matches
|
||||||
|
|
||||||
|
elemental integer(8) function logical_to_int64(b) result(i)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
logical, intent(in) :: b
|
||||||
|
|
||||||
|
if (b) then
|
||||||
|
i = 1
|
||||||
|
else
|
||||||
|
i = 0
|
||||||
|
end if
|
||||||
|
end function logical_to_int64
|
||||||
|
|
||||||
|
elemental integer(8) function zero_to_one(x) result(y)
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
integer(8), intent(in) :: x
|
||||||
|
|
||||||
|
if (x == 0) then
|
||||||
|
y = 1
|
||||||
|
else
|
||||||
|
y = x
|
||||||
|
end if
|
||||||
|
end function zero_to_one
|
||||||
|
end program advent04
|
Loading…
x
Reference in New Issue
Block a user