Add day 10 part 1
parent
f3b957628e
commit
921f6f1ac9
|
@ -0,0 +1,293 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Coordinate struct {
|
||||
row int
|
||||
col int
|
||||
}
|
||||
|
||||
type Pipe int
|
||||
|
||||
const (
|
||||
PipeUnknown Pipe = iota
|
||||
PipeVertical
|
||||
PipeHorizontal
|
||||
PipeL
|
||||
PipeJ
|
||||
Pipe7
|
||||
PipeF
|
||||
)
|
||||
|
||||
type PipeMap map[Coordinate]Pipe
|
||||
|
||||
var ErrNoPipe = errors.New("no pipe at location")
|
||||
|
||||
func (coordinate Coordinate) North() Coordinate {
|
||||
return Coordinate{row: coordinate.row - 1, col: coordinate.col}
|
||||
}
|
||||
|
||||
func (coordinate Coordinate) South() Coordinate {
|
||||
return Coordinate{row: coordinate.row + 1, col: coordinate.col}
|
||||
}
|
||||
|
||||
func (coordinate Coordinate) East() Coordinate {
|
||||
return Coordinate{row: coordinate.row, col: coordinate.col + 1}
|
||||
}
|
||||
|
||||
func (coordinate Coordinate) West() Coordinate {
|
||||
return Coordinate{row: coordinate.row, col: coordinate.col - 1}
|
||||
}
|
||||
|
||||
// Neighbors gets all the neighbors of the given coordinate (cardinal directions)
|
||||
func (coordinate Coordinate) Neighbors() []Coordinate {
|
||||
return []Coordinate{
|
||||
coordinate.North(),
|
||||
coordinate.South(),
|
||||
coordinate.East(),
|
||||
coordinate.West(),
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectsNorth will determine if the given pipe can connect to a pipe to its north
|
||||
func (pipe Pipe) ConnectsNorth() bool {
|
||||
return pipe == PipeVertical || pipe == PipeL || pipe == PipeJ
|
||||
}
|
||||
|
||||
// ConnectsSouth will determine if the given pipe can connect to a pipe to its south
|
||||
func (pipe Pipe) ConnectsSouth() bool {
|
||||
return pipe == PipeVertical || pipe == Pipe7 || pipe == PipeF
|
||||
}
|
||||
|
||||
// ConnectsEast will determine if the given pipe can connect to a pipe to its east
|
||||
func (pipe Pipe) ConnectsEast() bool {
|
||||
return pipe == PipeHorizontal || pipe == PipeF || pipe == PipeL
|
||||
}
|
||||
|
||||
// ConnectsWest will determine if the given pipe can connect to a pipe to its west
|
||||
func (pipe Pipe) ConnectsWest() bool {
|
||||
return pipe == PipeHorizontal || pipe == Pipe7 || pipe == PipeJ
|
||||
}
|
||||
|
||||
// Print will print the entire map in the form the puzzle presents it
|
||||
func (pipeMap PipeMap) Print() {
|
||||
positions := mapKeys(pipeMap)
|
||||
compareRow := func(a, b Coordinate) int {
|
||||
return cmp.Compare(a.row, b.row)
|
||||
}
|
||||
|
||||
compareCol := func(a, b Coordinate) int {
|
||||
return cmp.Compare(a.col, b.col)
|
||||
}
|
||||
|
||||
minRow := slices.MinFunc(positions, compareRow).row
|
||||
maxRow := slices.MaxFunc(positions, compareRow).row
|
||||
minCol := slices.MinFunc(positions, compareCol).col
|
||||
maxCol := slices.MaxFunc(positions, compareCol).col
|
||||
|
||||
for row := minRow; row <= maxRow; row++ {
|
||||
for col := minCol; col <= maxCol; col++ {
|
||||
location := Coordinate{row: row, col: col}
|
||||
pipe, ok := pipeMap[location]
|
||||
if !ok {
|
||||
fmt.Print(".")
|
||||
} else if pipe == PipeVertical {
|
||||
fmt.Print("|")
|
||||
} else if pipe == PipeHorizontal {
|
||||
fmt.Print("-")
|
||||
} else if pipe == PipeL {
|
||||
fmt.Print("L")
|
||||
} else if pipe == PipeJ {
|
||||
fmt.Print("J")
|
||||
} else if pipe == Pipe7 {
|
||||
fmt.Print("7")
|
||||
} else if pipe == PipeF {
|
||||
fmt.Print("F")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// PipesConnect will check if the pipes at the given positions connect
|
||||
func (pipeMap PipeMap) PipesConnect(position1, position2 Coordinate) bool {
|
||||
pipe1 := pipeMap[position1]
|
||||
pipe2 := pipeMap[position2]
|
||||
|
||||
if pipe1.ConnectsNorth() && pipe2.ConnectsSouth() && position2 == position1.North() {
|
||||
return true
|
||||
} else if pipe1.ConnectsSouth() && pipe2.ConnectsNorth() && position2 == position1.South() {
|
||||
return true
|
||||
} else if pipe1.ConnectsEast() && pipe2.ConnectsWest() && position2 == position1.East() {
|
||||
return true
|
||||
} else if pipe1.ConnectsWest() && pipe2.ConnectsEast() && position2 == position1.West() {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 && len(os.Args) != 3 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
inputFilename := os.Args[1]
|
||||
inputFile, err := os.Open(inputFilename)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not open input file: %s", err))
|
||||
}
|
||||
|
||||
defer inputFile.Close()
|
||||
|
||||
inputBytes, err := io.ReadAll(inputFile)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not read input file: %s", err))
|
||||
}
|
||||
|
||||
input := strings.TrimSpace(string(inputBytes))
|
||||
inputLines := strings.Split(input, "\n")
|
||||
pipeMap, startPosition, err := parsePipeMap(inputLines)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not parse pipes: %s", err))
|
||||
}
|
||||
|
||||
fmt.Printf("Part 1: %d\n", part1(pipeMap, startPosition))
|
||||
}
|
||||
|
||||
func part1(pipeMap PipeMap, startPosition Coordinate) int {
|
||||
type Visit struct {
|
||||
position Coordinate
|
||||
distance int
|
||||
}
|
||||
maxDistance := 0
|
||||
visited := map[Coordinate]struct{}{}
|
||||
toVisit := []Visit{{position: startPosition, distance: 0}}
|
||||
for len(toVisit) > 0 {
|
||||
visiting := toVisit[0]
|
||||
toVisit = toVisit[1:]
|
||||
|
||||
visited[visiting.position] = struct{}{}
|
||||
if visiting.distance > maxDistance {
|
||||
maxDistance = visiting.distance
|
||||
}
|
||||
|
||||
for _, neighbor := range visiting.position.Neighbors() {
|
||||
if pipeMap[neighbor] == PipeUnknown || !pipeMap.PipesConnect(visiting.position, neighbor) {
|
||||
continue
|
||||
} else if _, ok := visited[neighbor]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
neighborVisit := Visit{
|
||||
position: neighbor,
|
||||
distance: visiting.distance + 1,
|
||||
}
|
||||
|
||||
toVisit = append(toVisit, neighborVisit)
|
||||
}
|
||||
}
|
||||
|
||||
return maxDistance
|
||||
}
|
||||
|
||||
func parsePipeMap(inputLines []string) (PipeMap, Coordinate, error) {
|
||||
pipeMap := PipeMap{}
|
||||
startPosition := (*Coordinate)(nil)
|
||||
for row, line := range inputLines {
|
||||
for col, char := range line {
|
||||
pipe, err := parsePipeChar(char)
|
||||
if errors.Is(err, ErrNoPipe) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return nil, Coordinate{}, fmt.Errorf("malformed at (%d, %d): %w", row, col, err)
|
||||
}
|
||||
|
||||
position := Coordinate{row: row, col: col}
|
||||
pipeMap[position] = pipe
|
||||
if pipe == PipeUnknown {
|
||||
startPosition = &position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startPosition == nil {
|
||||
return nil, Coordinate{}, errors.New("no start position found")
|
||||
}
|
||||
|
||||
startPipeType, err := inferPipeType(pipeMap, *startPosition)
|
||||
if err != nil {
|
||||
return nil, Coordinate{}, fmt.Errorf("infer start pipe type: %w", err)
|
||||
}
|
||||
|
||||
pipeMap[*startPosition] = startPipeType
|
||||
|
||||
return pipeMap, *startPosition, nil
|
||||
}
|
||||
|
||||
func parsePipeChar(c rune) (Pipe, error) {
|
||||
switch c {
|
||||
case '.':
|
||||
return PipeUnknown, ErrNoPipe
|
||||
case '|':
|
||||
return PipeVertical, nil
|
||||
case '-':
|
||||
return PipeHorizontal, nil
|
||||
case 'L':
|
||||
return PipeL, nil
|
||||
case 'J':
|
||||
return PipeJ, nil
|
||||
case '7':
|
||||
return Pipe7, nil
|
||||
case 'F':
|
||||
return PipeF, nil
|
||||
case 'S':
|
||||
return PipeUnknown, nil
|
||||
default:
|
||||
return PipeUnknown, fmt.Errorf("invalid pipe char %c", c)
|
||||
}
|
||||
}
|
||||
|
||||
// inferPipeType will use the PipeMap to infer what type of Pipe should exist at the given location
|
||||
func inferPipeType(pipeMap PipeMap, position Coordinate) (Pipe, error) {
|
||||
north := pipeMap[Coordinate{row: position.row - 1, col: position.col}]
|
||||
south := pipeMap[Coordinate{row: position.row + 1, col: position.col}]
|
||||
west := pipeMap[Coordinate{row: position.row, col: position.col - 1}]
|
||||
east := pipeMap[Coordinate{row: position.row, col: position.col + 1}]
|
||||
|
||||
if north.ConnectsSouth() && south.ConnectsNorth() {
|
||||
return PipeVertical, nil
|
||||
} else if west.ConnectsEast() && east.ConnectsWest() {
|
||||
return PipeHorizontal, nil
|
||||
} else if north.ConnectsSouth() && east.ConnectsWest() {
|
||||
return PipeL, nil
|
||||
} else if north.ConnectsSouth() && west.ConnectsEast() {
|
||||
return PipeJ, nil
|
||||
} else if south.ConnectsNorth() && west.ConnectsEast() {
|
||||
return Pipe7, nil
|
||||
} else if south.ConnectsNorth() && east.ConnectsWest() {
|
||||
return PipeF, nil
|
||||
} else {
|
||||
return PipeUnknown, fmt.Errorf("infer pipe type: north=%v south=%v west=%v east=%v", north, south, west, east)
|
||||
}
|
||||
}
|
||||
|
||||
func mapKeys[T comparable, U any](m map[T]U) []T {
|
||||
res := make([]T, len(m))
|
||||
i := 0
|
||||
for key := range m {
|
||||
res[i] = key
|
||||
i++
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
Loading…
Reference in New Issue