#!/usr/bin/env python
"""
FSA module
This module implements simple deterministic and non-deterministic finite
state automata. It follows the description in Chapter 2 of Jurafsky and
Martin (2000) fairly closely.
@author: Rob Malouf
@organization: Dept. of Linguistics, San Diego State University
@contact: rmalouf@mail.sdsu.edu
@version: 1
@since: 30-January-2008
"""
class FSA(object):
"""
General finite-state automaton class
An FSA consists of:
- an alphabet (C{self.alphabet})
- a state-transition table (C{self.transition_table})
- an initial state (C{self.initial})
- a set of final states (C{self.final})
"""
def __init__(self,T,finals,initial=0):
"""
Create a new FSA object
@param T: the transition function
@type T: a C{list} of C{dict}s
@param finals: final states
@type finals: C{list}
@param initial: initial state (by default, 0)
@type initial: C{int}
@raise ValueError: the FSA is mis-specified somehow
"""
# transition function
self.transition_table = T
for state in self.transition_table:
for symbol in state:
# the value of the transition function should be a list of states
if not hasattr(state[symbol],'__getitem__'):
state[symbol] = [ state[symbol] ]
for newState in state[symbol]:
try:
self.transition_table[newState]
except:
raise ValueError,'%s not a valid state'%newState
# initial state
self.initial = initial
try:
self.transition_table[self.initial]
except:
raise ValueError,'%s not a valid initial state'%initial
# final states
try:
self.finals = set(finals)
for final in self.finals:
self.transition_table[final]
except:
raise ValueError,'%s not valid final states'%finals
# alphabet
self.alphabet = set()
for state in self.transition_table:
self.alphabet.update(state.keys())
def __repr__(self):
"""Produce a string representation of an FSA (e.g., for printing)."""
result = '{ %s\n' % (self.__class__.__name__)
result += ' Alphabet : %s\n'%self.alphabet
for index in xrange(len(self.transition_table)):
result += ' %s:' % index
if index == self.initial:
result += ' initial'
if index in self.finals:
result += ' final'
result += '\n'
for key in self.transition_table[index]:
result += ' %s => %s\n'%(key,self.transition_table[index][key])
result += '}'
return result
def visit(self,trace,index,state):
"""Trace: visit a node in an FSA"""
if trace:
print 'Obs %d at state %d'%(index,state)
def accept(self,trace):
"""Trace: FSA accepts a tape"""
if trace:
print 'ACCEPT'
def reject(self,trace,reason=None):
"""Trace: FSA rejects a tape"""
if trace:
if reason:
print 'REJECT (%s)'%reason
else:
print 'REJECT'
class DFSA(FSA):
"""Deterministic FSA class"""
def __init__(self,*args,**kwargs):
# create FSA
super(DFSA,self).__init__(*args,**kwargs)
# verify that it is deterministic
if '' in self.alphabet:
raise ValueError,'epsilon transitions not allowed in DFSAs'
for state in self.transition_table:
for newStates in state.values():
if len(newStates) > 1:
raise ValueError,'non-deterministic transitions not allowed in DFSAs'
def recognize(self,tape,trace=False):
"""verify whether an input tape is accepted by the FSA (see fig. 2.12)"""
index = 0
current_state = self.initial
while True:
self.visit(trace,index,current_state)
if index == len(tape):
# end of input tape
if current_state in self.finals:
self.accept(trace)
return True
else:
self.reject(trace,'%d is not an accepting state'%current_state)
return False
else:
if tape[index] in self.transition_table[current_state]:
# follow transition to next state
current_state = self.transition_table[current_state][tape[index]][0]
index += 1
else:
# no transition!
self.reject(trace,'no transition for "%s" at state %d'%(tape[index],current_state))
return False
class NFSA(FSA):
"""Non-deterministic FSA class"""
def recognize(self,tape,trace=False):
"""verify whether an input tape is accepted by the FSA (see fig. 2.19)"""
agenda = [(self.initial,0)]
while agenda:
(state,index) = agenda.pop()
self.visit(trace,index,state)
if index == len(tape):
# end of input tape
if state in self.finals:
self.accept(trace)
return True
else:
self.reject(trace,'%d is not an accepting state'%state)
return False
else:
# add new agenda items
if tape[index] in self.transition_table[state]:
for newState in self.transition_table[state][tape[index]]:
agenda.append((newState,index+1))
if '' in self.transition_table[state]:
for newState in self.transition_table[state]['']:
agenda.append((newState,index))
self.reject(trace,'no more agenda items')
return False
### some examples
# The sheep language machine (Fig 2.10, J & M)
fsa1 = DFSA( [ { 'f':1 }, # state 0 b => 1
{ 'a':2 }, # state 1 a => 2
{ 'a':3 }, # state 2 a => 3
{ 'a':3, '!':4}, # state 3 a => 3, ! => 4
{ } ], # state 4 undef for everything.
[ 4 ])
"""@var: An example DFSA"""
# Fig. 2.17
fsa2 = NFSA( [ { 'b':1 },
{ 'a':2 },
{ 'a':[ 2, 3 ] },
{ '!':4 },
{ } ],
[ 4 ] )
"""@var: An example NFSA"""
# Fig. 2.18
fsa3 = NFSA( [ { 'b':1 },
{ 'a':2 },
{ 'a':3 },
{ '' :2,
'!':4 },
{ } ],
[ 4 ] )
"""@var: An example NFSA with epsilon transitions"""
fsatest = DFSA( [ { 'one', 'two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','mid-': 1,'a': 3},
{ 'day', 'night','o`clock': 2 },
{ 'quarter to', 'quarter after' : 3 },
{ 'one', 'two','three','four','five','six','seven','eight','nine','ten','eleven','twelve': 4},
{ } ],
[ 2, 4 ] )
fsatest = NFSA( [ { 'one', 'two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','mid-','a':[ 1, 3 ] },
{ 'day', 'night','o`clock': 2 },
{ 'quarter to', 'quarter after' : 3 },
{ 'one', 'two','three','four','five','six','seven','eight','nine','ten','eleven','twelve':4 },
{ } ],
[ 2, 4] )