Skip to content

Commit

Permalink
[automata]: improve Graphviz code generation (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
moorara authored Nov 15, 2022
1 parent b3e149c commit 65bc11b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 72 deletions.
3 changes: 3 additions & 0 deletions automata/automata.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ func ToString(s string) String {
return res
}

// doubleKeyMap is a map (symbol table) data structure with two keys.
type doubleKeyMap[K1, K2, V any] symboltable.OrderedSymbolTable[K1, symboltable.OrderedSymbolTable[K2, V]]

var (
cmpState = generic.NewCompareFunc[State]()
cmpSymbol = generic.NewCompareFunc[Symbol]()
Expand Down
39 changes: 31 additions & 8 deletions automata/dfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package automata

import (
"fmt"
"strings"

"github.com/moorara/algo/generic"
"github.com/moorara/algo/internal/graphviz"
Expand All @@ -10,13 +11,11 @@ import (
"github.com/moorara/algo/symboltable"
)

type dfaTrans symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, State]]

// DFA implements a deterministic finite automaton.
type DFA struct {
Start State
Final States
trans dfaTrans
trans doubleKeyMap[State, Symbol, State]
}

// NewDFA creates a new deterministic finite automaton.
Expand Down Expand Up @@ -247,7 +246,7 @@ func (d *DFA) Minimize() *DFA {
}

// createGroupTrans create a map of states to symbols to the current partition's group representatives (instead of next states).
func (d *DFA) createGroupTrans(P set.Set[set.Set[State]], G set.Set[State]) dfaTrans {
func (d *DFA) createGroupTrans(P set.Set[set.Set[State]], G set.Set[State]) doubleKeyMap[State, Symbol, State] {
gtrans := symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[Symbol, State]](cmpState, eqSymbolState)

for _, s := range G.Members() { // For every state in the current group
Expand All @@ -270,7 +269,7 @@ func (d *DFA) createGroupTrans(P set.Set[set.Set[State]], G set.Set[State]) dfaT
}

// populateSubgroups creates new subgroups based on the transition map of a group and add them to the new partition.
func populateSubgroups(Pnew set.Set[set.Set[State]], gtrans dfaTrans) {
func populateSubgroups(Pnew set.Set[set.Set[State]], gtrans doubleKeyMap[State, Symbol, State]) {
eqFunc := func(a, b State) bool { return a == b }

kvs := gtrans.KeyValues()
Expand Down Expand Up @@ -344,12 +343,36 @@ func (d *DFA) Graphviz() string {
graph.AddNode(graphviz.NewNode(name, "", label, "", "", shape, "", ""))
}

// Group all the transitions with the same states and combine their symbols into one label

var edges doubleKeyMap[State, State, []string]
edges = symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[State, []string]](cmpState, nil)

for _, kv := range d.trans.KeyValues() {
from := fmt.Sprintf("%d", kv.Key)
from := kv.Key
tab, exist := edges.Get(from)
if !exist {
tab = symboltable.NewRedBlack[State, []string](cmpState, nil)
edges.Put(from, tab)
}

for _, kv := range kv.Val.KeyValues() {
label := string(kv.Key)
to := fmt.Sprintf("%d", kv.Val)
symbol, to := string(kv.Key), kv.Val
vals, _ := tab.Get(to)
vals = append(vals, symbol)
tab.Put(to, vals)
}
}

for _, kv := range edges.KeyValues() {
from := kv.Key
for _, kv := range kv.Val.KeyValues() {
from := fmt.Sprintf("%d", from)
to := fmt.Sprintf("%d", kv.Key)
symbols := kv.Val

sort.Quick(symbols, generic.NewCompareFunc[string]())
label := strings.Join(symbols, ",")

graph.AddEdge(graphviz.NewEdge(from, to, graphviz.EdgeTypeDirected, "", label, "", "", "", ""))
}
Expand Down
85 changes: 58 additions & 27 deletions automata/dfa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ func getTestDFAs() []*DFA {
d3.Add(3, 'a', 1)
d3.Add(3, 'b', 0)

return []*DFA{d0, d1, d2, d3}
d4 := NewDFA(0, States{1})
d4.Add(0, 'a', 1)
d4.Add(0, 'b', 1)
d4.Add(1, 'a', 1)
d4.Add(1, 'b', 1)

return []*DFA{d0, d1, d2, d3, d4}
}

func TestNewDFA(t *testing.T) {
Expand Down Expand Up @@ -392,9 +398,35 @@ func TestDFA_Graphviz(t *testing.T) {
expectedGraphviz string
}{
{
name: "Empty",
d: NewDFA(0, States{1}),
expectedGraphviz: `digraph "DFA" {
name: "Empty",
d: NewDFA(0, States{1}),
expectedGraphviz: dfaEmpty,
},
{
name: "First",
d: dfas[0],
expectedGraphviz: dfa01,
},
{
name: "Second",
d: dfas[1],
expectedGraphviz: dfa02,
},
{
name: "Third",
d: dfas[4],
expectedGraphviz: dfa03,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedGraphviz, tc.d.Graphviz())
})
}
}

var dfaEmpty = `digraph "DFA" {
rankdir=LR;
concentrate=false;
node [];
Expand All @@ -404,12 +436,9 @@ func TestDFA_Graphviz(t *testing.T) {
1 [label="1", shape=doublecircle];
start -> 0 [];
}`,
},
{
name: "First",
d: dfas[0],
expectedGraphviz: `digraph "DFA" {
}`

var dfa01 = `digraph "DFA" {
rankdir=LR;
concentrate=false;
node [];
Expand All @@ -421,20 +450,17 @@ func TestDFA_Graphviz(t *testing.T) {
3 [label="3", shape=doublecircle];
start -> 0 [];
0 -> 1 [label="a"];
0 -> 0 [label="b"];
0 -> 1 [label="a"];
1 -> 1 [label="a"];
1 -> 2 [label="b"];
2 -> 1 [label="a"];
2 -> 3 [label="b"];
3 -> 1 [label="a"];
3 -> 0 [label="b"];
}`,
},
{
name: "Second",
d: dfas[1],
expectedGraphviz: `digraph "DFA" {
3 -> 1 [label="a"];
}`

var dfa02 = `digraph "DFA" {
rankdir=LR;
concentrate=false;
node [];
Expand All @@ -457,13 +483,18 @@ func TestDFA_Graphviz(t *testing.T) {
3 -> 4 [label="b"];
4 -> 1 [label="a"];
4 -> 2 [label="b"];
}`,
},
}
}`

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedGraphviz, tc.d.Graphviz())
})
}
}
var dfa03 = `digraph "DFA" {
rankdir=LR;
concentrate=false;
node [];
start [style=invis];
0 [label="0", shape=circle];
1 [label="1", shape=doublecircle];
start -> 0 [];
0 -> 1 [label="a,b"];
1 -> 1 [label="a,b"];
}`
47 changes: 35 additions & 12 deletions automata/nfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package automata

import (
"fmt"
"strings"

"github.com/moorara/algo/generic"
"github.com/moorara/algo/internal/graphviz"
Expand All @@ -10,13 +11,11 @@ import (
"github.com/moorara/algo/symboltable"
)

type nfaTrans symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, States]]

// NFA implements a non-deterministic finite automaton.
type NFA struct {
Start State
Final States
trans nfaTrans
trans doubleKeyMap[State, Symbol, States]
}

// NewNFA creates a new non-deterministic finite automaton.
Expand Down Expand Up @@ -276,24 +275,48 @@ func (n *NFA) Graphviz() string {
graph.AddNode(graphviz.NewNode(name, "", label, "", "", shape, "", ""))
}

// Group all the transitions with the same states and combine their symbols into one label

var edges doubleKeyMap[State, State, []string]
edges = symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[State, []string]](cmpState, nil)

for _, kv := range n.trans.KeyValues() {
from := fmt.Sprintf("%d", kv.Key)
from := kv.Key
tab, exist := edges.Get(from)
if !exist {
tab = symboltable.NewRedBlack[State, []string](cmpState, nil)
edges.Put(from, tab)
}

for _, kv := range kv.Val.KeyValues() {
var label string
if symbol := kv.Key; symbol == E {
label = "ε"
var symbol string
if kv.Key == E {
symbol = "ε"
} else {
label = string(symbol)
symbol = string(kv.Key)
}

for _, s := range kv.Val {
to := fmt.Sprintf("%d", s)

graph.AddEdge(graphviz.NewEdge(from, to, graphviz.EdgeTypeDirected, "", label, "", "", "", ""))
for _, to := range kv.Val {
vals, _ := tab.Get(to)
vals = append(vals, symbol)
tab.Put(to, vals)
}
}
}

for _, kv := range edges.KeyValues() {
from := kv.Key
for _, kv := range kv.Val.KeyValues() {
from := fmt.Sprintf("%d", from)
to := fmt.Sprintf("%d", kv.Key)
symbols := kv.Val

sort.Quick(symbols, generic.NewCompareFunc[string]())
label := strings.Join(symbols, ",")

graph.AddEdge(graphviz.NewEdge(from, to, graphviz.EdgeTypeDirected, "", label, "", "", "", ""))
}
}

return graph.DotCode()
}
Loading

0 comments on commit 65bc11b

Please sign in to comment.