Compare commits
319 Commits
428ec93860
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a39f261ba9 | |||
| 0c77a62ab4 | |||
| 60ee8c0307 | |||
| dfe08db759 | |||
| 081f4ea295 | |||
| 34be9e0847 | |||
| 6ea67eac0c | |||
| bb366fbe17 | |||
| 681b7bae16 | |||
| c8ded5c42d | |||
| b37f1ec366 | |||
| 40e2e329e0 | |||
| fa5bf2e85b | |||
| ea1b57b17e | |||
| 174671e6f5 | |||
| 40bcf3052f | |||
| 1e634b7ee9 | |||
| 6e625dcf06 | |||
| 141216920d | |||
| b685e81c58 | |||
| 1adc10ea88 | |||
| db7c31cb39 | |||
| 1ad1da1309 | |||
| 228392fe83 | |||
| 4837cbf290 | |||
| 8503cee52b | |||
| b16b052115 | |||
| bc76283458 | |||
| 9d2a087801 | |||
| 0bd3b6dc69 | |||
| da81f67b7f | |||
| caa7da5a7d | |||
| eebe707ef9 | |||
| 8960dcb072 | |||
| 99eb0fbaa8 | |||
| a739425209 | |||
| af306eb73c | |||
| cdbc3f3b16 | |||
| ed445a8be7 | |||
| 8bbf6662b1 | |||
| b60c971d50 | |||
| 5dab84eeb3 | |||
| 2283b62503 | |||
| ccf50c12c9 | |||
| f25d7511aa | |||
| 243ccf3da9 | |||
| aedf9cd06d | |||
| c053c905ad | |||
| 7dff40745c | |||
| 0e8563b216 | |||
| 6720bbabc1 | |||
| 6035989da4 | |||
| dc16893777 | |||
| fdfad57cee | |||
| 9cf00b290f | |||
| 47072a4982 | |||
| 62748990cb | |||
| 42c69d44e5 | |||
| c6eb51a395 | |||
| 6777696df6 | |||
| a8e244f9ab | |||
| 911f9bfe7b | |||
| 24a65d3752 | |||
| 0bd0ec5f1e | |||
| 0ad418ed1f | |||
| ec48231aae | |||
| 5631822e73 | |||
| 630d32ba11 | |||
| 22500b7076 | |||
| 9d2d27b257 | |||
| 879509c7ba | |||
| a0805111b4 | |||
| b0cd4f37b1 | |||
| 8999f45aad | |||
| 838803c53e | |||
| c4063b5390 | |||
| 1c5bd1e448 | |||
| 6d9b2092bd | |||
| 9b218d763d | |||
| 10c5b0fbc6 | |||
| 503eec14c6 | |||
| deac7f97bb | |||
| a3fb7ac353 | |||
| 8f4e11215f | |||
| 78b0032578 | |||
| 536f6f52ff | |||
| 96ca1afb9b | |||
| 780263e78b | |||
| 49ff399f97 | |||
| 27d14b1711 | |||
| 89b2ec90f2 | |||
| f8a2e839b9 | |||
| 94ecbd27bf | |||
| b44a592808 | |||
| b00d2f13a3 | |||
| b26f1531f5 | |||
| 7de5fa7794 | |||
| 66e91e05a4 | |||
| a20bb8ab09 | |||
| c5fcc8b353 | |||
| 0357f263cc | |||
| 8488debc25 | |||
| 00dd40428b | |||
| 3d35a57723 | |||
| 0d40e32a39 | |||
| fd34db28cb | |||
| 65edd3258e | |||
| e914bc6492 | |||
| 0eed8089b6 | |||
| 1048d20cef | |||
| 00ccbaf0d0 | |||
| 51f733127c | |||
| d96febeae3 | |||
| 79b31dad19 | |||
| d5146e7e3e | |||
| d2d6f280b3 | |||
| 79d9f8d7cc | |||
| bcc4fc3432 | |||
| cc2d7d1a3d | |||
| 83f6db5e33 | |||
| 12b54d51d2 | |||
| 89c154de37 | |||
| ea037debed | |||
| a0a0c43690 | |||
| bce49d51f7 | |||
| 77a352aa2e | |||
| fd6db0cc65 | |||
| 2b548fa1ef | |||
| e0465b9b8d | |||
| 0523610080 | |||
| e341c12763 | |||
| 6cff0b7931 | |||
| f5bfe1e578 | |||
| 0fbe2956c3 | |||
| 9280430285 | |||
| b91e34bc8e | |||
| 9f10576a0c | |||
| 8a9f4cb506 | |||
| d3098c0d9c | |||
| 8be18b6b38 | |||
| b39b8c885b | |||
| 56901ca553 | |||
| e3c9da9621 | |||
| 070bd8be9a | |||
| c1ba2ca02b | |||
| 146b63706a | |||
| 1421062a75 | |||
| b9928789df | |||
| b7aafeec52 | |||
| a935e35d82 | |||
| e2c2d0df71 | |||
| e3a47b0e16 | |||
| 86370f27c8 | |||
| c61e573e14 | |||
| d16f70cf00 | |||
| 6f7561213e | |||
| 94b15548cf | |||
| 4e9e2b399c | |||
| 035e56bf53 | |||
| 61cf84aa8a | |||
| 5513ae8386 | |||
| f1730c30cb | |||
| ce7d42621f | |||
| 8eafb1f7c5 | |||
| 81be03e8ee | |||
| d662f693b8 | |||
| b0d16b1bac | |||
| 3eb9120dc3 | |||
| 85ae14acbf | |||
| b7a98033c6 | |||
| 8f265eae05 | |||
| fe20a0b654 | |||
| aa80e4eb8e | |||
| 0d029f2861 | |||
| edf94432f4 | |||
| 33552358f8 | |||
| 45d3e93a93 | |||
| 707f34e706 | |||
| a05450c73a | |||
| 6b95f5ced0 | |||
| 9caee546f0 | |||
| 8e831d85fe | |||
| b8ab5fae7b | |||
| 3262d1cbb8 | |||
| 766ee97dd3 | |||
| c41c96e628 | |||
| fb46fceb75 | |||
| c9fe217e4b | |||
| d88d64edd4 | |||
| e81194721c | |||
| a680e0ba48 | |||
| 345defec4d | |||
| 3756279dab | |||
| 70189f4295 | |||
| cdacf7ae06 | |||
| 074c762960 | |||
| 6babf31a20 | |||
| cdefd68320 | |||
| f28611a7bf | |||
| f98034b00c | |||
| 375b756718 | |||
| 6668e8ae1b | |||
| e58959778a | |||
| eb72fe9ebd | |||
| e355423675 | |||
| b1be29c21c | |||
| b04dcc5aea | |||
| 11b6227d0e | |||
| 31660c7510 | |||
| 728bbb2a06 | |||
| 16a99ba8d8 | |||
| 0949840317 | |||
| daec5a8671 | |||
| d66cd1179d | |||
| 2d3828c55d | |||
| b7a7bfb5c7 | |||
| be918bdf6c | |||
| c8517b674f | |||
| f3d73b7c4b | |||
| bcef8844ec | |||
| 20ab5fe4e5 | |||
| 959c05b769 | |||
| 4179c88afb | |||
| 0dc0c3af3d | |||
| c6dc950d3f | |||
| e5a1504f6b | |||
| 9da1fa02c8 | |||
| 99857d8ca5 | |||
| e198caf1b9 | |||
| 9dd5f1354f | |||
| 8ad1b166f3 | |||
| 1f3b42b266 | |||
| 9521677ca8 | |||
| 8938992384 | |||
| a0ce63e5a5 | |||
| a12c8df252 | |||
| 8d69f10924 | |||
| e41d1fa220 | |||
| da78d01d9f | |||
| ff4b7b281c | |||
| a3f54530f6 | |||
| 301d93157c | |||
| 7e0a1e71a7 | |||
| 962bc923f3 | |||
| 76568a801d | |||
| 1f5b8247a9 | |||
| 950eec898e | |||
| 2d6c89d7c9 | |||
| 6bc4c1b5a5 | |||
| 7b0ccf8a40 | |||
| ea62c2ce0a | |||
| 87a7dee129 | |||
| 53d72ae163 | |||
| bd8c2cca31 | |||
| 4013ad8330 | |||
| a344eef0e3 | |||
| 824eb6d5a2 | |||
| d8e7573204 | |||
| 98fb052039 | |||
| db685a1290 | |||
| 27a56dc7cd | |||
| 6a82336c99 | |||
| 90a924e640 | |||
| 228d8b475b | |||
| c3abc90a24 | |||
| 60ffe95f69 | |||
| 33bcb91d48 | |||
| 5d63f3c4d6 | |||
| ceb3502c96 | |||
| e6867b9cfb | |||
| d783d14ecc | |||
| 1fe2e30ef8 | |||
| a7e15569b4 | |||
| 41007ef20e | |||
| 3fb71d0cbf | |||
| 62969312e3 | |||
| 59974a4d29 | |||
| 44cceaad6d | |||
| 5274739cd3 | |||
| 8915de6145 | |||
| e96d308e5f | |||
| 3fdb921aae | |||
| 2d5e05ae8b | |||
| 2f06c7ab2d | |||
| 3723f84d1a | |||
| 314da54495 | |||
| 3ab410ea06 | |||
| 45c04431cc | |||
| 497314aa8b | |||
| ce1cc9d5cf | |||
| b2f1a2902c | |||
| de59203640 | |||
| 021d16cfd7 | |||
| d13ed719e1 | |||
| 507b27bc13 | |||
| e763653a96 | |||
| 13165c5fa3 | |||
| c5885f9bbf | |||
| 6931a63080 | |||
| 3f795a451d | |||
| c56c2aa449 | |||
| 19ea2cd1f6 | |||
| 8594cd44d3 | |||
| f8d8916b60 | |||
| 5545200823 | |||
| 0f33caf778 | |||
| 63f9aa5ad1 | |||
| 3602fc356b | |||
| 624649e6ea | |||
| 898a67090e | |||
| daaf966e12 | |||
| 5bdd01dfdd | |||
| 43b78620ca | |||
| bb9f9d4484 | |||
| 9ec566abf1 | |||
| 09e299539d | |||
| 587aabd80e | |||
| a9de4a3502 | |||
| cd160685f1 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bin/aoc
|
||||||
|
*.test
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module 2020/day01
|
|
||||||
|
|
||||||
go 1.25.4
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module 2020/day02
|
|
||||||
|
|
||||||
go 1.25.4
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module 2020/day03
|
|
||||||
|
|
||||||
go 1.25.4
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
61
Makefile
Normal file
61
Makefile
Normal 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
104
README.md
@@ -6,34 +6,99 @@ The goal is to practice programming and problem-solving habits while keeping eac
|
|||||||
|
|
||||||
It uses pure Go, no external dependencies.
|
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
|
## Requirements
|
||||||
|
|
||||||
- Go 1.25
|
- Go 1.25
|
||||||
- Puzzle input
|
- Puzzle input (or your session cookie to have it downloaded automatically)
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
yyyy/
|
├── cmd/
|
||||||
├── dayXX/
|
│ └── aoc/
|
||||||
│ ├── main.go # The main code to run to print solutions.
|
│ └── main.go # CLI entry point for running puzzles
|
||||||
│ ├── main_test.go # The test file that validates the logic.
|
└── internal/
|
||||||
│ └── input.txt # The day's puzzle input.
|
├── 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
|
```bash
|
||||||
cd yyyy/dayXX
|
make 2020D1
|
||||||
go run main.go
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
```bash
|
||||||
Part 1: <answer>
|
internal/2020/DayOne/code.go
|
||||||
Part 2: <answer>
|
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
|
## 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.
|
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
|
```bash
|
||||||
cd dayXX
|
make test
|
||||||
go test
|
```
|
||||||
|
|
||||||
|
Or run tests for a specific day:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/2020/DayOne/...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|||||||
56
cmd/aoc/main.go
Normal file
56
cmd/aoc/main.go
Normal 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)
|
||||||
|
}
|
||||||
61
internal/2015/DayEight/code.go
Normal file
61
internal/2015/DayEight/code.go
Normal 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
|
||||||
|
}
|
||||||
26
internal/2015/DayEight/code_test.go
Normal file
26
internal/2015/DayEight/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
97
internal/2015/DayEleven/code.go
Normal file
97
internal/2015/DayEleven/code.go
Normal 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
|
||||||
|
}
|
||||||
77
internal/2015/DayFive/code.go
Normal file
77
internal/2015/DayFive/code.go
Normal 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
|
||||||
|
}
|
||||||
32
internal/2015/DayFive/code_test.go
Normal file
32
internal/2015/DayFive/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
46
internal/2015/DayFour/code.go
Normal file
46
internal/2015/DayFour/code.go
Normal 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)
|
||||||
|
}
|
||||||
45
internal/2015/DayFour/code_test.go
Normal file
45
internal/2015/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
108
internal/2015/DayFourteen/code.go
Normal file
108
internal/2015/DayFourteen/code.go
Normal 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)
|
||||||
|
}
|
||||||
24
internal/2015/DayFourteen/code_test.go
Normal file
24
internal/2015/DayFourteen/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
106
internal/2015/DayNine/code.go
Normal file
106
internal/2015/DayNine/code.go
Normal 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
|
||||||
|
}
|
||||||
25
internal/2015/DayNine/code_test.go
Normal file
25
internal/2015/DayNine/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
44
internal/2015/DayOne/code.go
Normal file
44
internal/2015/DayOne/code.go
Normal 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
|
||||||
|
}
|
||||||
50
internal/2015/DayOne/code_test.go
Normal file
50
internal/2015/DayOne/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
77
internal/2015/DaySeven/code.go
Normal file
77
internal/2015/DaySeven/code.go
Normal 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))
|
||||||
|
}
|
||||||
50
internal/2015/DaySeven/code_test.go
Normal file
50
internal/2015/DaySeven/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
internal/2015/DaySix/code.go
Normal file
119
internal/2015/DaySix/code.go
Normal 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
|
||||||
|
}
|
||||||
25
internal/2015/DaySix/code_test.go
Normal file
25
internal/2015/DaySix/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
internal/2015/DayTen/code.go
Normal file
45
internal/2015/DayTen/code.go
Normal 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)
|
||||||
|
}
|
||||||
141
internal/2015/DayThirteen/code.go
Normal file
141
internal/2015/DayThirteen/code.go
Normal 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
|
||||||
|
}
|
||||||
28
internal/2015/DayThirteen/code_test.go
Normal file
28
internal/2015/DayThirteen/code_test.go
Normal 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
|
||||||
68
internal/2015/DayThree/code.go
Normal file
68
internal/2015/DayThree/code.go
Normal 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)
|
||||||
|
}
|
||||||
45
internal/2015/DayThree/code_test.go
Normal file
45
internal/2015/DayThree/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
68
internal/2015/DayTwelve/code.go
Normal file
68
internal/2015/DayTwelve/code.go
Normal 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
|
||||||
|
}
|
||||||
51
internal/2015/DayTwelve/code_test.go
Normal file
51
internal/2015/DayTwelve/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
48
internal/2015/DayTwo/code.go
Normal file
48
internal/2015/DayTwo/code.go
Normal 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
|
||||||
|
}
|
||||||
43
internal/2015/DayTwo/code_test.go
Normal file
43
internal/2015/DayTwo/code_test.go
Normal 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
18
internal/2015/register.go
Normal 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"
|
||||||
|
)
|
||||||
73
internal/2016/DayFive/code.go
Normal file
73
internal/2016/DayFive/code.go
Normal 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
|
||||||
|
}
|
||||||
58
internal/2016/DayFive/code_test.go
Normal file
58
internal/2016/DayFive/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
internal/2016/DayFour/code.go
Normal file
119
internal/2016/DayFour/code.go
Normal 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
|
||||||
|
}
|
||||||
29
internal/2016/DayFour/code_test.go
Normal file
29
internal/2016/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
91
internal/2016/DayOne/code.go
Normal file
91
internal/2016/DayOne/code.go
Normal 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
|
||||||
|
}
|
||||||
47
internal/2016/DayOne/code_test.go
Normal file
47
internal/2016/DayOne/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
internal/2016/DayThree/code.go
Normal file
49
internal/2016/DayThree/code.go
Normal 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
|
||||||
|
}
|
||||||
28
internal/2016/DayThree/code_test.go
Normal file
28
internal/2016/DayThree/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
internal/2016/DayTwo/code.go
Normal file
101
internal/2016/DayTwo/code.go
Normal 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
|
||||||
|
}
|
||||||
42
internal/2016/DayTwo/code_test.go
Normal file
42
internal/2016/DayTwo/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
internal/2016/register.go
Normal file
9
internal/2016/register.go
Normal 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"
|
||||||
|
)
|
||||||
64
internal/2018/DayFive/code.go
Normal file
64
internal/2018/DayFive/code.go
Normal 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
|
||||||
|
}
|
||||||
21
internal/2018/DayFive/code_test.go
Normal file
21
internal/2018/DayFive/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
133
internal/2018/DayFour/code.go
Normal file
133
internal/2018/DayFour/code.go
Normal 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
|
||||||
|
}
|
||||||
39
internal/2018/DayFour/code_test.go
Normal file
39
internal/2018/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
internal/2018/DayOne/code.go
Normal file
45
internal/2018/DayOne/code.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/2018/DayOne/code_test.go
Normal file
26
internal/2018/DayOne/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
internal/2018/DayThree/code.go
Normal file
101
internal/2018/DayThree/code.go
Normal 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
|
||||||
|
}
|
||||||
25
internal/2018/DayThree/code_test.go
Normal file
25
internal/2018/DayThree/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
internal/2018/DayTwo/code.go
Normal file
81
internal/2018/DayTwo/code.go
Normal 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
|
||||||
|
}
|
||||||
58
internal/2018/DayTwo/code_test.go
Normal file
58
internal/2018/DayTwo/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
internal/2018/register.go
Normal file
9
internal/2018/register.go
Normal 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"
|
||||||
|
)
|
||||||
94
internal/2020/DayEight/code.go
Normal file
94
internal/2020/DayEight/code.go
Normal 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
|
||||||
|
}
|
||||||
31
internal/2020/DayEight/code_test.go
Normal file
31
internal/2020/DayEight/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
79
internal/2020/DayFive/code.go
Normal file
79
internal/2020/DayFive/code.go
Normal 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
|
||||||
|
}
|
||||||
34
internal/2020/DayFive/code_test.go
Normal file
34
internal/2020/DayFive/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
internal/2020/DayFour/code.go
Normal file
153
internal/2020/DayFour/code.go
Normal 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
|
||||||
|
}
|
||||||
26
internal/2020/DayFour/code_test.go
Normal file
26
internal/2020/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
package main
|
package dayone
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"advent-of-code/internal/registry"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseInput(file string) []int {
|
func init() {
|
||||||
content, err := os.ReadFile(file)
|
registry.Register("2020D1", ParseInput, PartOne, PartTwo)
|
||||||
if err != nil {
|
}
|
||||||
log.Fatalf("Failed to read input file: %v", err)
|
|
||||||
}
|
func ParseInput(filepath string) []int {
|
||||||
|
content, _ := os.ReadFile(filepath)
|
||||||
lines := strings.Fields(string(content))
|
lines := strings.Fields(string(content))
|
||||||
var data []int
|
var data []int
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
num, err := strconv.Atoi(line)
|
num, _ := strconv.Atoi(line)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to convert string to int: %v", err)
|
|
||||||
}
|
|
||||||
data = append(data, num)
|
data = append(data, num)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
@@ -50,9 +47,3 @@ func PartTwo(data []int) int {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
|
||||||
data := parseInput("input.txt")
|
|
||||||
fmt.Println("Part 1:", PartOne(data))
|
|
||||||
fmt.Println("Part 2:", PartTwo(data))
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package dayone
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ func TestPartOne(t *testing.T) {
|
|||||||
expected := 514579
|
expected := 514579
|
||||||
got := PartOne(testInput)
|
got := PartOne(testInput)
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("PartOne(%v) = %d, want %d", testInput, got, expected)
|
t.Errorf("PartOne() = %d, want %d", got, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,6 +16,6 @@ func TestPartTwo(t *testing.T) {
|
|||||||
expected := 241861950
|
expected := 241861950
|
||||||
got := PartTwo(testInput)
|
got := PartTwo(testInput)
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("PartTwo(%v) = %d, want %d", testInput, got, expected)
|
t.Errorf("PartTwo() = %d, want %d", got, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
105
internal/2020/DaySeven/code.go
Normal file
105
internal/2020/DaySeven/code.go
Normal 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")
|
||||||
|
}
|
||||||
51
internal/2020/DaySeven/code_test.go
Normal file
51
internal/2020/DaySeven/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
69
internal/2020/DaySix/code.go
Normal file
69
internal/2020/DaySix/code.go
Normal 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
|
||||||
|
}
|
||||||
37
internal/2020/DaySix/code_test.go
Normal file
37
internal/2020/DaySix/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/2020/DayThree/code.go
Normal file
51
internal/2020/DayThree/code.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package daythree
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
package main
|
package daytwo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"advent-of-code/internal/registry"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseInput(file string) []string {
|
func init() {
|
||||||
content, err := os.ReadFile(file)
|
registry.Register("2020D2", ParseInput, PartOne, PartTwo)
|
||||||
if err != nil {
|
}
|
||||||
log.Fatalf("Failed to read input file: %v", err)
|
|
||||||
}
|
func ParseInput(filepath string) []string {
|
||||||
|
content, _ := os.ReadFile(filepath)
|
||||||
lines := strings.Split(string(content), "\n")
|
lines := strings.Split(string(content), "\n")
|
||||||
var data []string
|
data := make([]string, 0, len(lines))
|
||||||
for _, line := range lines {
|
return append(data, lines...)
|
||||||
data = append(data, line)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PartOne(data []string) int {
|
func PartOne(data []string) int {
|
||||||
@@ -74,9 +71,3 @@ func PartTwo(data []string) int {
|
|||||||
}
|
}
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
|
||||||
data := parseInput("input.txt")
|
|
||||||
fmt.Println("Part 1:", PartOne(data))
|
|
||||||
fmt.Println("Part 2:", PartTwo(data))
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package daytwo
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ func TestPartOne(t *testing.T) {
|
|||||||
expected := 2
|
expected := 2
|
||||||
got := PartOne(testInput)
|
got := PartOne(testInput)
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("PartOne(%v) = %d, want %d", testInput, got, expected)
|
t.Errorf("PartOne() = %d, want %d", got, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@ func TestPartTwo(t *testing.T) {
|
|||||||
expected := 1
|
expected := 1
|
||||||
got := PartTwo(testInput)
|
got := PartTwo(testInput)
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("PartTwo(%v) = %d, want %d", testInput, got, expected)
|
t.Errorf("PartTwo() = %d, want %d", got, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
internal/2020/register.go
Normal file
12
internal/2020/register.go
Normal 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"
|
||||||
|
)
|
||||||
155
internal/2021/DayFour/code.go
Normal file
155
internal/2021/DayFour/code.go
Normal 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
|
||||||
|
}
|
||||||
41
internal/2021/DayFour/code_test.go
Normal file
41
internal/2021/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
internal/2021/DayOne/code.go
Normal file
42
internal/2021/DayOne/code.go
Normal 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
|
||||||
|
}
|
||||||
21
internal/2021/DayOne/code_test.go
Normal file
21
internal/2021/DayOne/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
116
internal/2021/DayThree/code.go
Normal file
116
internal/2021/DayThree/code.go
Normal 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
|
||||||
|
}
|
||||||
34
internal/2021/DayThree/code_test.go
Normal file
34
internal/2021/DayThree/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
internal/2021/DayTwo/code.go
Normal file
59
internal/2021/DayTwo/code.go
Normal 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
|
||||||
|
}
|
||||||
28
internal/2021/DayTwo/code_test.go
Normal file
28
internal/2021/DayTwo/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
internal/2021/register.go
Normal file
8
internal/2021/register.go
Normal 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"
|
||||||
|
)
|
||||||
40
internal/2022/DayFour/code.go
Normal file
40
internal/2022/DayFour/code.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
28
internal/2022/DayFour/code_test.go
Normal file
28
internal/2022/DayFour/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
60
internal/2022/DayOne/code.go
Normal file
60
internal/2022/DayOne/code.go
Normal 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
|
||||||
|
}
|
||||||
36
internal/2022/DayOne/code_test.go
Normal file
36
internal/2022/DayOne/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
internal/2022/DayThree/code.go
Normal file
87
internal/2022/DayThree/code.go
Normal 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
|
||||||
|
}
|
||||||
28
internal/2022/DayThree/code_test.go
Normal file
28
internal/2022/DayThree/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
62
internal/2022/DayTwo/code.go
Normal file
62
internal/2022/DayTwo/code.go
Normal 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
|
||||||
|
}
|
||||||
25
internal/2022/DayTwo/code_test.go
Normal file
25
internal/2022/DayTwo/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
internal/2022/register.go
Normal file
8
internal/2022/register.go
Normal 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"
|
||||||
|
)
|
||||||
210
internal/2025/DayEight/code.go
Normal file
210
internal/2025/DayEight/code.go
Normal 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
|
||||||
|
}
|
||||||
42
internal/2025/DayEight/code_test.go
Normal file
42
internal/2025/DayEight/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
125
internal/2025/DayFive/code.go
Normal file
125
internal/2025/DayFive/code.go
Normal 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
|
||||||
|
}
|
||||||
33
internal/2025/DayFive/code_test.go
Normal file
33
internal/2025/DayFive/code_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
104
internal/2025/DayFour/code.go
Normal file
104
internal/2025/DayFour/code.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package dayfour
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"advent-of-code/internal/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
type position struct {
|
||||||
|
row, column int
|
||||||
|
}
|
||||||
|
|
||||||
|
var directions = []position{
|
||||||
|
{-1, -1},
|
||||||
|
{-1, 0},
|
||||||
|
{-1, 1},
|
||||||
|
{0, -1},
|
||||||
|
{0, 1},
|
||||||
|
{1, -1},
|
||||||
|
{1, 0},
|
||||||
|
{1, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Register("2025D4", ParseInput, PartOne, PartTwo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInput(filepath string) []string {
|
||||||
|
content, _ := os.ReadFile(filepath)
|
||||||
|
return strings.Split(string(content), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartOne(data []string) int {
|
||||||
|
isRoll := make(map[position]bool)
|
||||||
|
|
||||||
|
for row := range data {
|
||||||
|
for column := 0; column < len(data[row]); column++ {
|
||||||
|
if data[row][column] == '@' {
|
||||||
|
isRoll[position{row, column}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibleRolls := 0
|
||||||
|
for pos := range isRoll {
|
||||||
|
adjacentRolls := 0
|
||||||
|
for _, direction := range directions {
|
||||||
|
neighbor := position{pos.row + direction.row, pos.column + direction.column}
|
||||||
|
if isRoll[neighbor] {
|
||||||
|
adjacentRolls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if adjacentRolls < 4 {
|
||||||
|
accessibleRolls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessibleRolls
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartTwo(data []string) int {
|
||||||
|
isRoll := make(map[position]bool)
|
||||||
|
|
||||||
|
for row := range data {
|
||||||
|
for column := 0; column < len(data[row]); column++ {
|
||||||
|
if data[row][column] == '@' {
|
||||||
|
isRoll[position{row, column}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRemoved := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
accessibleRolls := make(map[position]bool)
|
||||||
|
|
||||||
|
for pos := range isRoll {
|
||||||
|
adjacentRolls := 0
|
||||||
|
for _, direction := range directions {
|
||||||
|
neighbor := position{pos.row + direction.row, pos.column + direction.column}
|
||||||
|
if isRoll[neighbor] {
|
||||||
|
adjacentRolls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if adjacentRolls < 4 {
|
||||||
|
accessibleRolls[pos] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accessibleRolls) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for pos := range accessibleRolls {
|
||||||
|
delete(isRoll, pos)
|
||||||
|
totalRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalRemoved
|
||||||
|
}
|
||||||
32
internal/2025/DayFour/code_test.go
Normal file
32
internal/2025/DayFour/code_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package dayfour
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var testInput = []string{
|
||||||
|
"..@@.@@@@.",
|
||||||
|
"@@@.@.@.@@",
|
||||||
|
"@@@@@.@.@@",
|
||||||
|
"@.@@@@..@.",
|
||||||
|
"@@.@@@@.@@",
|
||||||
|
".@@@@@@@.@",
|
||||||
|
".@.@.@.@@@",
|
||||||
|
"@.@@@.@@@@",
|
||||||
|
".@@@@@@@@.",
|
||||||
|
"@.@.@@@.@.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartOne(t *testing.T) {
|
||||||
|
expected := 13
|
||||||
|
got := PartOne(testInput)
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("PartOne() = %d, want %d", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartTwo(t *testing.T) {
|
||||||
|
expected := 43
|
||||||
|
got := PartTwo(testInput)
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("PartTwo() = %d, want %d", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
234
internal/2025/DayNine/code.go
Normal file
234
internal/2025/DayNine/code.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package daynine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"advent-of-code/internal/registry"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Register("2025D9", ParseInput, PartOne, PartTwo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
x, y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInput(filepath string) []string {
|
||||||
|
content, _ := os.ReadFile(filepath)
|
||||||
|
return strings.Split(string(content), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePoints(data []string) []Point {
|
||||||
|
points := make([]Point, 0, len(data))
|
||||||
|
for _, line := range data {
|
||||||
|
parts := strings.Split(line, ",")
|
||||||
|
x, _ := strconv.Atoi(parts[0])
|
||||||
|
y, _ := strconv.Atoi(parts[1])
|
||||||
|
points = append(points, Point{x: x, y: y})
|
||||||
|
}
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(x int) int {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func minMax(a, b int) (int, int) {
|
||||||
|
if a > b {
|
||||||
|
return b, a
|
||||||
|
}
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressCoordinates(points []Point) ([]int, []int, map[int]int, map[int]int) {
|
||||||
|
xs := make(map[int]bool)
|
||||||
|
ys := make(map[int]bool)
|
||||||
|
for _, point := range points {
|
||||||
|
xs[point.x] = true
|
||||||
|
ys[point.y] = true
|
||||||
|
}
|
||||||
|
sortedX := make([]int, 0, len(xs))
|
||||||
|
sortedY := make([]int, 0, len(ys))
|
||||||
|
for x := range xs {
|
||||||
|
sortedX = append(sortedX, x)
|
||||||
|
}
|
||||||
|
for y := range ys {
|
||||||
|
sortedY = append(sortedY, y)
|
||||||
|
}
|
||||||
|
slices.Sort(sortedX)
|
||||||
|
slices.Sort(sortedY)
|
||||||
|
|
||||||
|
mapX := make(map[int]int, len(sortedX))
|
||||||
|
mapY := make(map[int]int, len(sortedY))
|
||||||
|
for idx, x := range sortedX {
|
||||||
|
mapX[x] = 2 * idx
|
||||||
|
}
|
||||||
|
for idx, y := range sortedY {
|
||||||
|
mapY[y] = 2 * idx
|
||||||
|
}
|
||||||
|
return sortedX, sortedY, mapX, mapY
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawBoundary(grid [][]bool, points []Point, mapX, mapY map[int]int) {
|
||||||
|
for idx := range points {
|
||||||
|
current, next := points[idx], points[(idx+1)%len(points)]
|
||||||
|
cx1, cy1 := mapX[current.x], mapY[current.y]
|
||||||
|
cx2, cy2 := mapX[next.x], mapY[next.y]
|
||||||
|
|
||||||
|
if cx1 == cx2 {
|
||||||
|
minY, maxY := minMax(cy1, cy2)
|
||||||
|
for y := minY; y <= maxY; y++ {
|
||||||
|
grid[cx1][y] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minX, maxX := minMax(cx1, cx2)
|
||||||
|
for x := minX; x <= maxX; x++ {
|
||||||
|
grid[x][cy1] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLineFill(grid [][]bool, points []Point, mapX, mapY map[int]int, _, height int) {
|
||||||
|
type edge struct {
|
||||||
|
x, yMin, yMax int
|
||||||
|
}
|
||||||
|
edges := make([]edge, 0, len(points))
|
||||||
|
for idx := range points {
|
||||||
|
current, next := points[idx], points[(idx+1)%len(points)]
|
||||||
|
if current.x == next.x {
|
||||||
|
minY, maxY := minMax(mapY[current.y], mapY[next.y])
|
||||||
|
edges = append(edges, edge{mapX[current.x], minY, maxY})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := range height {
|
||||||
|
crossings := make([]int, 0, len(edges))
|
||||||
|
for _, e := range edges {
|
||||||
|
if e.yMin <= y && y < e.yMax {
|
||||||
|
crossings = append(crossings, e.x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(crossings)
|
||||||
|
for i := 0; i < len(crossings)-1; i += 2 {
|
||||||
|
for x := crossings[i]; x <= crossings[i+1]; x++ {
|
||||||
|
grid[x][y] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func forceEmptyGaps(grid [][]bool, sortedX, sortedY []int, width, height int) {
|
||||||
|
for idx := 0; idx < len(sortedX)-1; idx++ {
|
||||||
|
if sortedX[idx+1] == sortedX[idx]+1 {
|
||||||
|
cx := 2*idx + 1
|
||||||
|
for y := range height {
|
||||||
|
grid[cx][y] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for idx := 0; idx < len(sortedY)-1; idx++ {
|
||||||
|
if sortedY[idx+1] == sortedY[idx]+1 {
|
||||||
|
cy := 2*idx + 1
|
||||||
|
for x := range width {
|
||||||
|
grid[x][cy] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPrefixSum(grid [][]bool, width, height int) [][]int {
|
||||||
|
sum := make([][]int, width+1)
|
||||||
|
for idx := range sum {
|
||||||
|
sum[idx] = make([]int, height+1)
|
||||||
|
}
|
||||||
|
for i := range width {
|
||||||
|
for j := range height {
|
||||||
|
value := 0
|
||||||
|
if grid[i][j] {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
sum[i+1][j+1] = value + sum[i][j+1] + sum[i+1][j] - sum[i][j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSum(sum [][]int, x1, y1, x2, y2 int) int {
|
||||||
|
return sum[x2+1][y2+1] - sum[x1][y2+1] - sum[x2+1][y1] + sum[x1][y1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartOne(data []string) int {
|
||||||
|
points := parsePoints(data)
|
||||||
|
maxArea := 0
|
||||||
|
|
||||||
|
for i := range points {
|
||||||
|
for j := i + 1; j < len(points); j++ {
|
||||||
|
dx := abs(points[i].x - points[j].x)
|
||||||
|
dy := abs(points[i].y - points[j].y)
|
||||||
|
|
||||||
|
if area := (dx + 1) * (dy + 1); area > maxArea {
|
||||||
|
maxArea = area
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxArea
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartTwo(data []string) int {
|
||||||
|
points := parsePoints(data)
|
||||||
|
sortedX, sortedY, mapX, mapY := compressCoordinates(points)
|
||||||
|
|
||||||
|
width, height := 2*len(sortedX), 2*len(sortedY)
|
||||||
|
grid := make([][]bool, width)
|
||||||
|
for idx := range grid {
|
||||||
|
grid[idx] = make([]bool, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBoundary(grid, points, mapX, mapY)
|
||||||
|
scanLineFill(grid, points, mapX, mapY, width, height)
|
||||||
|
forceEmptyGaps(grid, sortedX, sortedY, width, height)
|
||||||
|
|
||||||
|
sum := buildPrefixSum(grid, width, height)
|
||||||
|
|
||||||
|
compressedPoints := make([]Point, len(points))
|
||||||
|
for idx, point := range points {
|
||||||
|
compressedPoints[idx] = Point{mapX[point.x], mapY[point.y]}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxArea := 0
|
||||||
|
for i := range points {
|
||||||
|
for j := i + 1; j < len(points); j++ {
|
||||||
|
p1, p2 := points[i], points[j]
|
||||||
|
dx := abs(p1.x - p2.x)
|
||||||
|
dy := abs(p1.y - p2.y)
|
||||||
|
|
||||||
|
if dx == 0 || dy == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
area := (dx + 1) * (dy + 1)
|
||||||
|
if area <= maxArea {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cx1, cy1 := compressedPoints[i].x, compressedPoints[i].y
|
||||||
|
cx2, cy2 := compressedPoints[j].x, compressedPoints[j].y
|
||||||
|
minCX, maxCX := minMax(cx1, cx2)
|
||||||
|
minCY, maxCY := minMax(cy1, cy2)
|
||||||
|
|
||||||
|
totalCells := (maxCX - minCX + 1) * (maxCY - minCY + 1)
|
||||||
|
if getSum(sum, minCX, minCY, maxCX, maxCY) == totalCells {
|
||||||
|
maxArea = area
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxArea
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user