refactor: massive refactor to have only one binary to call

This commit is contained in:
Kharec committed 2025-11-26 14:04:13 +01:00
1 parent 314da54495
commit 3723f84d1a
41 files changed
+272 -188

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
module 2020/day01
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2020/day02
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2020/day03
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2020/day04
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2020/day05
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2020/day06
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2021/day01
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2021/day02
go 1.25.4
-3
View File
@@ -1,3 +0,0 @@
module 2021/day03
go 1.25.4
+13
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)
+56 -16
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
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
View File
@@ -0,0 +1,3 @@
module advent-of-code
go 1.25.4
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
@@ -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))
}
@@ -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)
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+42
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,
}
}