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

302 lines
7.5 KiB
Go

package day25
import (
"fmt"
"log"
"os"
"strings"
mapset "github.com/deckarep/golang-set/v2"
)
type Graph struct {
Nodes map[string]*Node
}
type Node struct {
Name string
Neighbors mapset.Set[string]
}
func (n Node) String() string {
return fmt.Sprintf("[%s : %+v]", n.Name, n.Neighbors)
}
func ReadGraphFile(filename string) (g Graph) {
g.Nodes = map[string]*Node{}
bytes, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
text := strings.TrimSpace(string(bytes))
for _, line := range strings.Split(text, "\n") {
g.readGraphLine(line)
}
return
}
func (g *Graph) readGraphLine(l string) {
firstSplit := strings.Split(l, ":")
node, exists := g.Nodes[firstSplit[0]]
if !exists {
node = &Node{
Name: firstSplit[0],
Neighbors: mapset.NewSet[string](),
}
}
secondSplit := strings.Fields(firstSplit[1])
for _, neighborName := range secondSplit {
neighbor, exists := g.Nodes[neighborName]
if !exists {
neighbor = &Node{
Name: neighborName,
Neighbors: mapset.NewSet[string](),
}
g.Nodes[neighborName] = neighbor
}
neighbor.Neighbors.Add(node.Name)
node.Neighbors.Add(neighbor.Name)
}
g.Nodes[node.Name] = node
return
}
// NOTE this is so sad. nodeA.Neighbors.Remove(nodeB.Name) hangs for a reason i don't understand
func (g *Graph) RemoveEdge(a, b string) {
// log.Printf("entering remove edge for %s and %s", a, b)
nodeA, existsA := g.Nodes[a]
// log.Println("got first node", nodeA, existsA)
nodeB, existsB := g.Nodes[b]
// log.Println("got second node", nodeB, existsB)
if !existsA || !existsB {
panic("requesting not found node")
}
// log.Println("before removals")
// log.Println("before remove first", nodeA)
// nodeA.Neighbors = newANeighbors
nodeA.Neighbors = nodeA.Neighbors.Difference(mapset.NewSet[string](nodeB.Name))
// nodeA.Neighbors.Remove(nodeB.Name)
// log.Println("removed first", nodeA)
// log.Println("before remove second", nodeB)
// nodeB.Neighbors = newBNeighbors
nodeB.Neighbors = nodeB.Neighbors.Difference(mapset.NewSet[string](nodeA.Name))
// nodeB.Neighbors.Remove(nodeA.Name)
// log.Println("removed second", nodeB)
}
func (g *Graph) AddEdge(a, b string) {
nodeA, existsA := g.Nodes[a]
nodeB, existsB := g.Nodes[b]
if !existsA || !existsB {
panic("requesting not found node")
}
nodeA.Neighbors.Add(nodeB.Name)
nodeB.Neighbors.Add(nodeA.Name)
}
func (g *Graph) findCycle() (from, to string, exists bool) {
// log.Printf(">>>> starting new find cycle")
var firstNode *Node
for _, n := range g.Nodes {
firstNode = n
break
}
// log.Printf("initial search from %s and neighbors %+v", firstNode.Name, firstNode.Neighbors)
for neighborName := range firstNode.Neighbors.Iter() {
initialVisited := mapset.NewSet[string](firstNode.Name)
// log.Printf("initial dfs from %s to %s with initial visited %+v", firstNode.Name, neighborName, initialVisited)
from, to, exists = g.dfcCycle(firstNode.Name, neighborName, initialVisited)
if exists {
break
}
}
// log.Printf("<<<< cycle %t, from %s to %s", exists, from, to)
return
}
func (g *Graph) dfcCycle(fromName, atName string, visited mapset.Set[string]) (cycleFrom, cycleTo string, cycleExists bool) {
// log.Printf("> step from %+v to %+v. visited : %+v", fromName, atName, visited)
if visited.Cardinality() == len(g.Nodes) {
log.Println("exit by visited all")
return
}
atNode := g.Nodes[atName]
if visited.Contains(atName) {
return fromName, atName, true
}
for neighborName := range atNode.Neighbors.Iter() {
if neighborName == fromName {
continue
}
newVisited := visited.Clone()
newVisited.Add(atName)
cycleFrom, cycleTo, cycleExists = g.dfcCycle(atName, neighborName, newVisited)
if cycleExists {
break
}
}
return
}
func (g *Graph) ComponentFrom(fromName string) (component mapset.Set[string]) {
startNode := g.Nodes[fromName]
component = mapset.NewSet[string](startNode.Name)
toVisit := startNode.Neighbors.Clone()
for toVisit.Cardinality() > 0 {
runnerNodeName, _ := toVisit.Pop()
if component.Contains(runnerNodeName) {
continue
}
component.Add(runnerNodeName)
runnerNode := g.Nodes[runnerNodeName]
unvisitedNeighbors := runnerNode.Neighbors.Difference(component)
// log.Printf("adding %s to component. neighbors %+v, adding %+v to visit",
// runnerNodeName, runnerNode.Neighbors, unvisitedNeighbors)
toVisit = toVisit.Union(unvisitedNeighbors)
}
return
}
func (g *Graph) ToMermaid() (result string) {
result += "flowchart TD\n"
edges := mapset.NewSet[string]()
for _, node := range g.Nodes {
for neighborName := range node.Neighbors.Iter() {
var first, second string
if node.Name < neighborName {
first = node.Name
second = neighborName
} else {
first = neighborName
second = node.Name
}
edges.Add(fmt.Sprintf("\t%s --- %s\n", first, second))
}
}
for line := range edges.Iter() {
result += line
}
return
}
func (g *Graph)SaveAsMermaid(filename string) {
mmd := g.ToMermaid()
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func() {
if err := file.Close(); err != nil {
panic(err)
}
}()
file.WriteString(mmd)
}
type Edge struct {
smaller, bigger string
}
func (e Edge)String() string {
return fmt.Sprintf("%s/%s", e.smaller, e.bigger)
}
func CreateEdge(a, b string) Edge {
var smaller, bigger string
if a < b {
smaller = a
bigger = b
} else {
smaller = b
bigger = a
}
return Edge{smaller, bigger}
}
func (g *Graph) RemoveAllCycles() (removedEdges mapset.Set[Edge]) {
removedEdges = mapset.NewSet[Edge]()
hasCycle := true
var from, to string
for hasCycle {
from, to, hasCycle = g.findCycle()
if hasCycle {
// log.Printf("\n!!!! found cycle %s to %s\n", from, to)
edgeToRemove := CreateEdge(from, to)
removedEdges.Add(edgeToRemove)
g.RemoveEdge(from, to)
}
}
return
}
func (g *Graph) TryToSplit() (componentSizeMult int) {
// first remove all cycles
removedEdges := g.RemoveAllCycles()
g.SaveAsMermaid("after-removing-cycles.mmd")
// log.Printf("all removed edges %+v, two of them are necessary to split initial graph into 2 ", removedEdges)
triedEdges := mapset.NewSet[Edge]()
for _, node := range g.Nodes {
for neighborName := range node.Neighbors.Iter() {
edge := CreateEdge(neighborName, node.Name)
if triedEdges.Contains(edge) {
continue
}
triedEdges.Add(edge)
// first remove the edge
g.RemoveEdge(edge.bigger, edge.smaller)
// then ask for components of the nodes of removed edge
compA := g.ComponentFrom(edge.bigger)
compB := g.ComponentFrom(edge.smaller)
// iterate over the initially removed edges. only two of them should be 'connecting'
// i.e were necessary to remove
necessaryEdgesCount := 0
for initiallyRemovedEdge := range removedEdges.Iter() {
endA, endB := initiallyRemovedEdge.bigger, initiallyRemovedEdge.smaller
isNonNecessary := (compA.Contains(endA) && compA.Contains(endB)) || (compB.Contains(endA) && compB.Contains(endB))
if !isNonNecessary {
// log.Printf("with edge %+v test removed, the %+v also seems necessary", edge, initiallyRemovedEdge)
necessaryEdgesCount += 1
}
}
// log.Printf("with edge %+v test removed neessary count is %d", edge, necessaryEdgesCount)
// if we found 2 necessary, then our currently tried edge is the third necesary to remove
// and out two components are the searched
if necessaryEdgesCount == 2 {
return compA.Cardinality() * compB.Cardinality()
}
// in the end add edge back if not fitting
g.AddEdge(edge.bigger, edge.smaller)
}
}
// now huh. if we didn't find `necessaryEdgesCount == 2`
// that means 0, 1 or 3
return
}