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 } 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") newANeighbors := mapset.NewSet[string]() for oldNeighbor := range nodeA.Neighbors.Iter() { if oldNeighbor != nodeB.Name { newANeighbors.Add(oldNeighbor) } } log.Println("before remove first", nodeA) nodeA.Neighbors = newANeighbors // nodeA.Neighbors.Remove(nodeB.Name) log.Println("removed first", nodeA) newBNeighbors := mapset.NewSet[string]() for oldNeighbor := range nodeB.Neighbors.Iter() { if oldNeighbor != nodeA.Name { newBNeighbors.Add(oldNeighbor) } } log.Println("before remove second", nodeB) nodeB.Neighbors = newBNeighbors // nodeB.Neighbors.Remove(nodeA.Name) log.Println("removed second", nodeB) } 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 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 }