Compare commits

...

16 Commits

Author SHA1 Message Date
6d0abb8f6a Solve day 7 2025-12-08 21:41:08 -05:00
9081354ce8 Solve day 6 2025-12-07 02:02:43 -05:00
7a3463e8cc Solve day 5 2025-12-06 23:29:18 -05:00
1311cb830b Add profiling flamegraphs with pprof 2025-12-06 15:58:11 -05:00
74c28c9344 Performance optimization settings & switch to nightly rust 2025-12-06 13:51:45 -05:00
9de4d77a63 Improve perf of day 4 2025-12-05 01:25:14 -05:00
8f4bc5b802 Solve day 4 2025-12-05 01:01:38 -05:00
de1e4b5b4f day03: simplify digit parsing and remove invalid character handling
Weirdly improves speed of part1 but hurts speed of part2 🤷
2025-12-03 22:46:02 -05:00
df96d619b9 day03: clean up debug printing a bit 2025-12-03 22:20:12 -05:00
3a305e1bce u8s are faster than u32s 2025-12-03 22:13:51 -05:00
7a3c36000c Solve day 3 2025-12-03 22:03:24 -05:00
8e163be240 Optimize the hell out of day02 2025-12-03 19:55:51 -05:00
3d001c690f Glob import days in main.rs
So I don't need to update it every time.
2025-12-03 01:25:14 -05:00
5433430628 Macro-ify benches and unify days list to one file
Also update README with new options.
2025-12-03 01:22:09 -05:00
cdc0225ca8 Better CLI with clap and day runner macro 2025-12-03 00:53:27 -05:00
9cce1d7fdb day02 solved 2025-12-03 00:05:12 -05:00
22 changed files with 1785 additions and 36 deletions

2
.cargo/config.toml Normal file
View File

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

619
Cargo.lock generated
View File

@@ -21,14 +21,31 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
name = "advent-of-code-2025"
version = "0.1.0"
dependencies = [
"clap",
"color-eyre",
"criterion",
"itertools 0.14.0",
"pprof",
"rayon",
"test-log",
"tracing",
"tracing-error",
"tracing-subscriber",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -38,18 +55,77 @@ dependencies = [
"memchr",
]
[[package]]
name = "aligned-vec"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
dependencies = [
"equator",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -71,18 +147,46 @@ dependencies = [
"windows-link",
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
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 = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -123,6 +227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
@@ -131,8 +236,22 @@ version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -168,6 +287,21 @@ dependencies = [
"tracing-error",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "cpp_demangle"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.5.1"
@@ -180,7 +314,7 @@ dependencies = [
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"itertools 0.10.5",
"num-traits",
"once_cell",
"oorandom",
@@ -201,7 +335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
"itertools 0.10.5",
]
[[package]]
@@ -235,12 +369,57 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"uuid",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equator"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
dependencies = [
"equator-macro",
]
[[package]]
name = "equator-macro"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "equivalent"
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 0.61.2",
]
[[package]]
name = "eyre"
version = "0.6.12"
@@ -251,6 +430,42 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "findshlibs"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
dependencies = [
"cc",
"lazy_static",
"libc",
"winapi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "gimli"
version = "0.32.3"
@@ -268,6 +483,18 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
@@ -280,6 +507,34 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "inferno"
version = "0.11.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
dependencies = [
"ahash",
"indexmap",
"is-terminal",
"itoa",
"log",
"num-format",
"once_cell",
"quick-xml",
"rgb",
"str_stack",
]
[[package]]
name = "is-terminal"
version = "0.4.17"
@@ -288,9 +543,15 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.10.5"
@@ -300,6 +561,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@@ -328,6 +598,21 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[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.28"
@@ -349,6 +634,15 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memmap2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
dependencies = [
"libc",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -358,13 +652,34 @@ dependencies = [
"adler2",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "num-format"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
dependencies = [
"arrayvec",
"itoa",
]
[[package]]
@@ -391,6 +706,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "oorandom"
version = "11.1.5"
@@ -437,6 +758,29 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "pprof"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38a01da47675efa7673b032bf8efd8214f1917d89685e07e395ab125ea42b187"
dependencies = [
"aligned-vec",
"backtrace",
"cfg-if",
"criterion",
"findshlibs",
"inferno",
"libc",
"log",
"nix",
"once_cell",
"smallvec",
"spin",
"symbolic-demangle",
"tempfile",
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -446,6 +790,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.42"
@@ -455,6 +808,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"
@@ -504,12 +863,34 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rgb"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
dependencies = [
"bytemuck",
]
[[package]]
name = "rustc-demangle"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[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 0.61.2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -531,6 +912,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
@@ -583,12 +970,68 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "str_stack"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "symbolic-common"
version = "12.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60"
dependencies = [
"debugid",
"memmap2",
"stable_deref_trait",
"uuid",
]
[[package]]
name = "symbolic-demangle"
version = "12.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec"
dependencies = [
"cpp_demangle",
"rustc-demangle",
"symbolic-common",
]
[[package]]
name = "syn"
version = "2.0.111"
@@ -600,6 +1043,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 0.61.2",
]
[[package]]
name = "test-log"
version = "0.2.19"
@@ -621,6 +1077,26 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.9"
@@ -717,12 +1193,34 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -733,6 +1231,15 @@ dependencies = [
"winapi-util",
]
[[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"
@@ -788,21 +1295,52 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[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.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -812,6 +1350,77 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"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",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.31"

View File

@@ -4,19 +4,30 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
color-eyre = "0.6"
tracing = "0.1"
itertools = "0.14"
rayon = "1.11"
tracing = { version = "0.1", features = ["release_max_level_info"] }
tracing-error = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[dev-dependencies]
criterion = "0.5.1"
criterion = "0.5"
pprof = { version = "0.15" , features = ["flamegraph", "criterion"] }
test-log = { version = "0.2", default-features = false, features = ["trace"] }
# Improve perf on debug builds: https://docs.rs/color-eyre/latest/color_eyre/#improving-perf-on-debug-builds
[profile.dev.package.backtrace]
opt-level = 3
# Gotta go fast
[profile.release]
codegen-units = 1
lto = "fat"
panic = "abort"
debug = false # set true for profiling
[lib]
name = "aoc"
path = "src/lib.rs"

View File

@@ -6,26 +6,46 @@ Rusty and over-engineered edition.
By request of AoC creator, I haven't included the input files (e.g. src/input/day01.txt). Log into the Advent of Code site and save the inputs there to the src/input/ folder.
Then to run: `cargo run`.
To run all days: `cargo run`.
To run a specific day and/or part: `cargo run -- --day 1 --part 1`.
To run in super-fast prod mode: `cargo run --release`.
To run with debug logs enabled: `RUST_LOG=debug cargo run`.
To run the tests against included test input files: `RUST_LOG=debug cargo test -- --no-capture`.
To run all the tests against included test input files: `RUST_LOG=debug cargo test -- --no-capture`.
To run the tests for a specific day and/or part: `RUST_LOG=debug cargo test day01::test::test_part1 -- --no-capture`.
## Benchmarks
Because this is over-engineered, I've included benchmarks for each day's solution. Because, why not?
To run benchmarks: `cargo bench`.
To run benchmarks: `cargo bench`. Or a specific day and/or part: `cargo bench -- "day02 part1"`.
### Results
These were all run on my personal machine, an AMD Ryzen 9 3900X 12-Core Processor with 32 GB RAM, on Linux (WSL).
These were all run on my personal machine, an AMD Ryzen 9 3900X 12-Core Processor with 32 GB RAM, on Linux (WSL), with nightly rust.
Timings are given as: [lower-bound **best-estimate** upper-bound]
| Day | Part 1 | Part 2 |
|-----|--------|--------|
| 01 | [101.34 µs **101.95 µs** 102.61 µs] | [105.90 µs **106.40 µs** 106.95 µs] |
| 01 | [79.998 µs **80.349 µs** 80.721 µs] | [76.289 µs **76.616 µs** 76.950 µs] |
| 02 | [2.0386 ms **2.0483 ms** 2.0584 ms] | [2.0823 ms **2.0918 ms** 2.1015 ms] |
| 03 | [45.711 µs **45.937 µs** 46.177 µs] | [267.18 µs **267.95 µs** 268.75 µs] |
| 04 | [143.40 µs **144.00 µs** 144.73 µs] | [1.6165 ms **1.6258 ms** 1.6355 ms] |
| 05 | [187.25 µs **188.93 µs** 190.74 µs] | [63.809 µs **64.204 µs** 64.606 µs] |
| 06 | [128.44 µs **129.44 µs** 130.52 µs] | [165.05 µs **165.70 µs** 166.36 µs] |
| 07 | [83.803 µs **84.601 µs** 85.435 µs] | [81.456 µs **82.360 µs** 83.386 µs] |
## Profiling
To aid in increasing performance, the `pprof` crate can be used to generate flamegraphs off of the benchmarks.
To run profiling across all benchmarks: `cargo bench --bench aoc -- --profile-time 10`.
To run profile the benchmark for a specific day and/or part: `cargo bench --bench aoc -- --profile-time 30 "day01 part1"`.
The flamegraphs will be generated in `target/criterion/<benchmark_name>/profile/flamegraph.svg`.

View File

@@ -1,10 +1,36 @@
use aoc::day01;
use criterion::{Criterion, criterion_group, criterion_main};
use pprof::criterion::{Output, PProfProfiler};
fn day01_benchmark(c: &mut Criterion) {
c.bench_function("day01 part1", |b| b.iter(|| day01::part1(day01::INPUT)));
c.bench_function("day01 part2", |b| b.iter(|| day01::part2(day01::INPUT)));
const PPROF_SAMPLING_FREQ_HZ: i32 = 997;
macro_rules! bench_days {
($($day_num:literal => $day_mod:ident),* $(,)?) => {
$(
mod $day_mod {
use super::*;
use aoc::$day_mod;
pub fn part1(c: &mut Criterion) {
c.bench_function(concat!(stringify!($day_mod), " part1"), |b| {
b.iter(|| $day_mod::part1($day_mod::INPUT))
});
}
pub fn part2(c: &mut Criterion) {
c.bench_function(concat!(stringify!($day_mod), " part2"), |b| {
b.iter(|| $day_mod::part2($day_mod::INPUT))
});
}
}
)*
criterion_group! {
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(PPROF_SAMPLING_FREQ_HZ, Output::Flamegraph(None)));
targets = $($day_mod::part1, $day_mod::part2),*
}
criterion_main!(benches);
};
}
criterion_group!(benches, day01_benchmark);
criterion_main!(benches);
aoc::all_days!(bench_days);

View File

@@ -4,7 +4,7 @@ use color_eyre::{
Result,
eyre::{Error, eyre},
};
use tracing::{debug, info, instrument};
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
@@ -98,18 +98,6 @@ pub fn part2(input: &str) -> Result<i32> {
Ok(visited_zero_count)
}
pub fn solve() -> Result<()> {
info!("Day 1");
{
let _span = tracing::info_span!("day01").entered();
let p1 = part1(INPUT)?;
info!("Part 1: {}", p1);
let p2 = part2(INPUT)?;
info!("Part 2: {}", p2);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -0,0 +1 @@
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124

185
src/day02/mod.rs Normal file
View File

@@ -0,0 +1,185 @@
use std::{fmt::Display, str::FromStr};
use color_eyre::{
Result,
eyre::{Error, eyre},
};
use rayon::prelude::*;
use tracing::{debug, debug_span, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
#[derive(Clone, Debug)]
struct ProductRange(std::ops::RangeInclusive<i64>);
impl FromStr for ProductRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('-');
let start = parts
.next()
.ok_or(eyre!("Invalid product range: no start"))?
.parse::<i64>()?;
let end = parts
.next()
.ok_or(eyre!("Invalid product range: no end"))?
.parse::<i64>()?;
if parts.next().is_some() {
return Err(eyre!("Invalid product range: too many parts"));
}
Ok(ProductRange(start..=end))
}
}
impl Display for ProductRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.0.start(), self.0.end())
}
}
impl Iterator for ProductRange {
type Item = i64;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ProductRange {
fn invalid_ids(self) -> Result<Vec<i64>> {
let start = *self.0.start();
let end = *self.0.end();
let mut invalid_ids = Vec::new();
// Determine digit ranges we need to check
let start_digits = if start == 0 { 1 } else { start.ilog10() + 1 };
let end_digits = if end == 0 { 1 } else { end.ilog10() + 1 };
for num_digits in start_digits..=end_digits {
// Skip odd digit counts - they're all valid
if num_digits % 2 != 0 {
continue;
}
let half_digits = num_digits / 2;
let half_min = 10_i64.pow(half_digits - 1);
let half_max = 10_i64.pow(half_digits) - 1;
let multiplier = 10_i64.pow(half_digits) + 1; // Pre-calculate: half * multiplier = AABB pattern
// Generate all patterns where first half == second half
for half in half_min..=half_max {
let id = half * multiplier;
if id >= start && id <= end {
invalid_ids.push(id);
}
}
}
debug!("Invalid IDs: {:?}", &invalid_ids);
Ok(invalid_ids)
}
fn invalid_ids2(self) -> Result<Vec<i64>> {
let start = *self.0.start();
let end = *self.0.end();
let mut invalid_ids = std::collections::HashSet::new();
// Determine digit ranges we need to check
let start_digits = if start == 0 { 1 } else { start.ilog10() + 1 };
let end_digits = if end == 0 { 1 } else { end.ilog10() + 1 };
for num_digits in start_digits..=end_digits {
// Try all possible chunk sizes that divide evenly
for chunk_size in 1..=num_digits / 2 {
if num_digits % chunk_size != 0 {
continue;
}
let num_chunks = num_digits / chunk_size;
if num_chunks < 2 {
continue;
}
// Generate all possible chunk patterns
let chunk_min = 10_i64.pow(chunk_size - 1);
let chunk_max = 10_i64.pow(chunk_size) - 1;
let chunk_power = 10_i64.pow(chunk_size);
// Calculate multiplier for repeating pattern
// For ABCABC: chunk * (10^6 + 10^3 + 1) = chunk * 1001001
let mut multiplier = 0_i64;
for i in 0..num_chunks {
multiplier += chunk_power.pow(i);
}
for chunk in chunk_min..=chunk_max {
let id = chunk * multiplier;
if id >= start && id <= end {
invalid_ids.insert(id);
}
}
}
}
let invalid_ids: Vec<i64> = invalid_ids.into_iter().collect();
debug!("Invalid IDs: {:?}", &invalid_ids);
Ok(invalid_ids)
}
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<i64> {
input
.trim()
.split(',')
.collect::<Vec<_>>()
.into_par_iter()
.map(|range| {
let _span = debug_span!("range", range = %range).entered();
let range: ProductRange = range.parse()?;
Ok(range.invalid_ids()?.iter().sum::<i64>())
})
.sum()
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<i64> {
input
.trim()
.split(',')
.collect::<Vec<_>>()
.into_par_iter()
.map(|range| {
let _span = debug_span!("range", range = %range).entered();
let range: ProductRange = range.parse()?;
Ok(range.invalid_ids2()?.iter().sum::<i64>())
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(part1(TEST_INPUT1).unwrap(), 1227775554);
}
#[test]
fn test_part2() {
assert_eq!(part2(TEST_INPUT1).unwrap(), 4174379265);
}
}

View File

@@ -0,0 +1,4 @@
987654321111111
811111111111119
234234234234278
818181911112111

72
src/day03/mod.rs Normal file
View File

@@ -0,0 +1,72 @@
use color_eyre::Result;
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
#[derive(Debug, Clone, Copy)]
struct Battery {
column: usize,
joltage: u8,
}
impl Default for Battery {
fn default() -> Self {
Battery {
column: 0,
joltage: 0,
}
}
}
fn largest_output_joltage<const N: usize>(input: &str) -> Result<u64> {
let mut output_joltage: u64 = 0;
for line in input.trim().split('\n') {
let mut batteries: [Battery; N] = [Battery::default(); N];
let line_len = line.len();
for (column, joltage) in line.bytes().map(|c| c - b'0').enumerate() {
let min = N.saturating_sub(line_len - column);
for i in min..N {
if joltage > batteries[i].joltage {
batteries[i].column = column;
batteries[i].joltage = joltage;
batteries[i + 1..].fill(Battery::default());
break;
}
}
}
let line_joltage = batteries
.iter()
.fold(0u64, |acc, &b| acc * 10 + b.joltage as u64);
debug!(line, line_joltage);
output_joltage += line_joltage;
}
Ok(output_joltage)
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<u64> {
largest_output_joltage::<2>(input)
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<u64> {
largest_output_joltage::<12>(input)
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(part1(TEST_INPUT1).unwrap(), 357);
}
#[test]
fn test_part2() {
assert_eq!(part2(TEST_INPUT1).unwrap(), 3121910778619);
}
}

10
src/day04/input/test1.txt Normal file
View File

@@ -0,0 +1,10 @@
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.

172
src/day04/mod.rs Normal file
View File

@@ -0,0 +1,172 @@
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use color_eyre::{Result, eyre::eyre};
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
const ADJACENT_DELTAS: [(isize, isize); 8] = [
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cell {
Empty,
Paper,
AccessiblePaper,
}
impl FromStr for Cell {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"." => Ok(Cell::Empty),
"@" => Ok(Cell::Paper),
"x" => Ok(Cell::AccessiblePaper),
_ => Err(eyre!("Invalid cell character: {}", s)),
}
}
}
impl Cell {
fn from_byte(b: u8) -> Result<Self, color_eyre::Report> {
match b {
b'.' => Ok(Cell::Empty),
b'@' => Ok(Cell::Paper),
b'x' => Ok(Cell::AccessiblePaper),
_ => Err(eyre!("Invalid cell byte: {}", b)),
}
}
}
struct Grid<const R: usize, const C: usize> {
cells: [[Cell; C]; R],
}
impl<const R: usize, const C: usize> FromStr for Grid<R, C> {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut cells = [[Cell::Empty; C]; R];
for (row, line) in s.lines().enumerate() {
for (col, byte) in line.bytes().enumerate() {
cells[row][col] = Cell::from_byte(byte)?;
}
}
Ok(Grid { cells })
}
}
impl<const R: usize, const C: usize> Display for Grid<R, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for row in 0..R {
for col in 0..C {
let symbol = match self.cells[row][col] {
Cell::Empty => '.',
Cell::Paper => '@',
Cell::AccessiblePaper => 'x',
};
write!(f, "{}", symbol)?;
}
writeln!(f)?;
}
Ok(())
}
}
impl<const R: usize, const C: usize> Grid<R, C> {
fn count_accessible_papers(&mut self, replace_with: Cell) -> usize {
let mut count = 0;
for row in 0..R {
for col in 0..C {
if self.cells[row][col] != Cell::Paper {
continue;
}
let mut adjacent_papers = 0;
for &(dr, dc) in &ADJACENT_DELTAS {
let adj_row = row as isize + dr;
let adj_col = col as isize + dc;
if adj_row >= 0
&& adj_col >= 0
&& (adj_row as usize) < R
&& (adj_col as usize) < C
{
let adjacent = self.cells[adj_row as usize][adj_col as usize];
if matches!(adjacent, Cell::Paper | Cell::AccessiblePaper) {
adjacent_papers += 1;
}
}
}
if adjacent_papers < 4 {
self.cells[row][col] = replace_with;
count += 1;
}
}
}
count
}
}
fn solve_part1<const R: usize, const C: usize>(input: &str) -> Result<usize> {
let mut grid = Grid::<R, C>::from_str(input)?;
debug!("Parsed grid:\n{}", grid);
let count = grid.count_accessible_papers(Cell::AccessiblePaper);
debug!("Processed grid:\n{}", grid);
Ok(count)
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<usize> {
solve_part1::<135, 135>(input)
}
fn solve_part2<const R: usize, const C: usize>(input: &str) -> Result<usize> {
let mut grid = Grid::<R, C>::from_str(input)?;
debug!("Parsed grid:\n{}", grid);
let mut count = 0;
loop {
let removed = grid.count_accessible_papers(Cell::Empty);
if removed == 0 {
break;
}
debug!("Removed {} in grid:\n{}", removed, grid);
count += removed;
}
Ok(count)
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<usize> {
solve_part2::<135, 135>(input)
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(solve_part1::<10, 10>(TEST_INPUT1).unwrap(), 13);
}
#[test]
fn test_part2() {
assert_eq!(solve_part2::<10, 10>(TEST_INPUT1).unwrap(), 43);
}
}

11
src/day05/input/test1.txt Normal file
View File

@@ -0,0 +1,11 @@
3-5
10-14
16-20
12-18
1
5
8
11
17
32

125
src/day05/mod.rs Normal file
View File

@@ -0,0 +1,125 @@
use std::{fmt::Display, str::FromStr};
use color_eyre::{
Result,
eyre::{Error, eyre},
};
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
#[derive(Clone, Debug)]
struct FreshRange(pub std::ops::RangeInclusive<i64>);
impl FromStr for FreshRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('-');
let start = parts
.next()
.ok_or(eyre!("Invalid fresh range: no start"))?
.parse::<i64>()?;
let end = parts
.next()
.ok_or(eyre!("Invalid fresh range: no end"))?
.parse::<i64>()?;
if parts.next().is_some() {
return Err(eyre!("Invalid fresh range: too many parts"));
}
Ok(FreshRange(start..=end))
}
}
impl Display for FreshRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.0.start(), self.0.end())
}
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<usize> {
let mut processing_ranges = true;
let mut fresh_ranges = Vec::new();
let mut fresh_ingredients = 0;
for line in input.trim().lines() {
if line.is_empty() {
processing_ranges = false;
continue;
}
if processing_ranges {
let range = line.parse::<FreshRange>()?;
debug!(range = %range);
fresh_ranges.push(range);
} else {
let ingredient = line.parse::<i64>()?;
debug!(ingredient);
for range in &mut fresh_ranges {
if range.0.contains(&ingredient) {
fresh_ingredients += 1;
debug!(fresh_ingredients, "fresh!");
break;
}
}
}
}
Ok(fresh_ingredients)
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<usize> {
let mut fresh_ranges: Vec<Option<FreshRange>> = Vec::new();
for line in input.trim().lines() {
if line.is_empty() {
break;
}
let range = line.parse::<FreshRange>()?;
let mut overlap_range = range.clone();
debug!(range = %range);
for range_slot in fresh_ranges.iter_mut() {
if let Some(existing_range) = range_slot {
if overlap_range.0.end() < existing_range.0.start()
|| overlap_range.0.start() > existing_range.0.end()
{
continue;
}
let start = *overlap_range.0.start().min(existing_range.0.start());
let end = *overlap_range.0.end().max(existing_range.0.end());
if start <= end {
overlap_range = FreshRange(start..=end);
debug!(overlap_range = %overlap_range, existing_range = %existing_range, "merging existing range");
*range_slot = None; // this existing range is now completely merged with the current range
}
}
}
fresh_ranges.push(Some(overlap_range));
}
Ok(fresh_ranges
.iter()
.flatten()
.map(|r| (r.0.end() - r.0.start() + 1) as usize)
.sum())
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(part1(TEST_INPUT1).unwrap(), 3);
}
#[test]
fn test_part2() {
assert_eq!(part2(TEST_INPUT1).unwrap(), 14);
}
#[test]
fn test_part2_triple_overlap() {
assert_eq!(part2("3-4\n2-5\n1-6").unwrap(), 6);
}
}

View File

@@ -0,0 +1,4 @@
123 328 51 64
45 64 387 23
6 98 215 314
* + * +

212
src/day06/mod.rs Normal file
View File

@@ -0,0 +1,212 @@
use std::str::FromStr;
use color_eyre::{
Result,
eyre::{Context, Error, OptionExt, eyre},
};
use itertools::Itertools;
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
#[derive(Debug, Clone, Copy)]
enum Operation {
Add,
Multiply,
}
impl FromStr for Operation {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"+" => Ok(Operation::Add),
"*" => Ok(Operation::Multiply),
_ => Err(eyre!("invalid operation: {}", s)),
}
}
}
impl Operation {
fn from_byte(byte: u8) -> Result<Self> {
match byte {
b'+' => Ok(Operation::Add),
b'*' => Ok(Operation::Multiply),
_ => Err(eyre!("invalid operation byte: {}", byte)),
}
}
}
#[derive(Debug, Clone)]
struct Problem {
numbers: Vec<u64>,
operation: Operation,
}
#[derive(Debug, Clone)]
struct CephalopodProblem {
columns: Vec<Vec<u8>>,
operation: Operation,
width: u8,
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<u64> {
let mut lines = input.trim().lines();
let first_numbers: Vec<u64> = lines
.next()
.ok_or_eyre("no first line in input")?
.split_whitespace()
.map(|s| s.parse::<u64>().context("parsing number"))
.collect::<Result<Vec<u64>>>()?;
let mut problems = first_numbers
.into_iter()
.map(|n| Problem {
numbers: vec![n],
operation: Operation::Add,
})
.collect::<Vec<Problem>>();
for line in lines {
let first_byte = line.bytes().nth(0).ok_or_eyre("empty line in input")?;
if matches!(first_byte, b'*' | b'+') {
for (index, op_result) in line.split_whitespace().map(|s| s.parse()).enumerate() {
let op = op_result?;
problems[index].operation = op;
}
} else {
for (index, num_result) in line.split_whitespace().map(|s| s.parse()).enumerate() {
let num = num_result?;
problems[index].numbers.push(num);
}
}
}
Ok(problems
.into_iter()
.map(|problem| match problem.operation {
Operation::Add => {
let sum = problem.numbers.iter().sum::<u64>();
debug!("{} = {}", problem.numbers.iter().join(" + "), sum);
return sum;
}
Operation::Multiply => {
let product = problem.numbers.iter().product::<u64>();
debug!("{} = {}", problem.numbers.iter().join(" * "), product);
return product;
}
})
.sum())
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<usize> {
let mut lines = input.lines();
let last_line = lines.next_back().ok_or_eyre("no last line in input")?;
let mut problems = Vec::new();
let mut spaces: u8 = 1;
for byte in last_line.bytes().rev() {
if byte.is_ascii_whitespace() {
spaces += 1;
} else {
problems.push(CephalopodProblem {
columns: (0..spaces).map(|_| Vec::new()).collect(),
operation: Operation::from_byte(byte)?,
width: spaces,
});
spaces = 0;
}
}
debug!(last_problem = ?problems[0]);
debug!(second_last_problem = ?problems[1]);
problems.reverse();
for line in lines {
let bytes = line.as_bytes();
let mut offset = 0;
for i in 0..problems.len() {
if offset + (problems[i].width as usize) > bytes.len() {
return Err(eyre!("line too short for problem columns"));
}
for (cell_col, &byte) in bytes[offset..offset + problems[i].width as usize]
.iter()
.enumerate()
{
if !byte.is_ascii_whitespace() {
problems[i].columns[cell_col].push(byte - b'0');
}
}
offset += problems[i].width as usize + 1;
}
}
debug!(first_problem = ?problems[0]);
debug!(second_problem = ?problems[1]);
Ok(problems
.iter()
.map(|problem| match problem.operation {
Operation::Add => {
let sum: usize = problem
.columns
.iter()
.map(|col| {
col.iter()
.fold(0usize, |acc, &digit| acc * 10 + (digit as usize))
})
.sum();
debug!(
"{} = {}",
problem
.columns
.iter()
.map(|col| {
col.iter()
.fold(0usize, |acc, &digit| acc * 10 + (digit as usize))
.to_string()
})
.join(" + "),
sum
);
sum
}
Operation::Multiply => {
let product: usize = problem
.columns
.iter()
.map(|col| {
col.iter()
.fold(0usize, |acc, &digit| acc * 10 + (digit as usize))
})
.product();
debug!(
"{} = {}",
problem
.columns
.iter()
.map(|col| {
col.iter()
.fold(0usize, |acc, &digit| acc * 10 + (digit as usize))
.to_string()
})
.join(" * "),
product
);
product
}
})
.sum())
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(part1(TEST_INPUT1).unwrap(), 4277556);
}
#[test]
fn test_part2() {
assert_eq!(part2(TEST_INPUT1).unwrap(), 3263827);
}
}

16
src/day07/input/test1.txt Normal file
View File

@@ -0,0 +1,16 @@
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

180
src/day07/mod.rs Normal file
View File

@@ -0,0 +1,180 @@
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use color_eyre::{Result, eyre::eyre};
use tracing::{debug, instrument};
pub const INPUT: &str = include_str!("input/input.txt");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cell {
Source,
Splitter,
Beam(usize),
Empty,
}
impl FromStr for Cell {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"S" => Ok(Cell::Source),
"^" => Ok(Cell::Splitter),
"|" => Ok(Cell::Beam(1)),
"." => Ok(Cell::Empty),
_ => Err(eyre!("Invalid cell character: {}", s)),
}
}
}
impl Cell {
fn from_byte(b: u8) -> Result<Self, color_eyre::Report> {
match b {
b'S' => Ok(Cell::Source),
b'^' => Ok(Cell::Splitter),
b'|' => Ok(Cell::Beam(1)),
b'.' => Ok(Cell::Empty),
_ => Err(eyre!("Invalid cell byte: {}", b)),
}
}
}
struct Grid<const R: usize, const C: usize> {
cells: [[Cell; C]; R],
splits: usize,
}
impl<const R: usize, const C: usize> FromStr for Grid<R, C> {
type Err = color_eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut cells = [[Cell::Empty; C]; R];
for (row, line) in s.lines().enumerate() {
for (col, byte) in line.bytes().enumerate() {
cells[row][col] = Cell::from_byte(byte)?;
}
}
Ok(Grid { cells, splits: 0 })
}
}
impl<const R: usize, const C: usize> Display for Grid<R, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for row in 0..R {
for col in 0..C {
let symbol = match self.cells[row][col] {
Cell::Source => 'S',
Cell::Splitter => '^',
Cell::Beam(t) => (b'0' + t as u8) as char,
Cell::Empty => '.',
};
write!(f, "{}", symbol)?;
}
writeln!(f)?;
}
Ok(())
}
}
impl<const R: usize, const C: usize> Grid<R, C> {
fn emit_beam(&mut self) {
for row in 1..R {
for col in 0..C {
if matches!(self.cells[row][col], Cell::Beam(_)) {
// already filled by a splitter to the left
continue;
}
if matches!(self.cells[row - 1][col], Cell::Source | Cell::Beam(_)) {
let timelines = match self.cells[row - 1][col] {
Cell::Source => 1,
Cell::Beam(t) => t,
_ => unreachable!(),
};
if self.cells[row][col] == Cell::Splitter {
// assumption: splitter always has empty cells on left and right
let left = self.cells[row][col - 1];
let left_timelines = match self.cells[row - 1][col - 1] {
Cell::Source => 1,
Cell::Beam(t) => t,
_ => 0,
};
if let Cell::Beam(left_t) = left {
self.cells[row][col - 1] = Cell::Beam(timelines + left_t);
} else {
self.cells[row][col - 1] = Cell::Beam(timelines + left_timelines);
}
let right = self.cells[row][col + 1];
let right_timelines = match self.cells[row - 1][col + 1] {
Cell::Source => 1,
Cell::Beam(t) => t,
_ => 0,
};
if let Cell::Beam(right_t) = right {
self.cells[row][col + 1] = Cell::Beam(timelines + right_t);
} else {
self.cells[row][col + 1] = Cell::Beam(timelines + right_timelines);
}
self.splits += 1;
} else {
self.cells[row][col] = Cell::Beam(timelines);
}
}
}
debug!("After row {}:\n{}", row, self);
}
}
}
fn solve_part1<const R: usize, const C: usize>(input: &str) -> Result<usize> {
let mut grid = Grid::<R, C>::from_str(input)?;
grid.emit_beam();
Ok(grid.splits)
}
#[instrument(skip(input))]
pub fn part1(input: &str) -> Result<usize> {
solve_part1::<142, 141>(input)
}
fn solve_part2<const R: usize, const C: usize>(input: &str) -> Result<usize> {
let mut grid = Grid::<R, C>::from_str(input)?;
grid.emit_beam();
Ok(grid.cells[R - 1]
.into_iter()
.map(|c| {
if let Cell::Beam(timelines) = c {
timelines
} else {
0
}
})
.sum::<usize>())
}
#[instrument(skip(input))]
pub fn part2(input: &str) -> Result<usize> {
solve_part2::<142, 141>(input)
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
const TEST_INPUT1: &str = include_str!("input/test1.txt");
#[test]
fn test_part1() {
assert_eq!(solve_part1::<16, 15>(TEST_INPUT1).unwrap(), 21);
}
#[test]
fn test_part2() {
assert_eq!(solve_part2::<16, 15>(TEST_INPUT1).unwrap(), 40);
}
}

17
src/days.rs Normal file
View File

@@ -0,0 +1,17 @@
// Single source of truth for all implemented days
// Add new days here and they'll automatically be available in both the runner and benchmarks
#[macro_export]
macro_rules! all_days {
($macro_name:path) => {
$macro_name! {
1 => day01,
2 => day02,
3 => day03,
4 => day04,
5 => day05,
6 => day06,
7 => day07,
}
};
}

View File

@@ -1 +1,8 @@
pub mod day01;
pub mod day02;
pub mod day03;
pub mod day04;
pub mod day05;
pub mod day06;
pub mod day07;
pub mod days;

View File

@@ -1,5 +1,8 @@
pub mod day01;
mod days;
mod runner;
use aoc::*;
use clap::Parser;
use color_eyre::Result;
use tracing::info;
use tracing_error::ErrorLayer;
@@ -7,6 +10,21 @@ use tracing_subscriber::EnvFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::prelude::*;
#[derive(Parser, Debug)]
#[command(name = "Advent of Code 2025")]
#[command(about = "Solutions for Advent of Code 2025", long_about = None)]
struct Args {
/// Day to run (1-25). If not specified, runs all days.
#[arg(short, long)]
day: Option<u8>,
/// Part to run (1 or 2). If not specified, runs all parts.
#[arg(short, long)]
part: Option<u8>,
}
all_days!(runner::days);
fn main() -> Result<()> {
color_eyre::install()?;
@@ -20,10 +38,12 @@ fn main() -> Result<()> {
)
.init();
let args = Args::parse();
info!("Advent of Code 2025");
{
let _span = tracing::info_span!("aoc").entered();
day01::solve()?;
}
run_days(args.day, args.part)?;
Ok(())
}

57
src/runner.rs Normal file
View File

@@ -0,0 +1,57 @@
use color_eyre::Result;
use tracing::info;
macro_rules! days {
($($day_num:literal => $day_mod:ident),* $(,)?) => {
pub fn run_days(day: Option<u8>, part: Option<u8>) -> Result<()> {
match day {
$(
Some($day_num) => $crate::runner::run_day($day_num, part, $day_mod::part1, $day_mod::part2, $day_mod::INPUT)?,
)*
Some(d) => color_eyre::eyre::bail!("Day {} is not yet implemented", d),
None => {
$(
$crate::runner::run_day($day_num, None, $day_mod::part1, $day_mod::part2, $day_mod::INPUT)?;
)*
}
}
Ok(())
}
};
}
pub(crate) use days;
pub fn run_day<T1, T2>(
day: u8,
part: Option<u8>,
part1_fn: fn(&str) -> Result<T1>,
part2_fn: fn(&str) -> Result<T2>,
input: &str,
) -> Result<()>
where
T1: std::fmt::Display,
T2: std::fmt::Display,
{
info!("Day {}", day);
let day_name = format!("{:02}", day);
let _span = tracing::info_span!("day", day = %day_name).entered();
if part.is_none() || part == Some(1) {
let result = part1_fn(input)?;
info!("Part 1: {}", result);
}
if part.is_none() || part == Some(2) {
let result = part2_fn(input)?;
info!("Part 2: {}", result);
}
if let Some(p) = part {
if p != 1 && p != 2 {
color_eyre::eyre::bail!("Part {} is invalid. Must be 1 or 2.", p);
}
}
Ok(())
}