110 lines
2.6 KiB
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
|
||
|
}
|