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) (n Node) { firstSplit := strings.Split(l, ":") n.Name = firstSplit[0] n.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(n.Name) n.Neighbors.Add(neighbor.Name) } g.Nodes[n.Name] = &n 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) } } 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 != nodeB.Name { newBNeighbors.Add(oldNeighbor) } } 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 } initialVisited := mapset.NewSet[string](firstNode.Name) for neighborName := range firstNode.Neighbors.Iter() { 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) { return } atNode := g.Nodes[atName] if visited.Contains(atName) { return fromName, atName, true } newVisited := visited.Clone() newVisited.Add(atName) for neighborName := range atNode.Neighbors.Iter() { cycleFrom, cycleTo, cycleExists = g.dfcCycle(atName, neighborName, newVisited) if cycleExists { break } } return } func (g *Graph) DoThreeRemovals() { from, to, found := g.findCycle() log.Printf("first result %s, %s, %t", from, to, found) // graph_test.go:26: first result xhk, rhn, true g.RemoveEdge("xhk", "rhn") from, to, found = g.findCycle() log.Printf("second result %s, %s, %t", from, to, found) // graph_test.go:32: second result qnr, nvd, true g.RemoveEdge("qnr", "nvd") from, to, found = g.findCycle() log.Printf("third result %s, %s, %t", from, to, found) // graph_test.go:36: third result lhk, cmg, true g.RemoveEdge("lhk", "cmg") from, to, found = g.findCycle() log.Printf("fourth result %s, %s, %t", from, to, found) // OK, so there could be more than 3 cycles. // but if i want to break things into 2 components, i need to do more investigating 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 { edgeToRemove := CreateEdge(from, to) removedEdges.Add(edgeToRemove) g.RemoveEdge(from, to) } } return }