github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/memmodel/graph.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "io" 10 "strings" 11 ) 12 13 type Graph struct { 14 Nodes []*GNode 15 } 16 17 type GNode struct { 18 ID int 19 Label string 20 In map[int]bool 21 Out map[int]bool 22 } 23 24 func (g *Graph) NewNode(label string) *GNode { 25 id := len(g.Nodes) 26 node := &GNode{ID: id, Label: label, In: make(map[int]bool), Out: make(map[int]bool)} 27 g.Nodes = append(g.Nodes, node) 28 return node 29 } 30 31 func (g *Graph) Edge(from, to *GNode) { 32 from.Out[to.ID] = true 33 to.In[from.ID] = true 34 } 35 36 func (g *Graph) RemoveEdge(from, to *GNode) { 37 delete(from.Out, to.ID) 38 delete(to.In, from.ID) 39 } 40 41 func (g *Graph) MaximalCliques() [][]int { 42 cliques := [][]int{} 43 44 have := make([]bool, len(g.Nodes)) 45 for id := range g.Nodes { 46 if have[id] { 47 continue 48 } 49 50 clique := []int{id} 51 have[id] = true 52 for oid := id + 1; oid < len(g.Nodes); oid++ { 53 if have[oid] { 54 continue 55 } 56 57 // If this node it connected to all nodes in 58 // clique, add it to the clique. 59 onode := g.Nodes[oid] 60 for _, cid := range clique { 61 if !onode.In[cid] || !onode.Out[cid] { 62 goto notIn 63 } 64 } 65 clique = append(clique, oid) 66 have[oid] = true 67 68 notIn: 69 } 70 71 cliques = append(cliques, clique) 72 } 73 74 return cliques 75 } 76 77 func (g *Graph) CollapseNodes(groups [][]int) *Graph { 78 out := new(Graph) 79 80 // Create a node for each group. 81 groupNodes := []*GNode{} 82 oldToNew := make([]*GNode, len(g.Nodes)) 83 for _, group := range groups { 84 label := []string{} 85 for _, id := range group { 86 label = append(label, g.Nodes[id].Label) 87 } 88 groupNode := out.NewNode(strings.Join(label, "\n")) 89 groupNodes = append(groupNodes, groupNode) 90 for _, id := range group { 91 oldToNew[id] = groupNode 92 } 93 } 94 95 // Map old edges to new edges. 96 for oid, oldNode := range g.Nodes { 97 newNode := oldToNew[oid] 98 if newNode == nil { 99 continue 100 } 101 for to := range oldNode.Out { 102 newTo := oldToNew[to] 103 if newTo == nil { 104 continue 105 } 106 if newTo == newNode && g.Nodes[to] != oldNode { 107 // Eliminate edges within groups, 108 // unless they were originally 109 // self-edges. 110 continue 111 } 112 out.Edge(newNode, newTo) 113 } 114 } 115 116 return out 117 } 118 119 func (g *Graph) TransitiveReduction() { 120 // TODO: This assumes a DAG; it doesn't work with cycles. 121 type edge struct{ from, to *GNode } 122 toRemove := make(map[edge]bool) 123 visited := make([]bool, len(g.Nodes)) 124 var rec func(from, to *GNode, remove bool) 125 rec = func(from, to *GNode, remove bool) { 126 if remove { 127 if visited[to.ID] { 128 return 129 } 130 visited[to.ID] = true 131 toRemove[edge{from, to}] = true 132 } 133 for next := range to.Out { 134 rec(from, g.Nodes[next], true) 135 } 136 } 137 for _, node := range g.Nodes { 138 for i := range visited { 139 visited[i] = false 140 } 141 142 // Do a DFS starting from each node reachable from 143 // node and remove edges from node. 144 for child := range node.Out { 145 rec(node, g.Nodes[child], false) 146 } 147 } 148 for edge := range toRemove { 149 g.RemoveEdge(edge.from, edge.to) 150 } 151 152 } 153 154 func (g *Graph) ToDot(w io.Writer, nodePrefix string) { 155 for id, node := range g.Nodes { 156 name := fmt.Sprintf("%s%d", nodePrefix, id) 157 fmt.Fprintf(w, "%s [label=%q];\n", name, node.Label) 158 for oid := range node.Out { 159 fmt.Fprintf(w, "%s -> %s%d;\n", name, nodePrefix, oid) 160 } 161 } 162 }