Advent-of-Code-2023/day23/graph.go

249 lines
5.6 KiB
Go

package day23
import (
"fmt"
"log"
"slices"
mapset "github.com/deckarep/golang-set/v2"
)
type Node struct {
index int
c Coord
name string
}
func (n Node)Name() string {
var r string
if n.index < 25 {
num := 'A' + n.index
r = string(rune(num))
} else {
num := 'a' + n.index - 25
r = string(rune(num))
}
return r
}
type Graph struct {
nodes map[Coord]Node
nodesByIndex []Node
edges [][]int // from, to, length. excluding from, including to
}
func MaxDist(from, to Node) (result int) {
return
}
func PrintFieldWithGraph(g Graph, f Field) (result string) {
result += "\n"
for row := 0; row <= f.MaxRow; row++ {
for col := 0; col <= f.MaxCol; col++ {
symb := f.Cells[Coord{Row: row, Col: col}]
if symb != Tree {
coord := Coord{Row: row, Col: col}
node, exists := g.nodes[coord]
if exists {
result += fmt.Sprint(node.Name())
} else {
result += "."
}
} else {
result += " "
}
}
result += "\n"
}
return
}
func CreateGraph(f Field) (g Graph) {
startCoord := Coord{Row: 0, Col: f.StartCol}
// directly below start
initialPath := PathEnd{
end: Coord{Row: 1, Col: f.StartCol}, visited: mapset.NewSet[Coord](),
}
g = Graph{
nodes: map[Coord]Node{
startCoord: Node{
index: 0,
c: startCoord,
name: "A",
},
},
}
const presumedNodeCount = 36
g.edges = make([][]int, presumedNodeCount)
for i := 0; i < presumedNodeCount; i++ {
g.edges[i] = make([]int, presumedNodeCount)
}
recursiveGraphStep(f, initialPath, &g, startCoord, 1, mapset.NewSet[Coord]())
g.edges[0][0] = 0
g.nodesByIndex = make([]Node, len(g.nodes))
for _, node := range g.nodes {
g.nodesByIndex[node.index] = node
}
return
}
func (g *Graph)Neighbors(node Node) (nodes []Node) {
index := node.index
for toIndex, len := range g.edges[index] {
if len > 0 {
nodes = append(nodes, g.nodesByIndex[toIndex])
}
}
return
}
var maxSoFar int = -1
func CheckMaxSoFar(maybe int) {
if maybe > maxSoFar {
maxSoFar = maybe
}
}
func (g *Graph) DFSLenOnGraph(atNode Node, visited mapset.Set[int],
toNode Node, lenSoFar int) int {
if atNode == toNode {
CheckMaxSoFar(lenSoFar)
return lenSoFar
}
log.Printf("at %+v to %+v cur dist is %d.\t\t|max so far %d| \n", atNode, toNode, lenSoFar, maxSoFar)
neighbors := g.Neighbors(atNode)
toVisit := slices.DeleteFunc(neighbors, func(n Node) bool {
return visited.Contains(n.index)
})
if len(toVisit) == 0 {
return -1
}
max := -1
for _, nextNode := range toVisit {
newVisited := visited.Clone()
newVisited.Add(atNode.index)
dist := g.edges[atNode.index][nextNode.index]
maxFromNext := g.DFSLenOnGraph(nextNode, newVisited, toNode, lenSoFar + dist)
if maxFromNext > max {
max = maxFromNext
}
}
return max
}
// run dfs, remembering from which remembers from which node we go, which path already traversed
func recursiveGraphStep(f Field, p PathEnd, g *Graph, goingFrom Coord, goingLen int, visitedPathPoints mapset.Set[Coord]) {
// log.Printf("entering coord %+v. from %+v with len %d\n", p.end, goingFrom, goingLen)
// if visitedPathPoints.Contains(p.end) {
// return
// }
neighbors := f.NeighborsPart2(p.end)
isCrossRoad := len(neighbors) > 2
if isCrossRoad {
log.Println("this should be crossroad ", p.end)
}
isStart := p.end == Coord{Row: 0, Col: f.StartCol}
isEnd := p.end == f.EndCoord()
if isEnd {
log.Println("this should be end ", p.end)
}
isNode := isCrossRoad || isStart || isEnd
continuedPaths := ExtendPath(p, f)
if !isNode {
// just recurse into next paths, from same node, with increased len
visitedPathPoints.Add(p.end)
for _, nextStep := range continuedPaths {
recursiveGraphStep(f, nextStep, g, goingFrom, goingLen+1, visitedPathPoints)
}
} else {
node, known := g.nodes[p.end]
// check if known, if not known - create
if !known {
node = Node{
c: p.end,
index: len(g.nodes),
}
node.name = node.Name()
g.nodes[p.end] = node
log.Printf("creating node %s %+v\n", node.Name(), node)
}
from := g.nodes[goingFrom]
log.Printf("from %s to %s\n", from.Name(), node.Name())
// and add vertices to currently traversed
if g.edges[node.index][from.index] == 0 {
g.edges[node.index][from.index] = goingLen
g.edges[from.index][node.index] = goingLen
} else {
knownEdge := g.edges[node.index][from.index]
if goingLen > knownEdge {
g.edges[node.index][from.index] = goingLen
g.edges[from.index][node.index] = goingLen
}
}
// NOTE ah, it's possible to have two edges between i and j
// but, i only need the longest one
// log.Printf("adding edges between %d & %d of len %d\n", node.index, from.index, goingLen)
// continue with new 'from' and len of 1
if !known {
for _, nextStep := range continuedPaths {
log.Printf("from %s should recurse to %+v", node.Name(), nextStep)
recursiveGraphStep(f, nextStep, g, p.end, 1, visitedPathPoints)
}
}
}
return
}
func GraphToMermaid(g Graph) (result string) {
result += "\nflowchart LR\n"
lines := mapset.NewSet[string]()
for _, node := range g.nodes {
for to, len := range g.edges[node.index] {
var toNode Node
for _, other := range g.nodes {
if other.index == to {
toNode = other
}
}
if len > 0 {
var fromName, toName string
if node.index < toNode.index {
fromName = node.Name()
toName = toNode.Name()
} else {
fromName = toNode.Name()
toName = node.Name()
}
line := fmt.Sprintf("\t%s---|length %d|%s\n", fromName, len, toName)
lines.Add(line)
}
}
// result += fmt.Sprintf("%s--|%d|%s\n", a ...any)
}
for line := range lines.Iter() {
result += line
}
return
}