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  }