You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
8.7 KiB
Go
343 lines
8.7 KiB
Go
package day23
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"gitea.paas.celticinfo.fr/oabrivard/aoc2023/utils"
|
|
)
|
|
|
|
type Coord struct {
|
|
row int
|
|
col int
|
|
}
|
|
|
|
var cache map[Coord]int = map[Coord]int{}
|
|
|
|
func getNeighbors(matrix [][]byte, point Coord) []Coord {
|
|
result := []Coord{}
|
|
height := len(matrix)
|
|
width := len(matrix[0])
|
|
|
|
switch matrix[point.row][point.col] {
|
|
case '^':
|
|
if point.row > 0 && matrix[point.row-1][point.col] != 'O' && matrix[point.row-1][point.col] != '#' && matrix[point.row-1][point.col] != 'v' {
|
|
result = append(result, Coord{point.row - 1, point.col})
|
|
}
|
|
case 'v':
|
|
if point.row < height-1 && matrix[point.row+1][point.col] != 'O' && matrix[point.row+1][point.col] != '#' && matrix[point.row+1][point.col] != '^' {
|
|
result = append(result, Coord{point.row + 1, point.col})
|
|
}
|
|
case '>':
|
|
if point.col < width-1 && matrix[point.row][point.col+1] != 'O' && matrix[point.row][point.col+1] != '#' && matrix[point.row][point.col+1] != '<' {
|
|
result = append(result, Coord{point.row, point.col + 1})
|
|
}
|
|
case '<':
|
|
if point.col > 0 && matrix[point.row][point.col-1] != 'O' && matrix[point.row][point.col-1] != '#' && matrix[point.row][point.col-1] != '>' {
|
|
result = append(result, Coord{point.row, point.col - 1})
|
|
}
|
|
case '.':
|
|
if point.row > 0 && matrix[point.row-1][point.col] != 'O' && matrix[point.row-1][point.col] != '#' && matrix[point.row-1][point.col] != 'v' {
|
|
result = append(result, Coord{point.row - 1, point.col})
|
|
}
|
|
if point.row < height-1 && matrix[point.row+1][point.col] != 'O' && matrix[point.row+1][point.col] != '#' && matrix[point.row+1][point.col] != '^' {
|
|
result = append(result, Coord{point.row + 1, point.col})
|
|
}
|
|
if point.col > 0 && matrix[point.row][point.col-1] != 'O' && matrix[point.row][point.col-1] != '#' && matrix[point.row][point.col-1] != '>' {
|
|
result = append(result, Coord{point.row, point.col - 1})
|
|
}
|
|
if point.col < width-1 && matrix[point.row][point.col+1] != 'O' && matrix[point.row][point.col+1] != '#' && matrix[point.row][point.col+1] != '<' {
|
|
result = append(result, Coord{point.row, point.col + 1})
|
|
}
|
|
case 'O':
|
|
// nothing to do, since we already explored this point
|
|
default:
|
|
log.Fatalln("Impossible state")
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func findLongestPath(matrix [][]byte, start, end Coord) int {
|
|
|
|
if length, found := cache[start]; found {
|
|
return length
|
|
}
|
|
|
|
//fmt.Printf("(%d,%d) ", start.row, start.col)
|
|
if start.row == 21 && start.col == 17 {
|
|
fmt.Print("break")
|
|
}
|
|
|
|
if start == end {
|
|
return 0
|
|
}
|
|
|
|
maxlength := -1
|
|
|
|
neighbors := getNeighbors(matrix, start)
|
|
old := matrix[start.row][start.col]
|
|
matrix[start.row][start.col] = 'O'
|
|
|
|
for _, neighbor := range neighbors {
|
|
length := findLongestPath(matrix, neighbor, end)
|
|
|
|
if length > maxlength {
|
|
maxlength = length
|
|
}
|
|
}
|
|
|
|
matrix[start.row][start.col] = old
|
|
|
|
if maxlength == -1 {
|
|
return -1
|
|
}
|
|
|
|
//fmt.Printf("(%d,%d) = %d\n", start.row, start.col, maxlength+1)
|
|
|
|
cache[start] = maxlength + 1
|
|
return maxlength + 1
|
|
}
|
|
|
|
func Part1(lines []string) int {
|
|
matrix := [][]byte{}
|
|
|
|
for _, line := range lines {
|
|
matrix = append(matrix, []byte(line))
|
|
}
|
|
|
|
height := len(matrix)
|
|
width := len(matrix[0])
|
|
|
|
start := Coord{0, 1}
|
|
end := Coord{height - 1, width - 2}
|
|
result := findLongestPath(matrix, start, end)
|
|
|
|
return result
|
|
}
|
|
|
|
type Vertice struct {
|
|
ID int
|
|
coord Coord
|
|
edges map[int]*Edge
|
|
treated bool
|
|
}
|
|
|
|
type Edge struct {
|
|
left *Vertice
|
|
right *Vertice
|
|
cost int
|
|
}
|
|
|
|
var verticeID int = 0
|
|
var vertices map[int]*Vertice = map[int]*Vertice{}
|
|
|
|
func findExitCoords(matrix [][]byte, point, previous Coord) []Coord {
|
|
height := len(matrix)
|
|
width := len(matrix[0])
|
|
result := []Coord{}
|
|
|
|
if point.row > 0 && matrix[point.row-1][point.col] != '#' && previous.row != point.row-1 {
|
|
result = append(result, Coord{point.row - 1, point.col})
|
|
}
|
|
if point.row < height-1 && matrix[point.row+1][point.col] != '#' && previous.row != point.row+1 {
|
|
result = append(result, Coord{point.row + 1, point.col})
|
|
}
|
|
if point.col > 0 && matrix[point.row][point.col-1] != '#' && previous.col != point.col-1 {
|
|
result = append(result, Coord{point.row, point.col - 1})
|
|
}
|
|
if point.col < width-1 && matrix[point.row][point.col+1] != '#' && previous.col != point.col+1 {
|
|
result = append(result, Coord{point.row, point.col + 1})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func findNextJunction(matrix [][]byte, curr, previous, end Coord) (Coord, Coord, int) {
|
|
exitCoords := findExitCoords(matrix, curr, previous)
|
|
dist := 1
|
|
for len(exitCoords) == 1 && exitCoords[0] != end {
|
|
previous = curr
|
|
curr = exitCoords[0]
|
|
dist++
|
|
exitCoords = findExitCoords(matrix, curr, previous)
|
|
}
|
|
|
|
return curr, previous, dist
|
|
}
|
|
|
|
func buildGraph(matrix [][]byte, treated map[Coord]*Vertice, curr, previous, end Coord) *Vertice {
|
|
if vertice, found := treated[curr]; found {
|
|
return vertice
|
|
}
|
|
|
|
verticeID++
|
|
currVertice := Vertice{verticeID, curr, map[int]*Edge{}, false}
|
|
vertices[currVertice.ID] = &currVertice
|
|
treated[curr] = &currVertice
|
|
|
|
exitCoords := findExitCoords(matrix, curr, previous)
|
|
// here we have a junction or the end
|
|
for _, exitCoord := range exitCoords {
|
|
nextCoord, prevCoord, dist := findNextJunction(matrix, exitCoord, curr, end)
|
|
// create a vertice for next Coord
|
|
nextVertice := buildGraph(matrix, treated, nextCoord, prevCoord, end)
|
|
|
|
// Create two edges (one for both directions of the relationship)
|
|
edgeLeft := Edge{&currVertice, nextVertice, dist}
|
|
edgeRight := Edge{nextVertice, &currVertice, dist}
|
|
|
|
// add the left edge to the current vertice
|
|
currVertice.edges[nextVertice.ID] = &edgeLeft
|
|
|
|
// add the right edge to the target vertice
|
|
nextVertice.edges[currVertice.ID] = &edgeRight
|
|
}
|
|
|
|
return &currVertice
|
|
}
|
|
|
|
/*
|
|
type Vertice struct {
|
|
ID int
|
|
coord Coord
|
|
edges []*Edge
|
|
treated bool
|
|
}
|
|
|
|
type Edge struct {
|
|
left *Vertice
|
|
right *Vertice
|
|
cost int
|
|
}
|
|
|
|
var verticeID int = 0
|
|
|
|
func findExitCoords(matrix [][]byte, point Coord) []Coord {
|
|
height := len(matrix)
|
|
width := len(matrix[0])
|
|
result := []Coord{}
|
|
|
|
if point.row > 0 && matrix[point.row-1][point.col] != '#' {
|
|
result = append(result, Coord{point.row - 1, point.col})
|
|
}
|
|
if point.row < height-1 && matrix[point.row+1][point.col] != '#' {
|
|
result = append(result, Coord{point.row + 1, point.col})
|
|
}
|
|
if point.col > 0 && matrix[point.row][point.col-1] != '#' {
|
|
result = append(result, Coord{point.row, point.col - 1})
|
|
}
|
|
if point.col < width-1 && matrix[point.row][point.col+1] != '#' {
|
|
result = append(result, Coord{point.row, point.col + 1})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func findNextJunction(matrix [][]byte, curr, end Coord) (Coord, int) {
|
|
exitCoords := findExitCoords(matrix, curr)
|
|
dist := 1
|
|
for len(exitCoords) == 2 && exitCoords[0] != end && exitCoords[1] != end {
|
|
curr = exitCoords[0]
|
|
dist++
|
|
exitCoords = findExitCoords(matrix, curr)
|
|
}
|
|
|
|
return curr, dist
|
|
}
|
|
|
|
func buildGraph(matrix [][]byte, treated map[Coord]*Vertice, curr, end Coord) *Vertice {
|
|
if vertice, found := treated[curr]; found {
|
|
return vertice
|
|
}
|
|
|
|
verticeID++
|
|
currVertice := Vertice{verticeID, curr, []*Edge{}, false}
|
|
treated[curr] = &currVertice
|
|
|
|
exitCoords := findExitCoords(matrix, curr)
|
|
// here we have a junction or the end
|
|
for _, exitCoord := range exitCoords {
|
|
nextCoord, dist := findNextJunction(matrix, exitCoord, end)
|
|
// create a node for next Coord
|
|
nextNode := buildGraph(matrix, treated, nextCoord, end)
|
|
|
|
// Create an edge
|
|
edge := Edge{&currVertice, nextNode, dist}
|
|
|
|
// add this edge to the current node
|
|
currVertice.edges = append(currVertice.edges, &edge)
|
|
}
|
|
|
|
return &currVertice
|
|
}
|
|
*/
|
|
func printGraph(start *Vertice) {
|
|
if start.treated {
|
|
return
|
|
}
|
|
start.treated = true
|
|
for _, edge := range start.edges {
|
|
fmt.Printf("(%d,%d) --%d--> (%d,%d)\n", edge.left.coord.row, edge.left.coord.col, edge.cost, edge.right.coord.row, edge.right.coord.col)
|
|
}
|
|
|
|
for _, edge := range start.edges {
|
|
printGraph(edge.right)
|
|
}
|
|
|
|
}
|
|
|
|
func longestPath(start *Vertice, distSoFar int, visited map[int]bool, end Coord) int {
|
|
if start.coord == end {
|
|
return distSoFar
|
|
}
|
|
|
|
if visited[start.ID] {
|
|
return 0
|
|
}
|
|
|
|
visited[start.ID] = true
|
|
|
|
maximum := -1
|
|
|
|
for _, edge := range start.edges {
|
|
vertex := edge.right
|
|
visitedClone := utils.CloneMap(visited)
|
|
dist := longestPath(vertex, distSoFar+edge.cost, visitedClone, end)
|
|
|
|
if dist > maximum {
|
|
maximum = dist
|
|
}
|
|
}
|
|
|
|
return maximum
|
|
}
|
|
|
|
func Part2(lines []string) int {
|
|
height := len(lines)
|
|
width := len(lines[0])
|
|
matrix := make([][]byte, height)
|
|
|
|
for row, line := range lines {
|
|
matrix[row] = make([]byte, width)
|
|
for col, r := range line {
|
|
if r == '#' {
|
|
matrix[row][col] = '#'
|
|
} else {
|
|
matrix[row][col] = '.'
|
|
}
|
|
}
|
|
}
|
|
|
|
treated := map[Coord]*Vertice{}
|
|
start := Coord{0, 1}
|
|
end := Coord{height - 1, width - 2}
|
|
graph := buildGraph(matrix, treated, start, Coord{-1, -1}, end)
|
|
|
|
printGraph(graph)
|
|
|
|
result := longestPath(graph, 0, map[int]bool{}, end)
|
|
|
|
return result
|
|
}
|