Why does an NFA that accepts/rejects bit strings implemented with a Recursive Backtracking algorithm outperform one that is written using a Parallel Bitmap search?
set up two tests for these algorithms, to evaluate on both different length strings, and different numbers of inputs:
First, I created sets of 1,000 random bit strings of length n, where my sets were n = [4, 8, 16, 32, 64].
Second, I tested these on input.txt files with varying amounts of strings. In all cases, bit strings were length 10 with files of varying amounts of strings( 10^n strings with n = [1:6]).
I timed each of these tests using the Python .time module.
I expected Parallel Bitmap Search to run faster, but the Recursive method was more efficient.
Below is the code for the bit-mapped parallel search method:
"""
The current set of states, encoded bitwise:
state i is represented by the bit 1<<i.
"""
stateSet = 0
"""
Reset the current state set to {the start state}.
"""
def reset():
global stateSet
stateSet = 1<<0
"""
The transition function represented as an array.
The set of next states from a given state s and
character c is at delta[s,c-'0'].
"""
#rows = states
#cols = transitions
delta = [[1<<0|1<<1, 1<<0],\
[1<<2,0],\
[1<<2,1<<2]]
"""
Make one transition from state-set to state-set on
each char in the given string.
@param inp the String to use
"""
def process(inp):
global stateSet
for i in range(len(inp)):
c = inp[i]
nextSS = 0 # next state set, initially empty
for s in range(3): # for each state s
if (stateSet&(1<<s)) != 0: # if maybe in s
try:
nextSS |= delta[s][int(c)]
except:
# in effect, nextSS |= 0
pass
stateSet = nextSS # new state set after c
Recursive Method:
class State():
def __init__(self,name,is_start_state,is_final_state):
"""
*** constructor ***
1. name , the name of the state, i.e. q0
2. is_start_state , the name of the state, i.e. True if is start state
3. is_final_state , the name of the state, i.e. False if not final state
4. transitions , the transition table, contents are filled by add_transitions()
"""
self.name = name
self.is_start_state = is_start_state
self.is_final_state = is_final_state
self.transitions = {} #empty dict for transition table
def add_transitions(self,string, target_states):
#trans = (string, target_states)
self.transitions[string] = target_states
# NFA below
q0 = State("q0",True,False)
q1 = State("q1",False,False)
q2 = State("q2", False, True)
#q0 start, not final
q0.add_transitions("0",[q0,q1]) #adds an entry to the transitions dict:{ "0":[q0]}
q0.add_transitions("1",[q0])
#q1 not start, not final
q1.add_transitions("0",[q2])
#q2 not start, final
q2.add_transitions("0",[q2])
q2.add_transitions("1",[q2])
states = [q0,q1,q2]
"""
Test whether there is some path for the NFA to
reach an accepting state from the given state,
reading the given string at the given character
position.
@param state the current state
@param inp the input string
@param pos index of the next char in the string
@return true if the NFA accepts on some path
"""
def accept_step(state, inp, pos):
if pos == len(inp): # if no more to read
return state.is_final_state # accept if the reached state is final state
c = inp[pos] # get char
try:
nextStates = state.transitions[c] #char is dict key for transitions
except:
return False # no transition, just reject
for nextState in nextStates:
if accept_step(nextState, inp, (pos+1)):
return True
return False # all moves fail, return false
"""
Test whether the NFA accepts the string.
@param in the String to test
@return true if the NFA accepts on some path
"""
def accepts(str_input):
return accept_step(states[0], str_input, 0) # start in q0 at char 0