ginger/graph/graph.go

279 lines
5.6 KiB
Go
Raw Normal View History

// Package graph implements a generic directed graph type, with support for
// tuple vertices in addition to traditional "value" vertices.
package graph
import (
"fmt"
"strings"
)
// Value is any value which can be stored within a Graph.
type Value interface {
Equal(Value) bool
String() string
}
// OpenEdge is an un-realized Edge which can't be used for anything except
// constructing graphs. It has no meaning on its own.
type OpenEdge[V Value] struct {
val *V
tup []OpenEdge[V]
edgeVal V
}
func (oe OpenEdge[V]) equal(oe2 OpenEdge[V]) bool {
if !oe.edgeVal.Equal(oe2.edgeVal) {
return false
}
if oe.val != nil {
return oe2.val != nil && (*oe.val).Equal(*oe2.val)
}
if len(oe.tup) != len(oe2.tup) {
return false
}
for i := range oe.tup {
if !oe.tup[i].equal(oe2.tup[i]) {
return false
}
}
return true
}
func (oe OpenEdge[V]) String() string {
vertexType := "tup"
var fromStr string
if oe.val != nil {
vertexType = "val"
fromStr = (*oe.val).String()
} else {
strs := make([]string, len(oe.tup))
for i := range oe.tup {
strs[i] = oe.tup[i].String()
}
fromStr = fmt.Sprintf("[%s]", strings.Join(strs, ", "))
}
return fmt.Sprintf("%s(%s, %s)", vertexType, fromStr, oe.edgeVal.String())
}
// WithEdgeValue returns a copy of the OpenEdge with the given Value replacing
// the previous edge value.
//
// NOTE I _think_ this can be factored out once Graph is genericized.
func (oe OpenEdge[V]) WithEdgeValue(val V) OpenEdge[V] {
oe.edgeVal = val
return oe
}
// EdgeValue returns the Value which lies on the edge itself.
func (oe OpenEdge[V]) EdgeValue() V {
return oe.edgeVal
}
// FromValue returns the Value from which the OpenEdge was created via ValueOut,
// or false if it wasn't created via ValueOut.
func (oe OpenEdge[V]) FromValue() (V, bool) {
if oe.val == nil {
var zero V
return zero, false
}
return *oe.val, true
}
// FromTuple returns the tuple of OpenEdges from which the OpenEdge was created
// via TupleOut, or false if it wasn't created via TupleOut.
func (oe OpenEdge[V]) FromTuple() ([]OpenEdge[V], bool) {
if oe.val != nil {
return nil, false
}
return oe.tup, true
}
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents
// an edge (with edgeVal attached to it) coming from the ValueVertex containing
// val.
func ValueOut[V Value](val, edgeVal V) OpenEdge[V] {
return OpenEdge[V]{val: &val, edgeVal: edgeVal}
}
// TupleOut creates an OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) coming from the
// TupleVertex comprised of the given ordered-set of input edges.
//
// If len(ins) == 1 && edgeVal.IsZero(), then that single OpenEdge is
// returned as-is.
func TupleOut[V Value](ins []OpenEdge[V], edgeVal V) OpenEdge[V] {
if len(ins) == 1 {
in := ins[0]
var zero V
if edgeVal.Equal(zero) {
return in
}
if in.edgeVal.Equal(zero) {
in.edgeVal = edgeVal
return in
}
}
return OpenEdge[V]{
tup: ins,
edgeVal: edgeVal,
}
}
type graphValueIn[V Value] struct {
val V
edges []OpenEdge[V]
}
func (valIn graphValueIn[V]) cp() graphValueIn[V] {
cp := valIn
cp.edges = make([]OpenEdge[V], len(valIn.edges))
copy(cp.edges, valIn.edges)
return valIn
}
func (valIn graphValueIn[V]) equal(valIn2 graphValueIn[V]) bool {
if !valIn.val.Equal(valIn2.val) {
return false
}
if len(valIn.edges) != len(valIn2.edges) {
return false
}
outer:
for _, edge := range valIn.edges {
for _, edge2 := range valIn2.edges {
if edge.equal(edge2) {
continue outer
}
}
return false
}
return true
}
// Graph is an immutable container of a set of vertices. The Graph keeps track
// of all Values which terminate an OpenEdge (which may be a tree of Value/Tuple
// vertices).
//
// NOTE The current implementation of Graph is incredibly inefficient, there's
// lots of O(N) operations, unnecessary copying on changes, and duplicate data
// in memory.
type Graph[V Value] struct {
valIns []graphValueIn[V]
}
func (g *Graph[V]) cp() *Graph[V] {
cp := &Graph[V]{
valIns: make([]graphValueIn[V], len(g.valIns)),
}
copy(cp.valIns, g.valIns)
return cp
}
func (g *Graph[V]) String() string {
var strs []string
for _, valIn := range g.valIns {
for _, oe := range valIn.edges {
strs = append(
strs,
fmt.Sprintf("valIn(%s, %s)", oe.String(), valIn.val.String()),
)
}
}
return fmt.Sprintf("graph(%s)", strings.Join(strs, ", "))
}
// ValueIns returns, if any, all OpenEdges which lead to the given Value in the
// Graph (ie, all those added via AddValueIn).
func (g *Graph[V]) ValueIns(val Value) []OpenEdge[V] {
for _, valIn := range g.valIns {
if valIn.val.Equal(val) {
return valIn.cp().edges
}
}
return nil
}
// AddValueIn takes a OpenEdge and connects it to the Value vertex containing
// val, returning the new Graph which reflects that connection.
func (g *Graph[V]) AddValueIn(oe OpenEdge[V], val V) *Graph[V] {
edges := g.ValueIns(val)
for _, existingOE := range edges {
if existingOE.equal(oe) {
return g
}
}
// ValueIns returns a copy of edges, so we're ok to modify it.
edges = append(edges, oe)
valIn := graphValueIn[V]{val: val, edges: edges}
g = g.cp()
for i, existingValIn := range g.valIns {
if existingValIn.val.Equal(val) {
g.valIns[i] = valIn
return g
}
}
g.valIns = append(g.valIns, valIn)
return g
}
// Equal returns whether or not the two Graphs are equivalent in value.
func (g *Graph[V]) Equal(g2 *Graph[V]) bool {
if len(g.valIns) != len(g2.valIns) {
return false
}
outer:
for _, valIn := range g.valIns {
for _, valIn2 := range g2.valIns {
if valIn.equal(valIn2) {
continue outer
}
}
return false
}
return true
}