Today:
Homework 4 debrief.
Homework 5.
Reading questions.
For next time:
Finish Homework 5.
Prepare for an exam.
Optional: Reading questions below (for next Thursday).
Here's how an NFA processes strings. The key idea is that we keep track of all the states we might be in.
def process(self, string):
"""Reads a string and returns 'accept' if the FA accepts the
string and 'reject' otherwise.
"""
# start with the null closure of the start state
states = self.null_closure(self.start)
for symbol in string:
# start with an empty set and accumulate all the
# states that can be reached
new_states = set()
# for each state in the current set
for state in states:
try:
# find the next state for this symbol
next = self.trans[state, symbol]
# find the null closure of that state
closure = self.null_closure(next)
# and add it into the accumulator
new_states = new_states.union(closure)
except KeyError:
# an NFA can have states that don't have
# transitions for all symbols
pass
# the new set of states is the set of states we can reach
states = new_states
# if any of the states we can reach is an accept state, accept
for state in states:
if state in self.accept:
return 'accept'
return 'reject'
Here's how to convert a NFA to a DFA. The key idea is to keep track of all the states the NFA might be in, and create a state in the DFA for each (feasible) subset of the states in the NFA.
def make_dfa_from_nfa(nfa):
"""Makes an DFA equivalent to the given NFA.
Algorithm is based (loosely) on Sipser's Theorem 1.39.
"""
states = set()
alphabet = nfa.alphabet
trans = dict()
start = frozenset(nfa.null_closure(nfa.start))
queue = [start]
while queue:
state_set = queue.pop(0)
states.add(state_set)
for symbol in alphabet:
new_state_set = make_state_set(nfa, state_set, symbol)
trans[state_set, symbol] = new_state_set
if new_state_set not in states:
queue.append(new_state_set)
# loop through trans and build the set of accept states
accept = set()
for (state_set, symbol) in trans:
for state in state_set:
if state in nfa.accept:
accept.add(state_set)
break
return dfa.DFA(states, alphabet, trans, start, accept)
And here's make_state_set
def make_state_set(nfa, state_set, symbol):
"""Return the set of states that can be reached from state_set
on symbol.
"""
# start with an empty set and accumulate all the
# states that can be reached
new_state_set = set()
# for each state in the current set
for state in state_set:
try:
# find the next state for this symbol
next = nfa.trans[state, symbol]
# find the null closure of that state
closure = nfa.null_closure(next)
# and add it into the accumulator
new_state_set = new_state_set.union(closure)
except KeyError:
# an NFA can have states that don't have
# transitions for all symbols
pass
return frozenset(new_state_set)
Sipser, pages 109-123
1) What is the feature of a PDA that makes it capable of recognizing a language like O^n1^n when a DFA cannot?
2) In the formal definition of a PDA why is the stack alphabet different from the input alphabet?
3) If you were implementing the transition function δ as a dictionary in Python, what type would the keys have? What type would the values have?
4) In Figure 2.15, what is the sequence of states the PDA goes through while parsing 0101?
5) In constructing PDAs, what is the role of the special symbol $?
6) What are the two steps in proving that a language is context free iff a PDA recognizes it?
7) What kind of proof is given for Lemma 2.21 on page 115?
8) What kind of proof is given for Lemma 2.27 on page 119?
9) Why do we need the two "claims" on page 121?
[On page 122, the end of the proof is pretty abrupt -- you have to unwind the stack to see that the proof is complete.]
10) Do exercise 2.1 on page 128.
11) Do exercise 2.3 on page 128.