Compare commits

..

321 Commits

Author SHA1 Message Date
68d1803e8f feat: solve part two 2025-12-21 09:26:50 +01:00
a39f261ba9 test: add unit test for part two 2025-12-21 09:13:08 +01:00
0c77a62ab4 feat: solve part one 2025-12-21 09:12:16 +01:00
60ee8c0307 test: add unit test for part one 2025-12-21 09:05:19 +01:00
dfe08db759 chore: add 2022D4 dataset 2025-12-21 09:04:00 +01:00
081f4ea295 feat: register fourth day 2025-12-21 09:03:50 +01:00
34be9e0847 feat: solve part two using exhaustive search 2025-12-20 09:25:03 +01:00
6ea67eac0c test: add unit test for part two 2025-12-20 09:18:32 +01:00
bb366fbe17 refactor: extract reactPolymer to reuse it in PartTwo 2025-12-20 09:18:17 +01:00
681b7bae16 feat: solve part one 2025-12-20 09:15:52 +01:00
c8ded5c42d test: add unit test for part one 2025-12-20 09:12:51 +01:00
b37f1ec366 chore: add 20218D5 dataset 2025-12-20 09:08:46 +01:00
40e2e329e0 feat: register fifth day 2025-12-20 09:08:34 +01:00
fa5bf2e85b feat: refactor some code and solve part two 2025-12-14 10:14:18 +01:00
ea1b57b17e test: add unit test for part two 2025-12-14 10:10:30 +01:00
174671e6f5 feat: use min() and get rid of Reindeer struct 2025-12-14 10:02:57 +01:00
40bcf3052f test: add unit test for part one 2025-12-14 10:00:17 +01:00
1e634b7ee9 feat: implement partone in CalculateMaxDistance to make the test relevant 2025-12-14 10:00:11 +01:00
6e625dcf06 feat: register fourteenth day 2025-12-14 09:52:39 +01:00
141216920d chore: add 2015D14 dataset 2025-12-14 09:52:25 +01:00
b685e81c58 feat: solve part two 2025-12-12 18:35:11 +01:00
1adc10ea88 test: add test for part two 2025-12-12 18:35:05 +01:00
db7c31cb39 feat: solve part one using direct byte comparaison and efficient hex extraction 2025-12-12 18:30:25 +01:00
1ad1da1309 chore: add 2016D5 dataset 2025-12-12 18:30:07 +01:00
228392fe83 feat: register fifth day 2025-12-12 18:29:50 +01:00
4837cbf290 test: add unit test for part one 2025-12-12 18:29:20 +01:00
8503cee52b test: add unit test for part two 2025-12-10 18:41:25 +01:00
b16b052115 feat: solve part one using brute-force 2025-12-10 16:51:57 +01:00
bc76283458 test: add unit test for part one 2025-12-10 06:11:04 +01:00
9d2a087801 feat: register tenth day 2025-12-10 06:10:57 +01:00
0bd3b6dc69 chore: add 2025D10 dataset 2025-12-10 06:10:50 +01:00
da81f67b7f feat: solve part two 2025-12-09 20:59:57 +01:00
caa7da5a7d test: add reverse unit test for part two 2025-12-09 20:59:46 +01:00
eebe707ef9 feat: solve part one 2025-12-09 20:40:59 +01:00
8960dcb072 test: add unit test for part one 2025-12-09 20:14:22 +01:00
99eb0fbaa8 chore: 2016D4 dataset 2025-12-09 20:14:16 +01:00
a739425209 feat: register fourth day 2025-12-09 20:14:11 +01:00
af306eb73c feat: solve part two 2025-12-09 19:24:30 +01:00
cdbc3f3b16 feat: solve part one 2025-12-09 19:22:12 +01:00
ed445a8be7 test: add unit test for both parts 2025-12-09 19:21:59 +01:00
8bbf6662b1 chore: add 2016D3 input 2025-12-09 19:21:51 +01:00
b60c971d50 feat: register third day 2025-12-09 19:21:44 +01:00
5dab84eeb3 feat: solve part two 2025-12-09 14:40:37 +01:00
2283b62503 clean: oneliner for compute and check 2025-12-09 07:10:30 +01:00
ccf50c12c9 test: add unit test for part two 2025-12-09 06:45:39 +01:00
f25d7511aa feat: solve part one 2025-12-09 06:33:16 +01:00
243ccf3da9 test: unit test for part one 2025-12-09 06:14:02 +01:00
aedf9cd06d chore: day 9 input 2025-12-09 06:05:48 +01:00
c053c905ad feat: register ninth day 2025-12-09 06:05:42 +01:00
7dff40745c feat: solve part two using keypad with 10+ for letter and 0 for inexisting keys 2025-12-08 22:52:28 +01:00
0e8563b216 test: add printing unit test for part two (as it returns string) 2025-12-08 22:39:11 +01:00
6720bbabc1 feat: solve part one building the code digit by digit 2025-12-08 22:34:59 +01:00
6035989da4 test: add unit test for part one 2025-12-08 22:26:25 +01:00
dc16893777 chore: add 2016D2 dataset 2025-12-08 22:26:15 +01:00
fdfad57cee feat: register second day 2025-12-08 22:26:01 +01:00
9cf00b290f feat: solve part two using same logic and injecting me :) 2025-12-08 22:17:11 +01:00
47072a4982 feat: extract shared functions and solve part two 2025-12-08 22:01:31 +01:00
62748990cb test: unit test for part two 2025-12-08 21:55:29 +01:00
42c69d44e5 feat: solve part one 2025-12-08 21:51:40 +01:00
c6eb51a395 comment: no test for part two 2025-12-08 21:45:42 +01:00
6777696df6 test: add unit test for part one 2025-12-08 21:45:27 +01:00
a8e244f9ab feat: register third day 2025-12-08 21:43:39 +01:00
911f9bfe7b chore: add 2022D3 dataset 2025-12-08 21:43:15 +01:00
24a65d3752 feat: solve part one using brute-force permutation search 2025-12-08 21:26:06 +01:00
0bd0ec5f1e test: add unit test for part one 2025-12-08 21:24:58 +01:00
0ad418ed1f feat: register d13 2025-12-08 17:39:35 +01:00
ec48231aae chore: add D13 dataset 2025-12-08 17:39:07 +01:00
5631822e73 feat: tiny refactor for part two which we're solving using Kruskal's algorithm 2025-12-08 15:21:42 +01:00
630d32ba11 test: add unit test for part two 2025-12-08 12:30:10 +01:00
22500b7076 feat: solve part one 2025-12-08 12:26:37 +01:00
9d2d27b257 refactor: rename parameter to standardize 2025-12-08 11:42:12 +01:00
879509c7ba refactor: rename parameter 2025-12-08 11:40:08 +01:00
a0805111b4 test: add unit test for part one 2025-12-08 06:08:56 +01:00
b0cd4f37b1 feat: register day 8 2025-12-08 06:01:48 +01:00
8999f45aad chore: day8 dataset 2025-12-08 06:01:39 +01:00
838803c53e feat: solve part two 2025-12-07 22:01:36 +01:00
c4063b5390 test: unit test for part two 2025-12-07 21:59:04 +01:00
1c5bd1e448 feat: solve part one 2025-12-07 21:55:26 +01:00
6d9b2092bd test: add unit test for part one 2025-12-07 21:51:57 +01:00
9b218d763d feat: start 2016 2025-12-07 21:51:41 +01:00
10c5b0fbc6 feat: register day one 2025-12-07 21:39:52 +01:00
503eec14c6 chore: add 2016D1 dataset 2025-12-07 21:39:07 +01:00
deac7f97bb refactor: format imports 2025-12-07 13:34:14 +01:00
a3fb7ac353 feat: solve part two using recursive dfs + memoization 2025-12-07 12:24:40 +01:00
8f4e11215f test: add unit test for part two 2025-12-07 09:55:03 +01:00
78b0032578 feat: solve PartOne using DFS algorithm 2025-12-07 09:54:53 +01:00
536f6f52ff refactor: return directly 2025-12-07 09:35:20 +01:00
96ca1afb9b test: add unit test for PartOne 2025-12-07 09:30:18 +01:00
780263e78b feat: register day seven 2025-12-07 09:27:51 +01:00
49ff399f97 chore: D7 dataset (and it rhymes (in french only)) 2025-12-07 09:26:42 +01:00
27d14b1711 feat: solve part two 2025-12-07 00:00:16 +01:00
89b2ec90f2 test: add unit test for part two 2025-12-06 23:57:23 +01:00
f8a2e839b9 feat: solve part one using maps and regex 2025-12-06 23:55:34 +01:00
94ecbd27bf test: add unit test for part one 2025-12-06 23:45:52 +01:00
b44a592808 chore: D4 dataset 2025-12-06 23:44:13 +01:00
b00d2f13a3 feat: register day four 2025-12-06 23:44:01 +01:00
b26f1531f5 feat: solve part two 2025-12-06 23:43:01 +01:00
7de5fa7794 fix: update parsing to avoid doing split in both functions 2025-12-06 23:40:20 +01:00
66e91e05a4 test: update input to be a []string 2025-12-06 23:40:02 +01:00
a20bb8ab09 test: add unit test for part two 2025-12-06 23:36:12 +01:00
c5fcc8b353 feat: solve part one 2025-12-06 23:34:48 +01:00
0357f263cc test: add unit test for part one 2025-12-06 23:13:21 +01:00
8488debc25 feat: register day four 2025-12-06 23:11:46 +01:00
00dd40428b chore: 2021D4 dataset 2025-12-06 23:11:09 +01:00
3d35a57723 feat: solve part two using right-to-left processing 2025-12-06 12:08:57 +01:00
0d40e32a39 test: add unit test for part two 2025-12-06 11:48:22 +01:00
fd34db28cb feat: solve part one using grid transposition 2025-12-06 11:46:50 +01:00
65edd3258e test: unit test for part one 2025-12-06 11:07:39 +01:00
e914bc6492 feat: register day six 2025-12-06 11:04:13 +01:00
0eed8089b6 chpre: 2025D6 dataset 2025-12-06 11:03:34 +01:00
1048d20cef docs: update requirements 2025-12-05 09:26:46 +01:00
00ccbaf0d0 feat: solve part two using merged ranges 2025-12-05 09:08:10 +01:00
51f733127c test: add unit test for part two 2025-12-05 08:46:10 +01:00
d96febeae3 feat: solve part one using binary search on sorted range 2025-12-05 08:41:39 +01:00
79b31dad19 test: add unit test for part one 2025-12-05 08:03:50 +01:00
d5146e7e3e feat: include day five 2025-12-05 08:01:50 +01:00
d2d6f280b3 chore: 2025D5 dataset 2025-12-05 08:01:40 +01:00
79d9f8d7cc feat: solve part two using json and recursive approach 2025-12-04 21:10:42 +01:00
bcc4fc3432 test: add unit testing for part two 2025-12-04 21:00:19 +01:00
cc2d7d1a3d feat: solve part one using basic regex parsing 2025-12-04 20:59:09 +01:00
83f6db5e33 test: unit tests case for p1 2025-12-04 20:54:43 +01:00
12b54d51d2 feat: include D12 2025-12-04 20:44:48 +01:00
89c154de37 chore: add 2015D12 dataset 2025-12-04 20:44:17 +01:00
ea037debed fix: silence golangci-lint 2025-12-04 17:35:49 +01:00
a0a0c43690 feat: simplified input parsing by appending all lines directly 2025-12-04 17:32:36 +01:00
bce49d51f7 feat: applied De Morgan simplification 2025-12-04 17:32:18 +01:00
77a352aa2e feat: solve part two using the same logic, twice 2025-12-04 16:53:15 +01:00
fd6db0cc65 refactor: add code in helpers for part two 2025-12-04 16:52:28 +01:00
2b548fa1ef feat: solve part one with pruned bruteforce 2025-12-04 16:49:43 +01:00
e0465b9b8d clean: no test cases for this day 2025-12-04 16:32:43 +01:00
0523610080 chore: 2015D11 dataset 2025-12-04 16:24:24 +01:00
e341c12763 feat: include D11 2025-12-04 16:24:14 +01:00
6cff0b7931 feat: solve part two with iterative process 2025-12-04 07:05:56 +01:00
f5bfe1e578 test: unit test for part two 2025-12-04 06:48:41 +01:00
0fbe2956c3 refactor: didn't press :w it seems 2025-12-04 06:46:28 +01:00
9280430285 feat: solve part one using prebuilt map for rolls 2025-12-04 06:45:40 +01:00
b91e34bc8e test: add unit test for part one 2025-12-04 06:32:20 +01:00
9f10576a0c feat: include 2025D4 2025-12-04 06:30:23 +01:00
8a9f4cb506 chore: add 2025D4 dataset 2025-12-04 06:29:55 +01:00
d3098c0d9c cleaning: input.txt is either downloaded or created empty 2025-12-03 15:17:24 +01:00
8be18b6b38 refactor: better explain scaffolding 2025-12-03 15:16:25 +01:00
b39b8c885b feat: solve part two, still with greedy algorithm 2025-12-03 10:36:14 +01:00
56901ca553 test: add unit test for part two 2025-12-03 10:10:45 +01:00
e3c9da9621 feat: solve part one using a prebuild list to reduce comparisons 2025-12-03 10:09:13 +01:00
070bd8be9a fix: modify test, we're parsing [][]int instead of []string for this day 2025-12-03 09:57:12 +01:00
c1ba2ca02b test: add unit test for part one 2025-12-03 09:28:42 +01:00
146b63706a feat: include 2025D3 2025-12-03 09:26:32 +01:00
1421062a75 chore: 2025D3 dataset 2025-12-03 09:26:19 +01:00
b9928789df feat: solve day10 using traditionnal look and say 2025-12-02 23:12:11 +01:00
b7aafeec52 test: no test 2025-12-02 23:11:09 +01:00
a935e35d82 feat: include 2015D10 2025-12-02 23:08:47 +01:00
e2c2d0df71 chore: 2015D10 dataset 2025-12-02 22:56:27 +01:00
e3a47b0e16 feat: solve part two 2025-12-02 22:13:31 +01:00
86370f27c8 refactor: create functions that we gonna use for part two 2025-12-02 22:10:12 +01:00
c61e573e14 test: add unit test for part two 2025-12-02 21:57:27 +01:00
d16f70cf00 feat: solve part one with brute-force and permutation enumeration 2025-12-02 21:56:06 +01:00
6f7561213e test: add unit test for part one 2025-12-02 21:52:56 +01:00
94b15548cf feat: include 2015D9 2025-12-02 21:23:49 +01:00
4e9e2b399c chore: add 2015D9 dataset 2025-12-02 21:23:24 +01:00
035e56bf53 feat: solve part two 2025-12-02 21:21:49 +01:00
61cf84aa8a test: add unit test for part two 2025-12-02 20:39:54 +01:00
5513ae8386 feat: solve part one using basic parsing 2025-12-02 20:38:20 +01:00
f1730c30cb feat: register D8 and parse input 2025-12-02 20:14:37 +01:00
ce7d42621f test: add unit test for part one 2025-12-02 20:13:01 +01:00
8eafb1f7c5 feat: include 2015D8 2025-12-02 19:55:26 +01:00
81be03e8ee chore: add 2015D8 dataset 2025-12-02 19:54:54 +01:00
d662f693b8 feat: don't bruteforce every number, use recursion to generate patterns to look for 2025-12-02 18:09:45 +01:00
b0d16b1bac git: add .test files to ignore 2025-12-02 11:51:21 +01:00
3eb9120dc3 clean: remove test file 2025-12-02 11:50:40 +01:00
85ae14acbf docs: update readme 2025-12-02 09:58:52 +01:00
b7a98033c6 refactor: use package aggregators 2025-12-02 09:51:34 +01:00
8f265eae05 feat: add per-year aggregator packages so main.go remains readable 2025-12-02 09:51:21 +01:00
fe20a0b654 feat: solve part two using a bit of bruteforce 2025-12-02 07:48:48 +01:00
aa80e4eb8e revert: remove useless helper as PartOne and PartTwo will have a different behavior 2025-12-02 07:35:50 +01:00
0d029f2861 feat: unify invalid checks with exactTwo boolean parameter 2025-12-02 07:19:57 +01:00
edf94432f4 test: add unit test for part two 2025-12-02 06:53:45 +01:00
33552358f8 feat: solve p1 2025-12-02 06:52:30 +01:00
45d3e93a93 test: add unit test for p1 2025-12-02 06:46:42 +01:00
707f34e706 feat: include 2025D2 2025-12-02 06:36:56 +01:00
a05450c73a chore: add 2025D2 dataset 2025-12-02 06:27:22 +01:00
6b95f5ced0 test: add an almost arbitrary test for p2 2025-12-01 23:20:37 +01:00
9caee546f0 feat: solve part two by overriding wire b and re-evaluating circuit 2025-12-01 23:11:41 +01:00
8e831d85fe fix: replace Split with SplitSeq as it's a loop (thanks lsp) 2025-12-01 23:09:30 +01:00
b8ab5fae7b test: updateInput to simulate new ParseInput output 2025-12-01 23:08:22 +01:00
3262d1cbb8 refactor: build instruction in ParseInput and add evaluateWire helper 2025-12-01 23:07:29 +01:00
766ee97dd3 feat: solve part one using recursion and memoization 2025-12-01 22:59:28 +01:00
c41c96e628 test: add unit test for p1, based on manual calculations 2025-12-01 22:59:05 +01:00
fb46fceb75 refactor: just moving code here and there 2025-12-01 22:48:56 +01:00
c9fe217e4b feat: include 2015D7 2025-12-01 22:44:45 +01:00
d88d64edd4 chore: 2015D7 dataset 2025-12-01 22:43:24 +01:00
e81194721c refactoring: just better naming 2025-12-01 21:53:50 +01:00
a680e0ba48 refactor: create parseInstruction and instruction{} to use it in both parts 2025-12-01 21:31:51 +01:00
345defec4d feat: solve p2 2025-12-01 21:28:27 +01:00
3756279dab test: add unit test for p2 2025-12-01 20:52:57 +01:00
70189f4295 feat: solve part one 2025-12-01 20:47:02 +01:00
cdacf7ae06 test: add unit test for p1 from paper calculation 2025-12-01 20:46:55 +01:00
074c762960 feat: add 2015D6 dataset 2025-12-01 20:35:08 +01:00
6babf31a20 feat: include 2015D6 2025-12-01 20:35:00 +01:00
cdefd68320 feat: add 2018D3 dataset 2025-12-01 20:09:37 +01:00
f28611a7bf test: add unit tests for p1/p2 2025-12-01 20:09:29 +01:00
f98034b00c feat: solve both parts 2025-12-01 20:09:20 +01:00
375b756718 feat: include 2018D3 2025-12-01 20:09:12 +01:00
6668e8ae1b fix: remove empty branch 2025-12-01 19:37:28 +01:00
e58959778a docs: update readme 2025-12-01 19:36:25 +01:00
eb72fe9ebd build: get rid of "new" target 2025-12-01 19:35:01 +01:00
e355423675 feat: automatically download each day input 2025-12-01 19:32:01 +01:00
b1be29c21c feat: solve part two using modular arithmetic 2025-12-01 12:35:20 +01:00
b04dcc5aea test: add P2 unit test 2025-12-01 12:20:11 +01:00
11b6227d0e feat: add P1 solution 2025-12-01 12:19:01 +01:00
31660c7510 test: add P1 unit test 2025-12-01 12:18:54 +01:00
728bbb2a06 feat: add input for 2022D2 2025-12-01 12:18:42 +01:00
16a99ba8d8 feat: include 2022D2 2025-12-01 12:18:36 +01:00
0949840317 feat: solve P2, same as P1 but check every time the dial passes through 0 during the rotation 2025-12-01 07:41:50 +01:00
daec5a8671 test: add unit test for p2 2025-12-01 07:35:03 +01:00
d66cd1179d feat: solve P1 using modulo to keep it between boundaries 2025-12-01 07:31:15 +01:00
2d3828c55d test: design unit test for part one 2025-12-01 07:20:12 +01:00
b7a7bfb5c7 feat: add 2025D1 input 2025-12-01 07:20:01 +01:00
be918bdf6c feat: include 2025D1 let's gooooo 2025-12-01 07:19:55 +01:00
c8517b674f docs: explain the string answers trick 2025-11-30 13:35:55 +01:00
f3d73b7c4b feat: P2 solution using pairwise comparison 2025-11-30 13:08:46 +01:00
bcef8844ec build: only test internal/ 2025-11-30 13:04:18 +01:00
20ab5fe4e5 test: adapted p2 test as it expects string and not int 2025-11-30 13:04:08 +01:00
959c05b769 feat: add part one solution 2025-11-30 12:55:52 +01:00
4179c88afb feat: include 2018D2 2025-11-30 12:50:47 +01:00
0dc0c3af3d feat: 2018D2 dataset 2025-11-30 12:50:41 +01:00
c6dc950d3f test: add unit test for p1 2025-11-30 12:50:33 +01:00
e5a1504f6b feat: add solutions for P1/P2 2018D1 2025-11-30 12:46:09 +01:00
9da1fa02c8 feat: 2018D1 dataset 2025-11-30 12:46:01 +01:00
99857d8ca5 test: add unit tests for P1/P2 2025-11-30 12:45:53 +01:00
e198caf1b9 feat: include 2018D1 2025-11-30 12:45:45 +01:00
9dd5f1354f docs: comment years 2025-11-29 16:15:14 +01:00
8ad1b166f3 feat: add both solutions 2025-11-29 16:07:02 +01:00
1f3b42b266 feat: 2015D5 dataset 2025-11-29 16:06:54 +01:00
9521677ca8 test: add unit tests for both parts 2025-11-29 16:06:42 +01:00
8938992384 feat: include 2015D5 2025-11-29 16:06:34 +01:00
a0ce63e5a5 feat: solve d4 both parts using md5 bruteforce 2025-11-29 09:57:54 +01:00
a12c8df252 test: add unit tests for both parts 2025-11-29 09:57:23 +01:00
8d69f10924 feat: include 2015D4 2025-11-29 09:55:47 +01:00
e41d1fa220 feat: add 2015D4 input 2025-11-29 09:55:39 +01:00
da78d01d9f test: standardization 2025-11-29 09:39:33 +01:00
ff4b7b281c feat: add p2 solution 2025-11-28 17:59:03 +01:00
a3f54530f6 test: add unit test for p2 2025-11-28 17:56:35 +01:00
301d93157c feat: add part one solution 2025-11-28 17:51:51 +01:00
7e0a1e71a7 test: add unit test for p1 2025-11-28 17:51:10 +01:00
962bc923f3 feat: include 2015D3 2025-11-28 17:50:59 +01:00
76568a801d feat: add input for 2015D3 2025-11-28 17:50:29 +01:00
1f5b8247a9 feat: add p2 solution 2025-11-28 16:47:56 +01:00
950eec898e test: add p2 unit test 2025-11-28 16:47:52 +01:00
2d6c89d7c9 feat: add solution for p1 2025-11-28 16:45:04 +01:00
6bc4c1b5a5 test: add unit test for p1 2025-11-28 16:44:58 +01:00
7b0ccf8a40 feat: add 2015D2 input 2025-11-28 16:41:58 +01:00
ea62c2ce0a feat: include 2015D2 2025-11-28 16:41:46 +01:00
87a7dee129 feat: add solution for p2 2025-11-28 16:31:02 +01:00
53d72ae163 test: add unit test for p2 2025-11-28 16:29:35 +01:00
bd8c2cca31 feat: add day1 part 1 solution 2025-11-28 16:28:15 +01:00
4013ad8330 feat: add d1 input 2025-11-28 16:28:01 +01:00
a344eef0e3 test: add unit test for p1 2025-11-28 16:27:55 +01:00
824eb6d5a2 feat: include 2015D1 2025-11-28 16:27:49 +01:00
d8e7573204 build: add a new target to create files for a new day 2025-11-28 16:22:11 +01:00
98fb052039 feat: add p2 solution 2025-11-28 15:33:23 +01:00
db685a1290 test: add p2 unit test 2025-11-28 15:26:30 +01:00
27a56dc7cd refactor: create a execute() function to reuse in part two 2025-11-28 15:26:23 +01:00
6a82336c99 feat: add part one solution 2025-11-28 14:54:29 +01:00
90a924e640 feat: input for 2020D8 2025-11-28 14:54:21 +01:00
228d8b475b test: add unit test for part one 2025-11-28 14:54:16 +01:00
c3abc90a24 feat: include 2020D8 2025-11-28 14:54:05 +01:00
60ffe95f69 feat: implement PartTwo using DFS recursion 2025-11-28 12:53:29 +01:00
33bcb91d48 docs: add goal 2025-11-28 12:16:21 +01:00
5d63f3c4d6 test: add unit test for part two 2025-11-28 12:02:23 +01:00
ceb3502c96 refactor: prepare part two, which will use the same parsing logic + new structure 2025-11-28 12:02:14 +01:00
e6867b9cfb feat: include 2020D7 2025-11-28 11:32:01 +01:00
d783d14ecc test: add PartOne unit test 2025-11-28 11:31:55 +01:00
1fe2e30ef8 feat: use a BFS approach to solve part one 2025-11-28 11:31:43 +01:00
a7e15569b4 feat: add 2020D7 input 2025-11-28 11:28:28 +01:00
41007ef20e go: fmt 2025-11-28 11:28:19 +01:00
3fb71d0cbf refactor: standardize PartOne/PartTwo declarations 2025-11-28 09:01:11 +01:00
62969312e3 feat: more elegant solution, get rid of cmp 2025-11-26 17:12:36 +01:00
59974a4d29 build: make all targets silent, test output will still appear anyway 2025-11-26 16:11:26 +01:00
44cceaad6d build: make it silent 2025-11-26 16:10:41 +01:00
5274739cd3 build: create bin dir when building as it's git-ignored 2025-11-26 16:09:23 +01:00
8915de6145 feat: add 2022D1 solutions 2025-11-26 16:05:47 +01:00
e96d308e5f feat: input for 2022D1 2025-11-26 16:05:35 +01:00
3fdb921aae tests: add 2022D1 unit tests 2025-11-26 16:05:00 +01:00
2d5e05ae8b feat: include 2022D1 2025-11-26 16:04:51 +01:00
2f06c7ab2d feat: add gitignore 2025-11-26 14:05:39 +01:00
3723f84d1a refactor: massive refactor to have only one binary to call 2025-11-26 14:04:13 +01:00
314da54495 feat: complete part two 2025-11-25 23:12:50 +01:00
3ab410ea06 test: add unit test for part two 2025-11-25 23:12:42 +01:00
45c04431cc refactor: i -> idx 2025-11-25 22:45:49 +01:00
497314aa8b test: add unit test for part one 2025-11-25 22:44:46 +01:00
ce1cc9d5cf feat: complete part one 2025-11-25 22:44:37 +01:00
b2f1a2902c feat: add input 2025-11-25 22:44:30 +01:00
de59203640 go: init 2020/day06 package 2025-11-25 22:44:25 +01:00
021d16cfd7 test: Add unit test for PartTwo 2025-11-25 22:28:33 +01:00
d13ed719e1 feat: complete PartTwo 2025-11-25 22:28:25 +01:00
507b27bc13 refactor: add calculateSeatID helper to simplify code 2025-11-25 22:27:44 +01:00
e763653a96 test: add unit tests 2025-11-24 22:13:21 +01:00
13165c5fa3 feat: add main code 2025-11-24 22:13:16 +01:00
c5885f9bbf feat: add input 2025-11-24 22:13:12 +01:00
6931a63080 go: init 2021/day03 package 2025-11-24 22:13:08 +01:00
3f795a451d test: add unit tests 2025-11-24 21:35:28 +01:00
c56c2aa449 feat: add main code 2025-11-24 21:35:22 +01:00
19ea2cd1f6 feat: add input 2025-11-24 21:35:19 +01:00
8594cd44d3 go: init 2021/day02 package 2025-11-24 21:35:14 +01:00
f8d8916b60 feat: add main code 2025-11-24 21:24:48 +01:00
5545200823 test: add unit tests 2025-11-24 21:24:43 +01:00
0f33caf778 feat: add input 2025-11-24 21:24:22 +01:00
63f9aa5ad1 go:init 2021/day01 package 2025-11-24 21:24:18 +01:00
3602fc356b test: add unit test 2025-11-24 21:07:43 +01:00
624649e6ea feat: add main code 2025-11-24 21:07:34 +01:00
898a67090e feat: add input 2025-11-24 21:07:30 +01:00
daaf966e12 go: init 2020/day05 package 2025-11-24 21:07:27 +01:00
5bdd01dfdd refactor: directly return list from Split() 2025-11-24 18:55:37 +01:00
43b78620ca feat: add main code 2025-11-24 18:49:12 +01:00
bb9f9d4484 test: add unit test 2025-11-24 18:49:09 +01:00
9ec566abf1 typo: tt's -> it's (+ rephrasing) 2025-11-24 14:41:25 +01:00
09e299539d docs: explain how's everything is in go 1.25 :D 2025-11-24 14:40:00 +01:00
587aabd80e typo: faild -> failed 2025-11-24 14:32:16 +01:00
a9de4a3502 feat: add input 2025-11-24 14:11:36 +01:00
cd160685f1 go: init 2020/day04 package 2025-11-24 14:11:33 +01:00
428ec93860 refactor: use shared variable for input 2025-11-24 14:05:43 +01:00
168 changed files with 39391 additions and 220 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/aoc
*.test

View File

@@ -1,3 +0,0 @@
module 2020/day01
go 1.25.4

View File

@@ -1,21 +0,0 @@
package main
import "testing"
func TestPartOne(t *testing.T) {
input := []int{1721, 979, 366, 299, 675, 1456}
expected := 514579
got := PartOne(input)
if got != expected {
t.Errorf("PartOne(%v) = %d, want %d", input, got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []int{1721, 979, 366, 299, 675, 1456}
expected := 241861950
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo(%v) = %d, want %d", input, got, expected)
}
}

View File

@@ -1,3 +0,0 @@
module 2020/day02
go 1.25.4

View File

@@ -1,29 +0,0 @@
package main
import "testing"
func TestPartOne(t *testing.T) {
input := []string{
"1-3 a: abcde",
"1-3 b: cdefg",
"2-9 c: ccccccccc",
}
expected := 2
got := PartOne(input)
if got != expected {
t.Errorf("PartOne(%v) = %d, want %d", input, got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []string{
"1-3 a: abcde",
"1-3 b: cdefg",
"2-9 c: ccccccccc",
}
expected := 1
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo(%v) = %d, want %d", input, got, expected)
}
}

View File

@@ -1,3 +0,0 @@
module 2020/day03
go 1.25.4

View File

@@ -1,62 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Faild to read input file: %v", err)
}
lines := strings.Split(string(content), "\n")
var data []string
for _, line := range lines {
data = append(data, line)
}
return data
}
func PartOne(input []string) int {
trees := 0
column := 0
for row := range input {
if len(input[row]) == 0 {
continue
}
if input[row][column%len(input[row])] == '#' {
trees++
}
column += 3
}
return trees
}
func PartTwo(input []string) int {
result := 1
slopes := [][]int{{1, 1}, {3, 1}, {5, 1}, {7, 1}, {1, 2}}
for _, slope := range slopes {
trees := 0
column := 0
for row := 0; row < len(input); row += slope[1] {
if len(input[row]) == 0 {
continue
}
if input[row][column%len(input[row])] == '#' {
trees++
}
column += slope[0]
}
result *= trees
}
return result
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,45 +0,0 @@
package main
import "testing"
func TestPartOne(t *testing.T) {
input := []string{
"..##.......",
"#...#...#..",
".#....#..#.",
"..#.#...#.#",
".#...##..#.",
"..#.##.....",
".#.#.#....#",
".#........#",
"#.##...#...",
"#...##....#",
".#..#...#.#",
}
expected := 7
got := PartOne(input)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []string{
"..##.......",
"#...#...#..",
".#....#..#.",
"..#.#...#.#",
".#...##..#.",
"..#.##.....",
".#.#.#....#",
".#........#",
"#.##...#...",
"#...##....#",
".#..#...#.#",
}
expected := 336
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

61
Makefile Normal file
View File

@@ -0,0 +1,61 @@
GO = go
BIN = bin/aoc
.PHONY: build test clean
build:
@mkdir -p $(dir $(BIN))
@$(GO) build -o $(BIN) ./cmd/aoc
test:
@$(GO) test ./internal/...
clean:
@rm -f $(BIN)
%:
@DAY_ARG=$@; \
if echo $$DAY_ARG | grep -qE '^[0-9]{4}D[0-9]+$$'; then \
YEAR=$$(echo $$DAY_ARG | sed 's/D.*//'); \
DAY_NUM=$$(echo $$DAY_ARG | sed 's/.*D//'); \
DAY_NAME=$$(case $$DAY_NUM in \
1) echo "One" ;; \
2) echo "Two" ;; \
3) echo "Three" ;; \
4) echo "Four" ;; \
5) echo "Five" ;; \
6) echo "Six" ;; \
7) echo "Seven" ;; \
8) echo "Eight" ;; \
9) echo "Nine" ;; \
10) echo "Ten" ;; \
11) echo "Eleven" ;; \
12) echo "Twelve" ;; \
13) echo "Thirteen" ;; \
14) echo "Fourteen" ;; \
15) echo "Fifteen" ;; \
16) echo "Sixteen" ;; \
17) echo "Seventeen" ;; \
18) echo "Eighteen" ;; \
19) echo "Nineteen" ;; \
20) echo "Twenty" ;; \
21) echo "TwentyOne" ;; \
22) echo "TwentyTwo" ;; \
23) echo "TwentyThree" ;; \
24) echo "TwentyFour" ;; \
25) echo "TwentyFive" ;; \
*) echo "Unknown" ;; \
esac); \
mkdir -p internal/$$YEAR/Day$$DAY_NAME; \
mkdir -p internal/data/$$YEAR/Day$$DAY_NAME; \
touch internal/$$YEAR/Day$$DAY_NAME/code.go; \
touch internal/$$YEAR/Day$$DAY_NAME/code_test.go; \
if [ -n "$$ADVENTOFCODE_SESSION" ]; then \
curl -s -H "Cookie: session=$$ADVENTOFCODE_SESSION" \
https://adventofcode.com/$$YEAR/day/$$DAY_NUM/input \
| perl -pe 'chomp if eof' > internal/data/$$YEAR/Day$$DAY_NAME/input.txt; \
else \
touch internal/data/$$YEAR/Day$$DAY_NAME/input.txt; \
fi; \
echo "$$DAY_ARG ready to be solved."; \
fi

104
README.md
View File

@@ -6,34 +6,99 @@ The goal is to practice programming and problem-solving habits while keeping eac
It uses pure Go, no external dependencies.
Also, it's a fresh start from 2025. I do some exercises from other years along the way, which explains why no year is complete and why everything is in "recent" Go.
Ultimately, my goal is to complete all the years of Advent of Code here.
## Requirements
- Go 1.25
- Puzzle input
- Puzzle input (or your session cookie to have it downloaded automatically)
## Repository Structure
```
yyyy/
├── dayXX/
│ ├── main.go # The main code to run to print solutions.
│ ├── main_test.go # The test file that validates the logic.
│ └── input.txt # The day's puzzle input.
└── ...
├── cmd/
│ └── aoc/
│ └── main.go # CLI entry point for running puzzles
└── internal/
├── 2020/
│ ├── register.go # Aggregates import for main.go
│ ├── DayOne/
│ │ ├── code.go # Day 1 solution for 2020
│ │ └── code_test.go # Unit tests for Day 1 solution
│ └── ... # Other days for 2020
├── 2021/
│ └── ... # 2021 days code and tests
├── registry/
│ └── registry.go # Central map/registry for finding and running days
└── data/
├── 2020/
│ ├── DayOne/
│ │ └── input.txt # Puzzle input for Day 1 (2020)
│ └── ... # Inputs for other days
└── ... # Inputs for other years
```
Each day's code can be run with:
Each day's solution is organized into its own folder, named according to the day number (e.g., `DayOne`, `DayTwo`). Inside each folder, you'll find a `code.go` file with the Go implementation of the solution and a `code_test.go` file containing corresponding tests. The input data for each puzzle is located in the `internal/data` directory, mirroring the code structure for easy access.
Each year has its own `register.go` file to aggregate imports. To connect each solution to the CLI, the repository uses a central registry located in `internal/registry/registry.go`. This registry is essentially a map linking a day key (combining year and day number) to assign a "day runner" which is a struct that specifies the `ParseInput`, `PartOne` and `PartTwo` functions of the day's problem.
When running a solution, the CLI (`cmd/aoc/main.go`) looks up the appropriate runner from the registry based on your command-line input, uses the parsing function to load the input data, and then runs the desired part (or both parts) using the registered solution functions.
### Starting a new day
```bash
cd yyyy/dayXX
go run main.go
make 2020D1
```
Expected output:
If the `ADVENTOFCODE_SESSION` environment variable is set with your Advent of Code session cookie, the input will be downloaded from the AOC website right into the corresponding `internal/data` directory, else it will create an empty `input.txt` file.
Then, it will create the following files:
```bash
Part 1: <answer>
Part 2: <answer>
internal/2020/DayOne/code.go
internal/2020/DayOne/code_test.go
```
### Handling string answers
Occasionally, a puzzle requires a string as the answer. However, my solution framework expects PartOne/PartTwo functions to return integers.
To work around this, I print the string result (see [2018D2P2](https://git.kharec.info/Kharec/advent-of-code/tree/main/internal/2018/DayTwo/code.go)) and return a dummy integer to satisfy the type requirements.
Meanwhile, the corresponding [test](https://git.kharec.info/Kharec/advent-of-code/tree/main/internal/2018/DayTwo/code_test.go) captures the output and verifies that the correct string is produced.
It's not the most elegant solution, but since only a handful of days across all Advent of Code years require a string result, this compromise keeps the rest of the code simple.
## Usage
Build the CLI tool:
```bash
make
```
Run a day's solution:
```bash
bin/aoc 2020D1 # Run both parts
bin/aoc 2020D1P1 # Run only part one
bin/aoc 2020D1P2 # Run only part two
```
Example output:
```bash
$ bin/aoc 2020D1
197451
138233720
$ bin/aoc 2020D1P1
197451
$ bin/aoc 2020D1P2
138233720
```
## Tests
@@ -53,13 +118,18 @@ In this example, the calibration values of these four lines are 12, 38, 15, and
Adding these together produces 142.
```
I'm using these examples to validate my logic.
I'm using these examples each day to validate my logic.
If you want to run those, you can use:
Run all tests:
```bash
cd dayXX
go test
make test
```
Or run tests for a specific day:
```bash
go test ./internal/2020/DayOne/...
```
## Notes

56
cmd/aoc/main.go Normal file
View File

@@ -0,0 +1,56 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"advent-of-code/internal/registry"
_ "advent-of-code/internal/2015"
_ "advent-of-code/internal/2016"
_ "advent-of-code/internal/2018"
_ "advent-of-code/internal/2020"
_ "advent-of-code/internal/2021"
_ "advent-of-code/internal/2022"
_ "advent-of-code/internal/2025"
)
func capitalize(day string) string {
dayNames := map[string]string{
"1": "One", "2": "Two", "3": "Three", "4": "Four", "5": "Five",
"6": "Six", "7": "Seven", "8": "Eight", "9": "Nine", "10": "Ten",
"11": "Eleven", "12": "Twelve", "13": "Thirteen", "14": "Fourteen", "15": "Fifteen",
"16": "Sixteen", "17": "Seventeen", "18": "Eighteen", "19": "Nineteen", "20": "Twenty",
"21": "TwentyOne", "22": "TwentyTwo", "23": "TwentyThree", "24": "TwentyFour", "25": "TwentyFive",
}
return dayNames[day]
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <YEAR>D<DAY>[P<PART>]\n", os.Args[0])
os.Exit(1)
}
re := regexp.MustCompile(`^(\d{4})D(\d+)(?:P(\d+))?$`)
matches := re.FindStringSubmatch(os.Args[1])
if matches == nil {
fmt.Fprintf(os.Stderr, "Invalid format: %s\n", os.Args[1])
os.Exit(1)
}
year, dayNum, part := matches[1], matches[2], matches[3]
dayKey := fmt.Sprintf("%sD%s", year, dayNum)
dayName := fmt.Sprintf("Day%s", capitalize(dayNum))
input := filepath.Join("internal", "data", year, dayName, "input.txt")
runner, ok := registry.Days[dayKey].(registry.Runner)
if !ok {
log.Fatalf("Day not found: %s\n", dayKey)
}
runner.Run(input, part)
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module advent-of-code
go 1.25.4

View File

@@ -0,0 +1,61 @@
package dayeight
import (
"os"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D8", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
result := 0
for _, line := range data {
codeLength := len(line)
memoryLength := 0
for idx := 1; idx < len(line)-1; idx++ {
if line[idx] == '\\' && idx+1 < len(line)-1 {
switch line[idx+1] {
case '\\', '"':
memoryLength++
idx++
case 'x':
if idx+3 < len(line)-1 {
memoryLength++
idx += 3
}
}
} else {
memoryLength++
}
}
result += codeLength - memoryLength
}
return result
}
func PartTwo(data []string) int {
result := 0
for _, line := range data {
originalLength := len(line)
encodedLength := 2
for _, char := range line {
switch char {
case '\\', '"':
encodedLength += 2
default:
encodedLength++
}
}
result += encodedLength - originalLength
}
return result
}

View File

@@ -0,0 +1,26 @@
package dayeight
import "testing"
var testInput = []string{
`""`,
`"abc"`,
`"aaa\"aaa"`,
`"\x27"`,
}
func TestPartOne(t *testing.T) {
expected := 12
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 19
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,97 @@
package dayeleven
import (
"os"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D11", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func findNextValidPassword(data string) string {
bytes := []byte(data)
increment := func() {
for idx := len(bytes) - 1; idx >= 0; idx-- {
if bytes[idx] == 'z' {
bytes[idx] = 'a'
} else {
bytes[idx]++
if bytes[idx] == 'i' || bytes[idx] == 'o' || bytes[idx] == 'l' {
bytes[idx]++
}
for j := idx + 1; j < len(bytes); j++ {
bytes[j] = 'a'
}
return
}
}
}
increment()
for {
forbiddenPosition := -1
for idx := range bytes {
if bytes[idx] == 'i' || bytes[idx] == 'o' || bytes[idx] == 'l' {
forbiddenPosition = idx
break
}
}
if forbiddenPosition != -1 {
bytes[forbiddenPosition]++
if bytes[forbiddenPosition] == 'i' || bytes[forbiddenPosition] == 'o' || bytes[forbiddenPosition] == 'l' {
bytes[forbiddenPosition]++
}
for j := forbiddenPosition + 1; j < len(bytes); j++ {
bytes[j] = 'a'
}
continue
}
hasStraight := false
for idx := 0; idx < len(bytes)-2; idx++ {
if bytes[idx+1] == bytes[idx]+1 && bytes[idx+2] == bytes[idx]+2 {
hasStraight = true
break
}
}
pairChars := make(map[byte]bool)
for idx := 0; idx < len(bytes)-1; idx++ {
if bytes[idx] == bytes[idx+1] {
pairChars[bytes[idx]] = true
idx++
}
}
if hasStraight && len(pairChars) >= 2 {
break
}
increment()
}
return string(bytes)
}
func PartOne(data string) int {
result := findNextValidPassword(data)
println(result)
return 0
}
func PartTwo(data string) int {
firstPassword := findNextValidPassword(data)
secondPassword := findNextValidPassword(firstPassword)
println(secondPassword)
return 0
}

View File

@@ -0,0 +1,77 @@
package dayfive
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2015D5", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
count := 0
for _, line := range data {
vowelCount := strings.Count(line, "a") + strings.Count(line, "e") + strings.Count(line, "i") + strings.Count(line, "o") + strings.Count(line, "u")
if vowelCount < 3 {
continue
}
hasDoubleLetter := false
for i := 1; i < len(line); i++ {
if line[i] == line[i-1] {
hasDoubleLetter = true
break
}
}
if !hasDoubleLetter {
continue
}
if strings.Contains(line, "ab") || strings.Contains(line, "cd") || strings.Contains(line, "pq") || strings.Contains(line, "xy") {
continue
}
count++
}
return count
}
func PartTwo(data []string) int {
count := 0
for _, line := range data {
if len(line) < 4 {
continue
}
hasNonOverlappingPair := false
for i := 0; i < len(line)-1; i++ {
pair := line[i : i+2]
if strings.Contains(line[i+2:], pair) {
hasNonOverlappingPair = true
break
}
}
if !hasNonOverlappingPair {
continue
}
hasRepeatingLetterWithGap := false
for i := 0; i < len(line)-2; i++ {
if line[i] == line[i+2] {
hasRepeatingLetterWithGap = true
break
}
}
if hasRepeatingLetterWithGap {
count++
}
}
return count
}

View File

@@ -0,0 +1,32 @@
package dayfive
import "testing"
func TestPartOne(t *testing.T) {
input := []string{
"ugknbfddgicrmopn",
"aaa",
"jchzalrnumimnmhp",
"haegwjzuvuyypxyu",
"dvszwmarrgswjxmb",
}
expected := 2
got := PartOne(input)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []string{
"qjhvhtzxzqqjkmpb",
"xxyxx",
"uurcxstgmygtbstg",
"ieodomkazucvgmuy",
}
expected := 2
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,46 @@
package dayfour
import (
"advent-of-code/internal/registry"
"bytes"
"crypto/md5"
"os"
"strconv"
)
func init() {
registry.Register("2015D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []byte {
content, _ := os.ReadFile(filepath)
return bytes.TrimSpace(content)
}
func findHashWithZeroes(data []byte, zeroes int) int {
buffer := make([]byte, len(data), len(data)+10)
copy(buffer, data)
for idx := 1; ; idx++ {
buffer = buffer[:len(data)]
buffer = strconv.AppendInt(buffer, int64(idx), 10)
hash := md5.Sum(buffer)
switch zeroes {
case 5:
if hash[0] == 0 && hash[1] == 0 && hash[2]&0xF0 == 0 {
return idx
}
case 6:
if hash[0] == 0 && hash[1] == 0 && hash[2] == 0 {
return idx
}
}
}
}
func PartOne(data []byte) int {
return findHashWithZeroes(data, 5)
}
func PartTwo(data []byte) int {
return findHashWithZeroes(data, 6)
}

View File

@@ -0,0 +1,45 @@
package dayfour
import (
"testing"
)
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input []byte
expected int
}{
{"abcdef", []byte("abcdef"), 609043},
{"pqrstuv", []byte("pqrstuv"), 1048970},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne(tt.input)
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
tests := []struct {
name string
input []byte
expected int
}{
{"abcdef", []byte("abcdef"), 6742839},
{"pqrstuv", []byte("pqrstuv"), 5714438},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartTwo(tt.input)
if got != tt.expected {
t.Errorf("PartTwo() = %d, want %d", got, tt.expected)
}
})
}
}

View File

@@ -0,0 +1,108 @@
package dayfourteen
import (
"advent-of-code/internal/registry"
"os"
"regexp"
"strconv"
"strings"
)
var reindeerPattern = regexp.MustCompile(`\w+ can fly (\d+) km/s for (\d+) seconds, but then must rest for (\d+) seconds\.`)
const raceTime = 2503
type Reindeer struct {
Speed int
FlyTime int
RestTime int
Points int
}
func init() {
registry.Register("2015D14", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func parseReindeer(line string) Reindeer {
matches := reindeerPattern.FindStringSubmatch(line)
speed, _ := strconv.Atoi(matches[1])
flyTime, _ := strconv.Atoi(matches[2])
restTime, _ := strconv.Atoi(matches[3])
return Reindeer{Speed: speed, FlyTime: flyTime, RestTime: restTime}
}
func parseReindeers(data []string) []Reindeer {
reindeers := make([]Reindeer, 0, len(data))
for _, line := range data {
reindeers = append(reindeers, parseReindeer(line))
}
return reindeers
}
func (reindeer Reindeer) distanceAtTime(time int) int {
cycleTime := reindeer.FlyTime + reindeer.RestTime
fullCycles := time / cycleTime
distance := fullCycles * reindeer.Speed * reindeer.FlyTime
remainingTime := time % cycleTime
distance += reindeer.Speed * min(remainingTime, reindeer.FlyTime)
return distance
}
func calculateMaxDistance(data []string, time int) int {
reindeers := parseReindeers(data)
maxDistance := 0
for _, reindeer := range reindeers {
distance := reindeer.distanceAtTime(time)
if distance > maxDistance {
maxDistance = distance
}
}
return maxDistance
}
func calculateMaxPoints(data []string, time int) int {
reindeers := parseReindeers(data)
for second := 1; second <= time; second++ {
maxDistance := 0
distances := make([]int, len(reindeers))
for idx := range reindeers {
distance := reindeers[idx].distanceAtTime(second)
distances[idx] = distance
if distance > maxDistance {
maxDistance = distance
}
}
for idx := range reindeers {
if distances[idx] == maxDistance {
reindeers[idx].Points++
}
}
}
maxPoints := 0
for idx := range reindeers {
if reindeers[idx].Points > maxPoints {
maxPoints = reindeers[idx].Points
}
}
return maxPoints
}
func PartOne(data []string) int {
return calculateMaxDistance(data, raceTime)
}
func PartTwo(data []string) int {
return calculateMaxPoints(data, raceTime)
}

View File

@@ -0,0 +1,24 @@
package dayfourteen
import "testing"
var testInput = []string{
"Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.",
"Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds.",
}
func TestPartOne(t *testing.T) {
expected := 1120
got := calculateMaxDistance(testInput, 1000)
if got != expected {
t.Errorf("calculateMaxDistance(testInput, 1000) = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 689
got := calculateMaxPoints(testInput, 1000)
if got != expected {
t.Errorf("calculateMaxPoints(testInput, 1000) = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,106 @@
package daynine
import (
"os"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D9", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func buildDistanceMap(data []string) map[string]map[string]int {
distances := make(map[string]map[string]int)
for _, line := range data {
parts := strings.Split(line, " = ")
distance, _ := strconv.Atoi(parts[1])
route := strings.Split(parts[0], " to ")
from, to := route[0], route[1]
if distances[from] == nil {
distances[from] = make(map[string]int)
}
if distances[to] == nil {
distances[to] = make(map[string]int)
}
distances[from][to] = distance
distances[to][from] = distance
}
return distances
}
func getCities(distances map[string]map[string]int) []string {
cities := make([]string, 0, len(distances))
for city := range distances {
cities = append(cities, city)
}
return cities
}
func generatePermutations(cities []string) [][]string {
if len(cities) == 0 {
return [][]string{{}}
}
var result [][]string
for idx, city := range cities {
remaining := make([]string, len(cities)-1)
copy(remaining[:idx], cities[:idx])
copy(remaining[idx:], cities[idx+1:])
for _, permutations := range generatePermutations(remaining) {
result = append(result, append([]string{city}, permutations...))
}
}
return result
}
func calculateRouteDistance(route []string, distances map[string]map[string]int) int {
total := 0
for idx := 0; idx < len(route)-1; idx++ {
total += distances[route[idx]][route[idx+1]]
}
return total
}
func PartOne(data []string) int {
distances := buildDistanceMap(data)
cities := getCities(distances)
permutations := generatePermutations(cities)
minimalDistance := int(^uint(0) >> 1)
for _, route := range permutations {
total := calculateRouteDistance(route, distances)
if total < minimalDistance {
minimalDistance = total
}
}
return minimalDistance
}
func PartTwo(data []string) int {
distances := buildDistanceMap(data)
cities := getCities(distances)
permutations := generatePermutations(cities)
maximalDistance := 0
for _, route := range permutations {
total := calculateRouteDistance(route, distances)
if total > maximalDistance {
maximalDistance = total
}
}
return maximalDistance
}

View File

@@ -0,0 +1,25 @@
package daynine
import "testing"
var testInput = []string{
"London to Dublin = 464",
"London to Belfast = 518",
"Dublin to Belfast = 141",
}
func TestPartOne(t *testing.T) {
expected := 605
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 982
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,44 @@
package dayone
import (
"advent-of-code/internal/registry"
"os"
)
func init() {
registry.Register("2015D1", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func PartOne(data string) int {
floor := 0
for _, char := range data {
switch char {
case '(':
floor++
case ')':
floor--
}
}
return floor
}
func PartTwo(data string) int {
floor := 0
for idx, char := range data {
switch char {
case '(':
floor++
case ')':
floor--
}
if floor == -1 {
return idx + 1
}
}
return 0
}

View File

@@ -0,0 +1,50 @@
package dayone
import "testing"
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"(())", "(())", 0},
{"()()", "()()", 0},
{"(((", "(((", 3},
{"(()(()(", "(()(()(", 3},
{"))(((((", "))(((((", 3},
{"())", "())", -1},
{"))(", "))(", -1},
{")))", ")))", -3},
{")())())", ")())())", -3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne(tt.input)
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{")", ")", 1},
{"()())", "()())", 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartTwo(tt.input)
if got != tt.expected {
t.Errorf("PartTwo() = %d, want %d", got, tt.expected)
}
})
}
}

View File

@@ -0,0 +1,77 @@
package dayseven
import (
"maps"
"os"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D7", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) map[string]string {
content, _ := os.ReadFile(filepath)
instructions := make(map[string]string)
for line := range strings.SplitSeq(string(content), "\n") {
if parts := strings.Split(line, " -> "); len(parts) == 2 {
instructions[parts[1]] = parts[0]
}
}
return instructions
}
func evaluateWire(wire string, instructions map[string]string) uint16 {
cache := make(map[string]uint16)
var evaluate func(string) uint16
evaluate = func(wire string) uint16 {
if value, ok := cache[wire]; ok {
return value
}
if num, err := strconv.Atoi(wire); err == nil {
return uint16(num)
}
expression, exists := instructions[wire]
if !exists {
return 0
}
parts := strings.Fields(expression)
var result uint16
switch {
case len(parts) == 1:
result = evaluate(parts[0])
case parts[0] == "NOT":
result = ^evaluate(parts[1])
default:
left, right := evaluate(parts[0]), evaluate(parts[2])
switch parts[1] {
case "AND":
result = left & right
case "OR":
result = left | right
case "LSHIFT":
result = left << right
case "RSHIFT":
result = left >> right
}
}
cache[wire] = result
return result
}
return evaluate(wire)
}
func PartOne(data map[string]string) int {
return int(evaluateWire("a", data))
}
func PartTwo(data map[string]string) int {
signalA := PartOne(data)
instructionsCopy := make(map[string]string, len(data))
maps.Copy(instructionsCopy, data)
instructionsCopy["b"] = strconv.Itoa(signalA)
return int(evaluateWire("a", instructionsCopy))
}

View File

@@ -0,0 +1,50 @@
package dayseven
import (
"testing"
)
var testInput = map[string]string{
"x": "123",
"y": "456",
"d": "x AND y",
"e": "x OR y",
"f": "x LSHIFT 2",
"g": "y RSHIFT 2",
"h": "NOT x",
"i": "NOT y",
"a": "d",
}
func TestPartOne(t *testing.T) {
expected := 72
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
instructions := map[string]string{
"x": "10",
"y": "20",
"z": "x AND y",
"b": "z",
"w": "b LSHIFT 1",
"v": "NOT b",
"u": "w OR v",
"a": "u",
}
partOneResult := PartOne(instructions)
bValue := uint16(partOneResult)
w := bValue << 1
v := ^bValue
u := w | v
expected := int(u)
got := PartTwo(instructions)
if got != expected {
t.Errorf("PartTwo() = %d, want %d (PartOne result: %d)", got, expected, partOneResult)
}
}

View File

@@ -0,0 +1,119 @@
package daysix
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
type instruction struct {
operation string
x1, y1 int
x2, y2 int
}
func init() {
registry.Register("2015D6", ParseInput, PartOne, PartTwo)
}
func parseInstruction(line string) (instruction, bool) {
var op string
var remaining string
switch {
case strings.HasPrefix(line, "turn on "):
op = "on"
remaining = strings.TrimPrefix(line, "turn on ")
case strings.HasPrefix(line, "turn off "):
op = "off"
remaining = strings.TrimPrefix(line, "turn off ")
case strings.HasPrefix(line, "toggle "):
op = "toggle"
remaining = strings.TrimPrefix(line, "toggle ")
default:
return instruction{}, false
}
parts := strings.Split(remaining, " through ")
firstCoordinates := strings.Split(parts[0], ",")
secondCoordinates := strings.Split(parts[1], ",")
x1, _ := strconv.Atoi(firstCoordinates[0])
y1, _ := strconv.Atoi(firstCoordinates[1])
x2, _ := strconv.Atoi(secondCoordinates[0])
y2, _ := strconv.Atoi(secondCoordinates[1])
return instruction{operation: op, x1: x1, y1: y1, x2: x2, y2: y2}, true
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
func PartOne(data []string) int {
grid := make([]bool, 1000*1000)
for _, line := range data {
instruction, ok := parseInstruction(line)
if !ok {
continue
}
for y := instruction.y1; y <= instruction.y2; y++ {
for x := instruction.x1; x <= instruction.x2; x++ {
idx := y*1000 + x
switch instruction.operation {
case "on":
grid[idx] = true
case "off":
grid[idx] = false
case "toggle":
grid[idx] = !grid[idx]
}
}
}
}
count := 0
for _, lit := range grid {
if lit {
count++
}
}
return count
}
func PartTwo(data []string) int {
grid := make([]int, 1000*1000)
for _, line := range data {
instruction, ok := parseInstruction(line)
if !ok {
continue
}
for y := instruction.y1; y <= instruction.y2; y++ {
for x := instruction.x1; x <= instruction.x2; x++ {
idx := y*1000 + x
switch instruction.operation {
case "on":
grid[idx]++
case "off":
if grid[idx] > 0 {
grid[idx]--
}
case "toggle":
grid[idx] += 2
}
}
}
}
total := 0
for _, brightness := range grid {
total += brightness
}
return total
}

View File

@@ -0,0 +1,25 @@
package daysix
import "testing"
var testInput = []string{
"turn on 0,0 through 999,999",
"toggle 0,0 through 999,0",
"turn off 499,499 through 500,500",
}
func TestPartOne(t *testing.T) {
expected := 998996
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 1001996
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,45 @@
package dayten
import (
"os"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D10", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func applyLookAndSay(data string, iterations int) int {
for range iterations {
var result strings.Builder
idx := 0
for idx < len(data) {
digit := data[idx]
count := 1
for idx+count < len(data) && data[idx+count] == digit {
count++
}
result.WriteString(strconv.Itoa(count))
result.WriteByte(digit)
idx += count
}
data = result.String()
}
return len(data)
}
func PartOne(data string) int {
return applyLookAndSay(data, 40)
}
func PartTwo(data string) int {
return applyLookAndSay(data, 50)
}

View File

@@ -0,0 +1,141 @@
package daythirteen
import (
"advent-of-code/internal/registry"
"math"
"os"
"regexp"
"strconv"
"strings"
)
var inputPattern = regexp.MustCompile(`(\w+) would (gain|lose) (\d+) happiness units by sitting next to (\w+)\.?`)
func init() {
registry.Register("2015D13", ParseInput, PartOne, PartTwo)
}
func buildHappinessMap(data []string) map[string]map[string]int {
happinessMap := make(map[string]map[string]int)
for _, line := range data {
matches := inputPattern.FindStringSubmatch(line)
person := matches[1]
operation := matches[2]
value, _ := strconv.Atoi(matches[3])
neighbor := matches[4]
if happinessMap[person] == nil {
happinessMap[person] = make(map[string]int)
}
if operation == "gain" {
happinessMap[person][neighbor] = value
} else {
happinessMap[person][neighbor] = -value
}
}
return happinessMap
}
func getAllPeople(happinessMap map[string]map[string]int) []string {
people := make([]string, 0, len(happinessMap))
for person := range happinessMap {
people = append(people, person)
}
return people
}
func generatePermutations(items []string) [][]string {
if len(items) == 0 {
return [][]string{{}}
}
var result [][]string
for idx, item := range items {
remaining := make([]string, len(items)-1)
copy(remaining[:idx], items[:idx])
copy(remaining[idx:], items[idx+1:])
for _, permutation := range generatePermutations(remaining) {
result = append(result, append([]string{item}, permutation...))
}
}
return result
}
func calculateTotalHappiness(arrangement []string, happinessMap map[string]map[string]int) int {
totalHappiness := 0
arrangementLength := len(arrangement)
for idx := range arrangementLength {
currentPerson := arrangement[idx]
leftNeighbor := arrangement[(idx-1+arrangementLength)%arrangementLength]
rightNeighbor := arrangement[(idx+1)%arrangementLength]
totalHappiness += happinessMap[currentPerson][leftNeighbor]
totalHappiness += happinessMap[currentPerson][rightNeighbor]
}
return totalHappiness
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
happinessMap := buildHappinessMap(data)
allPeople := getAllPeople(happinessMap)
fixedPerson := allPeople[0]
remainingPeople := allPeople[1:]
permutations := generatePermutations(remainingPeople)
maxHappiness := math.MinInt
arrangement := make([]string, len(allPeople))
arrangement[0] = fixedPerson
for _, perm := range permutations {
copy(arrangement[1:], perm)
totalHappiness := calculateTotalHappiness(arrangement, happinessMap)
if totalHappiness > maxHappiness {
maxHappiness = totalHappiness
}
}
return maxHappiness
}
func PartTwo(data []string) int {
happinessMap := buildHappinessMap(data)
allPeople := getAllPeople(happinessMap)
me := "Me"
happinessMap[me] = make(map[string]int)
for _, person := range allPeople {
happinessMap[person][me] = 0
happinessMap[me][person] = 0
}
allPeople = append(allPeople, me)
fixedPerson := allPeople[0]
remainingPeople := allPeople[1:]
permutations := generatePermutations(remainingPeople)
maxTotalChange := math.MinInt
arrangement := make([]string, len(allPeople))
arrangement[0] = fixedPerson
for _, permutation := range permutations {
copy(arrangement[1:], permutation)
totalHappiness := calculateTotalHappiness(arrangement, happinessMap)
if totalHappiness > maxTotalChange {
maxTotalChange = totalHappiness
}
}
return maxTotalChange
}

View File

@@ -0,0 +1,28 @@
package daythirteen
import "testing"
var testInput = []string{
"Alice would gain 54 happiness units by sitting next to Bob",
"Alice would lose 79 happiness units by sitting next to Carol",
"Alice would lose 2 happiness units by sitting next to David",
"Bob would gain 83 happiness units by sitting next to Alice",
"Bob would lose 7 happiness units by sitting next to Carol",
"Bob would lose 63 happiness units by sitting next to David",
"Carol would lose 62 happiness units by sitting next to Alice",
"Carol would gain 60 happiness units by sitting next to Bob",
"Carol would gain 55 happiness units by sitting next to David",
"David would gain 46 happiness units by sitting next to Alice",
"David would lose 7 happiness units by sitting next to Bob",
"David would gain 41 happiness units by sitting next to Carol",
}
func TestPartOne(t *testing.T) {
expected := 330
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
// no test for part two

View File

@@ -0,0 +1,68 @@
package daythree
import (
"advent-of-code/internal/registry"
"os"
)
type coordinates struct {
x, y int
}
func init() {
registry.Register("2015D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func PartOne(data string) int {
houses := make(map[coordinates]int)
x, y := 0, 0
houses[coordinates{x, y}] = 1
for _, direction := range data {
switch direction {
case '>':
x++
case '<':
x--
case '^':
y++
case 'v':
y--
}
houses[coordinates{x, y}]++
}
return len(houses)
}
func PartTwo(data string) int {
houses := make(map[coordinates]int)
santaX, santaY := 0, 0
robotX, robotY := 0, 0
houses[coordinates{0, 0}] = 2
for idx, direction := range data {
var x, y *int
if idx%2 == 0 {
x, y = &santaX, &santaY
} else {
x, y = &robotX, &robotY
}
switch direction {
case '>':
*x++
case '<':
*x--
case '^':
*y++
case 'v':
*y--
}
houses[coordinates{*x, *y}]++
}
return len(houses)
}

View File

@@ -0,0 +1,45 @@
package daythree
import "testing"
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{">", ">", 2},
{"^>v<", "^>v<", 4},
{"^v^v^v^v^v", "^v^v^v^v^v", 2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne(tt.input)
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"^v", "^v", 3},
{"^>v<", "^>v<", 3},
{"^v^v^v^v^v", "^v^v^v^v^v", 11},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartTwo(tt.input)
if got != tt.expected {
t.Errorf("PartTwo() = %d, want %d", got, tt.expected)
}
})
}
}

View File

@@ -0,0 +1,68 @@
package daytwelve
import (
"encoding/json"
"os"
"regexp"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2015D12", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
re := regexp.MustCompile(`-?\d+`)
sum := 0
for _, line := range data {
matches := re.FindAllString(line, -1)
for _, match := range matches {
number, _ := strconv.Atoi(match)
sum += number
}
}
return sum
}
func PartTwo(data []string) int {
var sumNumbers func(any) int
sumNumbers = func(v any) int {
switch value := v.(type) {
case float64:
return int(value)
case []any:
sum := 0
for _, item := range value {
sum += sumNumbers(item)
}
return sum
case map[string]any:
sum := 0
for _, item := range value {
if str, ok := item.(string); ok && str == "red" {
return 0
}
sum += sumNumbers(item)
}
return sum
default:
return 0
}
}
sum := 0
for _, line := range data {
var value any
_ = json.Unmarshal([]byte(line), &value)
sum += sumNumbers(value)
}
return sum
}

View File

@@ -0,0 +1,51 @@
package daytwelve
import "testing"
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"[1,2,3]", "[1,2,3]", 6},
{"{\"a\":2,\"b\":4}", "{\"a\":2,\"b\":4}", 6},
{"[[[3]]]", "[[[3]]]", 3},
{"{\"a\":{\"b\":4},\"c\":-1}", "{\"a\":{\"b\":4},\"c\":-1}", 3},
{"{\"a\":[-1,1]}", "{\"a\":[-1,1]}", 0},
{"[-1,{\"a\":1}]", "[-1,{\"a\":1}]", 0},
{"[]", "[]", 0},
{"{}", "{}", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne([]string{tt.input})
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"[1,2,3]", "[1,2,3]", 6},
{"[1,{\"c\":\"red\",\"b\":2},3]", "[1,{\"c\":\"red\",\"b\":2},3]", 4},
{"{\"d\":\"red\",\"e\":[1,2,3,4],\"f\":5}", "{\"d\":\"red\",\"e\":[1,2,3,4],\"f\":5}", 0},
{"[1,\"red\",5]", "[1,\"red\",5]", 6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartTwo([]string{tt.input})
if got != tt.expected {
t.Errorf("PartTwo() = %d, want %d", got, tt.expected)
}
})
}
}

View File

@@ -0,0 +1,48 @@
package daytwo
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func init() {
registry.Register("2015D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
total := 0
for _, line := range data {
parts := strings.Split(line, "x")
length, _ := strconv.Atoi(parts[0])
width, _ := strconv.Atoi(parts[1])
height, _ := strconv.Atoi(parts[2])
total += 2*length*width + 2*width*height + 2*height*length + min(length*width, width*height, height*length)
}
return total
}
func PartTwo(data []string) int {
total := 0
for _, line := range data {
parts := strings.Split(line, "x")
length, _ := strconv.Atoi(parts[0])
width, _ := strconv.Atoi(parts[1])
height, _ := strconv.Atoi(parts[2])
smallest := min(length, width, height)
middle := length + width + height - smallest - max(length, width, height)
perimeter := 2 * (smallest + middle)
volume := length * width * height
total += perimeter + volume
}
return total
}

View File

@@ -0,0 +1,43 @@
package daytwo
import "testing"
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"2x3x4", "2x3x4", 58},
{"1x1x10", "1x1x10", 43},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne([]string{tt.input})
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"2x3x4", "2x3x4", 34},
{"1x1x10", "1x1x10", 14},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartTwo([]string{tt.input})
if got != tt.expected {
t.Errorf("PartTwo() = %d, want %d", got, tt.expected)
}
})
}
}

18
internal/2015/register.go Normal file
View File

@@ -0,0 +1,18 @@
package year2015
import (
_ "advent-of-code/internal/2015/DayEight"
_ "advent-of-code/internal/2015/DayEleven"
_ "advent-of-code/internal/2015/DayFive"
_ "advent-of-code/internal/2015/DayFour"
_ "advent-of-code/internal/2015/DayFourteen"
_ "advent-of-code/internal/2015/DayNine"
_ "advent-of-code/internal/2015/DayOne"
_ "advent-of-code/internal/2015/DaySeven"
_ "advent-of-code/internal/2015/DaySix"
_ "advent-of-code/internal/2015/DayTen"
_ "advent-of-code/internal/2015/DayThirteen"
_ "advent-of-code/internal/2015/DayThree"
_ "advent-of-code/internal/2015/DayTwelve"
_ "advent-of-code/internal/2015/DayTwo"
)

View File

@@ -0,0 +1,73 @@
package dayfive
import (
"advent-of-code/internal/registry"
"crypto/md5"
"fmt"
"os"
"strconv"
)
func init() {
registry.Register("2016D5", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func PartOne(data string) int {
doorIDBytes := []byte(data)
doorIDLen := len(doorIDBytes)
input := make([]byte, doorIDLen, doorIDLen+20)
copy(input, doorIDBytes)
password := make([]byte, 0, 8)
index := 0
hexChars := "0123456789abcdef"
for len(password) < 8 {
indexBytes := strconv.AppendInt(input[:doorIDLen], int64(index), 10)
hash := md5.Sum(indexBytes)
if hash[0] == 0 && hash[1] == 0 && hash[2] < 16 {
char := hexChars[hash[2]&0x0F]
password = append(password, char)
}
index++
}
fmt.Println(string(password))
return 0
}
func PartTwo(data string) int {
doorIDBytes := []byte(data)
doorIDLen := len(doorIDBytes)
input := make([]byte, doorIDLen, doorIDLen+20)
copy(input, doorIDBytes)
password := make([]byte, 8)
filled := make([]bool, 8)
filledCount := 0
index := 0
hexChars := "0123456789abcdef"
for filledCount < 8 {
indexBytes := strconv.AppendInt(input[:doorIDLen], int64(index), 10)
hash := md5.Sum(indexBytes)
if hash[0] == 0 && hash[1] == 0 && hash[2] < 16 {
position := int(hash[2] & 0x0F)
if position < 8 && !filled[position] {
char := hexChars[hash[3]>>4]
password[position] = char
filled[position] = true
filledCount++
}
}
index++
}
fmt.Println(string(password))
return 0
}

View File

@@ -0,0 +1,58 @@
package dayfive
import (
"bytes"
"os"
"strings"
"testing"
)
var testInput = "abc"
func TestPartOne(t *testing.T) {
expected := "18f47a30"
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
os.Stdout = w
PartOne(testInput)
_ = w.Close()
os.Stdout = oldStdout
var buffer bytes.Buffer
_, _ = buffer.ReadFrom(r)
got := strings.TrimSpace(buffer.String())
if got != expected {
t.Errorf("PartOne() printed %q, want %q", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := "05ace8e3"
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
os.Stdout = w
PartTwo(testInput)
_ = w.Close()
os.Stdout = oldStdout
var buffer bytes.Buffer
_, _ = buffer.ReadFrom(r)
got := strings.TrimSpace(buffer.String())
if got != expected {
t.Errorf("PartTwo() printed %q, want %q", got, expected)
}
}

View File

@@ -0,0 +1,119 @@
package dayfour
import (
"advent-of-code/internal/registry"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
func init() {
registry.Register("2016D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func isValidRoom(encryptedName string, expectedChecksum string) bool {
letterFrequency := [26]int{}
for _, character := range encryptedName {
if character >= 'a' && character <= 'z' {
letterFrequency[character-'a']++
}
}
type letterFrequencyPair struct {
letter rune
count int
}
letterFrequencyPairs := make([]letterFrequencyPair, 0, 26)
for index, frequency := range letterFrequency {
if frequency > 0 {
letterFrequencyPairs = append(letterFrequencyPairs, letterFrequencyPair{
letter: rune('a' + index),
count: frequency,
})
}
}
sort.Slice(letterFrequencyPairs, func(i, j int) bool {
if letterFrequencyPairs[i].count != letterFrequencyPairs[j].count {
return letterFrequencyPairs[i].count > letterFrequencyPairs[j].count
}
return letterFrequencyPairs[i].letter < letterFrequencyPairs[j].letter
})
if len(letterFrequencyPairs) < 5 {
return false
}
for index := range 5 {
if letterFrequencyPairs[index].letter != rune(expectedChecksum[index]) {
return false
}
}
return true
}
func decryptRoomName(encryptedName string, sectorID int) string {
result := strings.Builder{}
result.Grow(len(encryptedName))
shift := sectorID % 26
for _, char := range encryptedName {
if char == '-' {
result.WriteByte(' ')
} else if char >= 'a' && char <= 'z' {
shifted := ((int(char-'a') + shift) % 26) + 'a'
result.WriteByte(byte(shifted))
}
}
return result.String()
}
var roomPattern = regexp.MustCompile(`^(.+)-(\d+)(?:\[([a-z]{5})\])?$`)
func PartOne(data []string) int {
sum := 0
for _, line := range data {
if line == "" {
continue
}
matches := roomPattern.FindStringSubmatch(line)
if len(matches) < 4 || matches[3] == "" {
continue
}
encryptedName := matches[1]
sectorIdentifier, _ := strconv.Atoi(matches[2])
actualChecksum := matches[3]
if isValidRoom(encryptedName, actualChecksum) {
sum += sectorIdentifier
}
}
return sum
}
func PartTwo(data []string) int {
for _, line := range data {
matches := roomPattern.FindStringSubmatch(line)
encryptedName := matches[1]
sectorIdentifier, _ := strconv.Atoi(matches[2])
checksum := matches[3]
if !isValidRoom(encryptedName, checksum) {
continue
}
decrypted := decryptRoomName(encryptedName, sectorIdentifier)
if strings.Contains(decrypted, "northpole") {
return sectorIdentifier
}
}
return 0
}

View File

@@ -0,0 +1,29 @@
package dayfour
import (
"testing"
)
var testInput = []string{
"aaaaa-bbb-z-y-x-123[abxyz]",
"a-b-c-d-e-f-g-h-987[abcde]",
"not-a-real-room-404[oarel]",
"totally-real-room-200[decoy]",
"ijmockjgz-storage-5[gjoac]",
}
func TestPartOne(t *testing.T) {
expected := 1519
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 5
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,91 @@
package dayone
import (
"os"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2016D1", ParseInput, PartOne, PartTwo)
}
type position struct {
x, y int
}
func abs(n int) int {
if n < 0 {
return -n
}
return n
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
parts := strings.Split(string(content), ",")
for i, p := range parts {
parts[i] = strings.TrimSpace(p)
}
return parts
}
func PartOne(data []string) int {
x, y := 0, 0
directions := [][]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
currentDirection := 0
for _, instruction := range data {
turn := instruction[0]
distance, _ := strconv.Atoi(instruction[1:])
switch turn {
case 'R':
currentDirection = (currentDirection + 1) % 4
case 'L':
currentDirection = (currentDirection + 3) % 4
}
x += directions[currentDirection][0] * distance
y += directions[currentDirection][1] * distance
}
return abs(x) + abs(y)
}
func PartTwo(data []string) int {
x, y := 0, 0
directions := [][]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
currentDirection := 0
visited := make(map[position]bool)
visited[position{0, 0}] = true
for _, instruction := range data {
turn := instruction[0]
distance, _ := strconv.Atoi(instruction[1:])
switch turn {
case 'R':
currentDirection = (currentDirection + 1) % 4
case 'L':
currentDirection = (currentDirection + 3) % 4
}
directionX := directions[currentDirection][0]
directionY := directions[currentDirection][1]
for range distance {
x += directionX
y += directionY
pos := position{x, y}
if visited[pos] {
return abs(x) + abs(y)
}
visited[pos] = true
}
}
return 0
}

View File

@@ -0,0 +1,47 @@
package dayone
import (
"testing"
)
func TestPartOne(t *testing.T) {
tests := []struct {
name string
input []string
expected int
}{
{
name: "Example 1",
input: []string{"R2", "L3"},
expected: 5,
},
{
name: "Example 2",
input: []string{"R2", "R2", "R2"},
expected: 2,
},
{
name: "Example 3",
input: []string{"R5", "L5", "R5", "R3"},
expected: 12,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PartOne(tt.input)
if got != tt.expected {
t.Errorf("PartOne() = %d, want %d", got, tt.expected)
}
})
}
}
func TestPartTwo(t *testing.T) {
input := []string{"R8", "R4", "R4", "R8"}
expected := 4
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, 4)
}
}

View File

@@ -0,0 +1,49 @@
package daythree
import (
"advent-of-code/internal/registry"
"fmt"
"os"
"strings"
)
func init() {
registry.Register("2016D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) [][3]int {
content, _ := os.ReadFile(filepath)
lines := strings.Split(string(content), "\n")
var result [][3]int
for _, line := range lines {
var a, b, c int
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
result = append(result, [3]int{a, b, c})
}
return result
}
func PartOne(data [][3]int) int {
count := 0
for _, triangle := range data {
a, b, c := triangle[0], triangle[1], triangle[2]
if a+b > c && a+c > b && b+c > a {
count++
}
}
return count
}
func PartTwo(data [][3]int) int {
count := 0
for idx := 0; idx < len(data)-2; idx += 3 {
for column := range 3 {
a, b, c := data[idx][column], data[idx+1][column], data[idx+2][column]
if a+b > c && a+c > b && b+c > a {
count++
}
}
}
return count
}

View File

@@ -0,0 +1,28 @@
package daythree
import "testing"
func TestPartOne(t *testing.T) {
input := [][3]int{{5, 10, 25}}
expected := 0
got := PartOne(input)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := [][3]int{
{101, 301, 501},
{102, 302, 502},
{103, 303, 503},
{201, 401, 601},
{202, 402, 602},
{203, 403, 603},
}
expected := 6
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,101 @@
package daytwo
import (
"advent-of-code/internal/registry"
"fmt"
"os"
"strings"
)
func init() {
registry.Register("2016D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(instructions []string) int {
row, column := 1, 1
keypad := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
code := 0
for _, line := range instructions {
for _, move := range line {
switch move {
case 'U':
if row > 0 {
row--
}
case 'D':
if row < 2 {
row++
}
case 'L':
if column > 0 {
column--
}
case 'R':
if column < 2 {
column++
}
}
}
code = code*10 + keypad[row][column]
}
return code
}
func PartTwo(instructions []string) int {
row, column := 2, 0
keypad := [5][5]int{
{0, 0, 1, 0, 0},
{0, 2, 3, 4, 0},
{5, 6, 7, 8, 9},
{0, 10, 11, 12, 0},
{0, 0, 13, 0, 0},
}
isValidPosition := func(r, c int) bool {
return r >= 0 && r < 5 && c >= 0 && c < 5 && keypad[r][c] != 0
}
var codeBuilder strings.Builder
for _, line := range instructions {
for _, move := range line {
newRow, newColumn := row, column
switch move {
case 'U':
newRow--
case 'D':
newRow++
case 'L':
newColumn--
case 'R':
newColumn++
}
if isValidPosition(newRow, newColumn) {
row, column = newRow, newColumn
}
}
value := keypad[row][column]
if value < 10 {
codeBuilder.WriteByte(byte('0' + value))
} else {
codeBuilder.WriteByte(byte('A' + value - 10))
}
}
code := codeBuilder.String()
fmt.Println(code)
return 0
}

View File

@@ -0,0 +1,42 @@
package daytwo
import (
"bytes"
"os"
"strings"
"testing"
)
var testInput = []string{"ULL", "RRDDD", "LURDL", "UUUUD"}
func TestPartOne(t *testing.T) {
expected := 1985
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := "5DB3"
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
os.Stdout = w
PartTwo(testInput)
_ = w.Close()
os.Stdout = oldStdout
var buffer bytes.Buffer
_, _ = buffer.ReadFrom(r)
got := strings.TrimSpace(buffer.String())
if got != expected {
t.Errorf("PartTwo() printed %q, want %q", got, expected)
}
}

View File

@@ -0,0 +1,9 @@
package year2016
import (
_ "advent-of-code/internal/2016/DayFive"
_ "advent-of-code/internal/2016/DayFour"
_ "advent-of-code/internal/2016/DayOne"
_ "advent-of-code/internal/2016/DayThree"
_ "advent-of-code/internal/2016/DayTwo"
)

View File

@@ -0,0 +1,64 @@
package dayfive
import (
"advent-of-code/internal/registry"
"os"
"unicode"
)
func init() {
registry.Register("2018D5", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) string {
content, _ := os.ReadFile(filepath)
return string(content)
}
func reactPolymer(data string) int {
stack := []rune{}
for _, char := range data {
if len(stack) == 0 {
stack = append(stack, char)
continue
}
top := stack[len(stack)-1]
if unicode.ToLower(top) == unicode.ToLower(char) && top != char {
stack = stack[:len(stack)-1]
} else {
stack = append(stack, char)
}
}
return len(stack)
}
func PartOne(data string) int {
return reactPolymer(data)
}
func PartTwo(data string) int {
unitTypes := make(map[rune]bool)
for _, char := range data {
unitTypes[unicode.ToLower(char)] = true
}
shortestPolymerLength := len(data)
for unitType := range unitTypes {
filtered := make([]rune, 0, len(data))
for _, char := range data {
if unicode.ToLower(char) != unitType {
filtered = append(filtered, char)
}
}
length := reactPolymer(string(filtered))
if length < shortestPolymerLength {
shortestPolymerLength = length
}
}
return shortestPolymerLength
}

View File

@@ -0,0 +1,21 @@
package dayfive
import "testing"
var testInput = "dabAcCaCBAcCcaDA"
func TestPartOne(t *testing.T) {
expected := 10
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 4
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,133 @@
package dayfour
import (
"os"
"regexp"
"slices"
"strconv"
"strings"
"time"
"advent-of-code/internal/registry"
)
var (
guardRegex = regexp.MustCompile(`Guard #(\d+) begins shift`)
timeLayout = "2006-01-02 15:04"
)
func init() {
registry.Register("2018D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
type record struct {
timestamp time.Time
action string
guardID int
}
func PartOne(data []string) int {
records := make([]record, 0, len(data))
for _, line := range data {
timeStr, action, _ := strings.Cut(line[1:], "] ")
timestamp, _ := time.Parse(timeLayout, timeStr)
guardID := -1
if matches := guardRegex.FindStringSubmatch(action); matches != nil {
guardID, _ = strconv.Atoi(matches[1])
}
records = append(records, record{timestamp, action, guardID})
}
slices.SortFunc(records, func(a, b record) int {
return a.timestamp.Compare(b.timestamp)
})
guardSleepMinutes := make(map[int]int)
guardMinuteCount := make(map[int]map[int]int)
currentGuard := -1
for i := range records {
if records[i].guardID != -1 {
currentGuard = records[i].guardID
if guardMinuteCount[currentGuard] == nil {
guardMinuteCount[currentGuard] = make(map[int]int)
}
}
if records[i].action == "falls asleep" && i+1 < len(records) {
start, end := records[i].timestamp.Minute(), records[i+1].timestamp.Minute()
guardSleepMinutes[currentGuard] += end - start
for m := start; m < end; m++ {
guardMinuteCount[currentGuard][m]++
}
}
}
maxGuard, maxMinutes := -1, 0
for id, minutes := range guardSleepMinutes {
if minutes > maxMinutes {
maxGuard, maxMinutes = id, minutes
}
}
maxMinute, maxCount := -1, 0
for m, count := range guardMinuteCount[maxGuard] {
if count > maxCount {
maxMinute, maxCount = m, count
}
}
return maxGuard * maxMinute
}
func PartTwo(data []string) int {
records := make([]record, 0, len(data))
for _, line := range data {
timeStr, action, _ := strings.Cut(line[1:], "] ")
timestamp, _ := time.Parse(timeLayout, timeStr)
guardID := -1
if matches := guardRegex.FindStringSubmatch(action); matches != nil {
guardID, _ = strconv.Atoi(matches[1])
}
records = append(records, record{timestamp, action, guardID})
}
slices.SortFunc(records, func(a, b record) int {
return a.timestamp.Compare(b.timestamp)
})
guardMinuteCount := make(map[int]map[int]int)
currentGuard := -1
for idx := range records {
if records[idx].guardID != -1 {
currentGuard = records[idx].guardID
if guardMinuteCount[currentGuard] == nil {
guardMinuteCount[currentGuard] = make(map[int]int)
}
}
if records[idx].action == "falls asleep" && idx+1 < len(records) {
start, end := records[idx].timestamp.Minute(), records[idx+1].timestamp.Minute()
for m := start; m < end; m++ {
guardMinuteCount[currentGuard][m]++
}
}
}
maxGuard, maxMinute, maxCount := -1, -1, 0
for guardID, minuteCounts := range guardMinuteCount {
for minute, count := range minuteCounts {
if count > maxCount {
maxGuard, maxMinute, maxCount = guardID, minute, count
}
}
}
return maxGuard * maxMinute
}

View File

@@ -0,0 +1,39 @@
package dayfour
import "testing"
var testInput = []string{
"[1518-11-01 00:00] Guard #10 begins shift",
"[1518-11-01 00:05] falls asleep",
"[1518-11-01 00:25] wakes up",
"[1518-11-01 00:30] falls asleep",
"[1518-11-01 00:55] wakes up",
"[1518-11-01 23:58] Guard #99 begins shift",
"[1518-11-02 00:40] falls asleep",
"[1518-11-02 00:50] wakes up",
"[1518-11-03 00:05] Guard #10 begins shift",
"[1518-11-03 00:24] falls asleep",
"[1518-11-03 00:29] wakes up",
"[1518-11-04 00:02] Guard #99 begins shift",
"[1518-11-04 00:36] falls asleep",
"[1518-11-04 00:46] wakes up",
"[1518-11-05 00:03] Guard #99 begins shift",
"[1518-11-05 00:45] falls asleep",
"[1518-11-05 00:55] wakes up",
}
func TestPartOne(t *testing.T) {
expected := 240
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 4455
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,45 @@
package dayone
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func init() {
registry.Register("2018D1", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []int {
content, _ := os.ReadFile(filepath)
var data []int
for line := range strings.SplitSeq(string(content), "\n") {
num, _ := strconv.Atoi(line)
data = append(data, num)
}
return data
}
func PartOne(data []int) int {
sum := 0
for _, num := range data {
sum += num
}
return sum
}
func PartTwo(data []int) int {
seen := make(map[int]bool)
sum := 0
seen[0] = true
for {
for _, num := range data {
sum += num
if seen[sum] {
return sum
}
seen[sum] = true
}
}
}

View File

@@ -0,0 +1,26 @@
package dayone
import "testing"
var testInput = []int{
+1,
-2,
3,
1,
}
func TestPartOne(t *testing.T) {
expected := 3
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 2
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,101 @@
package daythree
import (
"advent-of-code/internal/registry"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
func init() {
registry.Register("2018D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
coverage := make(map[string]int)
re := regexp.MustCompile(`#\d+ @ (\d+),(\d+): (\d+)x(\d+)`)
for _, line := range data {
matches := re.FindStringSubmatch(line)
left, _ := strconv.Atoi(matches[1])
top, _ := strconv.Atoi(matches[2])
width, _ := strconv.Atoi(matches[3])
height, _ := strconv.Atoi(matches[4])
for x := left; x < left+width; x++ {
for y := top; y < top+height; y++ {
key := fmt.Sprintf("%d,%d", x, y)
coverage[key]++
}
}
}
overlapCount := 0
for _, count := range coverage {
if count >= 2 {
overlapCount++
}
}
return overlapCount
}
func PartTwo(data []string) int {
coverage := make(map[string]int)
re := regexp.MustCompile(`#\d+ @ (\d+),(\d+): (\d+)x(\d+)`)
for _, line := range data {
matches := re.FindStringSubmatch(line)
left, _ := strconv.Atoi(matches[1])
top, _ := strconv.Atoi(matches[2])
width, _ := strconv.Atoi(matches[3])
height, _ := strconv.Atoi(matches[4])
for x := left; x < left+width; x++ {
for y := top; y < top+height; y++ {
key := fmt.Sprintf("%d,%d", x, y)
coverage[key]++
}
}
}
reWithID := regexp.MustCompile(`#(\d+) @ (\d+),(\d+): (\d+)x(\d+)`)
for _, line := range data {
matches := reWithID.FindStringSubmatch(line)
if len(matches) != 6 {
continue
}
id, _ := strconv.Atoi(matches[1])
left, _ := strconv.Atoi(matches[2])
top, _ := strconv.Atoi(matches[3])
width, _ := strconv.Atoi(matches[4])
height, _ := strconv.Atoi(matches[5])
overlaps := false
for x := left; x < left+width && !overlaps; x++ {
for y := top; y < top+height; y++ {
key := fmt.Sprintf("%d,%d", x, y)
if coverage[key] > 1 {
overlaps = true
break
}
}
}
if !overlaps {
return id
}
}
return 0
}

View File

@@ -0,0 +1,25 @@
package daythree
import "testing"
var testInput = []string{
"#1 @ 1,3: 4x4",
"#2 @ 3,1: 4x4",
"#3 @ 5,5: 2x2",
}
func TestPartOne(t *testing.T) {
expected := 4
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 3
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,81 @@
package daytwo
import (
"advent-of-code/internal/registry"
"fmt"
"os"
"strings"
)
func init() {
registry.Register("2018D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
countWithTwo := 0
countWithThree := 0
for _, boxID := range data {
charCounts := make(map[rune]int)
for _, char := range boxID {
charCounts[char]++
}
hasExactlyTwo := false
hasExactlyThree := false
for _, count := range charCounts {
switch count {
case 2:
hasExactlyTwo = true
case 3:
hasExactlyThree = true
}
}
if hasExactlyTwo {
countWithTwo++
}
if hasExactlyThree {
countWithThree++
}
}
return countWithTwo * countWithThree
}
func PartTwo(data []string) int {
for idx := range data {
for otherIdx := idx + 1; otherIdx < len(data); otherIdx++ {
if data[idx] == "" || data[otherIdx] == "" {
continue
}
differenceCount := 0
differingPosition := -1
if len(data[idx]) != len(data[otherIdx]) {
continue
}
for position := 0; position < len(data[idx]); position++ {
if data[idx][position] != data[otherIdx][position] {
differenceCount++
differingPosition = position
}
}
if differenceCount == 1 {
common := data[idx][:differingPosition] + data[idx][differingPosition+1:]
fmt.Println(common)
return 0
}
}
}
return 0
}

View File

@@ -0,0 +1,58 @@
package daytwo
import (
"bytes"
"os"
"strings"
"testing"
)
func TestPartOne(t *testing.T) {
input := []string{
"abcdef",
"bababc",
"abbcde",
"abcccd",
"aabcdd",
"abcdee",
"ababab",
}
expected := 12
got := PartOne(input)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []string{
"abcde",
"fghij",
"klmno",
"pqrst",
"fguij",
"axcye",
"wvxyz",
}
expected := "fgij"
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Failed to create pipe: %v", err)
}
os.Stdout = w
PartTwo(input)
_ = w.Close()
os.Stdout = oldStdout
var buffer bytes.Buffer
_, _ = buffer.ReadFrom(r)
got := strings.TrimSpace(buffer.String())
if got != expected {
t.Errorf("PartTwo() printed %q, want %q", got, expected)
}
}

View File

@@ -0,0 +1,9 @@
package year2018
import (
_ "advent-of-code/internal/2018/DayFive"
_ "advent-of-code/internal/2018/DayFour"
_ "advent-of-code/internal/2018/DayOne"
_ "advent-of-code/internal/2018/DayThree"
_ "advent-of-code/internal/2018/DayTwo"
)

View File

@@ -0,0 +1,94 @@
package dayeight
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
type instruction struct {
operation string
argument int
}
func init() {
registry.Register("2020D8", ParseInput, PartOne, PartTwo)
}
func parseInstructions(data []string) []instruction {
instructions := make([]instruction, 0, len(data))
for _, line := range data {
line = strings.TrimSpace(line)
parts := strings.Fields(line)
if len(parts) != 2 {
continue
}
arg, _ := strconv.Atoi(parts[1])
instructions = append(instructions, instruction{operation: parts[0], argument: arg})
}
return instructions
}
func execute(instructions []instruction) (accumulator int, terminatedNormally bool) {
visited := make(map[int]bool)
accumulator = 0
pc := 0
for pc < len(instructions) {
if visited[pc] {
return accumulator, false
}
visited[pc] = true
instruction := instructions[pc]
switch instruction.operation {
case "acc":
accumulator += instruction.argument
pc++
case "jmp":
pc += instruction.argument
case "nop":
pc++
}
}
return accumulator, true
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
instructions := parseInstructions(data)
accumulator, _ := execute(instructions)
return accumulator
}
func PartTwo(data []string) int {
instructions := parseInstructions(data)
for idx := range instructions {
if instructions[idx].operation != "jmp" && instructions[idx].operation != "nop" {
continue
}
originalOperation := instructions[idx].operation
if originalOperation == "jmp" {
instructions[idx].operation = "nop"
} else {
instructions[idx].operation = "jmp"
}
accumulator, terminatedNormally := execute(instructions)
instructions[idx].operation = originalOperation
if terminatedNormally {
return accumulator
}
}
return 0
}

View File

@@ -0,0 +1,31 @@
package dayeight
import "testing"
var testInput = []string{
"nop +0",
"acc +1",
"jmp +4",
"acc +3",
"jmp -3",
"acc -99",
"acc +1",
"jmp -4",
"acc +6",
}
func TestPartOne(t *testing.T) {
expected := 5
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 8
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,79 @@
package dayfive
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2020D5", ParseInput, PartOne, PartTwo)
}
func calculateSeatID(pass string) int {
if len(pass) < 10 {
return -1
}
rowStr := pass[:7]
columnStr := pass[7:10]
row := 0
for idx, char := range rowStr {
if char == 'B' {
row |= 1 << (6 - idx)
}
}
column := 0
for idx, char := range columnStr {
if char == 'R' {
column |= 1 << (2 - idx)
}
}
return row*8 + column
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
maxSeatID := 0
for _, pass := range data {
seatID := calculateSeatID(pass)
if seatID > maxSeatID {
maxSeatID = seatID
}
}
return maxSeatID
}
func PartTwo(data []string) int {
seatIDs := make(map[int]bool)
minSeatID := 1000
maxSeatID := 0
for _, pass := range data {
if len(pass) < 10 {
continue
}
seatID := calculateSeatID(pass)
seatIDs[seatID] = true
if seatID < minSeatID {
minSeatID = seatID
}
if seatID > maxSeatID {
maxSeatID = seatID
}
}
for seatID := minSeatID + 1; seatID < maxSeatID; seatID++ {
if !seatIDs[seatID] && seatIDs[seatID-1] && seatIDs[seatID+1] {
return seatID
}
}
return 0
}

View File

@@ -0,0 +1,34 @@
package dayfive
import "testing"
func TestPartOne(t *testing.T) {
input := []string{
"FBFBBFFRLR",
"BFFFBBFRRR",
"FFFBBBFRRR",
"BBFFBBFRLL",
}
expected := 820
got := PartOne(input)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
input := []string{
"FFFFFFFLLL",
"FFFFFFFLLR",
"FFFFFFFLRL",
"FFFFFFFLRR",
"FFFFFFFRLR",
"FFFFFFFRRL",
"FFFFFFFRRR",
}
expected := 4
got := PartTwo(input)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,153 @@
package dayfour
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func init() {
registry.Register("2020D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
text := string(content)
passports := strings.Split(text, "\n\n")
result := make([]string, 0, len(passports))
for _, passport := range passports {
passport = strings.TrimSpace(passport)
if passport == "" {
continue
}
passport = strings.ReplaceAll(passport, "\n", " ")
passport = strings.Join(strings.Fields(passport), " ")
result = append(result, passport)
}
return result
}
func PartOne(data []string) int {
count := 0
required := []string{"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"}
for _, passport := range data {
fields := strings.Fields(passport)
present := make(map[string]bool)
for _, field := range fields {
if idx := strings.Index(field, ":"); idx != -1 {
present[field[:idx]] = true
}
}
missing := false
for _, field := range required {
if !present[field] {
missing = true
break
}
}
if !missing {
count++
}
}
return count
}
func PartTwo(data []string) int {
isNumberInRange := func(value string, min, max int, digits int) bool {
if len(value) != digits {
return false
}
n, err := strconv.Atoi(value)
if err != nil {
return false
}
return n >= min && n <= max
}
isValidHeight := func(value string) bool {
if len(value) < 3 {
return false
}
unit := value[len(value)-2:]
number := value[:len(value)-2]
n, err := strconv.Atoi(number)
if err != nil {
return false
}
if unit == "cm" {
return n >= 150 && n <= 193
}
if unit == "in" {
return n >= 59 && n <= 76
}
return false
}
isValidHairColor := func(value string) bool {
if len(value) != 7 || value[0] != '#' {
return false
}
for _, c := range value[1:] {
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
return false
}
}
return true
}
isValidPID := func(value string) bool {
if len(value) != 9 {
return false
}
for _, c := range value {
if c < '0' || c > '9' {
return false
}
}
return true
}
validECL := map[string]bool{
"amb": true, "blu": true, "brn": true, "gry": true,
"grn": true, "hzl": true, "oth": true,
}
required := []string{"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"}
count := 0
for _, passport := range data {
fields := map[string]string{}
for field := range strings.FieldsSeq(passport) {
parts := strings.SplitN(field, ":", 2)
if len(parts) == 2 {
fields[parts[0]] = parts[1]
}
}
passportValidators := map[string]func(string) bool{
"byr": func(v string) bool { return isNumberInRange(v, 1920, 2002, 4) },
"iyr": func(v string) bool { return isNumberInRange(v, 2010, 2020, 4) },
"eyr": func(v string) bool { return isNumberInRange(v, 2020, 2030, 4) },
"hgt": isValidHeight,
"hcl": isValidHairColor,
"ecl": func(v string) bool { return validECL[v] },
"pid": isValidPID,
}
valid := true
for _, key := range required {
value, ok := fields[key]
if !ok || !passportValidators[key](value) {
valid = false
break
}
}
if valid {
count++
}
}
return count
}

View File

@@ -0,0 +1,26 @@
package dayfour
import "testing"
var testInput = []string{
"ecl:gry pid:860033327 eyr:2020 hcl:#fffffd byr:1937 iyr:2017 cid:147 hgt:183cm",
"iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 hcl:#cfa07d byr:1929",
"hcl:#ae17e1 iyr:2013 eyr:2024 ecl:brn pid:760753108 byr:1931 hgt:179cm",
"hcl:#cfa07d eyr:2025 pid:166559648 iyr:2011 ecl:brn hgt:59in",
}
func TestPartOne(t *testing.T) {
expected := 2
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 2
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,25 +1,22 @@
package main
package dayone
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func parseInput(file string) []int {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2020D1", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []int {
content, _ := os.ReadFile(filepath)
lines := strings.Fields(string(content))
var data []int
for _, line := range lines {
num, err := strconv.Atoi(line)
if err != nil {
log.Fatalf("Failed to convert string to int: %v", err)
}
num, _ := strconv.Atoi(line)
data = append(data, num)
}
return data
@@ -50,9 +47,3 @@ func PartTwo(data []int) int {
}
return 0
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -0,0 +1,21 @@
package dayone
import "testing"
var testInput = []int{1721, 979, 366, 299, 675, 1456}
func TestPartOne(t *testing.T) {
expected := 514579
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 241861950
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,105 @@
package dayseven
import (
"advent-of-code/internal/registry"
"os"
"regexp"
"strconv"
"strings"
)
var (
rulePattern = regexp.MustCompile(`^(\w+ \w+) bags contain (.+)\.$`)
containedPattern = regexp.MustCompile(`(\d+) (\w+ \w+) bags?`)
)
type bagCount struct {
count int
bag string
}
func init() {
registry.Register("2020D7", ParseInput, PartOne, PartTwo)
}
func buildForwardGraph(data []string) map[string][]bagCount {
forwardGraph := make(map[string][]bagCount)
for _, line := range data {
matches := rulePattern.FindStringSubmatch(line)
if len(matches) != 3 {
continue
}
containerBag := matches[1]
containedString := matches[2]
if containedString == "no other bags" {
forwardGraph[containerBag] = []bagCount{}
continue
}
containedMatches := containedPattern.FindAllStringSubmatch(containedString, -1)
for _, match := range containedMatches {
if len(match) >= 3 {
count, _ := strconv.Atoi(match[1])
containedBag := match[2]
forwardGraph[containerBag] = append(forwardGraph[containerBag], bagCount{count: count, bag: containedBag})
}
}
}
return forwardGraph
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
forwardGraph := buildForwardGraph(data)
reverseGraph := make(map[string][]string)
for container, contained := range forwardGraph {
for _, child := range contained {
reverseGraph[child.bag] = append(reverseGraph[child.bag], container)
}
}
visited := make(map[string]bool)
queue := reverseGraph["shiny gold"]
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if visited[current] {
continue
}
visited[current] = true
for _, parent := range reverseGraph[current] {
if !visited[parent] {
queue = append(queue, parent)
}
}
}
return len(visited)
}
func PartTwo(data []string) int {
forwardGraph := buildForwardGraph(data)
var countIndividualBags func(string) int
countIndividualBags = func(bag string) int {
total := 0
for _, child := range forwardGraph[bag] {
total += child.count + child.count*countIndividualBags(child.bag)
}
return total
}
return countIndividualBags("shiny gold")
}

View File

@@ -0,0 +1,51 @@
package dayseven
import "testing"
var testInput = []string{
"light red bags contain 1 bright white bag, 2 muted yellow bags.",
"dark orange bags contain 3 bright white bags, 4 muted yellow bags.",
"bright white bags contain 1 shiny gold bag.",
"muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.",
"shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.",
"dark olive bags contain 3 faded blue bags, 4 dotted black bags.",
"vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.",
"faded blue bags contain no other bags.",
"dotted black bags contain no other bags.",
}
func TestPartOne(t *testing.T) {
expected := 4
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
secondTestInput := []string{
"shiny gold bags contain 2 dark red bags.",
"dark red bags contain 2 dark orange bags.",
"dark orange bags contain 2 dark yellow bags.",
"dark yellow bags contain 2 dark green bags.",
"dark green bags contain 2 dark blue bags.",
"dark blue bags contain 2 dark violet bags.",
"dark violet bags contain no other bags.",
}
t.Run("First Example", func(t *testing.T) {
expected := 32
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
})
t.Run("Second Example", func(t *testing.T) {
expected := 126
got := PartTwo(secondTestInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
})
}

View File

@@ -0,0 +1,69 @@
package daysix
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2020D6", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
func PartOne(data []string) int {
total := 0
groupAnswers := make(map[rune]bool)
for idx, line := range data {
if line != "" {
for _, char := range line {
if char >= 'a' && char <= 'z' {
groupAnswers[char] = true
}
}
}
if line == "" || idx == len(data)-1 {
total += len(groupAnswers)
groupAnswers = make(map[rune]bool)
}
}
total += len(groupAnswers)
return total
}
func PartTwo(data []string) int {
total := 0
groupAnswers := make(map[rune]int)
groupSize := 0
for idx, line := range data {
if line != "" {
groupSize++
for _, char := range line {
if char >= 'a' && char <= 'z' {
groupAnswers[char]++
}
}
}
if line == "" || idx == len(data)-1 {
for _, count := range groupAnswers {
if count == groupSize {
total++
}
}
groupAnswers = make(map[rune]int)
groupSize = 0
}
}
return total
}

View File

@@ -0,0 +1,37 @@
package daysix
import "testing"
var testInput = []string{
"abc",
"",
"a",
"b",
"c",
"",
"ab",
"ac",
"",
"a",
"a",
"a",
"a",
"",
"b",
}
func TestPartOne(t *testing.T) {
expected := 11
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 6
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,51 @@
package daythree
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2020D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
trees := 0
column := 0
for row := range data {
if len(data[row]) == 0 {
continue
}
if data[row][column%len(data[row])] == '#' {
trees++
}
column += 3
}
return trees
}
func PartTwo(data []string) int {
result := 1
slopes := [][]int{{1, 1}, {3, 1}, {5, 1}, {7, 1}, {1, 2}}
for _, slope := range slopes {
trees := 0
column := 0
for row := 0; row < len(data); row += slope[1] {
if len(data[row]) == 0 {
continue
}
if data[row][column%len(data[row])] == '#' {
trees++
}
column += slope[0]
}
result *= trees
}
return result
}

View File

@@ -0,0 +1,33 @@
package daythree
import "testing"
var testInput = []string{
"..##.......",
"#...#...#..",
".#....#..#.",
"..#.#...#.#",
".#...##..#.",
"..#.##.....",
".#.#.#....#",
".#........#",
"#.##...#...",
"#...##....#",
".#..#...#.#",
}
func TestPartOne(t *testing.T) {
expected := 7
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 336
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,24 +1,21 @@
package main
package daytwo
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2020D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
lines := strings.Split(string(content), "\n")
var data []string
for _, line := range lines {
data = append(data, line)
}
return data
data := make([]string, 0, len(lines))
return append(data, lines...)
}
func PartOne(data []string) int {
@@ -74,9 +71,3 @@ func PartTwo(data []string) int {
}
return valid
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -0,0 +1,25 @@
package daytwo
import "testing"
var testInput = []string{
"1-3 a: abcde",
"1-3 b: cdefg",
"2-9 c: ccccccccc",
}
func TestPartOne(t *testing.T) {
expected := 2
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 1
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

12
internal/2020/register.go Normal file
View File

@@ -0,0 +1,12 @@
package year2020
import (
_ "advent-of-code/internal/2020/DayEight"
_ "advent-of-code/internal/2020/DayFive"
_ "advent-of-code/internal/2020/DayFour"
_ "advent-of-code/internal/2020/DayOne"
_ "advent-of-code/internal/2020/DaySeven"
_ "advent-of-code/internal/2020/DaySix"
_ "advent-of-code/internal/2020/DayThree"
_ "advent-of-code/internal/2020/DayTwo"
)

View File

@@ -0,0 +1,155 @@
package dayfour
import (
"os"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
const boardSize = 5
func init() {
registry.Register("2021D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
type board struct {
numbers [boardSize][boardSize]int
positionMap map[int][2]int
rowCounts [boardSize]int
columnCounts [boardSize]int
marked map[int]bool
won bool
}
func newBoard() board {
return board{
positionMap: make(map[int][2]int, boardSize*boardSize),
marked: make(map[int]bool),
}
}
func parseBoards(lines []string) []board {
var boards []board
current := newBoard()
row := 0
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
if row == boardSize {
boards = append(boards, current)
current = newBoard()
row = 0
}
continue
}
fields := strings.Fields(line)
for column, field := range fields {
number, _ := strconv.Atoi(field)
current.numbers[row][column] = number
current.positionMap[number] = [2]int{row, column}
}
row++
}
if row == boardSize {
boards = append(boards, current)
}
return boards
}
func (b *board) mark(number int) bool {
if b.won {
return false
}
position, exists := b.positionMap[number]
if !exists {
return false
}
b.marked[number] = true
row, column := position[0], position[1]
b.rowCounts[row]++
b.columnCounts[column]++
if b.rowCounts[row] == boardSize || b.columnCounts[column] == boardSize {
b.won = true
return true
}
return false
}
func (b *board) sumUnmarked() int {
sum := 0
for row := range boardSize {
for column := range boardSize {
number := b.numbers[row][column]
if !b.marked[number] {
sum += number
}
}
}
return sum
}
func parseNumbers(line string) []int {
numbersStr := strings.Split(line, ",")
numbers := make([]int, 0, len(numbersStr))
for _, numStr := range numbersStr {
num, _ := strconv.Atoi(numStr)
numbers = append(numbers, num)
}
return numbers
}
func PartOne(data []string) int {
numbers := parseNumbers(data[0])
boards := parseBoards(data[1:])
for _, number := range numbers {
for idx := range boards {
if boards[idx].mark(number) {
return boards[idx].sumUnmarked() * number
}
}
}
return 0
}
func PartTwo(data []string) int {
numbers := parseNumbers(data[0])
boards := parseBoards(data[1:])
wonCount := 0
totalBoards := len(boards)
var lastWinner *board
var lastNumber int
for _, number := range numbers {
for idx := range boards {
if boards[idx].mark(number) {
wonCount++
lastWinner = &boards[idx]
lastNumber = number
if wonCount == totalBoards {
return lastWinner.sumUnmarked() * lastNumber
}
}
}
}
return 0
}

View File

@@ -0,0 +1,41 @@
package dayfour
import "testing"
var testInput = []string{
"7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1",
"",
"22 13 17 11 0",
" 8 2 23 4 24",
"21 9 14 16 7",
" 6 10 3 18 5",
" 1 12 20 15 19",
"",
" 3 15 0 2 22",
" 9 18 13 17 5",
"19 8 7 25 23",
"20 11 10 24 4",
"14 21 16 12 6",
"",
"14 21 17 24 4",
"10 16 15 9 19",
"18 8 23 26 20",
"22 11 13 6 5",
" 2 0 12 3 7",
}
func TestPartOne(t *testing.T) {
expected := 4512
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 1924
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,42 @@
package dayone
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func init() {
registry.Register("2021D1", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []int {
content, _ := os.ReadFile(filepath)
var data []int
for line := range strings.SplitSeq(string(content), "\n") {
num, _ := strconv.Atoi(line)
data = append(data, num)
}
return data
}
func PartOne(data []int) int {
count := 0
for i := 1; i < len(data); i++ {
if data[i] > data[i-1] {
count++
}
}
return count
}
func PartTwo(data []int) int {
count := 0
for i := 3; i < len(data); i++ {
if data[i] > data[i-3] {
count++
}
}
return count
}

View File

@@ -0,0 +1,21 @@
package dayone
import "testing"
var testInput = []int{199, 200, 208, 210, 200, 207, 240, 269, 260, 263}
func TestPartOne(t *testing.T) {
expected := 7
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 5
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,116 @@
package daythree
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2021D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
func PartOne(data []string) int {
bitlen := len(data[0])
ones := make([]int, bitlen)
for _, line := range data {
for i := range bitlen {
if line[i] == '1' {
ones[i]++
}
}
}
var gamma int
total := len(data)
for idx := range bitlen {
gamma <<= 1
if ones[idx] > total-ones[idx] {
gamma |= 1
}
}
mask := (1 << bitlen) - 1
epsilon := mask ^ gamma
return gamma * epsilon
}
func PartTwo(data []string) int {
bitlen := len(data[0])
oxygenIndices := make([]int, len(data))
for idx := range oxygenIndices {
oxygenIndices[idx] = idx
}
co2Indices := make([]int, len(data))
for idx := range co2Indices {
co2Indices[idx] = idx
}
for indice := 0; indice < bitlen && len(oxygenIndices) > 1; indice++ {
ones := 0
for _, idx := range oxygenIndices {
if data[idx][indice] == '1' {
ones++
}
}
target := byte('1')
if ones*2 < len(oxygenIndices) {
target = '0'
}
writeIdx := 0
for readIdx := 0; readIdx < len(oxygenIndices); readIdx++ {
if data[oxygenIndices[readIdx]][indice] == target {
oxygenIndices[writeIdx] = oxygenIndices[readIdx]
writeIdx++
}
}
oxygenIndices = oxygenIndices[:writeIdx]
}
for idx := 0; idx < bitlen && len(co2Indices) > 1; idx++ {
ones := 0
for _, indice := range co2Indices {
if data[indice][idx] == '1' {
ones++
}
}
target := byte('0')
if ones*2 < len(co2Indices) {
target = '1'
}
writeIdx := 0
for readIdx := 0; readIdx < len(co2Indices); readIdx++ {
if data[co2Indices[readIdx]][idx] == target {
co2Indices[writeIdx] = co2Indices[readIdx]
writeIdx++
}
}
co2Indices = co2Indices[:writeIdx]
}
oxygenRating := 0
for _, char := range data[oxygenIndices[0]] {
oxygenRating <<= 1
if char == '1' {
oxygenRating |= 1
}
}
co2Rating := 0
for _, char := range data[co2Indices[0]] {
co2Rating <<= 1
if char == '1' {
co2Rating |= 1
}
}
return oxygenRating * co2Rating
}

View File

@@ -0,0 +1,34 @@
package daythree
import "testing"
var testInput = []string{
"00100",
"11110",
"10110",
"10111",
"10101",
"01111",
"00111",
"11100",
"10000",
"11001",
"00010",
"01010",
}
func TestPartOne(t *testing.T) {
expected := 198
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 230
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,59 @@
package daytwo
import (
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func init() {
registry.Register("2021D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
horizontal := 0
depth := 0
for _, line := range data {
parts := strings.Split(line, " ")
direction := parts[0]
amount, _ := strconv.Atoi(parts[1])
switch direction {
case "forward":
horizontal += amount
case "down":
depth += amount
case "up":
depth -= amount
}
}
return horizontal * depth
}
func PartTwo(data []string) int {
horizontal := 0
depth := 0
aim := 0
for _, line := range data {
parts := strings.Split(line, " ")
direction := parts[0]
amount, _ := strconv.Atoi(parts[1])
switch direction {
case "forward":
horizontal += amount
depth += aim * amount
case "down":
aim += amount
case "up":
aim -= amount
}
}
return horizontal * depth
}

View File

@@ -0,0 +1,28 @@
package daytwo
import "testing"
var testInput = []string{
"forward 5",
"down 5",
"forward 8",
"up 3",
"down 8",
"forward 2",
}
func TestPartOne(t *testing.T) {
expected := 150
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 900
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,8 @@
package year2021
import (
_ "advent-of-code/internal/2021/DayFour"
_ "advent-of-code/internal/2021/DayOne"
_ "advent-of-code/internal/2021/DayThree"
_ "advent-of-code/internal/2021/DayTwo"
)

View File

@@ -0,0 +1,54 @@
package dayfour
import (
"advent-of-code/internal/registry"
"fmt"
"os"
"strings"
)
func init() {
registry.Register("2022D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
count := 0
for _, line := range data {
parts := strings.Split(line, ",")
var firstRangeStart, firstRangeEnd, secondRangeStart, secondRangeEnd int
fmt.Sscanf(parts[0], "%d-%d", &firstRangeStart, &firstRangeEnd)
fmt.Sscanf(parts[1], "%d-%d", &secondRangeStart, &secondRangeEnd)
minStart := min(firstRangeStart, secondRangeStart)
maxEnd := max(firstRangeEnd, secondRangeEnd)
if (minStart == firstRangeStart && maxEnd == firstRangeEnd) || (minStart == secondRangeStart && maxEnd == secondRangeEnd) {
count++
}
}
return count
}
func PartTwo(data []string) int {
count := 0
for _, line := range data {
parts := strings.Split(line, ",")
var firstRangeStart, firstRangeEnd, secondRangeStart, secondRangeEnd int
fmt.Sscanf(parts[0], "%d-%d", &firstRangeStart, &firstRangeEnd)
fmt.Sscanf(parts[1], "%d-%d", &secondRangeStart, &secondRangeEnd)
if firstRangeStart <= secondRangeEnd && secondRangeStart <= firstRangeEnd {
count++
}
}
return count
}

View File

@@ -0,0 +1,28 @@
package dayfour
import "testing"
var testInput = []string{
"2-4,6-8",
"2-3,4-5",
"5-7,7-9",
"2-8,3-7",
"6-6,4-6",
"2-6,4-8",
}
func TestPartOne(t *testing.T) {
expected := 2
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 4
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,60 @@
package dayone
import (
"advent-of-code/internal/registry"
"os"
"slices"
"strconv"
"strings"
)
func init() {
registry.Register("2022D1", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
maxCalories, currentElf := 0, 0
for _, line := range data {
if line == "" {
maxCalories = max(maxCalories, currentElf)
currentElf = 0
continue
}
val, _ := strconv.Atoi(line)
currentElf += val
}
return max(maxCalories, currentElf)
}
func PartTwo(data []string) int {
var totals []int
currentElf := 0
for _, line := range data {
if line == "" {
totals = append(totals, currentElf)
currentElf = 0
continue
}
val, _ := strconv.Atoi(line)
currentElf += val
}
totals = append(totals, currentElf)
slices.Sort(totals)
slices.Reverse(totals)
sum := 0
for _, val := range totals[:min(3, len(totals))] {
sum += val
}
return sum
}

View File

@@ -0,0 +1,36 @@
package dayone
import "testing"
var testInput = []string{
"1000",
"2000",
"3000",
"",
"4000",
"",
"5000",
"6000",
"",
"7000",
"8000",
"9000",
"",
"10000",
}
func TestPartOne(t *testing.T) {
expected := 24000
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 45000
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,87 @@
package daythree
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2022D3", ParseInput, PartOne, PartTwo)
}
func buildItemSet(items string) map[rune]bool {
itemSet := make(map[rune]bool)
for _, item := range items {
itemSet[item] = true
}
return itemSet
}
func findCommonItem(itemSet map[rune]bool, items string) rune {
for _, item := range items {
if itemSet[item] {
return item
}
}
return 0
}
func findIntersection(itemSet map[rune]bool, items string) map[rune]bool {
intersection := make(map[rune]bool)
for _, item := range items {
if itemSet[item] {
intersection[item] = true
}
}
return intersection
}
func getItemPriority(item rune) int {
if item >= 'a' && item <= 'z' {
return int(item - 'a' + 1)
}
if item >= 'A' && item <= 'Z' {
return int(item - 'A' + 27)
}
return 0
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
totalPriority := 0
for _, rucksack := range data {
compartmentSize := len(rucksack) / 2
firstCompartment := rucksack[:compartmentSize]
secondCompartment := rucksack[compartmentSize:]
firstCompartmentItems := buildItemSet(firstCompartment)
commonItem := findCommonItem(firstCompartmentItems, secondCompartment)
totalPriority += getItemPriority(commonItem)
}
return totalPriority
}
func PartTwo(data []string) int {
totalPriority := 0
for i := 0; i < len(data); i += 3 {
if i+2 >= len(data) {
break
}
firstRucksack := data[i]
secondRucksack := data[i+1]
thirdRucksack := data[i+2]
firstRucksackItems := buildItemSet(firstRucksack)
commonInFirstTwo := findIntersection(firstRucksackItems, secondRucksack)
badge := findCommonItem(commonInFirstTwo, thirdRucksack)
totalPriority += getItemPriority(badge)
}
return totalPriority
}

View File

@@ -0,0 +1,28 @@
package daythree
import "testing"
var testInput = []string{
"vJrwpWtwJgWrhcsFMMfFFhFp",
"jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL",
"PmmdzqPrVvPwwTWBwg",
"wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn",
"ttgJtRGJQctTZtZT",
"CrZsJsPPZsGzwwsLwLmpwMDw",
}
func TestPartOne(t *testing.T) {
expected := 157
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 70
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,62 @@
package daytwo
import (
"advent-of-code/internal/registry"
"os"
"strings"
)
func init() {
registry.Register("2022D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.Trim(string(content), "\n"), "\n")
}
func PartOne(data []string) int {
totalScore := 0
for _, line := range data {
opponent := line[0]
me := line[2]
shapeScore := int(me - 'X' + 1)
var outcomeScore int
if (opponent == 'A' && me == 'Y') || (opponent == 'B' && me == 'Z') || (opponent == 'C' && me == 'X') {
outcomeScore = 6
} else if (opponent == 'A' && me == 'X') || (opponent == 'B' && me == 'Y') || (opponent == 'C' && me == 'Z') {
outcomeScore = 3
} else {
outcomeScore = 0
}
totalScore += shapeScore + outcomeScore
}
return totalScore
}
func PartTwo(data []string) int {
totalScore := 0
for _, line := range data {
opponent := line[0]
outcome := line[2]
var me byte
switch outcome {
case 'Y':
me = 'X' + (opponent - 'A')
case 'Z':
me = 'X' + ((opponent-'A')+1)%3
default:
me = 'X' + ((opponent-'A')+2)%3
}
shapeScore := int(me - 'X' + 1)
outcomeScore := int(outcome-'X') * 3
totalScore += shapeScore + outcomeScore
}
return totalScore
}

View File

@@ -0,0 +1,25 @@
package daytwo
import "testing"
var testInput = []string{
"A Y",
"B X",
"C Z",
}
func TestPartOne(t *testing.T) {
expected := 15
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 12
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,8 @@
package year2022
import (
_ "advent-of-code/internal/2022/DayFour"
_ "advent-of-code/internal/2022/DayOne"
_ "advent-of-code/internal/2022/DayThree"
_ "advent-of-code/internal/2022/DayTwo"
)

View File

@@ -0,0 +1,210 @@
package dayeight
import (
"cmp"
"container/heap"
"os"
"slices"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
func init() {
registry.Register("2025D8", ParseInput, PartOne, PartTwo)
}
type JunctionBox struct {
X, Y, Z int
}
type UnionFind struct {
parent []int
size []int
}
type Connection struct {
firstJunctionBox, secondJunctionBox int
squaredDistance int
}
type ConnectionHeap []Connection
func (h ConnectionHeap) Len() int { return len(h) }
func (h ConnectionHeap) Less(i, j int) bool { return h[i].squaredDistance > h[j].squaredDistance }
func (h ConnectionHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *ConnectionHeap) Push(x any) { *h = append(*h, x.(Connection)) }
func (h *ConnectionHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func NewUnionFind(count int) *UnionFind {
parent := make([]int, count)
size := make([]int, count)
for idx := range parent {
parent[idx] = idx
size[idx] = 1
}
return &UnionFind{parent: parent, size: size}
}
func (uf *UnionFind) Find(junctionBox int) int {
if uf.parent[junctionBox] != junctionBox {
uf.parent[junctionBox] = uf.Find(uf.parent[junctionBox])
}
return uf.parent[junctionBox]
}
func (uf *UnionFind) Union(junctionBox1, junctionBox2 int) (bool, int) {
root1 := uf.Find(junctionBox1)
root2 := uf.Find(junctionBox2)
if root1 == root2 {
return false, root1
}
if uf.size[root1] < uf.size[root2] {
root1, root2 = root2, root1
}
uf.parent[root2] = root1
uf.size[root1] += uf.size[root2]
return true, root1
}
func parseJunctionBoxes(data []string) []JunctionBox {
var junctionBoxes []JunctionBox
for _, line := range data {
parts := strings.Split(line, ",")
x, _ := strconv.Atoi(parts[0])
y, _ := strconv.Atoi(parts[1])
z, _ := strconv.Atoi(parts[2])
junctionBoxes = append(junctionBoxes, JunctionBox{X: x, Y: y, Z: z})
}
return junctionBoxes
}
func generateAllConnections(junctionBoxes []JunctionBox) []Connection {
junctionBoxCount := len(junctionBoxes)
connections := make([]Connection, 0, junctionBoxCount*(junctionBoxCount-1)/2)
for i := range junctionBoxCount {
for j := i + 1; j < junctionBoxCount; j++ {
dx := junctionBoxes[i].X - junctionBoxes[j].X
dy := junctionBoxes[i].Y - junctionBoxes[j].Y
dz := junctionBoxes[i].Z - junctionBoxes[j].Z
squaredDistance := dx*dx + dy*dy + dz*dz
connections = append(connections, Connection{
firstJunctionBox: i,
secondJunctionBox: j,
squaredDistance: squaredDistance,
})
}
}
slices.SortFunc(connections, func(a, b Connection) int {
return cmp.Compare(a.squaredDistance, b.squaredDistance)
})
return connections
}
func findKSmallestConnections(junctionBoxes []JunctionBox, k int) []Connection {
junctionBoxCount := len(junctionBoxes)
connectionHeap := make(ConnectionHeap, 0, k)
heap.Init(&connectionHeap)
for i := range junctionBoxCount {
for j := i + 1; j < junctionBoxCount; j++ {
dx := junctionBoxes[i].X - junctionBoxes[j].X
dy := junctionBoxes[i].Y - junctionBoxes[j].Y
dz := junctionBoxes[i].Z - junctionBoxes[j].Z
squaredDistance := dx*dx + dy*dy + dz*dz
connection := Connection{
firstJunctionBox: i,
secondJunctionBox: j,
squaredDistance: squaredDistance,
}
if len(connectionHeap) < k {
heap.Push(&connectionHeap, connection)
} else if connection.squaredDistance < connectionHeap[0].squaredDistance {
heap.Pop(&connectionHeap)
heap.Push(&connectionHeap, connection)
}
}
}
connections := make([]Connection, 0, len(connectionHeap))
for connectionHeap.Len() > 0 {
connections = append(connections, heap.Pop(&connectionHeap).(Connection))
}
slices.SortFunc(connections, func(a, b Connection) int {
return cmp.Compare(a.squaredDistance, b.squaredDistance)
})
return connections
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
junctionBoxes := parseJunctionBoxes(data)
junctionBoxCount := len(junctionBoxes)
targetPairs := 1000
if junctionBoxCount <= 20 {
targetPairs = 10
}
connections := findKSmallestConnections(junctionBoxes, targetPairs)
uf := NewUnionFind(junctionBoxCount)
for _, connection := range connections {
uf.Union(connection.firstJunctionBox, connection.secondJunctionBox)
}
circuitSizes := make(map[int]int)
for idx := range junctionBoxes {
root := uf.Find(idx)
circuitSizes[root] = uf.size[root]
}
sizes := make([]int, 0, len(circuitSizes))
for _, size := range circuitSizes {
sizes = append(sizes, size)
}
slices.Sort(sizes)
slices.Reverse(sizes)
return sizes[0] * sizes[1] * sizes[2]
}
func PartTwo(data []string) int {
junctionBoxes := parseJunctionBoxes(data)
connections := generateAllConnections(junctionBoxes)
uf := NewUnionFind(len(junctionBoxes))
var lastConnection Connection
for _, connection := range connections {
merged, root := uf.Union(connection.firstJunctionBox, connection.secondJunctionBox)
if merged {
lastConnection = connection
if uf.size[root] == len(junctionBoxes) {
break
}
}
}
return junctionBoxes[lastConnection.firstJunctionBox].X * junctionBoxes[lastConnection.secondJunctionBox].X
}

View File

@@ -0,0 +1,42 @@
package dayeight
import "testing"
var testInput = []string{
"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",
}
func TestPartOne(t *testing.T) {
expected := 40
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 25272
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,125 @@
package dayfive
import (
"os"
"slices"
"sort"
"strconv"
"strings"
"advent-of-code/internal/registry"
)
type freshRange struct {
start int
end int
}
func init() {
registry.Register("2025D5", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(data []string) int {
var freshRanges []freshRange
var ingredientIDs []int
separatorFound := false
for _, line := range data {
if line == "" {
separatorFound = true
continue
}
if !separatorFound {
startStr, endStr, _ := strings.Cut(line, "-")
start, _ := strconv.Atoi(startStr)
end, _ := strconv.Atoi(endStr)
freshRanges = append(freshRanges, freshRange{start: start, end: end})
} else {
id, _ := strconv.Atoi(line)
ingredientIDs = append(ingredientIDs, id)
}
}
slices.SortFunc(freshRanges, func(a, b freshRange) int {
switch {
case a.start < b.start:
return -1
case a.start > b.start:
return 1
default:
return 0
}
})
freshCount := 0
for _, id := range ingredientIDs {
idx := sort.Search(len(freshRanges), func(idx int) bool {
return freshRanges[idx].start > id
})
for i := idx - 1; i >= 0 && freshRanges[i].start <= id; i-- {
if id <= freshRanges[i].end {
freshCount++
break
}
}
}
return freshCount
}
func PartTwo(data []string) int {
var freshRanges []freshRange
for _, line := range data {
if line == "" {
break
}
startStr, endStr, _ := strings.Cut(line, "-")
start, _ := strconv.Atoi(startStr)
end, _ := strconv.Atoi(endStr)
freshRanges = append(freshRanges, freshRange{start: start, end: end})
}
slices.SortFunc(freshRanges, func(a, b freshRange) int {
switch {
case a.start < b.start:
return -1
case a.start > b.start:
return 1
default:
return 0
}
})
var mergedRanges []freshRange
for _, r := range freshRanges {
if len(mergedRanges) == 0 {
mergedRanges = append(mergedRanges, r)
continue
}
lastRange := &mergedRanges[len(mergedRanges)-1]
if r.start <= lastRange.end+1 {
if r.end > lastRange.end {
lastRange.end = r.end
}
} else {
mergedRanges = append(mergedRanges, r)
}
}
totalFreshIDs := 0
for _, r := range mergedRanges {
totalFreshIDs += r.end - r.start + 1
}
return totalFreshIDs
}

View File

@@ -0,0 +1,33 @@
package dayfive
import "testing"
var testInput = []string{
"3-5",
"10-14",
"16-20",
"12-18",
"",
"1",
"5",
"8",
"11",
"17",
"32",
}
func TestPartOne(t *testing.T) {
expected := 3
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
func TestPartTwo(t *testing.T) {
expected := 14
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

Some files were not shown because too many files have changed in this diff Show More