In this problem, we have a bit of a challenge, and I've accordingly labeled it with
2 gold stars, but what we want to do here,
is optimize a finite state machine.
So to go through an example, let's say we have this finite state machine.
There's 1 accepting state at state 1, and then we have these few others.
If you quickly look at it and think about what's going on,
once you take a path down the b to state 2,
you can never get to the accepting state.
From state 2, 3, or 4, there's no way to get to state 1.
So any string that goes down this path is always going to fail.
This state machine is equivalent to the one that doesn't include any of these states.
So we can make it a lot simpler.
Why would we want to do this?
Well, if you haven't noticed we've been using a lot of regular expressions
in building our web browser.
Those regular expressions are represented as finite state machines,
and that's how they're processed.
In order to make our web browser faster, it turns out we want as small finite state machines
as possible.
So what we're going to do is write code.
Given a definition of finite state machine like the that we have here,
we're going to identify states that don't matter towards the execution,
and we call those dead states and remove them from the definition,
while maintaining the exact same language, while recognizing the exact same strings
that our finite state machine did before.
So how are we going to this? Let's come up with a plan.
So here we have "The Plan."
Step 1--Let's find the live states and the dead states.
We're going to do this by just finding the live states and assuming everything else is dead.
So how are we going to do that?
Well, we're going to find all the states, and we can do that by iterating through our dictionary,
and with each state, we're going to run nfsmaccepts,
which is from homework 1, and it's a procedure that given a definition
for a finite state machine, a starting state, and the accepting states,
it sees if it's possible to get from that state to any accepting state--
if it's possible to succeed from where we're at.
So if I give this definition in state 3, it's going to tell me, no, we can't get to state 1
or any accepting state for that matter.
If I give a 1, it's going to say, yep, this is all good.
So that's actually going to be your definition for live versus dead.
It can actually find a live state versus a dead state, which is awesome.
Step 2--We're going to create a new finite state machine that doesn't have any
of the dead states.
In order to make it a really good, kind of clean, definition, we have to take some care.
We don't want to include any transitions that lead to dead states.
We want to remove all of the dead states,
and we also want to remove states that no longer point to any live states.
So here I have a bunch of little subparts.
We're going to go through each edge state, each entry in our dictionary.
If the current state is dead, we're not going to copy it into our new finite state machine.
Otherwise, we're going to go through all of the destinations it had in the original
finite state machine and prepare to copy over any that are still alive.
If there are none that are still alive, we're going to remove that edge completely.
We're not going to copy it into a new one.
Once we have repeated that process in every state edge thing in the graph,
we're going to update our accepting state list accordingly.
We don't want to have any accepting states that are dead.
So let's go to the solution.
One of the first things I did is I copied in nondeterministic finite state machine accepts
directly from unit 1 homework.
It hasn't changed a bit.
Now I'm going to do my trimming of my finite state machine.
So like I said in my outline, I'm going to find all the states,
just so I have a record of them, and it doesn't really matter if I have duplicates.
It might slow down the trimming a bit, but I'm doing this to save time
when I'm running the execution, not for the trimming so much.
For each state, if it's live, I can tell by running nfsmaccepts,
and if it is live, I'm going to add it to a list of live states.
Now I'm going to create a new dictionary of edges--my new representation,
and go through all of the old ones to see if they're still live and update them accordingly.
So for each edge in the old dictionary, if that state is live,
then I'm going to calculate the new destinations, meaning I'm going to remove the destinations
that are now dead.
So if that destination is live, I'm going to append it to the list.
But I only want to set this new edge to my new finite state machine dictionary
if the destinations are not empty.
There's really no point in having an edge that goes to nowhere,
that goes to fail.
We just kind of always assume that if the edge doesn't exist, then we're going to fail.
Lastly, I want to update my accepting states to only those that are live.
I'm going to return the tuple of the new edges and the new accepting states.
And that's it.
Look! It's true! So much true!
True means good. It means meaning and greatness.