From 921f6f1ac9d00c75ae1d4a8a28d573bf64bbd6ff Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sun, 10 Dec 2023 11:49:55 -0500 Subject: [PATCH] Add day 10 part 1 --- day10/main.go | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 day10/main.go diff --git a/day10/main.go b/day10/main.go new file mode 100644 index 0000000..eca2fea --- /dev/null +++ b/day10/main.go @@ -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 +}