Alright... onto the new stuff.
The underlying topic for today is to discuss NFAs with more depth and compare/contrast them to DFAs. In other words, we'll see a striking equivalence of the two which will help us understand the regular languages with more depth (since we'll now have two different perspectives for looking at the same thing and adding another perspective is always useful).
Note that subset construction is also referred to as the powerset construction.
At brief high-level, I think the Wikipedia page for "Powerset construction" gives a nice definition:
Subset Construction:
The powerset construction or subset construction is a standard method for converting a nondeterministic finite automaton into a deterministic finite automaton which recognizes the same formal language.
Recall the tabular form of a DFA. From there, we can represent that table (also possible in software if you'd like) as we saw last lecture. We can perform a similar process with an NFA to form a table. From there on, we can also represent it through software and since we can form the table (see below) in the first place, we can represent the NFA as a DFA.
The key here is that we can assign "subsets" as states instead of just original singular states. See the conversion below.
First, we have our NFA:
Now we represent it via table:
And from this, we form our DFA:
(Note: see "Guide to Subset Construction" for more implementation details).
It is indeed the case that whatever you can do with an NFA, you can also do with a DFA. But as one might imagine, the amount of states and transitions in the DFA version of some NFA could be exponentially larger.
This fact helps use give clarity to the notion that one might suggest at this point: "Why would we build NFAs instead of DFAs if they both can represent the same language?". As stated above, they can be much more compact.
Moreover, certain things which we'll see later, are easier to worth with DFAs than NFAs and vise versa. See 37:00
from lecture for a good example.
Recall from last lecture where we showed "A language is called a regular language if there exists a DFA such that .". Since we just stated that NFAs are functionally the same as DFAs, we also can extrapolate the above theorem to this:
A language is called a regular language if there exists a NFA such that .
So a regular language can now be thought of a language as an NFA or also a DFA. And to answer the philosophical question posed above, such a result means that we can work with NFAs instead (and vise versa) for certain problems which could lead to a new discovery/a new way of looking at things.
Let us now discuss and probe some more properties about regular languages (RLs). We will toy around with some set theory operations by framing them as propositional logic connectives with languages as statements.
If and are regular languages, is ?
Yes... just make an NFA with two -transitions like so:
This is like the "or" case (existential).
If and are regular languages, is as well?
This is like the "and" case and is also much harder to show than the "or" case. Let us try to see if this is true.
This is pretty interesting actually. We can indeed get the "and" case by applying De Morgan's Law to set theory. We can form "and" in propositional logic using just ~
and or
via: ~(~p or ~q)
which, in this context using languages and compliments, we get:
You might imagine that constructing some machine for this would be a multi-step process (e.g. lots of DFA-NFA, NFA-DFA conversions).
Works the same as you've seen in programming languages. If you have and , the concatenated version is notated as .
Note, some interesting properties which could be useful during manipulations:
We can also perform concatenations over languages instead of just strings. Formally, we define concatenation over two languages and as:
Concatenation:
This is, in some ways, like the cartesian product of two languages where is "the set of all strings that can be made by concatenating string in with a string in .".
You can also think of it as "The set of strings that can be split into two pieces: a piece from and a piece from .". See 53:00
for more about the "splitting" intuition and an example use case. Briefly speaking, concatenating two automaton is like having each individual one complete some task, then the other would complete another task, then combine them together into one. We combine them by saying "do this, then do this".
Definitively, we can define a series of steps for concatenating together two machines:
We talked about how concatenation is almost like a "product". So consider some language . We perform the concatenation of with to get . We can do it again to get . And again: . Looks a little like exponentials in some sense right?
We have the following characteristics:
and, inductively:
We now introduce one more operation which is somewhat nuanced, but useful nonetheless:
Kleene Closure:
which is mathematically equivalent to:
See 1:08
for a more detailed explanation. Briefly speaking, recall . This is applying the same notion to languages (operating on strings instead of languages internally) via the notation .
Remember how we defined closure properties in the previous lecture. Notice that we just examined a bunch of closure properties here.
If and are regular languages over an alphabet , then so are the following languages: