refactor: massive refactor to have only one binary to call

This commit is contained in:
2025-11-26 14:03:32 +01:00
parent 314da54495
commit 3723f84d1a
41 changed files with 272 additions and 188 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

13
Makefile Normal file
View File

@@ -0,0 +1,13 @@
GO = go
BIN = bin/aoc
.PHONY: build test clean
build:
$(GO) build -o $(BIN) ./cmd/aoc
test:
$(GO) test ./...
clean:
rm -f $(BIN)

View File

@@ -16,26 +16,61 @@ Also, it's a fresh start from 2025. I do some exercises from other years along t
## Repository Structure
```
yyyy/
├── dayXX/
── main.go # The main code to run to print solutions.
│ ├── main_test.go # The test file that validates the logic.
── input.txt # The day's puzzle input.
└── ...
├── cmd/
│ └── aoc/
── main.go # CLI entry point that runs solutions
── internal/
── 2020/
│ ├── DayOne/
│ │ ├── code.go # Go solution for Day 1 (2020)
│ │ └── code_test.go # Unit tests for Day 1
│ └── ...
├── 2021/
│ └── ... # Additional years and days
├── registry/
│ └── registry.go # Central registry for day runners
└── data/
├── 2020/
│ ├── DayOne/
│ │ └── input.txt # Puzzle input for Day 1 (2020)
│ └── ...
└── ...
```
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.
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.
## Usage
Build the CLI tool:
```bash
cd yyyy/dayXX
go run main.go
make
```
Expected output:
Run a day's solution:
```bash
Part 1: <answer>
Part 2: <answer>
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
@@ -55,13 +90,18 @@ In this example, the calibration values of these four lines are 12, 38, 15, and
Adding these together produces 142.
```
I'm using these examples to validate my logic.
I'm using these examples each day to validate my logic.
If you want to run those, you can use:
Run all tests:
```bash
cd dayXX
go test
make test
```
Or run tests for a specific day:
```bash
go test ./internal/2020/DayOne/...
```
## Notes

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

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

3
go.mod Normal file
View File

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

View File

@@ -1,18 +1,13 @@
package main
package dayfive
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
return strings.Split(string(content), "\n")
func init() {
registry.Register("2020D5", ParseInput, PartOne, PartTwo)
}
func calculateSeatID(pass string) int {
@@ -39,6 +34,11 @@ func calculateSeatID(pass string) int {
return row*8 + column
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
func PartOne(input []string) int {
maxSeatID := 0
for _, pass := range input {
@@ -77,9 +77,3 @@ func PartTwo(input []string) int {
return 0
}
func main() {
input := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(input))
fmt.Println("Part 2:", PartTwo(input))
}

View File

@@ -1,4 +1,4 @@
package main
package dayfive
import "testing"
@@ -32,3 +32,4 @@ func TestPartTwo(t *testing.T) {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,18 +1,18 @@
package main
package dayfour
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2020D4", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
text := string(content)
passports := strings.Split(text, "\n\n")
@@ -152,8 +152,3 @@ func PartTwo(data []string) int {
return count
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,4 +1,4 @@
package main
package dayfour
import "testing"
@@ -13,7 +13,7 @@ func TestPartOne(t *testing.T) {
expected := 2
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
@@ -21,6 +21,7 @@ func TestPartTwo(t *testing.T) {
expected := 2
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

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

View File

@@ -1,4 +1,4 @@
package main
package dayone
import "testing"
@@ -8,7 +8,7 @@ func TestPartOne(t *testing.T) {
expected := 514579
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
@@ -16,6 +16,7 @@ func TestPartTwo(t *testing.T) {
expected := 241861950
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,17 +1,17 @@
package main
package daysix
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2020D6", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
@@ -68,8 +68,3 @@ func PartTwo(input []string) int {
return total
}
func main() {
input := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(input))
fmt.Println("Part 2:", PartTwo(input))
}

View File

@@ -1,8 +1,8 @@
package main
package daysix
import "testing"
var input = []string{
var testInput = []string{
"abc",
"",
"a",
@@ -22,7 +22,7 @@ var input = []string{
func TestPartOne(t *testing.T) {
expected := 11
got := PartOne(input)
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne() = %d, want %d", got, expected)
}
@@ -30,8 +30,9 @@ func TestPartOne(t *testing.T) {
func TestPartTwo(t *testing.T) {
expected := 6
got := PartTwo(input)
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,17 +1,17 @@
package main
package daythree
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2020D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
@@ -50,8 +50,3 @@ func PartTwo(input []string) int {
return result
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,4 +1,4 @@
package main
package daythree
import "testing"
@@ -31,3 +31,4 @@ func TestPartTwo(t *testing.T) {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

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

View File

@@ -1,4 +1,4 @@
package main
package daytwo
import "testing"
@@ -12,7 +12,7 @@ func TestPartOne(t *testing.T) {
expected := 2
got := PartOne(testInput)
if got != expected {
t.Errorf("PartOne(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartOne() = %d, want %d", got, expected)
}
}
@@ -20,6 +20,7 @@ func TestPartTwo(t *testing.T) {
expected := 1
got := PartTwo(testInput)
if got != expected {
t.Errorf("PartTwo(%v) = %d, want %d", testInput, got, expected)
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,24 +1,21 @@
package main
package dayone
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func parseInput(file string) []int {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("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, err := strconv.Atoi(line)
if err != nil {
log.Fatalf("Failed to convert string to int: %v", err)
}
num, _ := strconv.Atoi(line)
data = append(data, num)
}
return data
@@ -44,8 +41,3 @@ func PartTwo(data []int) int {
return count
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,4 +1,4 @@
package main
package dayone
import "testing"
@@ -19,3 +19,4 @@ func TestPartTwo(t *testing.T) {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,17 +1,17 @@
package main
package daythree
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2021D3", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(strings.TrimSpace(string(content)), "\n")
}
@@ -115,8 +115,3 @@ func PartTwo(data []string) int {
return oxygenRating * co2Rating
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,4 +1,4 @@
package main
package daythree
import "testing"
@@ -32,3 +32,4 @@ func TestPartTwo(t *testing.T) {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -1,18 +1,18 @@
package main
package daytwo
import (
"fmt"
"log"
"advent-of-code/internal/registry"
"os"
"strconv"
"strings"
)
func parseInput(file string) []string {
content, err := os.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
func init() {
registry.Register("2021D2", ParseInput, PartOne, PartTwo)
}
func ParseInput(filepath string) []string {
content, _ := os.ReadFile(filepath)
return strings.Split(string(content), "\n")
}
@@ -58,8 +58,3 @@ func PartTwo(data []string) int {
return horizontal * depth
}
func main() {
data := parseInput("input.txt")
fmt.Println("Part 1:", PartOne(data))
fmt.Println("Part 2:", PartTwo(data))
}

View File

@@ -1,4 +1,4 @@
package main
package daytwo
import "testing"
@@ -26,3 +26,4 @@ func TestPartTwo(t *testing.T) {
t.Errorf("PartTwo() = %d, want %d", got, expected)
}
}

View File

@@ -0,0 +1,42 @@
package registry
import (
"fmt"
"os"
)
type Runner interface {
Run(input string, part string)
}
type DayRunner[T any] struct {
ParseInput func(string) T
PartOne func(T) int
PartTwo func(T) int
}
func (r *DayRunner[T]) Run(input string, part string) {
data := r.ParseInput(input)
switch part {
case "":
fmt.Println(r.PartOne(data))
fmt.Println(r.PartTwo(data))
case "1":
fmt.Println(r.PartOne(data))
case "2":
fmt.Println(r.PartTwo(data))
default:
fmt.Fprintf(os.Stderr, "Invalid part: %s\n", part)
os.Exit(1)
}
}
var Days = make(map[string]any)
func Register[T any](key string, parseInput func(string) T, partOne func(T) int, partTwo func(T) int) {
Days[key] = &DayRunner[T]{
ParseInput: parseInput,
PartOne: partOne,
PartTwo: partTwo,
}
}