From 57a979ec687e0742db9dc1a6e8b414d834c96d05 Mon Sep 17 00:00:00 2001 From: Milad Irannejad Date: Tue, 18 Oct 2022 10:38:20 -0400 Subject: [PATCH] Update Automata API --- automata/dfa.go | 105 +++++++---------- automata/dfa_test.go | 271 ++++++++++++++++++------------------------- automata/nfa.go | 207 +++++++++++++++------------------ automata/nfa_test.go | 263 ++++++++++++++++++----------------------- 4 files changed, 361 insertions(+), 485 deletions(-) diff --git a/automata/dfa.go b/automata/dfa.go index a381e0d..e2f2a95 100644 --- a/automata/dfa.go +++ b/automata/dfa.go @@ -8,34 +8,40 @@ import ( "github.com/moorara/algo/symboltable" ) -// DTrans is the type for a deterministic finite automaton transition function (table). -type DTrans struct { - tab symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, State]] +// DFA implements a deterministic finite automaton. +type DFA struct { + Start State + Final States + trans symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, State]] } -// NewNFATrans creates a new deterministic finite automaton transition function (table). -func NewDTrans() *DTrans { +// NewDFA creates a new deterministic finite automaton. +// Finite automata are recognizers; they simply say yes or no for each possible input string. +func NewDFA(start State, final States) *DFA { cmpKey := generic.NewCompareFunc[State]() - return &DTrans{ - tab: symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[Symbol, State]](cmpKey), + + return &DFA{ + Start: start, + Final: final, + trans: symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[Symbol, State]](cmpKey), } } -// Add adds a new transition for a deterministic finite automaton transition function (table). -func (t *DTrans) Add(s State, a Symbol, next State) { - if v, ok := t.tab.Get(s); ok { +// Add adds a new transition to the DFA. +func (d *DFA) Add(s State, a Symbol, next State) { + if v, ok := d.trans.Get(s); ok { v.Put(a, next) } else { cmpKey := generic.NewCompareFunc[Symbol]() v = symboltable.NewRedBlack[Symbol, State](cmpKey) v.Put(a, next) - t.tab.Put(s, v) + d.trans.Put(s, v) } } // Next returns the next state from a given state and for a given symbol. -func (t *DTrans) Next(s State, a Symbol) State { - if v, ok := t.tab.Get(s); ok { +func (d *DFA) Next(s State, a Symbol) State { + if v, ok := d.trans.Get(s); ok { if next, ok := v.Get(a); ok { return next } @@ -44,17 +50,17 @@ func (t *DTrans) Next(s State, a Symbol) State { return State(-1) } -// Symbols returns the set of DFA states -func (t *DTrans) States() States { +// Symbols returns the set of all states of the DFA. +func (d *DFA) States() States { states := States{} - for _, kv := range t.tab.KeyValues() { + for _, kv := range d.trans.KeyValues() { if s := kv.Key; !states.Contains(s) { states = append(states, s) } } - for _, kv := range t.tab.KeyValues() { + for _, kv := range d.trans.KeyValues() { for _, kv := range kv.Val.KeyValues() { if s := kv.Val; !states.Contains(s) { states = append(states, s) @@ -65,11 +71,11 @@ func (t *DTrans) States() States { return states } -// Symbols returns the set of DFA input symbols. -func (t *DTrans) Symbols() Symbols { +// Symbols returns the set of all input symbols of the DFA +func (d *DFA) Symbols() Symbols { symbols := Symbols{} - for _, kv := range t.tab.KeyValues() { + for _, kv := range d.trans.KeyValues() { for _, kv := range kv.Val.KeyValues() { if a := kv.Key; a != E && !symbols.Contains(a) { symbols = append(symbols, a) @@ -80,44 +86,11 @@ func (t *DTrans) Symbols() Symbols { return symbols } -// DFA implements a deterministic finite automaton. -type DFA struct { - trans *DTrans - start State - final States -} - -// NewDFA creates a new deterministic finite automaton. -// Finite automata are recognizers; they simply say yes or no for each possible input string. -func NewDFA(trans *DTrans, start State, final States) *DFA { - return &DFA{ - trans: trans, - start: start, - final: final, - } -} - -// Accept determines whether or not an input string is recognized (accepted) by the DFA. -func (d *DFA) Accept(s String) bool { - var curr State - for curr = d.start; len(s) > 0; s = s[1:] { - curr = d.trans.Next(curr, s[0]) - } - - return d.final.Contains(curr) -} - -// UpdateFinal updates the final states of the current DFA. -// This is usually required after joining another DFA. -func (d *DFA) UpdateFinal(final States) { - d.final = final -} - // Join merges another DFA with the current one and returns the set of new merged states. func (d *DFA) Join(dfa *DFA) States { // Find the maximum state number base := State(0) - for _, s := range d.trans.States() { + for _, s := range d.States() { if s > base { base = s } @@ -126,38 +99,48 @@ func (d *DFA) Join(dfa *DFA) States { // Use the maximum state number in the current DFA as the offset for the new states base += 1 - for _, kv := range dfa.trans.tab.KeyValues() { + for _, kv := range dfa.trans.KeyValues() { s := base + kv.Key for _, kv := range kv.Val.KeyValues() { a, next := kv.Key, base+kv.Val - d.trans.Add(s, a, next) + d.Add(s, a, next) } } states := States{} - for _, s := range dfa.trans.States() { + for _, s := range dfa.States() { states = append(states, base+s) } return states } +// Accept determines whether or not an input string is recognized (accepted) by the DFA. +func (d *DFA) Accept(s String) bool { + var curr State + for curr = d.Start; len(s) > 0; s = s[1:] { + curr = d.Next(curr, s[0]) + } + + return d.Final.Contains(curr) +} + // Graphviz returns the transition graph of the DFA in DOT Language format. func (d *DFA) Graphviz() string { graph := graphviz.NewGraph(true, true, false, "DFA", graphviz.RankDirLR, "", "", "") - for _, state := range d.trans.States() { + for _, state := range d.States() { name := fmt.Sprintf("%d", state) label := fmt.Sprintf("%d", state) var shape graphviz.Shape - if d.final.Contains(state) { + if d.Final.Contains(state) { shape = graphviz.ShapeDoubleCircle } else { shape = graphviz.ShapeCircle } - if state == d.start { + if state == d.Start { graph.AddNode(graphviz.NewNode("start", "", "", "", graphviz.StyleInvis, "", "", "")) graph.AddEdge(graphviz.NewEdge("start", name, graphviz.EdgeTypeDirected, "", "", "", "", "", "")) } @@ -165,7 +148,7 @@ func (d *DFA) Graphviz() string { graph.AddNode(graphviz.NewNode(name, "", label, "", "", shape, "", "")) } - for _, kv := range d.trans.tab.KeyValues() { + for _, kv := range d.trans.KeyValues() { from := fmt.Sprintf("%d", kv.Key) for _, kv := range kv.Val.KeyValues() { diff --git a/automata/dfa_test.go b/automata/dfa_test.go index d8ccd62..2ba4669 100644 --- a/automata/dfa_test.go +++ b/automata/dfa_test.go @@ -7,126 +7,112 @@ import ( ) func getTestDFAs() []*DFA { - t1 := NewDTrans() - t1.Add(0, 'a', 1) - t1.Add(0, 'b', 0) - t1.Add(1, 'a', 1) - t1.Add(1, 'b', 2) - t1.Add(2, 'a', 1) - t1.Add(2, 'b', 3) - t1.Add(3, 'a', 1) - t1.Add(3, 'b', 0) - - t2 := NewDTrans() - t2.Add(0, 'a', 1) - t2.Add(0, 'b', 2) - t2.Add(1, 'a', 1) - t2.Add(1, 'b', 3) - t2.Add(2, 'a', 1) - t2.Add(2, 'b', 2) - t2.Add(3, 'a', 1) - t2.Add(3, 'b', 4) - t2.Add(4, 'a', 1) - t2.Add(4, 'b', 2) - - t3 := NewDTrans() - t3.Add(0, 'a', 1) - t3.Add(0, 'b', 0) - t3.Add(1, 'a', 1) - t3.Add(1, 'b', 2) - t3.Add(2, 'a', 1) - t3.Add(2, 'b', 3) - t3.Add(3, 'a', 1) - t3.Add(3, 'b', 0) - t3.Add(3, 'a', 4) - t3.Add(4, 'a', 5) - t3.Add(4, 'b', 6) - t3.Add(5, 'a', 5) - t3.Add(5, 'b', 7) - t3.Add(6, 'a', 5) - t3.Add(6, 'b', 6) - t3.Add(7, 'a', 5) - t3.Add(7, 'b', 8) - t3.Add(8, 'a', 5) - t3.Add(8, 'b', 6) - - return []*DFA{ - { - trans: t1, - start: State(0), - final: States{3}, - }, - { - trans: t2, - start: State(0), - final: States{4}, - }, - { - trans: t3, - start: State(0), - final: States{8}, - }, - } + d0 := NewDFA(0, States{3}) + d0.Add(0, 'a', 1) + d0.Add(0, 'b', 0) + d0.Add(1, 'a', 1) + d0.Add(1, 'b', 2) + d0.Add(2, 'a', 1) + d0.Add(2, 'b', 3) + d0.Add(3, 'a', 1) + d0.Add(3, 'b', 0) + + d1 := NewDFA(0, States{4}) + d1.Add(0, 'a', 1) + d1.Add(0, 'b', 2) + d1.Add(1, 'a', 1) + d1.Add(1, 'b', 3) + d1.Add(2, 'a', 1) + d1.Add(2, 'b', 2) + d1.Add(3, 'a', 1) + d1.Add(3, 'b', 4) + d1.Add(4, 'a', 1) + d1.Add(4, 'b', 2) + + d2 := NewDFA(0, States{8}) + d2.Add(0, 'a', 1) + d2.Add(0, 'b', 0) + d2.Add(1, 'a', 1) + d2.Add(1, 'b', 2) + d2.Add(2, 'a', 1) + d2.Add(2, 'b', 3) + d2.Add(3, 'a', 1) + d2.Add(3, 'b', 0) + d2.Add(3, 'a', 4) + d2.Add(4, 'a', 5) + d2.Add(4, 'b', 6) + d2.Add(5, 'a', 5) + d2.Add(5, 'b', 7) + d2.Add(6, 'a', 5) + d2.Add(6, 'b', 6) + d2.Add(7, 'a', 5) + d2.Add(7, 'b', 8) + d2.Add(8, 'a', 5) + d2.Add(8, 'b', 6) + + return []*DFA{d0, d1, d2} } -func TestNewDTrans(t *testing.T) { - dtrans := NewDTrans() - assert.NotNil(t, dtrans) +func TestNewDFA(t *testing.T) { + d := getTestDFAs()[0] + + dfa := NewDFA(d.Start, d.Final) + assert.NotNil(t, dfa) } -func TestDTrans_Add(t *testing.T) { - dtrans := NewDTrans() +func TestDFA_Add(t *testing.T) { + dfa := NewDFA(0, States{1, 2}) tests := []struct { - name string - dtrans *DTrans - s State - a Symbol - next State + name string + d *DFA + s State + a Symbol + next State }{ { - name: "NewState", - dtrans: dtrans, - s: State(0), - a: 'a', - next: State(1), + name: "NewState", + d: dfa, + s: State(0), + a: 'a', + next: State(1), }, { - name: "ExistingState", - dtrans: dtrans, - s: State(0), - a: 'b', - next: State(2), + name: "ExistingState", + d: dfa, + s: State(0), + a: 'b', + next: State(2), }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - tc.dtrans.Add(tc.s, tc.a, tc.next) + tc.d.Add(tc.s, tc.a, tc.next) }) } } -func TestDTrans_Next(t *testing.T) { +func TestDFA_Next(t *testing.T) { dfas := getTestDFAs() tests := []struct { name string - dtrans *DTrans + d *DFA s State a Symbol expectedState State }{ { name: "First", - dtrans: dfas[0].trans, + d: dfas[0], s: State(0), a: 'a', expectedState: State(1), }, { name: "Second", - dtrans: dfas[1].trans, + d: dfas[1], s: State(0), a: 'b', expectedState: State(2), @@ -135,91 +121,108 @@ func TestDTrans_Next(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - state := tc.dtrans.Next(tc.s, tc.a) + state := tc.d.Next(tc.s, tc.a) assert.Equal(t, tc.expectedState, state) }) } } -func TestDTrans_States(t *testing.T) { +func TestDFA_States(t *testing.T) { dfas := getTestDFAs() tests := []struct { name string - dtrans *DTrans + d *DFA expectedStates States }{ { name: "First", - dtrans: dfas[0].trans, + d: dfas[0], expectedStates: States{0, 1, 2, 3}, }, { name: "Second", - dtrans: dfas[1].trans, + d: dfas[1], expectedStates: States{0, 1, 2, 3, 4}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedStates, tc.dtrans.States()) + assert.Equal(t, tc.expectedStates, tc.d.States()) }) } } -func TestDTrans_Symbols(t *testing.T) { +func TestDFA_Symbols(t *testing.T) { dfas := getTestDFAs() tests := []struct { name string - dtrans *DTrans + d *DFA expectedSymbols Symbols }{ { name: "First", - dtrans: dfas[0].trans, + d: dfas[0], expectedSymbols: Symbols{'a', 'b'}, }, { name: "Second", - dtrans: dfas[1].trans, + d: dfas[1], expectedSymbols: Symbols{'a', 'b'}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedSymbols, tc.dtrans.Symbols()) + assert.Equal(t, tc.expectedSymbols, tc.d.Symbols()) }) } } -func TestNewDFA(t *testing.T) { - d := getTestDFAs()[0] - - dfa := NewDFA(d.trans, d.start, d.final) - assert.NotNil(t, dfa) -} +func TestDFA_Join(t *testing.T) { + dfas := getTestDFAs() -func TestDFA_UpdateFinal(t *testing.T) { - dfa := getTestDFAs()[0] + type edge struct { + s State + a Symbol + next State + } tests := []struct { - name string - d *DFA - final States + name string + d *DFA + dfa *DFA + extraTrans []edge + newFinal States + expectedStates States + expectedDFA *DFA }{ { - name: "OK", - d: dfa, - final: States{2}, + name: "OK", + d: dfas[0], + dfa: dfas[1], + extraTrans: []edge{ + {3, 'a', 4}, + }, + newFinal: States{8}, + expectedStates: States{4, 5, 6, 7, 8}, + expectedDFA: dfas[2], }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - tc.d.UpdateFinal(tc.final) + states := tc.d.Join(tc.dfa) + for _, e := range tc.extraTrans { + tc.d.Add(e.s, e.a, e.next) + } + tc.d.Final = tc.newFinal + + assert.Equal(t, tc.expectedStates, states) + // This is a trick to avoid comparing the symbol tables with their internal structures. + assert.Equal(t, tc.expectedDFA.Graphviz(), tc.d.Graphviz()) }) } } @@ -255,52 +258,6 @@ func TestDFA_Accept(t *testing.T) { } } -func TestDFA_Join(t *testing.T) { - dfas := getTestDFAs() - - type edge struct { - s State - a Symbol - next State - } - - tests := []struct { - name string - d *DFA - dfa *DFA - extraTrans []edge - newFinal States - expectedStates States - expectedDFA *DFA - }{ - { - name: "OK", - d: dfas[0], - dfa: dfas[1], - extraTrans: []edge{ - {3, 'a', 4}, - }, - newFinal: States{8}, - expectedStates: States{4, 5, 6, 7, 8}, - expectedDFA: dfas[2], - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - states := tc.d.Join(tc.dfa) - for _, e := range tc.extraTrans { - tc.d.trans.Add(e.s, e.a, e.next) - } - tc.d.UpdateFinal(tc.newFinal) - - assert.Equal(t, tc.expectedStates, states) - // This is a trick to avoid comparing the symbol tables with their internal structures. - assert.Equal(t, tc.expectedDFA.Graphviz(), tc.d.Graphviz()) - }) - } -} - func TestDFA_Graphviz(t *testing.T) { dfas := getTestDFAs() diff --git a/automata/nfa.go b/automata/nfa.go index 5504c54..f77730f 100644 --- a/automata/nfa.go +++ b/automata/nfa.go @@ -9,34 +9,74 @@ import ( "github.com/moorara/algo/symboltable" ) -// NTrans is the type for a non-deterministic finite automaton transition function (table). -type NTrans struct { - tab symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, States]] +// NFA implements a non-deterministic finite automaton. +type NFA struct { + Start State + Final States + trans symboltable.OrderedSymbolTable[State, symboltable.OrderedSymbolTable[Symbol, States]] } -// NewNTrans creates a new non-deterministic finite automaton transition function (table). -func NewNTrans() *NTrans { +// NewNFA creates a new non-deterministic finite automaton. +// Finite automata are recognizers; they simply say yes or no for each possible input string. +func NewNFA(start State, final States) *NFA { cmpKey := generic.NewCompareFunc[State]() - return &NTrans{ - tab: symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[Symbol, States]](cmpKey), + + return &NFA{ + Start: start, + Final: final, + trans: symboltable.NewRedBlack[State, symboltable.OrderedSymbolTable[Symbol, States]](cmpKey), + } +} + +// εClosure returns the set of NFA states reachable from some NFA state s in set T on ε-transitions alone. +// εClosure(T) = Union(εClosure(s)) for all s ∈ T. +func (n *NFA) εClosure(T States) States { + closure := T + + stack := list.NewStack[State](1024, nil) + for _, s := range T { + stack.Push(s) + } + + for !stack.IsEmpty() { + t, _ := stack.Pop() + for _, u := range n.Next(t, E) { + if !closure.Contains(u) { + closure = append(closure, u) + stack.Push(u) + } + } + } + + return closure +} + +// move returns the set of NFA states to which there is a transition on input symbol a from some state s in T. +func (n *NFA) move(T States, a Symbol) States { + states := States{} + for _, s := range T { + U := n.Next(s, a) + states = append(states, U...) } + + return states } -// Add adds a new transition for a non-deterministic finite automaton transition function (table). -func (t *NTrans) Add(s State, a Symbol, next States) { - if v, ok := t.tab.Get(s); ok { +// Add adds a new transition to the NFA. +func (n *NFA) Add(s State, a Symbol, next States) { + if v, ok := n.trans.Get(s); ok { v.Put(a, next) } else { cmpKey := generic.NewCompareFunc[Symbol]() v = symboltable.NewRedBlack[Symbol, States](cmpKey) v.Put(a, next) - t.tab.Put(s, v) + n.trans.Put(s, v) } } // Next returns the next set of states from a given state and for a given symbol. -func (t *NTrans) Next(s State, a Symbol) States { - if v, ok := t.tab.Get(s); ok { +func (n *NFA) Next(s State, a Symbol) States { + if v, ok := n.trans.Get(s); ok { if next, ok := v.Get(a); ok { return next } @@ -45,17 +85,17 @@ func (t *NTrans) Next(s State, a Symbol) States { return States{} } -// Symbols returns the set of DFA states -func (t *NTrans) States() States { +// Symbols returns the set of all states of the NFA. +func (n *NFA) States() States { states := States{} - for _, kv := range t.tab.KeyValues() { + for _, kv := range n.trans.KeyValues() { if s := kv.Key; !states.Contains(s) { states = append(states, s) } } - for _, kv := range t.tab.KeyValues() { + for _, kv := range n.trans.KeyValues() { for _, kv := range kv.Val.KeyValues() { for _, s := range kv.Val { if !states.Contains(s) { @@ -68,11 +108,11 @@ func (t *NTrans) States() States { return states } -// Symbols returns the set of NFA input symbols. -func (t *NTrans) Symbols() Symbols { +// Symbols returns the set of all input symbols of the NFA. +func (n *NFA) Symbols() Symbols { symbols := Symbols{} - for _, kv := range t.tab.KeyValues() { + for _, kv := range n.trans.KeyValues() { for _, kv := range kv.Val.KeyValues() { if a := kv.Key; a != E && !symbols.Contains(a) { symbols = append(symbols, a) @@ -83,84 +123,11 @@ func (t *NTrans) Symbols() Symbols { return symbols } -// NFA implements a non-deterministic finite automaton. -type NFA struct { - trans *NTrans - start State - final States -} - -// NewNFA creates a new non-deterministic finite automaton. -// Finite automata are recognizers; they simply say yes or no for each possible input string. -func NewNFA(trans *NTrans, start State, final States) *NFA { - return &NFA{ - trans: trans, - start: start, - final: final, - } -} - -// εClosure returns the set of NFA states reachable from some NFA state s in set T on ε-transitions alone. -// εClosure(T) = Union(εClosure(s)) for all s ∈ T. -func (n *NFA) εClosure(T States) States { - closure := T - - stack := list.NewStack[State](1024, nil) - for _, s := range T { - stack.Push(s) - } - - for !stack.IsEmpty() { - t, _ := stack.Pop() - for _, u := range n.trans.Next(t, E) { - if !closure.Contains(u) { - closure = append(closure, u) - stack.Push(u) - } - } - } - - return closure -} - -// move returns the set of NFA states to which there is a transition on input symbol a from some state s in T. -func (n *NFA) move(T States, a Symbol) States { - states := States{} - for _, s := range T { - U := n.trans.Next(s, a) - states = append(states, U...) - } - - return states -} - -// UpdateFinal updates the final states of the current NFA. -// This is usually required after joining another NFA. -func (n *NFA) UpdateFinal(final States) { - n.final = final -} - -// Accept determines whether or not an input string is recognized (accepted) by the NFA. -func (n *NFA) Accept(s String) bool { - var S States - for S = n.εClosure(States{n.start}); len(s) > 0; s = s[1:] { - S = n.εClosure(n.move(S, s[0])) - } - - for _, s := range S { - if n.final.Contains(s) { - return true - } - } - - return false -} - // Join merges another NFA with the current one and returns the set of new merged states. func (n *NFA) Join(nfa *NFA) States { // Find the maximum state number base := State(0) - for _, s := range n.trans.States() { + for _, s := range n.States() { if s > base { base = s } @@ -169,7 +136,7 @@ func (n *NFA) Join(nfa *NFA) States { // Use the maximum state number in the current NFA as the offset for the new states base += 1 - for _, kv := range nfa.trans.tab.KeyValues() { + for _, kv := range nfa.trans.KeyValues() { s := base + kv.Key for _, kv := range kv.Val.KeyValues() { a := kv.Key @@ -179,29 +146,45 @@ func (n *NFA) Join(nfa *NFA) States { next[i] = base + n } - n.trans.Add(s, a, next) + n.Add(s, a, next) } } states := States{} - for _, s := range nfa.trans.States() { + for _, s := range nfa.States() { states = append(states, base+s) } return states } +// Accept determines whether or not an input string is recognized (accepted) by the NFA. +func (n *NFA) Accept(s String) bool { + var S States + for S = n.εClosure(States{n.Start}); len(s) > 0; s = s[1:] { + S = n.εClosure(n.move(S, s[0])) + } + + for _, s := range S { + if n.Final.Contains(s) { + return true + } + } + + return false +} + // ToDFA constructs a new DFA accepting the same language as the NFA. // It implements the subset construction algorithm. func (n *NFA) ToDFA() *DFA { - symbols := n.trans.Symbols() + symbols := n.Symbols() - Dtrans := NewDTrans() + dfa := NewDFA(0, nil) Dstates := newMarkList[States](func(s, t States) bool { return s.Equals(t) }) - Dstates.AddUnmarked(n.εClosure(States{n.start})) + Dstates.AddUnmarked(n.εClosure(States{n.Start})) for T, i := Dstates.GetUnmarked(); i >= 0; T, i = Dstates.GetUnmarked() { Dstates.MarkByIndex(i) @@ -214,45 +197,41 @@ func (n *NFA) ToDFA() *DFA { j = Dstates.AddUnmarked(U) } - Dtrans.Add(State(i), a, State(j)) + dfa.Add(State(i), a, State(j)) } } - Dstart := State(0) - Dfinal := States{} + dfa.Start = State(0) + dfa.Final = States{} for i, s := range Dstates.Values() { - for _, f := range n.final { + for _, f := range n.Final { if s.Contains(f) { - Dfinal = append(Dfinal, State(i)) + dfa.Final = append(dfa.Final, State(i)) break } } } - return &DFA{ - trans: Dtrans, - start: Dstart, - final: Dfinal, - } + return dfa } // Graphviz returns the transition graph of the NFA in DOT Language format. func (n *NFA) Graphviz() string { graph := graphviz.NewGraph(true, true, false, "NFA", graphviz.RankDirLR, "", "", graphviz.ShapeCircle) - for _, state := range n.trans.States() { + for _, state := range n.States() { name := fmt.Sprintf("%d", state) label := fmt.Sprintf("%d", state) var shape graphviz.Shape - if n.final.Contains(state) { + if n.Final.Contains(state) { shape = graphviz.ShapeDoubleCircle } else { shape = graphviz.ShapeCircle } - if state == n.start { + if state == n.Start { graph.AddNode(graphviz.NewNode("start", "", "", "", graphviz.StyleInvis, "", "", "")) graph.AddEdge(graphviz.NewEdge("start", name, graphviz.EdgeTypeDirected, "", "", "", "", "", "")) } @@ -260,7 +239,7 @@ func (n *NFA) Graphviz() string { graph.AddNode(graphviz.NewNode(name, "", label, "", "", shape, "", "")) } - for _, kv := range n.trans.tab.KeyValues() { + for _, kv := range n.trans.KeyValues() { from := fmt.Sprintf("%d", kv.Key) for _, kv := range kv.Val.KeyValues() { diff --git a/automata/nfa_test.go b/automata/nfa_test.go index 504cb0a..91e3c45 100644 --- a/automata/nfa_test.go +++ b/automata/nfa_test.go @@ -7,121 +7,107 @@ import ( ) func getTestNFAs() []*NFA { - t1 := NewNTrans() - t1.Add(0, E, States{1, 3}) - t1.Add(1, 'a', States{2}) - t1.Add(2, 'a', States{2}) - t1.Add(3, 'b', States{4}) - t1.Add(4, 'b', States{4}) - - t2 := NewNTrans() - t2.Add(0, E, States{1, 7}) - t2.Add(1, E, States{2, 4}) - t2.Add(2, 'a', States{3}) - t2.Add(3, E, States{6}) - t2.Add(4, 'b', States{5}) - t2.Add(5, E, States{6}) - t2.Add(6, E, States{1, 7}) - t2.Add(7, 'a', States{8}) - t2.Add(8, 'b', States{9}) - t2.Add(9, 'b', States{10}) - - t3 := NewNTrans() - t3.Add(0, E, States{1, 3}) - t3.Add(1, 'a', States{2}) - t3.Add(2, 'a', States{2}) - t3.Add(3, 'b', States{4}) - t3.Add(4, 'b', States{4}) - t3.Add(2, E, States{5}) - t3.Add(4, E, States{5}) - t3.Add(5, E, States{6, 12}) - t3.Add(6, E, States{7, 9}) - t3.Add(7, 'a', States{8}) - t3.Add(8, E, States{11}) - t3.Add(9, 'b', States{10}) - t3.Add(10, E, States{11}) - t3.Add(11, E, States{6, 12}) - t3.Add(12, 'a', States{13}) - t3.Add(13, 'b', States{14}) - t3.Add(14, 'b', States{15}) - - return []*NFA{ - { - trans: t1, - start: State(0), - final: States{2, 4}, - }, - { - trans: t2, - start: State(0), - final: States{10}, - }, - { - trans: t3, - start: State(0), - final: States{15}, - }, - } + d0 := NewNFA(0, States{2, 4}) + d0.Add(0, E, States{1, 3}) + d0.Add(1, 'a', States{2}) + d0.Add(2, 'a', States{2}) + d0.Add(3, 'b', States{4}) + d0.Add(4, 'b', States{4}) + + d1 := NewNFA(0, States{10}) + d1.Add(0, E, States{1, 7}) + d1.Add(1, E, States{2, 4}) + d1.Add(2, 'a', States{3}) + d1.Add(3, E, States{6}) + d1.Add(4, 'b', States{5}) + d1.Add(5, E, States{6}) + d1.Add(6, E, States{1, 7}) + d1.Add(7, 'a', States{8}) + d1.Add(8, 'b', States{9}) + d1.Add(9, 'b', States{10}) + + d2 := NewNFA(0, States{15}) + d2.Add(0, E, States{1, 3}) + d2.Add(1, 'a', States{2}) + d2.Add(2, 'a', States{2}) + d2.Add(3, 'b', States{4}) + d2.Add(4, 'b', States{4}) + d2.Add(2, E, States{5}) + d2.Add(4, E, States{5}) + d2.Add(5, E, States{6, 12}) + d2.Add(6, E, States{7, 9}) + d2.Add(7, 'a', States{8}) + d2.Add(8, E, States{11}) + d2.Add(9, 'b', States{10}) + d2.Add(10, E, States{11}) + d2.Add(11, E, States{6, 12}) + d2.Add(12, 'a', States{13}) + d2.Add(13, 'b', States{14}) + d2.Add(14, 'b', States{15}) + + return []*NFA{d0, d1, d2} } -func TestNewNTrans(t *testing.T) { - ntrans := NewNTrans() - assert.NotNil(t, ntrans) +func TestNewNFA(t *testing.T) { + n := getTestNFAs()[0] + + nfa := NewNFA(n.Start, n.Final) + assert.NotNil(t, nfa) } -func TestNTrans_Add(t *testing.T) { - ntrans := NewNTrans() +func TestNFA_Add(t *testing.T) { + nfa := NewNFA(0, States{1, 2, 3, 4}) tests := []struct { - name string - ntrans *NTrans - s State - a Symbol - next States + name string + n *NFA + s State + a Symbol + next States }{ { - name: "NewState", - ntrans: ntrans, - s: State(0), - a: 'a', - next: States{1, 2}, + name: "NewState", + n: nfa, + s: State(0), + a: 'a', + next: States{1, 2}, }, { - name: "ExistingState", - ntrans: ntrans, - s: State(0), - a: 'b', - next: States{3, 4}, + name: "ExistingState", + n: nfa, + s: State(0), + a: 'b', + next: States{3, 4}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - tc.ntrans.Add(tc.s, tc.a, tc.next) + tc.n.Add(tc.s, tc.a, tc.next) }) } } -func TestNTrans_Next(t *testing.T) { +func TestNFA_Next(t *testing.T) { nfas := getTestNFAs() tests := []struct { name string - ntrans *NTrans + n *NFA s State a Symbol expectedStates States }{ { name: "First", - ntrans: nfas[0].trans, + n: nfas[0], s: State(0), a: E, expectedStates: States{1, 3}, }, { name: "Second", - ntrans: nfas[1].trans, + n: nfas[1], s: State(1), a: E, expectedStates: States{2, 4}, @@ -130,122 +116,62 @@ func TestNTrans_Next(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - states := tc.ntrans.Next(tc.s, tc.a) + states := tc.n.Next(tc.s, tc.a) assert.Equal(t, tc.expectedStates, states) }) } } -func TestNTrans_States(t *testing.T) { +func TestNFA_States(t *testing.T) { nfas := getTestNFAs() tests := []struct { name string - ntrans *NTrans + n *NFA expectedStates States }{ { name: "First", - ntrans: nfas[0].trans, + n: nfas[0], expectedStates: States{0, 1, 2, 3, 4}, }, { name: "Second", - ntrans: nfas[1].trans, + n: nfas[1], expectedStates: States{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedStates, tc.ntrans.States()) + assert.Equal(t, tc.expectedStates, tc.n.States()) }) } } -func TestNTrans_Symbols(t *testing.T) { +func TestNFA_Symbols(t *testing.T) { nfas := getTestNFAs() tests := []struct { name string - ntrans *NTrans + n *NFA expectedSymbols Symbols }{ { name: "First", - ntrans: nfas[0].trans, + n: nfas[0], expectedSymbols: Symbols{'a', 'b'}, }, { name: "Second", - ntrans: nfas[1].trans, + n: nfas[1], expectedSymbols: Symbols{'a', 'b'}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedSymbols, tc.ntrans.Symbols()) - }) - } -} - -func TestNewNFA(t *testing.T) { - n := getTestNFAs()[0] - - nfa := NewNFA(n.trans, n.start, n.final) - assert.NotNil(t, nfa) -} - -func TestNFA_UpdateFinal(t *testing.T) { - nfa := getTestNFAs()[0] - - tests := []struct { - name string - n *NFA - final States - }{ - { - name: "OK", - n: nfa, - final: States{4}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - tc.n.UpdateFinal(tc.final) - }) - } -} - -func TestNFA_Accept(t *testing.T) { - nfa := getTestNFAs()[0] - - tests := []struct { - name string - n *NFA - s String - expectedResult bool - }{ - { - name: "Accepted", - n: nfa, - s: ToString("aaaa"), - expectedResult: true, - }, - { - name: "NotAccepted", - n: nfa, - s: ToString("abbb"), - expectedResult: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - b := tc.n.Accept(tc.s) - assert.Equal(t, tc.expectedResult, b) + assert.Equal(t, tc.expectedSymbols, tc.n.Symbols()) }) } } @@ -286,9 +212,9 @@ func TestNFA_Join(t *testing.T) { t.Run(tc.name, func(t *testing.T) { states := tc.n.Join(tc.nfa) for _, e := range tc.extraTrans { - tc.n.trans.Add(e.s, e.a, e.next) + tc.n.Add(e.s, e.a, e.next) } - tc.n.UpdateFinal(tc.newFinal) + tc.n.Final = tc.newFinal assert.Equal(t, tc.expectedStates, states) // This is a trick to avoid comparing the symbol tables with their internal structures. @@ -297,6 +223,37 @@ func TestNFA_Join(t *testing.T) { } } +func TestNFA_Accept(t *testing.T) { + nfa := getTestNFAs()[0] + + tests := []struct { + name string + n *NFA + s String + expectedResult bool + }{ + { + name: "Accepted", + n: nfa, + s: ToString("aaaa"), + expectedResult: true, + }, + { + name: "NotAccepted", + n: nfa, + s: ToString("abbb"), + expectedResult: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + b := tc.n.Accept(tc.s) + assert.Equal(t, tc.expectedResult, b) + }) + } +} + func TestNFA_ToDFA(t *testing.T) { dfas := getTestDFAs() nfas := getTestNFAs()