Solve day 10 part 2
parent
921f6f1ac9
commit
554d8bb2f5
247
day10/main.go
247
day10/main.go
|
@ -1,3 +1,5 @@
|
||||||
|
// I definitely overcomplicated this problem, but it took me a very long time to visualize things properly
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -27,9 +29,16 @@ const (
|
||||||
PipeF
|
PipeF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ScanDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScanDirectionHorizontal ScanDirection = iota
|
||||||
|
ScanDirectionVertical
|
||||||
|
)
|
||||||
|
|
||||||
type PipeMap map[Coordinate]Pipe
|
type PipeMap map[Coordinate]Pipe
|
||||||
|
|
||||||
var ErrNoPipe = errors.New("no pipe at location")
|
var ErrMissingPipe = errors.New("no pipe at location")
|
||||||
|
|
||||||
func (coordinate Coordinate) North() Coordinate {
|
func (coordinate Coordinate) North() Coordinate {
|
||||||
return Coordinate{row: coordinate.row - 1, col: coordinate.col}
|
return Coordinate{row: coordinate.row - 1, col: coordinate.col}
|
||||||
|
@ -47,8 +56,8 @@ func (coordinate Coordinate) West() Coordinate {
|
||||||
return Coordinate{row: coordinate.row, col: coordinate.col - 1}
|
return Coordinate{row: coordinate.row, col: coordinate.col - 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neighbors gets all the neighbors of the given coordinate (cardinal directions)
|
// CardinalNeighbors gets all the neighbors of the given coordinate (cardinal directions)
|
||||||
func (coordinate Coordinate) Neighbors() []Coordinate {
|
func (coordinate Coordinate) CardinalNeighbors() []Coordinate {
|
||||||
return []Coordinate{
|
return []Coordinate{
|
||||||
coordinate.North(),
|
coordinate.North(),
|
||||||
coordinate.South(),
|
coordinate.South(),
|
||||||
|
@ -57,6 +66,18 @@ func (coordinate Coordinate) Neighbors() []Coordinate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllNeighbors gets all the neighbors of the given coordinate in all directions
|
||||||
|
func (coordinate Coordinate) AllNeighbors() []Coordinate {
|
||||||
|
diagonalNeighbors := []Coordinate{
|
||||||
|
{row: coordinate.row - 1, col: coordinate.col - 1},
|
||||||
|
{row: coordinate.row - 1, col: coordinate.col + 1},
|
||||||
|
{row: coordinate.row + 1, col: coordinate.col - 1},
|
||||||
|
{row: coordinate.row + 1, col: coordinate.col + 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(coordinate.CardinalNeighbors(), diagonalNeighbors...)
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectsNorth will determine if the given pipe can connect to a pipe to its north
|
// ConnectsNorth will determine if the given pipe can connect to a pipe to its north
|
||||||
func (pipe Pipe) ConnectsNorth() bool {
|
func (pipe Pipe) ConnectsNorth() bool {
|
||||||
return pipe == PipeVertical || pipe == PipeL || pipe == PipeJ
|
return pipe == PipeVertical || pipe == PipeL || pipe == PipeJ
|
||||||
|
@ -77,8 +98,44 @@ func (pipe Pipe) ConnectsWest() bool {
|
||||||
return pipe == PipeHorizontal || pipe == Pipe7 || pipe == PipeJ
|
return pipe == PipeHorizontal || pipe == Pipe7 || pipe == PipeJ
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print will print the entire map in the form the puzzle presents it
|
// IsCorner indicates whether or not a pipe is a corner
|
||||||
func (pipeMap PipeMap) Print() {
|
func (pipe Pipe) IsCorner() bool {
|
||||||
|
return pipe == PipeJ || pipe == Pipe7 || pipe == PipeF || pipe == PipeL
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStraight indicates whether or not a pipe is straight
|
||||||
|
func (pipe Pipe) IsStraight() bool {
|
||||||
|
return pipe == PipeHorizontal || pipe == PipeVertical
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParallelToScan indicates whether or not a pipe moves only parallel to the scan direction
|
||||||
|
func (pipe Pipe) IsParallelToScan(scanDir ScanDirection) bool {
|
||||||
|
if scanDir == ScanDirectionHorizontal {
|
||||||
|
return pipe == PipeHorizontal
|
||||||
|
} else {
|
||||||
|
return pipe == PipeVertical
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectedNeighbors gets only the connected neighbors to a pipe at a position
|
||||||
|
func (pipeMap PipeMap) ConnectedNeighbors(position Coordinate) []Coordinate {
|
||||||
|
_, ok := pipeMap[position]
|
||||||
|
if !ok {
|
||||||
|
return []Coordinate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedNeighbors := []Coordinate{}
|
||||||
|
for _, neighbor := range position.CardinalNeighbors() {
|
||||||
|
if pipeMap.PipesConnect(position, neighbor) {
|
||||||
|
connectedNeighbors = append(connectedNeighbors, neighbor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectedNeighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeBounds finds the bounds of the pieps on the map
|
||||||
|
func (pipeMap PipeMap) PipeBounds() (Coordinate, Coordinate) {
|
||||||
positions := mapKeys(pipeMap)
|
positions := mapKeys(pipeMap)
|
||||||
compareRow := func(a, b Coordinate) int {
|
compareRow := func(a, b Coordinate) int {
|
||||||
return cmp.Compare(a.row, b.row)
|
return cmp.Compare(a.row, b.row)
|
||||||
|
@ -87,14 +144,19 @@ func (pipeMap PipeMap) Print() {
|
||||||
compareCol := func(a, b Coordinate) int {
|
compareCol := func(a, b Coordinate) int {
|
||||||
return cmp.Compare(a.col, b.col)
|
return cmp.Compare(a.col, b.col)
|
||||||
}
|
}
|
||||||
|
|
||||||
minRow := slices.MinFunc(positions, compareRow).row
|
minRow := slices.MinFunc(positions, compareRow).row
|
||||||
maxRow := slices.MaxFunc(positions, compareRow).row
|
maxRow := slices.MaxFunc(positions, compareRow).row
|
||||||
minCol := slices.MinFunc(positions, compareCol).col
|
minCol := slices.MinFunc(positions, compareCol).col
|
||||||
maxCol := slices.MaxFunc(positions, compareCol).col
|
maxCol := slices.MaxFunc(positions, compareCol).col
|
||||||
|
|
||||||
for row := minRow; row <= maxRow; row++ {
|
return Coordinate{row: minRow, col: minCol}, Coordinate{row: maxRow, col: maxCol}
|
||||||
for col := minCol; col <= maxCol; col++ {
|
}
|
||||||
|
|
||||||
|
// Print will print the entire map in the form the puzzle presents it
|
||||||
|
func (pipeMap PipeMap) Print() {
|
||||||
|
minCorner, maxCorner := pipeMap.PipeBounds()
|
||||||
|
for row := minCorner.row; row <= maxCorner.row; row++ {
|
||||||
|
for col := minCorner.col; col <= maxCorner.col; col++ {
|
||||||
location := Coordinate{row: row, col: col}
|
location := Coordinate{row: row, col: col}
|
||||||
pipe, ok := pipeMap[location]
|
pipe, ok := pipeMap[location]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -162,6 +224,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Part 1: %d\n", part1(pipeMap, startPosition))
|
fmt.Printf("Part 1: %d\n", part1(pipeMap, startPosition))
|
||||||
|
fmt.Printf("Part 2: %d\n", part2(pipeMap, startPosition))
|
||||||
}
|
}
|
||||||
|
|
||||||
func part1(pipeMap PipeMap, startPosition Coordinate) int {
|
func part1(pipeMap PipeMap, startPosition Coordinate) int {
|
||||||
|
@ -181,10 +244,8 @@ func part1(pipeMap PipeMap, startPosition Coordinate) int {
|
||||||
maxDistance = visiting.distance
|
maxDistance = visiting.distance
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, neighbor := range visiting.position.Neighbors() {
|
for _, neighbor := range pipeMap.ConnectedNeighbors(visiting.position) {
|
||||||
if pipeMap[neighbor] == PipeUnknown || !pipeMap.PipesConnect(visiting.position, neighbor) {
|
if _, ok := visited[neighbor]; ok {
|
||||||
continue
|
|
||||||
} else if _, ok := visited[neighbor]; ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,13 +261,171 @@ func part1(pipeMap PipeMap, startPosition Coordinate) int {
|
||||||
return maxDistance
|
return maxDistance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func part2(pipeMap PipeMap, startPosition Coordinate) int {
|
||||||
|
mainLoopMap := traceMainLoop(pipeMap, startPosition)
|
||||||
|
|
||||||
|
regions := findEmptyRegions(mainLoopMap, startPosition)
|
||||||
|
area := 0
|
||||||
|
for _, region := range regions {
|
||||||
|
isAccessible := isRegionExternallyAccessible(mainLoopMap, region)
|
||||||
|
if !isAccessible {
|
||||||
|
area += len(region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return area
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceMainLoop walks the pipes and finds the pipes relevant to the problem
|
||||||
|
func traceMainLoop(pipeMap PipeMap, startPosition Coordinate) PipeMap {
|
||||||
|
visited := map[Coordinate]Pipe{}
|
||||||
|
toVisit := []Coordinate{startPosition}
|
||||||
|
for len(toVisit) > 0 {
|
||||||
|
visitingPosition := toVisit[0]
|
||||||
|
toVisit = toVisit[1:]
|
||||||
|
|
||||||
|
visited[visitingPosition] = pipeMap[visitingPosition]
|
||||||
|
|
||||||
|
for _, neighbor := range pipeMap.ConnectedNeighbors(visitingPosition) {
|
||||||
|
if _, ok := visited[neighbor]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toVisit = append(toVisit, neighbor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visited
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRegionExternallyAccessible indicates whether or not all of the given coordinates are internal to the loop
|
||||||
|
func isRegionExternallyAccessible(pipeMap PipeMap, region []Coordinate) bool {
|
||||||
|
for _, pos := range region {
|
||||||
|
if !isInsideViaRay(pipeMap, pos, ScanDirectionHorizontal) || !isInsideViaRay(pipeMap, pos, ScanDirectionVertical) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInsideViaRay casts a ray in the given direction, counting the number of edge crossings to determine
|
||||||
|
// if a tile is inside
|
||||||
|
func isInsideViaRay(pipeMap PipeMap, target Coordinate, direction ScanDirection) bool {
|
||||||
|
cursor := target
|
||||||
|
if direction == ScanDirectionHorizontal {
|
||||||
|
cursor.col = 0
|
||||||
|
} else {
|
||||||
|
cursor.row = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeBuffer := []Pipe{}
|
||||||
|
crossings := 0
|
||||||
|
for cursor != target {
|
||||||
|
if pipeMap[cursor] != PipeUnknown {
|
||||||
|
pipeBuffer = append(pipeBuffer, pipeMap[cursor])
|
||||||
|
}
|
||||||
|
|
||||||
|
if direction == ScanDirectionHorizontal {
|
||||||
|
cursor.col++
|
||||||
|
} else {
|
||||||
|
cursor.row++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crossings += numRayCrossings(direction, pipeBuffer)
|
||||||
|
|
||||||
|
return crossings%2 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// numRayCrossings counts the number of times a ray crosses a pipe
|
||||||
|
func numRayCrossings(scanDirection ScanDirection, scannedPipes []Pipe) int {
|
||||||
|
crossings := 0
|
||||||
|
for _, pipe := range scannedPipes {
|
||||||
|
if pipe.IsStraight() && !pipe.IsParallelToScan(scanDirection) {
|
||||||
|
crossings++
|
||||||
|
} else if pipe.IsCorner() {
|
||||||
|
if scanDirection == ScanDirectionHorizontal && (pipe == PipeL || pipe == PipeJ) {
|
||||||
|
// If we are scanning horizontally, and we cross one of these chars, we are internal if we cross something of this variety only once
|
||||||
|
crossings++
|
||||||
|
} else if scanDirection == ScanDirectionVertical && (pipe == PipeF || pipe == PipeL) {
|
||||||
|
// ditto for vertically
|
||||||
|
crossings++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crossings
|
||||||
|
}
|
||||||
|
|
||||||
|
// findEmptyRegions finds all of the locations where there are empty positions on the graph
|
||||||
|
func findEmptyRegions(pipeMap PipeMap, startPosition Coordinate) [][]Coordinate {
|
||||||
|
emptyPositions := findEmptyPositions(pipeMap)
|
||||||
|
regions := [][]Coordinate{}
|
||||||
|
visited := map[Coordinate]struct{}{}
|
||||||
|
for _, position := range emptyPositions {
|
||||||
|
if _, ok := visited[position]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
floodedPositions := flood(pipeMap, position)
|
||||||
|
regions = append(regions, floodedPositions)
|
||||||
|
for _, flooded := range floodedPositions {
|
||||||
|
visited[flooded] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions
|
||||||
|
}
|
||||||
|
|
||||||
|
// findEmptyPositions finds all empty positions on the graph
|
||||||
|
func findEmptyPositions(pipeMap PipeMap) []Coordinate {
|
||||||
|
minCorner, maxCorner := pipeMap.PipeBounds()
|
||||||
|
empty := []Coordinate{}
|
||||||
|
for row := minCorner.row; row < maxCorner.row; row++ {
|
||||||
|
for col := minCorner.col; col < maxCorner.col; col++ {
|
||||||
|
pos := Coordinate{row: row, col: col}
|
||||||
|
if pipeMap[pos] == PipeUnknown {
|
||||||
|
empty = append(empty, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// flood performs a flood fill to locate neighboring empty spots
|
||||||
|
func flood(pipeMap PipeMap, start Coordinate) []Coordinate {
|
||||||
|
minCorner, maxCorner := pipeMap.PipeBounds()
|
||||||
|
toVisit := []Coordinate{start}
|
||||||
|
visited := map[Coordinate]struct{}{}
|
||||||
|
for len(toVisit) > 0 {
|
||||||
|
visiting := toVisit[0]
|
||||||
|
toVisit = toVisit[1:]
|
||||||
|
if _, ok := pipeMap[visiting]; ok {
|
||||||
|
// If we've hit a pipe on the bounding box, don't keep filling
|
||||||
|
continue
|
||||||
|
} else if visiting.row < minCorner.row || visiting.col < minCorner.col || visiting.row > maxCorner.row || visiting.col > maxCorner.col {
|
||||||
|
// if we've moved out of bounds, don't continue either
|
||||||
|
continue
|
||||||
|
} else if _, ok := visited[visiting]; ok {
|
||||||
|
// If we've already visited this, we don't need to try again
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
visited[visiting] = struct{}{}
|
||||||
|
toVisit = append(toVisit, visiting.CardinalNeighbors()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapKeys(visited)
|
||||||
|
}
|
||||||
|
|
||||||
func parsePipeMap(inputLines []string) (PipeMap, Coordinate, error) {
|
func parsePipeMap(inputLines []string) (PipeMap, Coordinate, error) {
|
||||||
pipeMap := PipeMap{}
|
pipeMap := PipeMap{}
|
||||||
startPosition := (*Coordinate)(nil)
|
startPosition := (*Coordinate)(nil)
|
||||||
for row, line := range inputLines {
|
for row, line := range inputLines {
|
||||||
for col, char := range line {
|
for col, char := range line {
|
||||||
pipe, err := parsePipeChar(char)
|
pipe, err := parsePipeChar(char)
|
||||||
if errors.Is(err, ErrNoPipe) {
|
if errors.Is(err, ErrMissingPipe) {
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, Coordinate{}, fmt.Errorf("malformed at (%d, %d): %w", row, col, err)
|
return nil, Coordinate{}, fmt.Errorf("malformed at (%d, %d): %w", row, col, err)
|
||||||
|
@ -237,7 +456,7 @@ func parsePipeMap(inputLines []string) (PipeMap, Coordinate, error) {
|
||||||
func parsePipeChar(c rune) (Pipe, error) {
|
func parsePipeChar(c rune) (Pipe, error) {
|
||||||
switch c {
|
switch c {
|
||||||
case '.':
|
case '.':
|
||||||
return PipeUnknown, ErrNoPipe
|
return PipeUnknown, ErrMissingPipe
|
||||||
case '|':
|
case '|':
|
||||||
return PipeVertical, nil
|
return PipeVertical, nil
|
||||||
case '-':
|
case '-':
|
||||||
|
|
Loading…
Reference in New Issue