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

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
}