ginger/gim/constraint/constraint.go

110 lines
2.6 KiB
Go

// Package constraint implements an extremely simple constraint engine.
// Elements, and constraints on those elements, are given to the engine, which
// uses those constraints to generate an output. Elements are defined as a
// string
package constraint
import (
"github.com/mediocregopher/ginger/gg"
)
// Constraint describes a constraint on an element. The Elem field must be
// filled in, as well as exactly one other field
type Constraint struct {
Elem string
// LT says that Elem is less than this element
LT string
}
const ltEdge = gg.Str("lt")
// Engine processes sets of constraints to generate an output
type Engine struct {
g *gg.Graph
}
// NewEngine initializes and returns an empty Engine
func NewEngine() *Engine {
return &Engine{g: gg.Null}
}
// AddConstraint adds the given constraint to the engine's set, returns false if
// the constraint couldn't be added due to a conflict with a previous constraint
func (e *Engine) AddConstraint(c Constraint) bool {
elem := gg.Str(c.Elem)
g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), gg.Str(c.LT))
// Check for loops in g starting at c.Elem, bail if there are any
{
seen := map[*gg.Vertex]bool{}
start := g.Value(elem)
var hasLoop func(v *gg.Vertex) bool
hasLoop = func(v *gg.Vertex) bool {
if seen[v] {
return v == start
}
seen[v] = true
for _, out := range v.Out {
if hasLoop(out.To) {
return true
}
}
return false
}
if hasLoop(start) {
return false
}
}
e.g = g
return true
}
// Solve uses the constraints which have been added to the engine to give a
// possible solution. The given element is one which has been added to the
// engine and whose value is known to be zero.
func (e *Engine) Solve() map[string]int {
m := map[string]int{}
if len(e.g.Values()) == 0 {
return m
}
vElem := func(v *gg.Vertex) string {
return string(v.Value.(gg.Str))
}
// first the roots are determined to be the elements with no In edges, which
// _must_ exist since the graph presumably has no loops
var roots []*gg.Vertex
e.g.Walk(nil, func(v *gg.Vertex) bool {
if len(v.In) == 0 {
roots = append(roots, v)
m[vElem(v)] = 0
}
return true
})
// sanity check
if len(roots) == 0 {
panic("no roots found in graph somehow")
}
// a vertex's value is then the length of the longest path from it to one of
// the roots
var walk func(*gg.Vertex, int)
walk = func(v *gg.Vertex, val int) {
if elem := vElem(v); val > m[elem] {
m[elem] = val
}
for _, out := range v.Out {
walk(out.To, val+1)
}
}
for _, root := range roots {
walk(root, 0)
}
return m
}