add day-18 + benchmarks

This commit is contained in:
2024-12-18 18:48:11 +00:00
parent 6cb3fd1654
commit 9d7a9d5791
23 changed files with 658 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
goos: linux
goarch: amd64
pkg: github.com/onyx-and-iris/aoc2024/day-18/internal/two
cpu: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
BenchmarkSolve-12 1 3756699083 ns/op
ok github.com/onyx-and-iris/aoc2024/day-18/internal/two 3.760s

View File

@@ -0,0 +1,118 @@
package two
import (
"errors"
"math"
"slices"
"strings"
"github.com/onyx-and-iris/aoc2024/day-18/internal/queue"
)
type graph struct {
start point
end point
data []string
}
func newGraph(width, height, numCorruptions int, corruptedCoords [][]int) *graph {
var data []string
var sb strings.Builder
for range height {
for range width {
sb.WriteRune('.')
}
data = append(data, sb.String())
sb.Reset()
}
for _, coords := range corruptedCoords[:numCorruptions] {
data[coords[1]] = replaceAtIndex(data[coords[1]], '#', coords[0])
}
return &graph{point{0, 0}, point{len(data[0]) - 1, len(data) - 1}, data}
}
func (g *graph) String() string {
return strings.Join(g.data, "\n")
}
func (g *graph) isOutOfBounds(p point) bool {
return p.x < 0 || p.y < 0 || p.y >= len(g.data) || p.x >= len(g.data[p.y])
}
func (g *graph) valueAt(p point) rune {
return rune(g.data[p.y][p.x])
}
func (g *graph) addCorruption(coords []int) {
g.data[coords[1]] = replaceAtIndex(g.data[coords[1]], '#', coords[0])
}
func (g *graph) dijkstra(start, end point) ([]point, error) {
queue := queue.New[point]()
queue.Enqueue(start)
visited := make(map[point]struct{})
costs := make(map[point]int)
prev := make(map[point]point)
for !queue.IsEmpty() {
current := queue.Dequeue()
// we found a shortest path
if current == end {
return g.generatePath(start, end, prev), nil
}
_, ok := visited[current]
if ok {
continue
}
visited[current] = struct{}{}
for _, n := range neighbours(current) {
if g.isOutOfBounds(n) {
continue
}
if g.valueAt(n) == '#' {
continue
}
_, ok := costs[n]
if !ok {
costs[n] = math.MaxInt
}
new_cost := costs[current] + 1
if new_cost < costs[n] {
costs[n] = new_cost
prev[n] = current
queue.Enqueue(n)
}
}
}
return nil, errors.New("unable to find a shortest path")
}
func (g *graph) generatePath(start, end point, prev map[point]point) []point {
path := []point{end}
node := prev[end]
for node != start {
path = append(path, prev[node])
node = prev[node]
}
return path
}
func (g *graph) debug(path []point) string {
temp := slices.Clone(g.data)
for _, p := range path {
if g.valueAt(p) == '#' {
continue
}
temp[p.y] = replaceAtIndex(temp[p.y], 'O', p.x)
}
return strings.Join(temp, "\n")
}

View File

@@ -0,0 +1,10 @@
package two
func neighbours(p point) [4]point {
return [4]point{
{p.x, p.y - 1}, // N
{p.x + 1, p.y}, // E
{p.x, p.y + 1}, // S
{p.x - 1, p.y}, // W
}
}

View File

@@ -0,0 +1,6 @@
package two
type point struct {
x int
y int
}

View File

@@ -0,0 +1,39 @@
package two
import (
"bytes"
"fmt"
"github.com/onyx-and-iris/aoc2024/day-18/internal/config"
log "github.com/sirupsen/logrus"
)
func Solve(buf []byte, config config.Config) (string, error) {
r := bytes.NewReader(buf)
graph, corruptedCoords, err := parseLines(r, config)
if err != nil {
return "", err
}
log.Debugf("start: %v end: %v", graph.start, graph.end)
indx := runUntilNoPath(graph, corruptedCoords, config)
return fmt.Sprintf("%d,%d", corruptedCoords[indx][0], corruptedCoords[indx][1]), nil
}
func runUntilNoPath(graph *graph, corruptedCoords [][]int, config config.Config) int {
for i, coords := range corruptedCoords[config.NumCorruptions+1:] {
nextCorruption := point{coords[0], coords[1]}
log.Debugf("adding corruption %v", nextCorruption)
graph.addCorruption(coords)
path, err := graph.dijkstra(graph.start, graph.end)
if err != nil {
log.Debug(err)
return config.NumCorruptions + i + 1
}
log.Debugf("\n%s\n", graph.debug(path))
}
return 0
}

View File

@@ -0,0 +1,21 @@
package two
import (
_ "embed"
"os"
"testing"
"github.com/onyx-and-iris/aoc2024/day-18/internal/config"
)
//go:embed testdata/input.txt
var data []byte
func BenchmarkSolve(b *testing.B) {
os.Stdout, _ = os.Open(os.DevNull)
Solve(data, config.Config{
Width: 71,
Height: 71,
NumCorruptions: 1024,
})
}

View File

@@ -0,0 +1,42 @@
package two
import (
"bufio"
"io"
"strconv"
"strings"
"github.com/onyx-and-iris/aoc2024/day-18/internal/config"
)
func parseLines(r io.Reader, config config.Config) (*graph, [][]int, error) {
corruptedCoords := [][]int{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
corruptedCoords = append(corruptedCoords, func() []int {
x := strings.Split(scanner.Text(), ",")
return []int{mustConv(x[0]), mustConv(x[1])}
}())
}
if err := scanner.Err(); err != nil {
return nil, nil, err
}
graph := newGraph(config.Width, config.Height, config.NumCorruptions, corruptedCoords)
return graph, corruptedCoords, nil
}
func mustConv(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
func replaceAtIndex(s string, r rune, i int) string {
out := []rune(s)
out[i] = r
return string(out)
}