|
 |
CS 3723
Programming Languages |
9.1 Simulating FAs
|
|
Note: This program uses no classes. See
9.2 Using a Class for the same program
as a Python class.
Simulating
Finite Automat in Python:
I decided to use finite automata as an early exercise in studying Python.
I would write code to simulate the actions of a finite automaton.
These would include DFAs, NFAs, and NFAs with ε-moves.
First step is to read a file representation of the automaton
as a graph, and translate it into an internal form.
We need to be able to search for information about the FA.
Here I'm only interested in the graph of an FA, but the code below
works for an arbitrary graph, with limitations about the
names of components. This code
only reads in a static version of the graph and does not handle
dynamic changes.
I have working code in Java and in C for this problem.
Suppose there are n vertices (states for the FA).
My representation using these traditional languages had
an array of n items, each a class instance containing information
about the vertex and containing a link to the adjacency list,
a linked list. This linked list required another self-referential class.
In Python, a list or a tuple (= list that can't be modified)
can serve as a class, since the elements of the list can be anything
at all. You don't get the initialization that a class has,
but otherwise this is very simple.
A Specific Example
of a NFA: First see the FA as a diagram:
Sample NFA with ε-moves |

NFA for (a|b|c)*(ab|aac).
Note: the dot on the arrow from state 0 to state 1 represents
(a|b|c)
|
Next is the file representation of the graph and the internal
representation. The file starts with the number of vertices
(16), followed by a string giving the symbols on transitions
(abc) not including the symbol for ε, which is @.
Next comes a string giving the status of each vertex
(1 = start, 2 = terminal, 3 = start and terminal).
Finally in a list of all the transitions.
Vertices must be labeled with successive integers starting
with 0. There must be a single start state, which doesn't have
to be 0. There can be any number >= 1 of terminal states.
The start state can also be terminal.
The internal form is a list of 16 items (one for each vertex).
Each item is tuple, giving the status followed by the adjacency
list for that vertex. The adjacency list is a list of tuples.
FAs in Python |
File Format |
Internal Form |
---|
16
abc
0010000000000002
0 a 1
0 b 1
0 c 1
1 @ 3
1 @ 0
2 @ 0
2 @ 3
3 @ 14
4 a 5
5 @ 6
6 b 7
7 @ 15
8 a 9
9 @ 10
10 a 11
11 @ 12
12 c 13
13 @ 15
14 @ 4
14 @ 8
| [(0, [('a', 1), ('b', 1), ('c', 1)]),
(0, [('@', 3), ('@', 0)]),
(1, [('@', 0), ('@', 3)]),
(0, [('@', 14)]),
(0, [('a', 5)]),
(0, [('@', 6)]),
(0, [('b', 7)]),
(0, [('@', 15)]),
(0, [('a', 9)]),
(0, [('@', 10)]),
(0, [('a', 11)]),
(0, [('@', 12)]),
(0, [('c', 13)]),
(0, [('@', 15)]),
(0, [('@', 4), ('@', 8)]),
(2, [])]
|
Here is Python code that reads the file and converts the external
representation of the graph to the internal representation.
FAs in Python, Internal Form |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 |
# fa.py: build internal fa
import sys
import re
dfa = True
fa = []
ss = 0 # start state
stn = None # num of states
terms = [] # list of terminal states
symbs = None # array of symbols
eps = False
eps_symb = '@'
# fa is the actual finite automata
# for i in range(0,stnum):
# fa[i] is the ith vertex data
# fa[i][0] gives status of ith vertex
# fa[i][1] gives the ith adjacency list
def read_fa(filename):
f = open(filename,'r')
line = f.readline()
# get first digit of line 0
r1 = re.compile(R"\s*(\d+).*")
m1 = r1.search(line)
global stn # the number of states
stn = int(m1.group(1))
global symbs # all symbs in string
# @ = espilon handled as special case
symbs = f.readline()
# stn many ints on separate lines,
# giving the status of each state
# 1 = start, 2 = term, 3 = start and term
# stp is the status list for each state
stp = f.readline() # each state status
ssn = 0 # num of start states (must be 1)
tn = 0 # num of term states (at least 1)
for i in range(0,stn):
fa.append((int(stp[i]), []))
if stp[i] == '1' or stp[i] == '3':
global ss
ss = i
ssn = ssn + 1
if stp[i] == '2' or stp[i] == '3':
terms.append(i)
tn = tn + 1
if ssn != 1:
sys.stdout.write("ERR, != 1 start st")
if tn < 1:
sys.stdout.write("ERR, 0 term states")
# now work on transition data
r2 = re.compile(R"\s*(\d+)\s+(\S)\s+(\d+)")
for line in f:
# read tail_state symb head_state
m2 = r2.search(line)
st1 = int(m2.group(1))
st2 = int(m2.group(3))
sym = m2.group(2)
if sym == eps_symb: # check for eps
global eps
eps = True
# append onto the adjacency list
fa[st1][1].append( (sym, st2) )
global dfa
dfa = check_dfa()
| def check_dfa():
for i in range(0,stn):
for ch in symbs: # string of all symbs
t = lookup(i, ch)
if t != None and len(t) >= 2:
return False
return True
def lookup(state, symb):
ret = []
if state < 0 or state >= len(fa):
return None
elif len(fa[state][1]) == 0:
return None
else:
t = len(fa[state][1])
for i in range(0,t):
if fa[state][1][i][0] == symb:
ret.append(fa[state][1][i][1])
return ret
def get_start_state():
return ss
def terminals():
return terms
def is_terminal(state):
if fa[state][0] == 2 or fa[state][0] == 3:
return True
return False
def is_dfa():
return dfa
def printfa():
if eps:
sys.stdout.write("NFA with epsilon: ")
elif dfa:
sys.stdout.write("DFA: ")
else:
sys.stdout.write("NFA: ")
sys.stdout.write("States " + str(stn) +
", Symbols: " + symbs + "\n")
for i in range(0,stn):
if i < 10:
sys.stdout.write(" " + str(i))
else:
sys.stdout.write(str(i))
if i == ss:
sys.stdout.write("s")
else:
sys.stdout.write(" ")
if is_terminal(i):
sys.stdout.write("t:")
else:
sys.stdout.write(" :")
print(fa[i])
|
Finally, there are two Python programs that use the program
fa.py as a library module: dfa.py which handles
DFAs, and nfa.py which handles NFAs, with or without
ε-moves. Of course, as far as simulation is concerned,
an NFA is just a special case of a DFA, so the NFA program will also handle
all the DFA examples, producing single-set results at each stage.
dfa.py:
code to simulate DFA |
# dfa.py: simulate a dfa
import auto
import sys
auto.read_fa("dfa4.auto")
auto.printfa()
sys.stdout.write("\n")
f = open("dfa4.input",'r')
instr = f.readline()
sys.stdout.write("Input: " + instr + "\n")
sys.stdout.write("Simulation Run ...\n")
s1 = auto.get_start_state()
for ch in instr:
if ch != '$':
s2 = auto.lookup(s1, ch)[0]
sys.stdout.write(str(s1) + " " +
ch + " " + str(s2))
if auto.is_terminal(s2):
sys.stdout.write(" term")
sys.stdout.write("\n")
s1 = s2
else:
break
|
DFA Runs: sample runs.
nfa.py:
code to simulate NFA |
# nfa.py: simulate an nfa
import auto
import sys
def is_term(s): # s set, contains term?
if not s:
return False
else:
for x in s:
if auto.is_terminal(x):
return True
return False
def eps_cl(s): # s a set, epsilon closure
# could use s for b, save one list
t = list(s) # list t is stack or queue
b = set(t) # set of "black" states
while len(t) != 0:
u = t.pop() # remove end elt
# use @ for epsilon
r = auto.lookup(u,auto.eps_symb)
if r != None:
for v in r:
if not (v in b):
b.add(v) # add
t.append(v) # push
return b
auto.read_fa("dfa1.auto")
auto.printfa()
sys.stdout.write("\n")
|
# OK, let's simulate this sucker
f = open("dfa1.input",'r')
instr = f.readline()
ss = auto.get_start_state()
x = []
x.append(auto.get_start_state())
s1 = set(x)
s1 = eps_cl(s1) # start state
sys.stdout.write("Start:\n " +
str(list(s1)) + "\n")
i = 0
while instr[i] != '$':
s2 = set()
for y in s1:
s = auto.lookup(y, instr[i])
if not s:
s = set()
else:
s = set(s)
s2 = s2 | s # as sets, union
# now got set, need epsilon colsure
s2 = eps_cl(s2)
sys.stdout.write(instr[i] + ": " +
str(list(s2)))
if is_term(s2):
sys.stdout.write(" term ")
sys.stdout.write("\n")
s1 = s2
i = i + 1 # next instr
|
NFA Runs: sample runs.
(Revision date: 2014-06-17.
Please use ISO 8601,
the International Standard.)
|