Compare commits

...

36 Commits

Author SHA1 Message Date
c35270a6a2 day11: perf 2025-12-12 11:51:57 -08:00
845138dd2d day12: cache and bool 2025-12-12 03:42:47 -08:00
fd6b0c846b day12: cleanup and speedup 2025-12-12 03:10:32 -08:00
004591374c day12: part1. salty about this one, here's all my useless code 2025-12-12 00:15:38 -08:00
d0c14d004f grid: add rotated/mirrored 2025-12-12 00:15:15 -08:00
8cb33180ad day12: boilerplate 2025-12-11 21:00:06 -08:00
19426d1dc8 day11: cleanup 2025-12-11 00:36:59 -08:00
292638ea1d day11: part2 2025-12-11 00:23:27 -08:00
ef4de807d4 day11: part1 2025-12-10 21:26:25 -08:00
866544d416 day10: working but way too slow simd implementation 2025-12-10 10:15:01 -08:00
0a5e5c8798 day10: small performance boost 2025-12-09 22:45:38 -08:00
73b44b1377 day10: part1 2025-12-09 22:13:56 -08:00
72d7dc1cbb day9: still not working but corner-based algorithm 2025-12-09 11:36:14 -08:00
33275b3918 day9: part1 and bruteforce part 2 (some cleanups and clippies) 2025-12-08 22:17:14 -08:00
3f9f7afd27 day8: avoid having to manually define all the cmp traits 2025-12-08 13:32:48 -08:00
522e10a3ea day8: add heap-based impl 2025-12-08 13:16:57 -08:00
b5f6bcde11 day8: avoid floats for performance 2025-12-08 12:19:21 -08:00
db214ca958 day8: we don't need stable sort and unstable is faster 2025-12-08 02:55:09 -08:00
09ca512fcc day8: some cleanups 2025-12-08 01:50:43 -08:00
c97f31e0fd day8: part2 solution 2025-12-08 01:04:15 -08:00
41f9e971ee day8: part1 solution 2025-12-08 00:59:30 -08:00
e97931acf3 day8: non working progress 2025-12-08 00:53:32 -08:00
95b6c0ed9e day8: boilerplate 2025-12-07 21:00:20 -08:00
ba2cfffc3f day7: optimize away needing a second grid 2025-12-07 01:14:56 -08:00
30644bcbfc day7: remember to use some convenience functions from my grid 2025-12-07 00:27:24 -08:00
8f3ac2aacd day7: part2 + clippies 2025-12-07 00:10:32 -08:00
f99f4e766a day7: part1 2025-12-06 21:17:06 -08:00
86fd61aa15 day7: boilerplate 2025-12-06 21:00:23 -08:00
c8e03e6d6d day6: state machine solution, much cleaner 2025-12-06 00:44:47 -08:00
bacbf36171 day6: both solutions 2025-12-05 23:48:19 -08:00
57932373d6 day5: add RangeSet based impl 2025-12-05 18:02:45 -08:00
324923b284 utils: add RangeSet early implementation based on RBTree 2025-12-05 18:02:33 -08:00
2158b8cf35 remove unused cargo deps 2025-12-05 16:26:16 -08:00
f51dc9c145 day2: clever and fast solution 2025-12-05 04:30:34 -08:00
09cd17d3ff grid: clippies 2025-12-05 04:30:33 -08:00
73765e857a day5: bugfixes for rangeset 2025-12-05 01:09:36 -08:00
28 changed files with 2818 additions and 159 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[build]
rustflags = ["-C", "target-cpu=native"]

View File

@@ -9,7 +9,7 @@ repos:
language: system
- id: rust-clippy
name: Rust clippy
entry: cargo clippy --lib --all-features --tests -- -D warnings
entry: cargo clippy --lib --all-features --tests --
pass_filenames: false
types: [file, rust]
language: system

667
Cargo.lock generated
View File

@@ -8,7 +8,7 @@ version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"cfg-if 1.0.4",
"once_cell",
"version_check",
"zerocopy",
@@ -64,30 +64,23 @@ version = "0.1.0"
dependencies = [
"aoc-runner",
"aoc-runner-derive",
"atoi",
"bitflags",
"cached",
"colored",
"grid",
"indicatif",
"itertools",
"lazy_static",
"misc",
"nom",
"rayon",
"regex",
"rustc-hash",
"thread_local",
"rstest",
"rustc_data_structures",
"wide",
]
[[package]]
name = "atoi"
version = "2.0.0"
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
@@ -95,6 +88,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -107,6 +106,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]]
name = "cached"
version = "0.56.0"
@@ -116,7 +121,7 @@ dependencies = [
"ahash",
"cached_proc_macro",
"cached_proc_macro_types",
"hashbrown",
"hashbrown 0.15.5",
"once_cell",
"thiserror",
"web-time",
@@ -140,21 +145,18 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "console"
version = "0.16.1"
@@ -165,7 +167,7 @@ dependencies = [
"libc",
"once_cell",
"unicode-width",
"windows-sys 0.61.2",
"windows-sys",
]
[[package]]
@@ -234,6 +236,15 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "ena"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5"
dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "1.0.0"
@@ -246,6 +257,22 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
@@ -258,10 +285,77 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if 1.0.4",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "grid"
version = "0.1.0"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -273,12 +367,45 @@ dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"rustc-rayon 0.5.1",
]
[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
]
[[package]]
name = "indicatif"
version = "0.18.3"
@@ -293,6 +420,15 @@ dependencies = [
"web-time",
]
[[package]]
name = "intrusive-collections"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
dependencies = [
"memoffset",
]
[[package]]
name = "itertools"
version = "0.14.0"
@@ -308,6 +444,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.83"
@@ -330,6 +476,27 @@ version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
@@ -337,19 +504,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "misc"
version = "0.1.0"
name = "memmap2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4"
dependencies = [
"num-traits",
"libc",
]
[[package]]
name = "nom"
version = "8.0.0"
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"memchr",
"autocfg",
]
[[package]]
name = "misc"
version = "0.1.0"
dependencies = [
"intrusive-collections",
"num-traits",
]
[[package]]
@@ -361,18 +538,72 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if 1.0.4",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -391,6 +622,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rayon"
version = "1.11.0"
@@ -411,6 +648,15 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "regex"
version = "1.12.2"
@@ -441,10 +687,134 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustc-hash"
version = "2.1.1"
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rstest"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
dependencies = [
"futures-timer",
"futures-util",
"rstest_macros",
]
[[package]]
name = "rstest_macros"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
dependencies = [
"cfg-if 1.0.4",
"glob",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn 2.0.111",
"unicode-ident",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-rayon"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9974ab223660e61c1b4e7b43b827379df286736ca988308ce7e16f59f2d89246"
dependencies = [
"crossbeam-deque",
"either",
"rustc-rayon-core 0.3.2",
]
[[package]]
name = "rustc-rayon"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd9fb077db982d7ceb42a90471e5a69a990b58f71e06f0d8340bb2cf35eb751"
dependencies = [
"either",
"rustc-rayon-core 0.5.1",
]
[[package]]
name = "rustc-rayon-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "564bfd27be8db888d0fa76aa4335e7851aaed0c2c11ad1e93aeb9349f6b88500"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "rustc-rayon-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f42932dcd3bcbe484b38a3ccf79b7906fac41c02d408b5b1bac26da3416efdb"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "rustc_data_structures"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38bae9c6afa27015bcaa2869e03bb111ecf0d0e0edc2da559a91d4057174c9a"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"cfg-if 0.1.10",
"ena",
"indexmap 1.9.3",
"jobserver",
"libc",
"memmap2",
"parking_lot",
"rustc-hash",
"rustc-rayon 0.3.2",
"rustc-rayon-core 0.3.2",
"stable_deref_trait",
"tempfile",
"tracing",
"winapi",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
@@ -458,6 +828,27 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "safe_arch"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed"
dependencies = [
"bytemuck",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
@@ -500,6 +891,24 @@ dependencies = [
"serde_core",
]
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
@@ -528,6 +937,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.17"
@@ -549,12 +971,64 @@ dependencies = [
]
[[package]]
name = "thread_local"
version = "1.1.9"
name = "toml_datetime"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [
"cfg-if",
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
dependencies = [
"indexmap 2.12.1",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "tracing-core"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
dependencies = [
"once_cell",
]
[[package]]
@@ -581,13 +1055,22 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
dependencies = [
"cfg-if",
"cfg-if 1.0.4",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
@@ -636,21 +1119,44 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "wide"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbace5de6cfc4866f684318ad85761c89380cfb191982ae96aa65c295bf5897e"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -661,68 +1167,19 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.6"
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"memchr",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"

View File

@@ -6,20 +6,16 @@ version = "0.1.0"
[dependencies]
aoc-runner = "0.3.0"
aoc-runner-derive = "0.3.0"
atoi = "2.0.0"
bitflags = "2.6.0"
cached = "0.56.0"
colored = "3.0.0"
grid = {version = "0.1.0", path = "utils/grid"}
indicatif = { version = "0.18.3", features = ["rayon"] }
itertools = "0.14.0"
lazy_static = "1.5.0"
misc = {path = "utils/misc"}
nom = "8.0.0"
rayon = "1.10.0"
rayon = "1.11.0"
regex = "1.11.1"
rustc-hash = "2.1.0"
thread_local = "1.1.8"
rstest = "0.26.1"
rustc_data_structures = "0.1.2"
wide = "1.0.2"
[profile.release]
lto = true

View File

@@ -1,6 +1,6 @@
<!-- AOC TILES BEGIN -->
<h1 align="center">
2025 - 10 ⭐ - Rust
2025 - 21 ⭐ - Rust
</h1>
<a href="src/day1.rs">
<img src=".aoc_tiles/tiles/2025/01.png" width="161px">
@@ -17,4 +17,25 @@
<a href="src/day5.rs">
<img src=".aoc_tiles/tiles/2025/05.png" width="161px">
</a>
<a href="src/day6.rs">
<img src=".aoc_tiles/tiles/2025/06.png" width="161px">
</a>
<a href="src/day7.rs">
<img src=".aoc_tiles/tiles/2025/07.png" width="161px">
</a>
<a href="src/day8.rs">
<img src=".aoc_tiles/tiles/2025/08.png" width="161px">
</a>
<a href="src/day9.rs">
<img src=".aoc_tiles/tiles/2025/09.png" width="161px">
</a>
<a href="src/day10.rs">
<img src=".aoc_tiles/tiles/2025/10.png" width="161px">
</a>
<a href="src/day11.rs">
<img src=".aoc_tiles/tiles/2025/11.png" width="161px">
</a>
<a href="src/day12.rs">
<img src=".aoc_tiles/tiles/2025/12.png" width="161px">
</a>
<!-- AOC TILES END -->

331
src/day10.rs Normal file
View File

@@ -0,0 +1,331 @@
use std::{
collections::{BinaryHeap, VecDeque},
hash::Hash,
iter::{repeat, repeat_n},
};
use aoc_runner_derive::{aoc, aoc_generator};
use indicatif::{ProgressBar, ProgressStyle};
use itertools::Itertools;
use regex::Regex;
use wide::{CmpGt, i16x16};
#[derive(Clone, Debug, Default)]
struct MachineDefinition {
desired: Vec<bool>,
buttons: Vec<Vec<usize>>,
buttons2: Vec<i16x16>,
buttons_max: i16x16,
joltages: i16x16,
}
impl MachineDefinition {
fn create<'a>(&'a self) -> Machine<'a> {
Machine {
d: self,
lights: Vec::from_iter(repeat_n(false, self.desired.len())),
}
}
fn create2<'a>(&'a self) -> JoltMachine<'a> {
JoltMachine {
d: self,
joltages: i16x16::splat(0),
}
}
}
impl From<&str> for MachineDefinition {
fn from(value: &str) -> Self {
let parse_re = Regex::new(
r##"\[(?<desired>[.#]+)\] (?<buttons>(\([0-9,]+\) ?)+) \{(?<joltages>[0-9,]+)\}"##,
)
.unwrap();
let parts = parse_re.captures(value).unwrap();
let joltages: [i16; 16] = parts["joltages"]
.split(',')
.map(|n| n.parse().unwrap())
.chain(repeat(0))
.take(16)
.collect_array()
.unwrap();
let buttons = parts["buttons"]
.split_ascii_whitespace()
.map(|s| {
s[1..s.len() - 1]
.split(',')
.map(|n| n.parse().unwrap())
.collect()
})
.sorted_unstable_by_key(|s: &Vec<usize>| s.len())
.rev()
.collect_vec();
let mut buttons2 = Vec::new();
let mut buttons_max = [0i16; 16];
for (i, b) in buttons.iter().enumerate() {
let mut but = [0i16; 16];
for i in b {
but[*i] = 1;
}
buttons2.push(i16x16::new(but));
// find the joltage this button affects with the lowest value
// it is the max number of presses for this button
buttons_max[i] = b.iter().map(|idx| joltages[*idx]).min().unwrap();
}
MachineDefinition {
desired: parts["desired"].chars().map(|c| c == '#').collect(),
buttons: buttons,
buttons2: buttons2,
buttons_max: i16x16::new(buttons_max),
joltages: i16x16::new(joltages),
}
}
}
#[derive(Clone, Debug)]
struct Machine<'a> {
d: &'a MachineDefinition,
lights: Vec<bool>,
}
#[derive(Clone, Debug)]
struct JoltMachine<'a> {
d: &'a MachineDefinition,
joltages: i16x16,
}
impl<'a> Machine<'a> {
/// Get the state after pressing `button`, returns None if the state is as desired.
fn press(&self, button: usize) -> Option<Self> {
let mut new_state = self.lights.clone();
for light in &self.d.buttons[button] {
new_state[*light] = !new_state[*light];
}
if new_state == self.d.desired {
None
} else {
Some(Machine {
d: self.d,
lights: new_state,
})
}
}
/// Get the possible states from the current position
fn next_states(&self) -> Vec<(usize, Option<Self>)> {
self.d
.buttons
.iter()
.enumerate()
.map(|(i, _but)| (i, self.press(i)))
.collect()
}
}
impl<'a> JoltMachine<'a> {
fn press_jolts(&self, button: usize, presses: &i16x16) -> (i16x16, Option<Self>) {
// let mut new_joltage = self.joltages.clone();
// // for jolt in &self.d.buttons[button] {
// // new_joltage[*jolt] += 1;
// // }
let new_joltage = self.joltages + self.d.buttons2[button];
let mut new_presses = presses.clone();
new_presses.as_mut_array()[button] += 1;
if new_joltage == self.d.joltages {
(new_presses, None)
} else {
(
new_presses,
Some(Self {
d: self.d,
joltages: new_joltage,
}),
)
}
}
fn next_states_jolt(&self, presses: &i16x16) -> Vec<(i16x16, Option<Self>)> {
self.d
.buttons
.iter()
.enumerate()
.map(|(i, _but)| self.press_jolts(i, &presses))
// .inspect(|(p, o)| println!(" {p:?} {o:?}\n"))
// joltages monotonically increase, so cull any where a joltage is higher than needed
.filter(|(presses, candidate)| {
!presses.simd_gt(self.d.buttons_max).any()
&& candidate.as_ref().is_none_or(|c| {
!c.joltages.simd_gt(self.d.joltages).any()
// !c.joltages
// .iter()
// .zip(self.d.joltages.iter())
// .any(|(candidate, expected)| candidate > expected)
})
})
.collect()
}
}
#[derive(Debug, Clone)]
struct PressSet<'a> {
machine: Machine<'a>,
presses: usize,
}
#[derive(Debug, Clone)]
struct PressSet2<'a> {
machine: JoltMachine<'a>,
presses: i16x16,
}
// NOTE: All compares are reversed so our max heap becomes a min heap
impl<'a> Eq for PressSet<'a> {}
impl<'a> Eq for PressSet2<'a> {}
impl<'a> PartialEq for PressSet<'a> {
fn eq(&self, other: &Self) -> bool {
other.presses.eq(&self.presses)
}
}
impl<'a> PartialEq for PressSet2<'a> {
fn eq(&self, other: &Self) -> bool {
other.presses.eq(&self.presses)
}
}
impl<'a> PartialOrd for PressSet<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> PartialOrd for PressSet2<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for PressSet<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.presses.cmp(&self.presses)
}
}
impl<'a> Ord for PressSet2<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.presses.reduce_add().cmp(&self.presses.reduce_add())
}
}
fn find_best(md: &MachineDefinition) -> usize {
let m = md.create();
let mut to_check = BinaryHeap::new();
to_check.push(PressSet {
presses: 0,
machine: m,
});
while let Some(candidate) = to_check.pop() {
let cm = candidate.machine.clone();
for next in cm.next_states() {
let presses = candidate.presses + 1;
if let Some(new_m) = next.1 {
to_check.push(PressSet {
presses,
machine: new_m.clone(),
})
} else {
return presses;
}
}
}
panic!()
}
fn find_best_jolts(md: &MachineDefinition) -> usize {
let m = md.create2();
let mut to_check = VecDeque::new();
to_check.push_back(PressSet2 {
presses: i16x16::splat(0),
machine: m,
});
let mut pb = ProgressBar::no_length()
.with_style(
ProgressStyle::with_template(
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
)
.unwrap(),
)
.with_finish(indicatif::ProgressFinish::AndLeave);
while let Some(candidate) = to_check.pop_front() {
pb.inc(1);
pb.set_length(to_check.len() as u64);
let cm = candidate.machine.clone();
for (presses, next) in cm.next_states_jolt(&candidate.presses) {
if let Some(new_m) = next {
to_check.push_back(PressSet2 {
presses,
machine: new_m.clone(),
})
} else {
return presses.reduce_add() as usize;
}
}
}
panic!()
}
#[aoc_generator(day10)]
fn parse(input: &str) -> Vec<MachineDefinition> {
input.lines().map(|l| l.into()).collect()
}
#[aoc(day10, part1)]
fn part1(input: &[MachineDefinition]) -> u64 {
input
.iter()
.map(find_best)
// .inspect(|sol| println!(" [{sol:?}]"))
.map(|sol| sol as u64)
.sum()
}
#[aoc(day10, part2)]
fn part2(input: &[MachineDefinition]) -> u64 {
input
.iter()
.map(find_best_jolts)
.map(|sol| sol as u64)
.sum()
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
const EXAMPLE: &str = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}";
#[rstest]
#[case(EXAMPLE, 7)]
fn part1_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part1(&parse(input)), expected);
}
#[rstest]
#[case(EXAMPLE, 33)]
fn part2_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part2(&parse(input)), expected);
}
}

203
src/day11.rs Normal file
View File

@@ -0,0 +1,203 @@
use aoc_runner_derive::{aoc, aoc_generator};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
type Edges = FxHashMap<String, Vec<String>>;
#[aoc_generator(day11)]
fn parse(input: &str) -> Edges {
let mut edges: Edges = FxHashMap::default();
for l in input.lines() {
let (k, rest) = l.split_once(": ").unwrap();
for v in rest.split_ascii_whitespace() {
edges.entry(k.to_string()).or_default().push(v.to_string());
}
}
edges
}
fn find_path(cur: &str, goal: &str, edges: &Edges) -> u64 {
if cur == goal {
return 1;
}
if let Some(nexts) = edges.get(cur) {
nexts.iter().map(|n| find_path(n, goal, edges)).sum()
} else {
0
}
}
fn mark_paths<'a>(
cur: &'a str,
edges: &'a Edges,
mut reachable: FxHashSet<&'a str>,
) -> FxHashSet<&'a str> {
reachable.insert(cur);
if let Some(nexts) = edges.get(cur) {
for n in nexts {
if !reachable.contains(&n.as_str()) {
reachable = mark_paths(n, edges, reachable);
}
}
}
reachable
}
fn count_paths_impl<'a>(
cur: &'a str,
goal: &str,
edges: &'a Edges,
memo: &mut FxHashMap<&'a str, u64>,
) -> u64 {
if cur == goal {
return 1;
}
if let Some(v) = memo.get(cur) {
return *v;
}
if let Some(nexts) = edges.get(cur) {
let ret = nexts
.iter()
.map(|n| count_paths_impl(n, goal, edges, memo))
.sum();
memo.insert(cur, ret);
ret
} else {
memo.insert(cur, 0);
0
}
}
fn count_paths(cur: &str, goal: &str, edges: &Edges) -> u64 {
let mut cache = FxHashMap::default();
count_paths_impl(cur, goal, edges, &mut cache)
}
#[aoc(day11, part1, NaiveDfs)]
fn part1(fwd: &Edges) -> u64 {
find_path("you", "out", fwd)
}
#[aoc(day11, part1, MemoDfs)]
fn part1_memo(fwd: &Edges) -> u64 {
count_paths("you", "out", fwd)
}
#[aoc(day11, part2, MemoDfs)]
fn part2_memo(edges: &Edges) -> u64 {
// we assume all valid paths reach svr->fft->dac->out in that order. this holds on the example and the input.
// to handle both possible orderings, we need to sum paths fft->dac and dac->fft, and then partition the graph
// (just remove the node) at dac & fft before computing the sum of dac->out and fft->out.
//
// this is our performance implementation though and it's faster to not do that work. we do the posterity for the
// naive solution
count_paths("svr", "fft", edges)
* count_paths("fft", "dac", edges)
* count_paths("dac", "out", edges)
}
#[aoc(day11, part2, NaiveDfs)]
fn part2(edges: &Edges) -> u64 {
let mut rev = Edges::from_iter(edges.keys().map(|k| (k.to_owned(), vec![])));
for (from, tos) in edges {
for to in tos {
rev.entry(to.to_owned()).or_default().push(from.to_owned());
}
}
let mut reachable_dac = mark_paths("dac", edges, FxHashSet::default());
reachable_dac = mark_paths("dac", &rev, reachable_dac);
let mut reachable_fft = mark_paths("fft", edges, FxHashSet::default());
reachable_fft = mark_paths("fft", &rev, reachable_fft);
let reachable: FxHashSet<&str> = reachable_dac
.intersection(&reachable_fft)
.copied()
.collect();
let unreachable: FxHashSet<&str> = FxHashSet::from_iter(edges.keys().map(|k| k.as_str()))
.difference(&reachable)
.copied()
.collect();
let mut reachable_edges = edges.clone();
for k in &unreachable {
reachable_edges.remove(*k);
}
for (_k, v) in reachable_edges.iter_mut() {
for ur in &unreachable {
if let Some(idx) = v.iter().position(|s| s == ur) {
v.remove(idx);
}
}
}
// A real graph structure would probably notice that the graphs get split here
let mut reachable_edges_excl_fft = reachable_edges.clone();
reachable_edges_excl_fft.remove("fft");
let mut reachable_edges_excl_dac = reachable_edges.clone();
reachable_edges_excl_dac.remove("dac");
find_path("svr", "fft", &reachable_edges)
// only svr->fft->dac->out paths appear in my input and in the example, but include the dac->fft ordering
// as well, for posterity. It ~doubles runtime.
* (find_path("fft", "dac", &reachable_edges) + find_path("dac", "fft", &reachable_edges))
* (find_path("dac", "out", &reachable_edges_excl_fft) + find_path("fft", "out", &reachable_edges_excl_dac))
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
const EXAMPLE: &str = "aaa: you hhh
you: bbb ccc
bbb: ddd eee
ccc: ddd eee fff
ddd: ggg
eee: out
fff: out
ggg: out
hhh: ccc fff iii
iii: out";
const EXAMPLE2: &str = "svr: aaa bbb
aaa: fft
fft: ccc
bbb: tty
tty: ccc
ccc: ddd eee
ddd: hub
hub: fff
eee: dac
dac: fff
fff: ggg hhh
ggg: out
hhh: out";
#[rstest]
#[case(EXAMPLE, 5)]
fn part1_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part1(&parse(input)), expected);
}
#[rstest]
#[case(EXAMPLE, 5)]
fn part1_memo_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part1_memo(&parse(input)), expected);
}
#[rstest]
#[case(EXAMPLE2, 2)]
fn part2_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part2(&parse(input)), expected);
}
#[rstest]
#[case(EXAMPLE2, 2)]
fn part2_memo_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part2_memo(&parse(input)), expected);
}
}

249
src/day12.rs Normal file
View File

@@ -0,0 +1,249 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::{Coord2d, Grid, MirrorAxis};
use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::Display;
use std::iter::repeat_n;
type CellType = bool;
#[derive(Debug, Clone)]
struct PresentShape {
variants: Vec<Grid<CellType>>,
}
impl From<&str> for PresentShape {
fn from(value: &str) -> Self {
let (_idx, rest) = value.split_once(":\n").unwrap();
let shape_raw: Grid<u8> = rest.parse().unwrap();
let mut shape: Grid<CellType> = shape_raw.same_shape(false);
for pos in shape_raw.find_all(&b'#') {
shape.set(&pos, true);
}
Self {
variants: (0..4)
.map(|rot| shape.rotated(rot))
.chain([MirrorAxis::X, MirrorAxis::Y].map(|axis| shape.mirrored(axis)))
.unique()
.collect(),
}
}
}
impl Display for PresentShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.variants[0]))
}
}
impl PresentShape {
fn popcount(&self) -> u64 {
self.variants[0].count(&true) as u64
}
}
#[derive(Debug, Clone)]
struct Present {
shape: usize,
}
#[derive(Debug, Clone)]
struct ChristmasTree {
area: Grid<CellType>,
presents: Vec<Present>,
}
#[derive(Debug, Clone)]
struct PresentsProblem {
trees: Vec<ChristmasTree>,
shapes: Vec<PresentShape>,
}
#[aoc_generator(day12)]
fn parse(input: &str) -> PresentsProblem {
let mut parts = input.split("\n\n").collect_vec();
let trees_s = parts.pop().unwrap();
let shapes: Vec<PresentShape> = parts.into_iter().map(|p| p.into()).collect();
let mut trees = Vec::new();
for l in trees_s.lines() {
let (d, el) = l.split_once(": ").unwrap();
let (w, h) = d.split_once('x').unwrap();
let present_counts: Vec<usize> = el
.split_ascii_whitespace()
.map(|count| count.parse().unwrap())
.collect_vec();
let presents = present_counts
.iter()
.enumerate()
.flat_map(|(i, count)| repeat_n(Present { shape: i }, *count))
.collect();
trees.push(ChristmasTree {
area: Grid::with_shape(w.parse().unwrap(), h.parse().unwrap(), false),
presents,
});
}
PresentsProblem { trees, shapes }
}
// place b on a and test if any # overlap any non-.
// fn overlaps(a: &Grid<CellType>, b: &Grid<CellType>, p: &Coord2d) -> bool {
// b.find_all(&true)
// .any(|c| a.get(&(c + p)).is_some_and(|c| *c))
// }
impl PresentsProblem {
fn place_shape(
&self,
mut t: ChristmasTree,
idx: usize,
pos: Coord2d,
variant: usize,
) -> Option<ChristmasTree> {
let variant = &self.shapes[t.presents[idx].shape].variants[variant];
for xy in variant.find_all(&true) {
if let Some(was) = t.area.set(&(pos + &(xy)), true)
&& was
{
// overlapped
return None;
}
}
Some(t)
}
fn place_next(
&self,
t: ChristmasTree,
cur: usize,
cache: &mut HashSet<(usize, Grid<CellType>)>, // impossible shapes
) -> Option<ChristmasTree> {
if cur == t.presents.len() {
// done
return Some(t);
}
if cache.contains(&(cur, t.area.clone())) {
// doomed
return None;
}
let variants = &self.shapes[t.presents[cur].shape].variants;
for (i, _v) in variants.iter().enumerate() {
for y in 0..&t.area.height() - 2 {
for x in 0..&t.area.width() - 2 {
if let Some(new_t) = self.place_shape(
t.clone(),
cur,
Coord2d {
x: x as i64,
y: y as i64,
},
i,
) {
if let Some(solution) = self.place_next(new_t, cur + 1, cache) {
return Some(solution);
}
}
}
}
}
cache.insert((cur, t.area.clone()));
None
}
}
#[aoc(day12, part1)]
fn part1(input: &PresentsProblem) -> u64 {
let input = input.clone();
let mut count = 0;
for t in input.trees.iter() {
let area = (t.area.width() * t.area.height()) as u64;
let (occupied_area, present_count) = t
.presents
.iter()
.map(|p| (input.shapes[p.shape].popcount(), 1))
.reduce(|(o_a, pc_a), (o_b, pc_b)| (o_a + o_b, pc_a + pc_b))
.unwrap();
if occupied_area > area {
// definitely impossible
continue;
}
let available_3x3s = (t.area.width() / 3) * (t.area.height() / 3);
if available_3x3s >= present_count {
// definitely_possible
count += 1;
continue;
}
if input
.place_next(t.clone(), 0, &mut HashSet::new())
.is_some()
{
count += 1;
continue;
}
}
count
}
#[aoc(day12, part2)]
fn part2(_input: &PresentsProblem) -> u64 {
0
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
const EXAMPLE: &str = "0:
###
##.
##.
1:
###
##.
.##
2:
.##
###
##.
3:
##.
###
##.
4:
###
#..
###
5:
###
.#.
###
4x4: 0 0 0 0 2 0
12x5: 1 0 1 0 2 2
12x5: 1 0 1 0 3 2";
#[rstest]
#[case(EXAMPLE, 2)]
fn part1_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part1(&parse(input)), expected);
}
#[rstest]
#[case(EXAMPLE, 0)]
fn part2_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part2(&parse(input)), expected);
}
}

View File

@@ -1,6 +1,7 @@
use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools;
use misc::POW10;
use std::cmp::{max, min};
use std::ops::RangeInclusive;
#[aoc_generator(day2)]
@@ -88,15 +89,15 @@ fn part2_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
let n_digits = (product.ilog10() + 1) as usize;
for n in 1..=n_digits / 2 {
let repetitions = n_digits / n;
if !n_digits.is_multiple_of(n) {
continue;
}
let decade = POW10[n_digits - n];
let repetitions = n_digits / n;
let decade = POW10[n_digits - n]; // get the power of 10 to split the leftmost n digits
let lhs = product / decade;
let remainder = product % decade;
// for each repetition we multiply by 10^(rep * n)
// for each repetition we multiply by 10^(rep * n) to add the appropriate zeros
let expected_remainder = (0..repetitions - 1).map(|rep| lhs * POW10[rep * n]).sum();
if remainder == expected_remainder {
@@ -139,6 +140,48 @@ fn part1_clever(input: &[RangeInclusive<u64>]) -> u64 {
invalid_sum
}
const fn n_digits(n: &u64) -> usize {
(n.ilog10() + 1) as usize
}
fn generate_repeaters(r: &RangeInclusive<u64>, n: usize) -> Vec<u64> {
let mut invalids = Vec::new();
for r in split_by_decade(r) {
let n_digits = n_digits(r.start());
if !n_digits.is_multiple_of(n) || n_digits < 2 {
continue;
}
let repetitions = n_digits / n;
let decade = POW10[n_digits - n];
for lhs in (r.start() / decade)..=(r.end() / decade) {
let repeater = (0..repetitions)
.map(|rep| lhs * POW10[rep * n])
.sum::<u64>();
if r.contains(&repeater) {
invalids.push(repeater)
}
}
}
invalids
}
fn split_by_decade(r: &RangeInclusive<u64>) -> impl Iterator<Item = RangeInclusive<u64>> {
let (start, end) = (*r.start(), *r.end());
(n_digits(r.start())..n_digits(r.end()) + 1)
.map(move |nd| RangeInclusive::new(max(start, POW10[nd - 1]), min(end, POW10[nd] - 1)))
}
#[aoc(day2, part2, Clever)]
fn part2_clever(input: &[RangeInclusive<u64>]) -> u64 {
input
.iter()
.flat_map(|r| (1..=n_digits(r.end()) / 2).map(|nd| generate_repeaters(r, nd)))
.flatten()
.unique()
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -167,4 +210,8 @@ mod tests {
fn part1_clever_example() {
assert_eq!(part1_clever(&parse(EXAMPLE)), 1227775554);
}
#[test]
fn part2_clever_example() {
assert_eq!(part2_clever(&parse(EXAMPLE)), 4174379265);
}
}

View File

@@ -1,4 +1,4 @@
use std::cmp::{max, min};
use std::cmp::max;
use std::fmt::Debug;
use std::ops::RangeInclusive;
@@ -43,49 +43,51 @@ impl RangeSet {
Self { ranges: Vec::new() }
}
fn simplify(&mut self) {
// sort the ranges by start
if self.ranges.len() < 2 {
return;
}
let mut modified = true;
while modified {
let mut new_ranges = Vec::new();
modified = false;
// sort the ranges by start
self.ranges.sort_by_key(|s| s.start);
for chunk in self.ranges.chunks(2) {
if chunk.len() == 1 {
new_ranges.push(chunk[0].clone());
continue;
let mut new_ranges = Vec::new();
let mut pos = 1;
while pos <= self.ranges.len() {
// part of a disgusting hack to avoid skipping or double adding at the end
if pos == self.ranges.len() {
pos -= 1;
}
let (l, r) = (&chunk[0], &chunk[1]);
let (l, r) = (&self.ranges[pos - 1], &self.ranges[pos]);
if r.start <= l.end + 1 {
modified = true;
new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into())
} else {
new_ranges.push(l.clone());
new_ranges.push(r.clone());
}
}
if !self.ranges.len().is_multiple_of(2) && new_ranges.len() >= 2 {
let (l, r) = (new_ranges.pop().unwrap(), new_ranges.pop().unwrap());
if r.start <= l.end + 1 && r.end >= l.start - 1 {
modified = true;
new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into())
new_ranges.push((l.start, max(l.end, r.end)).into());
// if we merged, skip checking the next range to avoid double-adding it
// this screws us up at the very end, so need the disgusting hack above
pos += 2;
} else if pos < self.ranges.len() - 1 {
// keep only the lhs if we didn't merge
new_ranges.push(l.clone());
pos += 1;
} else {
// at the end, keep both sides, otherwise the rhs gets excluded (if it didn't merge)
new_ranges.push(l.clone());
new_ranges.push(r.clone());
pos += 2;
}
}
self.ranges = new_ranges;
}
}
fn add(&mut self, s: Span) {
self.ranges.push(s);
self.simplify()
}
}
#[aoc_generator(day5, part1)]
#[aoc_generator(day5)]
fn parse(input: &str) -> Database {
let mut fresh_ingredients = Vec::new();
let mut available_ingredients = Vec::new();
@@ -111,7 +113,16 @@ fn parse(input: &str) -> Database {
}
}
#[aoc_generator(day5, part2)]
#[aoc(day5, part1)]
fn part1(input: &Database) -> u64 {
input
.available_ingredients
.iter()
.filter(|i| input.fresh_ingredients.iter().any(|r| r.contains(i)))
.count() as u64
}
#[aoc_generator(day5, part2, Naive)]
fn parse2(input: &str) -> Database2 {
let mut fresh_ingredients = Vec::new();
for line in input.lines() {
@@ -124,25 +135,25 @@ fn parse2(input: &str) -> Database2 {
Database2 { fresh_ingredients }
}
#[aoc(day5, part1)]
fn part1(input: &Database) -> u64 {
input
.available_ingredients
.iter()
.filter(|i| input.fresh_ingredients.iter().any(|r| r.contains(i)))
.count() as u64
}
#[aoc(day5, part2)]
#[aoc(day5, part2, Naive)]
fn part2(input: &Database2) -> u64 {
let mut all_ingredients = RangeSet::new();
for r in &input.fresh_ingredients {
all_ingredients.add(r.clone())
all_ingredients.add(r.clone());
}
all_ingredients.simplify();
all_ingredients.ranges.iter().map(|r| r.len()).sum::<u64>()
}
#[aoc(day5, part2, RangeSet)]
fn part2_rangeset(input: &Database) -> u64 {
let mut all_ingredients = misc::range::RangeSet::new();
for r in &input.fresh_ingredients {
all_ingredients.add(r);
}
all_ingredients.store.iter().map(|r| r.end - r.start).sum()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -168,4 +179,9 @@ mod tests {
fn part2_example() {
assert_eq!(part2(&parse2(EXAMPLE)), 14);
}
#[test]
fn part2_set_example() {
assert_eq!(part2_rangeset(&parse(EXAMPLE)), 14);
}
}

280
src/day6.rs Normal file
View File

@@ -0,0 +1,280 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::Grid;
use itertools::Itertools;
use misc::POW10;
use std::{
fmt::{Display, Write},
iter::repeat_n,
str::FromStr,
};
#[repr(u8)]
enum Op {
Add = b'+',
Mul = b'*',
}
impl From<&str> for Op {
fn from(value: &str) -> Self {
match value {
"+" => Op::Add,
"*" => Op::Mul,
c => panic!("Invalid op `{c}`"),
}
}
}
impl From<&u8> for Op {
fn from(value: &u8) -> Self {
match value {
b'+' => Op::Add,
b'*' => Op::Mul,
c => panic!("Invalid op `{c}`"),
}
}
}
impl Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Op::Add => f.write_char('+'),
Op::Mul => f.write_char('*'),
}
}
}
impl Op {
fn f(&self, lhs: &u64, rhs: &u64) -> u64 {
match self {
Op::Add => lhs + rhs,
Op::Mul => lhs * rhs,
}
}
}
#[aoc_generator(day6, part1)]
fn parse(input: &str) -> (Grid<u64>, Vec<Op>) {
let mut rows = input
.lines()
.map(|l| l.split_ascii_whitespace().collect_vec())
.collect_vec();
let ops = rows
.pop()
.unwrap()
.iter()
.map(|op| Op::from(*op))
.collect_vec();
let mut grid = Grid::with_shape(rows[0].len(), rows.len(), 0);
for (y, r) in rows.iter().enumerate() {
for (x, v) in r.iter().enumerate() {
grid.set(&(x, y), v.parse().unwrap());
}
}
(grid, ops)
}
#[aoc(day6, part1)]
fn part1((grid, ops): &(Grid<u64>, Vec<Op>)) -> u64 {
(0..grid.width())
.map(|x| {
grid.col_iter(x as i64)
.unwrap()
.cloned()
.reduce(|l, r| ops[x].f(&l, &r))
.unwrap()
})
.sum()
}
// fn split_digits(x: &u64) -> Vec<u8> {
// let n_digits = x.ilog10() as usize;
// (0..=n_digits)
// .rev()
// .map(|n| ((x / POW10[n]) % 10) as u8)
// .collect()
// }
#[aoc_generator(day6, part2, Spaghett)]
fn parse2(input: &str) -> (Grid<Vec<char>>, Vec<Op>) {
let mut rows = input.lines().collect_vec();
let ops = rows.pop().unwrap();
let col_starts = ops
.chars()
.enumerate()
.filter(|(_i, c)| *c == '+' || *c == '*')
.map(|(i, _c)| i)
.collect_vec();
let col_lengths = col_starts
.iter()
.tuple_windows()
.map(|(l, r)| r - l - 1)
.collect_vec();
let split_rows = rows
.iter()
.map(|r| {
col_starts
.iter()
.zip(col_lengths.iter())
.map(|(s, l)| &r[*s..s + l])
.chain(repeat_n(&r[*col_starts.last().unwrap()..], 1))
.collect_vec()
})
.collect_vec();
let ops = ops.split_ascii_whitespace().map(Op::from).collect_vec();
let mut grid = Grid::with_shape(split_rows[0].len(), split_rows.len(), Vec::new());
for (y, r) in split_rows.iter().enumerate() {
for (x, v) in r.iter().enumerate() {
grid.set(&(x, y), v.chars().collect_vec());
}
}
(grid, ops)
}
#[aoc(day6, part2, Spaghett)]
fn part2((grid, ops): &(Grid<Vec<char>>, Vec<Op>)) -> u64 {
let mut columns = Vec::new();
for col in 0..grid.width() {
let mut digit = 0;
let mut col_values = Vec::new();
loop {
let val = grid
.col_iter(col as i64)
.unwrap()
.filter_map(|s| s.get(digit))
.filter(|c| !c.is_ascii_whitespace())
.map(|v| v.to_digit(10).unwrap() as u64)
.fold(0, |acc, n| acc * 10 + n);
if val == 0 {
columns.push(col_values);
break;
}
col_values.push(val);
digit += 1;
}
}
columns
.iter()
.enumerate()
.map(|(col, v)| v.iter().cloned().reduce(|l, r| ops[col].f(&l, &r)).unwrap())
.sum::<u64>()
}
#[aoc_generator(day6, part2, StateMachine)]
fn parse2_walk(input: &str) -> Grid<u8> {
Grid::from_str(input).unwrap()
}
enum NextPos {
Advance((usize, usize)),
NewCol((usize, usize)),
Done,
}
#[aoc(day6, part2, StateMachine)]
fn part2_walk(g: &Grid<u8>) -> u64 {
let mut accum = 0;
// start at the bottom right
let mut pos = (g.width() - 1, g.height() - 1);
let mut digits = 0;
let mut cur_value = 0;
let mut cur_values = Vec::new();
let mut cur_op = Op::Add;
let mut last_col = false;
let height = g.height() - 1;
let next_pos = |(x, y)| -> NextPos {
if y == 0 {
if x == 0 {
return NextPos::Done;
}
return NextPos::NewCol((x - 1, height));
}
NextPos::Advance((x, y - 1))
};
while let Some(cur_char) = g.get(&pos) {
match cur_char {
b'*' | b'+' => {
last_col = true;
cur_op = cur_char.into()
}
cur_char if cur_char.is_ascii_digit() => {
cur_value += (*cur_char - b'0') as u64 * POW10[digits];
digits += 1;
}
_ => {}
}
pos = match next_pos(pos) {
NextPos::Advance(p) => p,
NextPos::NewCol(p) => {
// add the value
if cur_value != 0 {
cur_values.push(cur_value);
cur_value = 0;
}
digits = 0;
// this was a last col, so reduce and accumulate
if last_col {
accum += cur_values
.iter()
.cloned()
.reduce(|l, r| cur_op.f(&l, &r))
.unwrap_or(0);
// clear the state
cur_values.clear();
last_col = false;
}
p
}
NextPos::Done => {
if cur_value != 0 {
cur_values.push(cur_value);
}
// accumulate the final result
accum += cur_values
.iter()
.cloned()
.reduce(|l, r| cur_op.f(&l, &r))
.unwrap_or(0);
return accum;
}
}
}
unreachable!()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = r"123 328 51 64
45 64 387 23
6 98 215 314
* + * + ";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EXAMPLE)), 4277556);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse2(EXAMPLE)), 3263827);
}
#[test]
fn part2_walk_example() {
assert_eq!(part2_walk(&parse2_walk(EXAMPLE)), 3263827);
}
}

102
src/day7.rs Normal file
View File

@@ -0,0 +1,102 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::{Coord2d, Grid};
use std::str::FromStr;
#[aoc_generator(day7)]
fn parse(input: &str) -> Grid<u8> {
Grid::from_str(input).unwrap()
}
fn emit_beams(grid: &mut Grid<u8>, pos: Coord2d) -> u64 {
match grid.set(&pos, b'|') {
None | Some(b'|') => 0,
Some(b'.') | Some(b'S') => emit_beams(grid, pos + &(0, 1)),
Some(b'^') => 1 + emit_beams(grid, pos + &(-1, 0)) + emit_beams(grid, pos + &(1, 0)),
_ => panic!(),
}
}
#[aoc(day7, part1)]
fn part1(input: &Grid<u8>) -> u64 {
let mut grid = input.clone();
emit_beams(&mut grid, input.find(&b'S').unwrap())
}
#[aoc(day7, part2)]
fn part2(input: &Grid<u8>) -> u64 {
let mut col_counts = vec![0; input.width()];
col_counts[input.find(&b'S').unwrap().x as usize] = 1;
// Start row is already set up
for y in 1..input.height() {
// for each column with non-zero counts
for x in 0..input.width() {
if col_counts[x] == 0 {
continue;
}
match input.get(&(x, y)) {
// if our current position is a caret, add that count to our neighbours
Some(b'^') => {
for col in [x as i64 - 1, x as i64 + 1]
.into_iter()
.filter(|x| *x >= 0 && *x < input.width() as i64)
{
col_counts[col as usize] += col_counts[x];
}
col_counts[x] = 0;
}
// if our current position is a . do nothing, it's already counted
Some(b'.') => continue,
Some(c) => panic!("unexpected character {c}"),
None => panic!("How did we end up outside the grid?"),
}
}
}
col_counts.iter().sum::<u64>()
}
#[cfg(test)]
mod tests {
use super::*;
const TRIVIAL_EXAMPLE: &str = "...S...
.......
...^...
..^.^.^
...^.^.
.......
.......";
const EXAMPLE: &str = ".......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EXAMPLE)), 21);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EXAMPLE)), 40);
}
#[test]
fn part2_trivial_example() {
assert_eq!(part2(&parse(TRIVIAL_EXAMPLE)), 7);
}
}

249
src/day8.rs Normal file
View File

@@ -0,0 +1,249 @@
use std::{cmp::Reverse, collections::BinaryHeap, fmt::Display};
use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools;
#[derive(PartialEq, Clone, Debug)]
struct Junction {
pos: (i64, i64, i64),
}
fn squared_distance(a: &Junction, b: &Junction) -> u64 {
// if a.pos == b.pos {
// 0
// } else {
(a.pos.0 - b.pos.0).pow(2) as u64
+ (a.pos.1 - b.pos.1).pow(2) as u64
+ (a.pos.2 - b.pos.2).pow(2) as u64
// }
}
impl Junction {
fn squared_distance(&self, other: &Junction) -> u64 {
squared_distance(self, other)
}
}
impl Display for Junction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"({},{},{})",
self.pos.0, self.pos.1, self.pos.2
))
}
}
impl From<&str> for Junction {
fn from(value: &str) -> Self {
Self {
pos: value
.split(',')
.map(|v| v.parse().unwrap())
.collect_tuple()
.unwrap(),
}
}
}
#[derive(Clone)]
struct Circuits {
junctions: Vec<Junction>,
circuits: Vec<Vec<usize>>,
}
impl Circuits {
fn find_circuit(&self, junction: usize) -> usize {
self.circuits
.iter()
.enumerate()
.find(|(_i, c)| c.contains(&junction))
.map(|(i, _c)| i)
.unwrap()
}
fn merge_circuits(&mut self, a: usize, b: usize) {
if a == b {
return;
}
let mut items = std::mem::take(&mut self.circuits[b]);
self.circuits[a].append(&mut items);
self.circuits.remove(b);
}
fn connect(&mut self, a: usize, b: usize) {
let a_circuit = self.find_circuit(a);
let b_circuit = self.find_circuit(b);
self.merge_circuits(a_circuit, b_circuit); // both are already in circuits, merge them
}
}
#[aoc_generator(day8)]
fn parse(input: &str) -> Circuits {
let junctions = input.lines().map(|l| l.into()).collect_vec();
Circuits {
circuits: Vec::from_iter((0..junctions.len()).map(|i| vec![i])),
junctions,
}
}
fn part1_impl(input: &Circuits, n: usize) -> u64 {
let mut circuits = input.clone();
for (a, b, _d) in circuits
.junctions
.iter()
.enumerate()
.tuple_combinations()
.map(|((a_pos, a), (b_pos, b))| (a_pos, b_pos, a.squared_distance(b)))
.sorted_unstable_by_key(|(_a_pos, _b_pos, d)| *d)
.take(n)
{
circuits.connect(a, b)
}
circuits
.circuits
.iter()
.map(|c| c.len())
.sorted_unstable()
.rev()
.take(3)
.reduce(|a, b| a * b)
.unwrap() as u64
}
#[aoc(day8, part1, Sorted)]
fn part1(input: &Circuits) -> u64 {
part1_impl(input, 1000)
}
#[aoc(day8, part2, Sorted)]
fn part2(input: &Circuits) -> u64 {
let mut circuits = input.clone();
for (a, b, _d) in circuits
.junctions
.iter()
.enumerate()
.tuple_combinations()
.map(|((a_pos, a), (b_pos, b))| (a_pos, b_pos, a.squared_distance(b)))
.sorted_unstable_by_key(|(_a_pos, _b_pos, d)| *d)
{
circuits.connect(a, b);
if circuits.circuits.len() == 1 {
return (circuits.junctions[a].pos.0 * circuits.junctions[b].pos.0) as u64;
}
}
panic!()
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct JunctionPair {
d: u64,
a: usize,
b: usize,
}
fn make_heap(circuits: &Circuits) -> BinaryHeap<Reverse<JunctionPair>> {
BinaryHeap::from_iter(
circuits
.junctions
.iter()
.enumerate()
.tuple_combinations()
.map(|((a_pos, a), (b_pos, b))| {
Reverse(JunctionPair {
a: a_pos,
b: b_pos,
d: a.squared_distance(b),
})
}),
)
}
fn part1_heaped_impl(input: &Circuits, n: usize) -> u64 {
let mut circuits = input.clone();
let mut distances = make_heap(&circuits);
for _ in 0..n {
let pair = distances.pop().unwrap().0;
circuits.connect(pair.a, pair.b);
}
circuits
.circuits
.iter()
.map(|c| c.len())
.sorted_unstable()
.rev()
.take(3)
.reduce(|a, b| a * b)
.unwrap() as u64
}
#[aoc(day8, part1, Heaped)]
fn part1_heaped(input: &Circuits) -> u64 {
part1_heaped_impl(input, 1000)
}
#[aoc(day8, part2, Heaped)]
fn part2_heaped(input: &Circuits) -> u64 {
let mut circuits = input.clone();
let mut distances = make_heap(&circuits);
while let Some(Reverse(jp)) = distances.pop() {
circuits.connect(jp.a, jp.b);
if circuits.circuits.len() == 1 {
return (circuits.junctions[jp.a].pos.0 * circuits.junctions[jp.b].pos.0) as u64;
}
}
panic!()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE: &str = "162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689";
#[test]
fn part1_example() {
assert_eq!(part1_impl(&parse(EXAMPLE), 10), 40);
}
#[test]
fn part1_heaped_example() {
assert_eq!(part1_heaped_impl(&parse(EXAMPLE), 10), 40);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EXAMPLE)), 25272);
}
#[test]
fn part2_heaped_example() {
assert_eq!(part2_heaped(&parse(EXAMPLE)), 25272);
}
}

459
src/day9.rs Normal file
View File

@@ -0,0 +1,459 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::{AsCoord2d, Coord2d, Grid};
use indicatif::{ParallelProgressIterator, ProgressIterator, ProgressStyle};
use itertools::Itertools;
use rayon::prelude::*;
use std::collections::HashMap;
use std::{
cmp::{max, min},
fmt::Display,
};
#[aoc_generator(day9)]
fn parse(input: &str) -> Vec<Coord2d> {
input
.lines()
.map(|l| l.parse::<Coord2d>().unwrap())
.map(|c| Coord2d {
x: c.x + 1, // allow a buffer zone
y: c.y + 1,
})
.collect()
}
#[aoc(day9, part1)]
fn part1(input: &Vec<Coord2d>) -> u64 {
input
.iter()
.tuple_combinations()
.inspect(|(a, b)| {
println!(
"{a} vs {b} = {}",
a.x().abs_diff(b.x()) * a.y().abs_diff(b.y())
)
})
.map(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1))
.sorted_unstable()
.next_back()
.unwrap()
}
fn build_grid(reds: &Vec<Coord2d>) -> Grid<u8> {
let width = reds.iter().map(|c| c.x()).max().unwrap() + 3;
let height = reds.iter().map(|c| c.y()).max().unwrap() + 3;
let mut grid = Grid::with_shape(width as usize, height as usize, b'.');
let mut prev = reds.last().unwrap();
for c in reds {
// mark c filled
grid.set(&c, b'#');
// build a line of green between it and the previous
if c.x() == prev.x() {
// vertical
for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) {
grid.set(&(c.x(), y), b'X');
}
} else if c.y() == prev.y() {
// horiztonal
for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) {
grid.set(&(x, c.y()), b'X');
}
} else {
panic!()
}
prev = c
}
grid
}
fn build_hashgrid(reds: &Vec<Coord2d>) -> HashMap<Coord2d, u8> {
let mut grid = HashMap::new();
let mut prev = reds.last().unwrap();
for c in reds {
// mark c filled
grid.insert(*c, b'#');
// build a line of green between it and the previous
if c.x() == prev.x() {
// vertical
for y in (min(c.y(), prev.y()) + 1)..max(c.y(), prev.y()) {
grid.insert(Coord2d { x: c.x(), y }, b'X');
}
} else if c.y() == prev.y() {
// horiztonal
for x in (min(c.x(), prev.x()) + 1)..max(c.x(), prev.x()) {
grid.insert(Coord2d { x, y: c.y() }, b'X');
}
} else {
panic!()
}
prev = c
}
grid
}
// FIXME: TOTALLY BROKEN
fn flood_fill(grid: &mut Grid<u8>) {
#[derive(Debug, Eq, PartialEq)]
enum State {
OffLine(bool), // Off a line(true=inside)
OnLine(bool), // On a line(previous state)
}
for y in 0..grid.height() {
let mut state = State::OffLine(false);
for x in 0..grid.width() {
match grid.get(&(x, y)) {
Some(b'.') => {
if state == State::OffLine(true) || state == State::OnLine(false) {
grid.set(&(x, y), b'X');
state = State::OffLine(true)
} else {
state = State::OffLine(false)
}
}
Some(b'#') | Some(b'X') => {
state = State::OnLine(match state {
State::OnLine(s) => s,
State::OffLine(s) => s,
})
}
None => panic!("overran the grid"),
Some(c) => panic!("unexpected value {c}"),
}
}
}
}
// #[aoc(day9, part2, Brute)]
fn part2(reds: &Vec<Coord2d>) -> u64 {
let mut grid = build_grid(reds);
flood_fill(&mut grid);
println!("{grid}");
let pairs = reds
.iter()
.tuple_combinations()
.sorted_unstable_by_key(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1))
.rev()
.progress()
.collect_vec();
let (a, b) = pairs
.par_iter()
.progress_with_style(
ProgressStyle::with_template(
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
)
.unwrap(),
)
.filter(|(a, b)| (a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1) < 2972065369) // FIXME: PROGRESS CAPTURE
.map(|(a, b)| {
for y in (min(a.y(), b.y()))..=max(a.y(), b.y()) {
for x in (min(a.x(), b.x()))..=max(a.x(), b.y()) {
if *grid.get(&(x, y)).unwrap() == b'.' {
return (false, a, b);
}
}
}
(true, a, b)
})
.find_map_first(|(good, a, b)| if good { Some((a, b)) } else { None })
.unwrap();
println!("win: {a} {b}");
(a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)
}
fn line_square_intersection<LT: AsCoord2d, ST: AsCoord2d>(
&(l1, l2): &(&LT, &LT),
&(sq1, sq2): &(&ST, &ST),
) -> bool
where
LT: Clone + Display,
ST: Clone + Display,
{
let min_x = min(sq1.x(), sq2.x());
let max_x = max(sq1.x(), sq2.x());
let min_y = min(sq1.y(), sq2.y());
let max_y = max(sq1.y(), sq2.y());
// println!("({},{}) X [{},{}]", l1, l2, sq1, sq2);
// line is horizontal
if l1.y() == l2.y() {
// println!(" horizontal at y={}", l1.y());
// above, below, or touching square
if l1.y() <= min_y || l1.y() >= max_y {
// println!(" y out of range or touching");
false
// start inside
} else if min(l1.x(), l2.x()) >= min_x && min(l1.x(), l2.x()) <= max_x {
// println!(" start inside");
true
//end inside
} else if max(l1.x(), l2.x()) >= min_x && max(l1.x(), l2.x()) <= max_x {
// println!(" end inside");
true
} else {
// println!(" no overlap on x");
false
}
// line is vertical
} else {
// println!(" vertical at x={}", l1.x());
if l1.x() <= min_x || l1.x() >= max_x {
// println!(" x out of range or touching");
false
// start inside
} else if min(l1.y(), l2.y()) >= min_y && min(l1.y(), l2.y()) <= max_y {
// println!(" start inside");
true
//end inside
} else if max(l1.y(), l2.y()) >= min_y && max(l1.y(), l2.y()) <= max_y {
// println!(" end inside");
true
} else {
// println!(" no overlap on y");
false
}
}
}
trait Rectangle {
fn top_left(&self) -> Coord2d;
fn bottom_right(&self) -> Coord2d;
fn in_bounds<T: AsCoord2d>(&self, b: &T) -> bool {
b.x() >= self.top_left().x
&& b.x() <= self.bottom_right().x()
&& b.y() >= self.top_left().y()
&& b.y() <= self.bottom_right().y()
}
fn area(&self) -> u64 {
let a = self.top_left();
let b = self.bottom_right();
(a.x().abs_diff(b.x()) + 1) * (a.y().abs_diff(b.y()) + 1)
}
}
impl Rectangle for (&Coord2d, &Coord2d) {
fn top_left(&self) -> Coord2d {
Coord2d {
x: min(self.0.x, self.1.x),
y: min(self.0.y, self.1.y),
}
}
fn bottom_right(&self) -> Coord2d {
Coord2d {
x: max(self.0.x, self.1.x),
y: max(self.0.y, self.1.y),
}
}
}
// true = clockwise
fn outside_points(prev: &Coord2d, cur: &Coord2d, next: &Coord2d) -> Vec<Coord2d> {
let l1_diff = ((cur.x - prev.x).signum(), (cur.y - prev.y).signum());
let l2_diff = ((next.x - cur.x).signum(), (next.y - cur.y).signum());
match (l1_diff, l2_diff) {
// CW
((1, 0), (0, 1)) => vec![cur + (0, -1), cur + (1, -1), cur + (1, 0)], // x^ y^
((0, 1), (-1, 0)) => vec![cur + (1, 0), cur + (1, 1), cur + (0, -1)], // y^ xv
((-1, 0), (0, -1)) => vec![cur + (0, 1), cur + (-1, 1), cur + (-1, 0)], // xv yv
((0, -1), (1, 0)) => vec![cur + (-1, 0), cur + (-1, -1), cur + (0, -1)], // yv x^
// CCW
((0, 1), (1, 0)) => vec![cur + (1, -1)],
((1, 0), (0, -1)) => vec![cur + (-1, -1)],
((0, -1), (-1, 0)) => vec![cur + (-1, 1)],
((-1, 0), (0, 1)) => vec![cur + (1, 1)],
((0, _), (0, _)) | ((_, 0), (_, 0)) => vec![], // colinear
_ => panic!(
"unexpected line arrangement {:?} {:?} @ [({},{}), ({},{}), ({},{})",
l1_diff, l2_diff, prev.x, prev.y, cur.x, cur.y, next.x, next.y
),
}
}
#[aoc(day9, part2, Lines)]
fn part2_lines(reds: &Vec<Coord2d>) -> u64 {
// let mut grid = build_hashgrid(reds);
let mut grid = build_grid(reds);
println!("{grid}");
let outside_points: HashMap<&Coord2d, Vec<Coord2d>> = reds
.iter()
.circular_tuple_windows()
.map(|(a, b, c)| (b, outside_points(a, b, c)))
.collect();
// for corner in reds {
// for p in &outside_points[corner] {
// if *grid.get(p).unwrap() == b'.' {
// grid.set(p, b'O');
// }
// }
// }
// println!("{grid}");
// still iterate over the possible rectangles in size order
for rect in reds
.iter()
.tuple_combinations()
.sorted_unstable_by_key(|rect: &(&Coord2d, &Coord2d)| rect.area())
.rev()
.progress_with_style(
ProgressStyle::with_template(
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
)
.unwrap(),
)
{
println!(
"[{},{}] = {}",
rect.top_left(),
rect.bottom_right(),
rect.area()
);
// then for each corner, check if any of its outside edges are inside and not part of another line
if reds
.iter()
.filter(|corner| rect.in_bounds(corner))
.inspect(|c| println!(" corner: {c}"))
.any(|corner| {
outside_points[corner]
.iter()
.inspect(|p| println!(" {} = {}", p, *grid.get(p).unwrap() as char))
.any(|p| rect.in_bounds(p) && *grid.get(p).unwrap() == b'.')
})
{
continue;
}
println!(
"win! {},{} = {}",
rect.top_left(),
rect.bottom_right(),
rect.area()
);
grid.set(&rect.top_left(), b'*');
grid.set(&rect.bottom_right(), b'*');
println!("{grid}");
return rect.area();
}
panic!()
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
const EXAMPLE: &str = "7,1
11,1
11,7
9,7
9,5
2,5
2,3
7,3";
#[test]
fn part1_example() {
assert_eq!(part1(&parse(EXAMPLE)), 50);
}
#[test]
fn part2_example() {
assert_eq!(part2(&parse(EXAMPLE)), 24);
}
#[rstest]
#[case(
"7,1
11,1
11,7
9,7
9,5
2,5
2,3
7,3",
24
)]
#[case(
"4,2
13,2
13,4
8,4
8,6
11,6
11,10
4,10",
40
)]
#[case(
"3,2
17,2
17,13
13,13
13,11
15,11
15,8
11,8
11,15
18,15
18,17
4,17
4,12
6,12
6,5
3,5",
66
)]
#[case(
"2,2
8,2
8,6
5,6
5,4
2,4",
21
)]
#[case(
"3,1
12,1
12,4
9,4
9,8
3,8",
56
)]
#[case(
"7,1
11,1
11,3
13,3
13,5
11,5
11,7
9,7
9,5
2,5
2,3
7,3",
36
)]
#[case(
"1,2
8,2
8,4
6,4
6,5
9,5
9,8
1,8",
56
)]
fn part2_lines_example(#[case] input: &str, #[case] answer: u64) {
assert_eq!(part2_lines(&parse(input)), answer);
}
}

View File

@@ -1,8 +1,15 @@
mod day1;
mod day10;
mod day11;
mod day12;
mod day2;
mod day3;
mod day4;
mod day5;
mod day6;
mod day7;
mod day8;
mod day9;
use aoc_runner_derive::aoc_lib;

View File

@@ -1,7 +1,7 @@
[package]
name = "grid"
version = "0.1.0"
edition = "2021"
edition = "2024"
[dependencies]

View File

@@ -22,22 +22,42 @@ pub const ADJACENT_OFFSETS: [&(i64, i64); 8] = [
/// NESW
pub const CARDINAL_OFFSETS: [&(i64, i64); 4] = [&(0, -1), &(-1, 0), &(1, 0), &(0, 1)];
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MirrorAxis {
X,
Y,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Coord2d {
pub x: i64,
pub y: i64,
}
impl Debug for Coord2d {
impl Display for Coord2d {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("({}, {})", self.x, self.y))
}
}
impl FromStr for Coord2d {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (l, r) = s.split_once(',').ok_or("Can't split on ,")?;
Ok(Coord2d {
x: l.parse()?,
y: r.parse()?,
})
}
}
pub trait AsCoord2d {
fn to_coord(self) -> Coord2d;
fn x(&self) -> i64;
fn y(&self) -> i64;
fn manhattan<T: AsCoord2d>(&self, other: &T) -> u64 {
self.x().abs_diff(other.x()) + self.y().abs_diff(other.y())
}
}
impl<T: AsCoord2d> Sub<T> for &Coord2d {
@@ -266,7 +286,7 @@ impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for OffsetsIter<'a, T> {
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct Grid<T> {
pub data: Vec<T>,
width: i64,
@@ -452,7 +472,7 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
}
/// Return an iterator over the 8 neighbours of c. The iterator skips neighbouring positions outside of the grid.
pub fn adjacent_iter<C: AsCoord2d + Copy>(&self, c: &C) -> OffsetsIter<T> {
pub fn adjacent_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
OffsetsIter {
grid: self,
origin: c.to_coord(),
@@ -462,7 +482,7 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
}
/// Return an iterator over the 4 cardinal neighbours of c. The iterator skips neighbouring positions outside of the grid.
pub fn cardinal_iter<C: AsCoord2d + Copy>(&self, c: &C) -> OffsetsIter<T> {
pub fn cardinal_iter<'a, C: AsCoord2d + Copy>(&'a self, c: &'a C) -> OffsetsIter<'a, T> {
OffsetsIter {
grid: self,
origin: c.to_coord(),
@@ -471,6 +491,79 @@ impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
}
}
pub fn rotated(&self, mut rot: u8) -> Self {
rot %= 4;
match rot {
0 => self.clone(),
1 => {
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
for y in 0..self.height() {
for x in 0..self.width() {
n.set(
&(self.height() - y - 1, x),
self.get(&(x as u64, y as u64)).unwrap().clone(),
);
}
}
n
}
2 => {
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
for y in 0..self.height() {
for x in 0..self.width() {
n.set(
&(self.width() - x - 1, self.height() - y - 1),
self.get(&(x as u64, y as u64)).unwrap().clone(),
);
}
}
n
}
3 => {
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
for y in 0..self.height() {
for x in 0..self.width() {
n.set(
&(self.height() - y - 1, self.width() - x - 1),
self.get(&(x as u64, y as u64)).unwrap().clone(),
);
}
}
n
}
_ => unreachable!("invalid rotation"),
}
}
pub fn mirrored(&self, axis: MirrorAxis) -> Self {
match axis {
MirrorAxis::X => {
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
for y in 0..self.height() {
for x in 0..self.width() {
n.set(
&(self.width() - x - 1, y),
self.get(&(x as u64, y as u64)).unwrap().clone(),
);
}
}
n
}
MirrorAxis::Y => {
let mut n = Grid::with_shape(self.height(), self.width(), self.data[0].clone()); // fill will be overwritten anyway
for y in 0..self.height() {
for x in 0..self.width() {
n.set(
&(x, &self.height() - y - 1),
self.get(&(x as u64, y as u64)).unwrap().clone(),
);
}
}
n
}
}
}
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
// if (self.width as usize) < needle.len() {
// return Vec::new();

19
utils/misc/Cargo.lock generated
View File

@@ -8,10 +8,29 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "intrusive-collections"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
dependencies = [
"memoffset",
]
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "misc"
version = "0.1.0"
dependencies = [
"intrusive-collections",
"num-traits",
]

View File

@@ -1,7 +1,8 @@
[package]
name = "misc"
version = "0.1.0"
edition = "2021"
edition = "2024"
[dependencies]
intrusive-collections = "0.9.7"
num-traits = "0.2.19"

View File

@@ -2,6 +2,8 @@ use num_traits::Signed;
use std::fmt::Display;
use std::ops::{Add, AddAssign};
pub mod range;
const POW10MAX: usize = u64::MAX.ilog10() as usize;
pub const POW10: [u64; POW10MAX] = pow10_lut();
@@ -105,6 +107,7 @@ mod tests {
#[test]
fn test_pow10() {
#[allow(clippy::needless_range_loop)]
for i in 0..POW10MAX {
assert_eq!(POW10[i], 10u64.pow(i as u32))
}

124
utils/misc/src/range.rs Normal file
View File

@@ -0,0 +1,124 @@
use intrusive_collections::Bound as IBound;
use intrusive_collections::{KeyAdapter, intrusive_adapter};
use intrusive_collections::{RBTreeLink, rbtree::RBTree};
use std::cmp::{max, min};
use std::ops::{Add, Bound, RangeBounds, Sub};
#[derive(Debug)]
pub struct NaiveRange<T> {
link: RBTreeLink,
pub start: T,
pub end: T,
}
intrusive_adapter!(
pub NaiveRangeAdapter<T> = Box<NaiveRange<T>>: NaiveRange<T> { link: RBTreeLink }
);
impl<'a, T: Ord + Clone> KeyAdapter<'a> for NaiveRangeAdapter<T> {
type Key = T;
fn get_key(&self, node: &'a NaiveRange<T>) -> T {
node.start.clone()
}
}
impl<T: Ord> NaiveRange<T> {
fn new(start: T, end: T) -> Self {
Self {
link: RBTreeLink::new(),
start,
end,
}
}
}
pub struct RangeSet<T: Ord> {
pub store: RBTree<NaiveRangeAdapter<T>>,
}
fn normalize_range<R: RangeBounds<T>, T: Copy + Ord + Add<Output = T> + From<u8>>(
r: &R,
) -> NaiveRange<T> {
let start = match r.start_bound() {
Bound::Included(x) => *x,
Bound::Excluded(x) => *x + T::from(1),
Bound::Unbounded => panic!(),
};
let end = match r.end_bound() {
Bound::Included(x) => *x + T::from(1),
Bound::Excluded(x) => *x,
Bound::Unbounded => panic!(),
};
NaiveRange::new(start, end)
}
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> Default for RangeSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> RangeSet<T> {
pub fn new() -> Self {
Self {
store: RBTree::new(NaiveRangeAdapter::<T>::new()),
}
}
pub fn add<R: RangeBounds<T>>(&mut self, r: &R) {
//normalize r to Range
let mut new_r = normalize_range(r);
// Find the position of nearest entry >= and <= than new_r's start
let mut cur = self.store.lower_bound_mut(IBound::Included(&new_r.start));
while let Some(val) = cur.get()
&& new_r.end >= val.start
{
// then remove them and update our new range
new_r.start = min(new_r.start, val.start);
new_r.end = max(new_r.end, val.end);
cur.remove(); // moves the cursor ahead
}
// Move the cursor back one (this will be the element before our insertion point, 'below' start)
cur.move_prev();
while let Some(val) = cur.get()
&& new_r.start <= val.end
{
new_r.start = min(new_r.start, val.start);
new_r.end = max(new_r.end, val.end);
cur.remove();
cur.move_prev();
}
// We are already in position, we just checked the previous element and it's before us
// since we fell out of the loop, so we need to go after this position
cur.insert_after(Box::new(new_r));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ranges() {
let mut set = RangeSet::new();
// edge cases are tricky to construct, run the aoc2025 day 5 puzzle and compare :)
set.add(&(0..10));
set.add(&(100..1000));
set.add(&(50..100));
set.add(&(50..100));
set.add(&(1001..2000));
set.add(&(2000..2001));
set.add(&(-10..-2));
set.add(&(-1..-1));
let end = set
.store
.iter()
.map(|r| (r.start, r.end))
.collect::<Vec<_>>();
assert_eq!(end, [(-10, 10), (50, 2001)])
}
}