Categorygithub.com/JasonGoemaat/go-aoc
repositorypackage
0.0.7
Repository: https://github.com/jasongoemaat/go-aoc.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author

# README

go-aoc

Library with helper functions for solving Advent of Code puzzles.

See:

  • General - library functions for running tests with expected output, manipulating directories/files
  • Parsing - parsing functions to help with input
  • Area - structs and funcs to help with maps/grids
  • AStar - structs and funcs to do A* pathfinding on Areas
  • Tui - package for running a step-based algorithm and displaying a nice text UI

Easy quickstart

Add create an empty directory for your project and initialize a go module inside, then add this module:

mkdir mysolvers
cd mysolvers
go mod init example.com/mysolvers
go get github.com/JasonGoemaat/go-aoc

You can put your files anywhere, the important thing is to have a '.go' file with func main() in it and package main at the top. The sample inputs go in the same directory. Here's how i do it:

mysolvers
├── go.mod
└── 2024
    ├── 1
    │   ├── input.aoc
    │   ├── main.go
    │   └── sample.aoc
    └── 2
        ├── input.aoc
        ├── main.go
        └── sample.aoc

Then I run from the 'mysolvers' directory with:

go run 2024/2/main.go

Which produces output like this:

PS C:\git\go\advent-workspace\go-aoc-solutions> go run 2024/2/main.go
0ms part1("sample.aoc") = 2 (GOOD)
4ms part1("input.aoc") = 660 (GOOD)
0ms part2("sample.aoc") = 4 (GOOD)
4ms part2("input.aoc") = 689 (BAD - Expected 889)

2024/2/main.go looks like this, calling aoc.Local for each run passing the solving function, a description, the input file name (that is in the same directory as the go file), and the expected output.

package main

import (
	aoc "github.com/JasonGoemaat/go-aoc/aoc"
)

func main() {
	// https://adventofcode.com/2024/day/2
	aoc.Local(part1, "part1", "sample.aoc", 2)
	aoc.Local(part1, "part1", "input.aoc", 660)
	aoc.Local(part2, "part2", "sample.aoc", 4)
	aoc.Local(part2, "part2", "input.aoc", 889) // intentionally wrong
}

func part1(contents string) interface{} {
    return 0 // should actually solve puzzle part 1
}

func part2(contents string) interface{} {
    return 0 // should actually solve puzzle part 2
}

Parsing

The aoc.Local call handles loading the named file from the same directory as the '.go' file and passes the text contents of the file as a string to the solver function (part1 and part2 is what I named them above).

Using regular expressions to split strings in go is pretty easy. As an example, this is used in aoc.ParseGroups() to split into a string that contains empty lines dividing groups of input:

var reDoubleCRLF = regexp.MustCompile("[\r]?[\n][\r]?[\n]")
var sections := reDoubleCRLF.Split(text)

The aoc.ParseGroups() function omits the last group if empty, I was thinking there might be a blank line at the end of the file, but the last line doesn't end in a newline so I don't think that's necessary.

See parsing_test.go for some examples.

aoc.ParseGroups(content string) []string

Splits sample input with empty lines. An example is 2024 Day 5 which has first a list of rules of which page must be before another, then a section with CSV of page numbers on each line:

47|53
97|13

13,53,47,97
47,53,97,13

aoc.ParseGroups will return a slice with each section in it's own string.

aoc.ParseLinesToInts(content string) [][]int

This takes a string and parses each line into a slice of numbers, identifying numbers by consecutive digits using the regex \d+.

As an example, the input:

13,53,47,97
93: 8 4 172

Would return a slice of two slices containing integers:

result := [][]int{
    {13, 53, 47, 97},
    {93, 8, 4, 172}
}

An example would be the same input shown in aoc.ParseGroups above from 2024 Day 5 and using aoc.parseLinesToints() to get at the numbers in each:

groups := aoc.ParseGroups(content)
rulesInput := aoc.ParseLinesToInts(aoc.ParseLines(groups[0]))
updates := aoc.ParseLinesToInts(aoc.ParseLines(groups[1]))

Testing

There is a aoc.ExpectJson(t *testing.T, expected, actual interface{}) function that makes it easy to compare complex go structures and slices to make sure they contain the same data. It fails the test if the JSON serialization of whatever two values that are passed is different. It falls back to go's %v formatting if serialization fails for some reason (maybe if there is a cycle?). If it fails, it logs the expected and actual values and since it calls t.Helper(), the line number reported is from the calling test.