{"version":1,"lectureId":"01M14TXMSYRCBP5CTHVWH3TN01","attempt":0,"publication":{"slug":"regex-to-automata","title":"Automata Behind Regular Expressions","subject":"computer-science","summary":"Regular expressions are neither magic nor string comparison. They are compiled into small machines that read your input one character at a time, and this lecture builds one by hand. Four translation rules turn the pattern (a|b)*abb into a nondeterministic state machine, drawn as circles and arrows. A string is then fed through it with several states alive at once, and that machine is determinised by tracking sets of live states until a deterministic one falls out of the bookkeeping. It closes with two honest limits: a harmless looking pattern whose deterministic machine grows exponentially, and balanced brackets, which no regular expression can match at all, proved concretely by finding a loop the machine cannot avoid repeating. For working programmers. No automata background assumed.","metaDescription":"Build the state machine behind a regular expression, run a string through it, determinise it, and meet the two things regexes cannot do.","transcript":"You write a pattern, you hand it to a library, and a moment later you have your matches back. I have used regular expressions almost every working day for years, and for most of that time I had no picture at all of what happens in between. It turns out to be something small and completely concrete. So that is the question for the next twenty minutes. What actually runs when a regular expression matches a string? We are going to build one of these machines by hand, feed a string through it, watch it turn into a faster machine, and then find two things it flatly cannot do. Start as small as anything can be. Here is the pattern a b. One a, then one b, and nothing else. And here is its machine. Three circles. They are called states, and the machine is sitting in exactly one of them at any moment. The little arrow on the left marks where it starts, so before it reads anything it is in q nought. The arrows between the circles are transitions, and each one carries a character. This one says: if you are in q nought and the next character is an a, move to q one. This one says: from q one, a b takes you to q two. And that second circle drawn around q two is the whole point of the picture. It means accepting. If the string runs out while the machine is sitting there, the pattern matched. If it runs out anywhere else, it did not. So feed it the string a b. We begin in q nought. The first character is an a, so we follow the a arrow and land on q one. The next character is a b, which carries us to q two. The string has run out and we are sitting in the accepting state, so this one matched. Two characters, two arrows, no searching and no going back. Now feed it a a instead. The first a is fine and puts us in q one. Then the second a arrives, and there is no arrow out of q one labelled a. The machine has nowhere to go, so it stops, and it reports no match. That is the whole vocabulary. States, transitions, one place to start, and one or more accepting states. Everything for the rest of this lecture is that same picture with more arrows on it. Here is the thing nobody tells you. The machine is not guessed at, it is computed, and it follows the shape of the expression. There are only four kinds of piece in a basic regular expression, and each one has its own translation. A single character is one arrow between two states. That is rule one and that is the whole of it. A choice, written a bar b, is two arrows leaving the same state, one for each alternative. Whichever character actually turns up, one of those arrows is available to take. A star means any number of repetitions, including none at all, and that is an arrow that comes straight back to the state it left. Go round it as often as you like, or never. And concatenation, writing one piece after another, glues the end of the first onto the start of the second. Four rules, and between them they translate anything built from characters, choice, star and concatenation. One honest note before we use them. The textbook construction bolts a couple of extra bookkeeping states onto every single piece, so the drawing gets enormous very fast. I am going to draw the tidied result instead. It accepts exactly the same strings, and you can actually see it. Now a pattern you might genuinely write. Bracket, a bar b, close bracket, star, then a b b. In words: any number of a's and b's, and then the string has to finish with the letters a, b, b. Take the star first. Rule two says the choice inside it is two arrows off one state, and rule three says the star sends them back where they came from. So both arrows leave q nought and both return to q nought, and I have drawn them as one loop carrying two labels. Read that loop as: sit here and swallow whatever you like. Now rule four, concatenation, glues the rest of the pattern onto the end of it. The rest is three plain characters, so rule one gives us three arrows in a row. An a takes us from q nought to q one. A b takes us on to q two. And a final b takes us to q three. And q three is the accepting state, because arriving there is exactly what it means to have finished with a b b. Four states, five arrows, and every one of them came off a rule. Now look hard at q nought, because there is something wrong with it. Two different arrows leave that state carrying the letter a. One is the loop and one goes to q one. So when an a arrives, the machine has no way to decide. Is this a in the middle of the string, or is it the a that starts the ending we are looking for? It cannot know until it has read what comes after. That is what nondeterministic means. The fix is not to decide. Here is an input string, a a b b, and here is the list of states the machine could possibly be in. Before we read anything that list has exactly one entry, q nought. Read the first a. From q nought, two arrows are labelled a: the loop, which keeps us where we are, and the one across to q one. We refuse to choose, so we take both. The machine is now in q nought and q one at the same time. Read the second a. From q nought we get q nought and q one all over again. From q one there is no a arrow at all, so that possibility simply dies. The set has not changed, but the q one in it is a brand new thread, not the old one. Read a b. The loop keeps q nought alive, and from q one the b arrow moves us on to q two. So the live set is now q nought and q two, and the thread that was in q one has moved along with it. Suppose the string stopped right here, at a a b. We would be holding q nought and q two, and neither of those is an accepting state. So that string does not match, and we can say so without trying anything else. But it does not stop. Read the last b. The loop keeps q nought once more, and from q two the b arrow carries us into q three. Now the string has run out and one of the states we are holding is accepting, so the pattern matched. And that is the rule in general. If any possibility in your live set is an accepting state, it is a match. Notice what we never had to do. We never backed up and tried a different route through the pattern. We carried every possibility forward together and we read each character exactly once. Nondeterministic does not mean guessing. It means holding several answers at the same time. That machine works, and it is honest about not knowing. But look at what it costs. On every character we had to take a whole set of states, follow every arrow out of every one of them, and build the next set. So here is the idea that fixes it, and it is almost embarrassingly simple. Those sets are the only thing that ever mattered. So make each set a state in its own right. Then the new machine has no choices left in it anywhere. One state, one character, one next state. That is what deterministic means, and it is why the second machine can be a table you index into. First write down the old machine as a table, straight off the diagram. From q nought an a goes to q nought and q one, and a b goes back to q nought. From q one, an a goes nowhere and a b goes to q two. From q two, a b goes to q three. And q three has no arrows out of it at all. Now start the new machine from the only set we know, the one we begin in. Just q nought. Call it A. From A on an a, the table says q nought and q one. That is a set we have not met before, so it earns a name of its own. Call it B. From A on a b, we get just q nought again, which is A itself. Now do B, which is q nought and q one. On an a, q nought gives q nought and q one, and q one gives nothing. So we land back on B. On a b, q nought gives q nought and q one gives q two, so we get q nought and q two. New set. Call it C. Then C, which is q nought and q two. On an a we get q nought and q one, which is B again. On a b, q two gives q three, so we get q nought and q three. One more new set. Call it D. And finally D, which is q nought and q three. On an a we get B. On a b, q three gives nothing, so we get plain q nought, which is A. And that is the moment the whole thing stops. Every set we reached was already on the list. Four sets, and the search has nothing left to find. So draw them. Four states, A, B, C and D, and now every state has exactly one arrow leaving it for a and exactly one for b. From A an a goes to B, and a b comes straight back to A. From B an a stays on B, and a b drops down to C. From C an a climbs back to B, and a b carries on to D. And from D an a cuts across to B, while a b returns to A. Which one accepts? D does, and for one reason only. D is the set that contains q three, and q three was the accepting state of the old machine. That is the whole rule: a set accepts when anything in it accepts. And now run a a b b through this one. We start in A. The first a takes us to B. The second a keeps us there. Then a b drops us down to C, and the final b carries us into D. One state at every moment. No sets, no threads, and a single table lookup for each character. That is about as fast as a matcher can be, and it does not care how complicated the pattern was. There is a catch, though, and it is a big one. Here is a pattern that looks completely harmless. Any number of a's and b's, then an a, then any two characters. Read in plain English, it says the third character from the end is an a. Translate it with the same four rules and you get four states. A loop that swallows anything, an a that commits, and then two arrows that will take whatever they are given. That is a small machine, and simulating it with a live set is cheap. At most four states are ever alive at once. Now determinise it and watch what happens. The deterministic machine is not allowed to hold possibilities. When an a arrives it has to commit right then to whether this is the a we are looking for. But it cannot know that, because the answer depends on how many characters come afterwards. So it does the only thing left. It remembers the last three characters it has seen, and when the input finally ends it looks at the oldest of the three. How many things is that to remember? Two possibilities for each of three positions. Two times two times two. Eight states, where the nondeterministic machine had four. Now replace the three with an n. The nondeterministic machine grows very gently: it needs n plus one states, one for each position it still has to watch go by. That is the green line. The deterministic one has to remember the last n characters, and the number of things it might need to remember is two to the n. That is the red one. At n equals three the two are still close. Push it to five and the gap has opened. Push it to eight and the red curve has left the picture behind entirely, while the green one has crawled up to nine. Put some numbers on it. With n of ten, eleven states have become one thousand and twenty four. Push n up to twenty and those twenty one states have become the figure on the bottom row, which is over a million. And nobody has done anything wrong here. This is not a bug in an implementation. It is the price of removing the choices. The choices were carrying information, and if you refuse to defer them, you have to store that information as states instead. Which is why serious engines mostly do not build the whole deterministic machine up front. Some simulate the nondeterministic one directly, carrying the live set exactly as we did by hand a few minutes ago. Others build deterministic states lazily. They construct a state the first time some input actually reaches it, cache it, and throw the cache away when it grows too large. You pay for the states your data really visits and not for the ones it never does. One last limit, and this one is not about size. It is about something a regular expression cannot do at all, no matter how long you are willing to make it. Here is the language. Some number of opening brackets, followed by exactly the same number of closing brackets, and nothing else. Two open and two closed is in. Three and three is in. Two open and one closed is not. Everybody's first instinct is to reach for a star. There is no pattern that does it, and I want to show you why on the actual machine, because the argument is short and it is completely concrete. Suppose there were such a pattern. Then there is a machine for it, and that machine has some fixed number of states. It does not matter what the number is, so let us say four. Now feed it four opening brackets and nothing else yet. It starts somewhere, and each bracket moves it along, so we can watch the states it visits. Count the visits. Where it was before any bracket, then after one, after two, after three, after four. That is five visits, and I have written the count above each one. Five visits, and the machine only has four states to be in. So two of these five have to be the very same state. There is nowhere else for them to be. That is the whole of the argument, and it is just counting. Say it is this one and this one. Then they are not two states, they are one state that the machine passed through twice. Give it a name, q. And now look at what sits between them. The machine left q, read two opening brackets, and arrived back at q. That is a loop, and the machine has no way of knowing it went round it. So send it round twice. It reads two more opening brackets, comes back to q again, and it is in precisely the state it would have been in anyway. Then the four closing brackets do exactly what they did before, and it accepts. But count the string it has just accepted. Six opening brackets, and still only four closing ones. The machine has accepted something that is not balanced, which is the one thing we assumed it never did. And the four was arbitrary. Whatever number of states you name, I feed it that many opening brackets, count one more visit than it has states, and find the same loop. Every fixed number loses. So what actually went wrong? Not the star, and not the choice. The machine ran out of memory. To match brackets you have to remember how many are still open, and that number has no ceiling. A machine with a fixed number of states can remember a fixed number of things. That is the real boundary, and every language that needs to count without limit sits on the far side of it. Which is why the recursion and the backreferences in your favourite engine are not regular expressions in this sense at all. They are the features that step outside, and the price of stepping outside is that they can no longer be run as a finite machine. So that is the whole picture. Your pattern becomes a machine, one arrow per character, one state at a time. Nondeterminism means holding several possibilities together rather than guessing. Determinising trades those possibilities for states, sometimes at a terrible price. And a machine with a fixed number of states can only ever remember a fixed amount. Next time a pattern is slow, or refuses to do what you want at all, you now know which of those four facts you are standing on.","watch":{"version":1,"scenes":[{"title":"What a Pattern Really Is","start":0,"end":142.29166666666666,"objects":{"accept2":"a Circle [blue] drawn in fig (center=(8.2, 2.0), radius=0.42)","card":"a Title that says \"Foundations of Computing — Automata Behind Regular Expressions\"","enter":"a Vector [gray] drawn in fig (start=(0.45, 2.0), end=(1.16, 2.0))","fig":"a Figure (x_range=(0.0, 10.0), y_range=(0.0, 4.0), aspect=(10.0, 4.0))","pattern":"a Math [text] that says \"$upright(\"ab\")$\"","question":"a Panel that says \"What actually runs when a regular expression matches a string?\"","ring":"a Circle [blue] drawn in fig (center=(1.8, 2.0), radius=0.55)","ring_2":"a Circle [blue] drawn in fig (center=(5.0, 2.0), radius=0.55)","ring_3":"a Circle [blue] drawn in fig (center=(8.2, 2.0), radius=0.55)","tag":"a Math [text] that says \"$q_0$\" drawn in fig","tag_2":"a Math [text] that says \"$q_1$\" drawn in fig","tag_3":"a Math [text] that says \"$q_2$\" drawn in fig","vector":"a Vector [gray] labelled \"a\" drawn in fig (start=(2.44, 2.0), end=(4.36, 2.0))","vector_2":"a Vector [gray] labelled \"b\" drawn in fig (start=(5.64, 2.0), end=(7.56, 2.0))","vocab_1":"a Text [text] that says \"States are the circles. The machine sits in one of them.\"","vocab_2":"a Text [text] that says \"Transitions are the arrows, each labelled with a character.\"","vocab_3":"a Text [text] that says \"A double circle accepts. Finish there and the pattern matched.\""},"beats":[{"start":0,"say":"You write a pattern, you hand it to a library, and a moment later you have your matches back. I have used regular expressions almost every working day for years, and for most of that time I had no picture at all of what happens in between.","live":[],"does":[[0,"card is shown on the screen, written out."],[1.5,"card: enter:write-left-to-right."],[13.4555,"card is hidden from the screen — left the board."]]},{"start":14.6555,"say":"It turns out to be something small and completely concrete. So that is the question for the next twenty minutes. What actually runs when a regular expression matches a string?","live":null,"does":[[19.439,"question is shown on the screen, written out."]]},{"start":25.8325,"say":"We are going to build one of these machines by hand, feed a string through it, watch it turn into a faster machine, and then find two things it flatly cannot do.","live":["question"],"does":[[34.748999999999995,"question moves to a new place on the board."]]},{"start":35.349,"say":"Start as small as anything can be. Here is the pattern a b. One a, then one b, and nothing else. And here is its machine.","live":null,"does":[[38.507,"pattern is shown on the screen, written out."],[43.882,"fig is shown on the screen, written out."],[43.882,"ring is shown on the screen, written out."],[43.882,"tag is shown on the screen, written out."],[43.882,"ring_2 is shown on the screen, written out."],[43.882,"tag_2 is shown on the screen, written out."],[43.882,"ring_3 is shown on the screen, written out."],[43.882,"tag_3 is shown on the screen, written out."],[43.882,"enter is shown on the screen, written out."]]},{"start":45.13249999999999,"say":"Three circles. They are called states, and the machine is sitting in exactly one of them at any moment. The little arrow on the left marks where it starts, so before it reads anything it is in q nought.","live":["question","pattern","fig","ring","tag","ring_2","tag_2","ring_3","tag_3","enter"],"does":[[47.45499999999999,"vocab_1 is shown on the screen, written out."],[53.54999999999999,"ring is emphasized."]]},{"start":57.830499999999994,"say":"The arrows between the circles are transitions, and each one carries a character. This one says: if you are in q nought and the next character is an a, move to q one. This one says: from q one, a b takes you to q two.","live":["question","pattern","vocab_1","fig","ring","tag","ring_2","tag_2","ring_3","tag_3","enter"],"does":[[59.791999999999994,"vector is shown on the screen, drawn."],[60.09199999999999,"vector_2 is shown on the screen, drawn."],[61.812,"vocab_2 is shown on the screen, written out."]]},{"start":74.0225,"say":"And that second circle drawn around q two is the whole point of the picture. It means accepting. If the string runs out while the machine is sitting there, the pattern matched. If it runs out anywhere else, it did not.","live":["question","pattern","vocab_1","vocab_2","fig","ring","tag","ring_2","tag_2","ring_3","tag_3","enter","vector","vector_2"],"does":[[75.14799999999998,"accept2 is shown on the screen, written out."],[79.54799999999999,"vocab_3 is shown on the screen, written out."]]},{"start":88.461,"say":"So feed it the string a b. We begin in q nought. The first character is an a, so we follow the a arrow and land on q one. The next character is a b, which carries us to q two.","live":["question","pattern","vocab_1","vocab_2","vocab_3","fig","ring","tag","ring_2","tag_2","ring_3","tag_3","enter","vector","vector_2","accept2"],"does":[[96.13499999999999,"vector is indicated — a transient flash."],[97.52899999999998,"ring is no longer emphasized."],[97.52899999999998,"ring_2 is emphasized."],[101.09299999999999,"vector_2 is indicated — a transient flash."],[101.69699999999999,"ring_2 is no longer emphasized."],[101.69699999999999,"ring_3 is emphasized."]]},{"start":103.3185,"say":"The string has run out and we are sitting in the accepting state, so this one matched. Two characters, two arrows, no searching and no going back.","live":null,"does":[]},{"start":113.3455,"say":"Now feed it a a instead. The first a is fine and puts us in q one. Then the second a arrives, and there is no arrow out of q one labelled a. The machine has nowhere to go, so it stops, and it reports no match.","live":null,"does":[[113.3455,"ring_3 is no longer emphasized."],[113.3455,"ring is emphasized."],[117.45499999999998,"ring is no longer emphasized."],[117.45499999999998,"ring_2 is emphasized."],[124.43299999999998,"ring_2 is indicated — a transient flash."],[125.65199999999999,"ring_2 is no longer emphasized."]]},{"start":128.7485,"say":"That is the whole vocabulary. States, transitions, one place to start, and one or more accepting states. Everything for the rest of this lecture is that same picture with more arrows on it.","live":null,"does":[[141.25,"fig is hidden from the screen — left the board."],[141.25,"ring is hidden from the screen — fig left the board."],[141.25,"tag is hidden from the screen — fig left the board."],[141.25,"ring_2 is hidden from the screen — fig left the board."],[141.25,"tag_2 is hidden from the screen — fig left the board."],[141.25,"ring_3 is hidden from the screen — fig left the board."],[141.25,"tag_3 is hidden from the screen — fig left the board."],[141.25,"enter is hidden from the screen — fig left the board."],[141.25,"vector is hidden from the screen — fig left the board."],[141.25,"vector_2 is hidden from the screen — fig left the board."],[141.25,"accept2 is hidden from the screen — fig left the board."],[141.25,"pattern is hidden from the screen — left the board."],[141.25,"question is hidden from the screen — left the board."],[141.25,"vocab_1 is hidden from the screen — left the board."],[141.25,"vocab_2 is hidden from the screen — left the board."],[141.25,"vocab_3 is hidden from the screen — left the board."]]}]},{"title":"Building the Machine","start":142.29166666666666,"end":453.84783333333326,"objects":{"accept3":"a Circle [blue] drawn in fig (center=(10.3, 1.9), radius=0.42)","alive":"a Math [text] that says \"${ q_0 }$\"","enter":"a Vector [gray] drawn in fig (start=(0.3, 1.9), end=(0.96, 1.9))","fig":"a Figure (x_range=(0.0, 11.6), y_range=(0.0, 4.4), aspect=(11.6, 4.4))","head":"a Vector [gray] drawn in fig (start=(1.6, 3.02), end=(1.9460000000000002, 2.8200000000000003))","head_build":"a Heading that says \"Building $(a|b)^* a b b$\"","head_rules":"a Heading that says \"Four Rules of Translation\"","head_run":"a Heading that says \"Several States Alive at Once\"","ring":"a Circle [blue] drawn in fig (center=(1.6, 1.9), radius=0.55)","ring_2":"a Circle [gray] drawn in fig (center=(1.6, 2.62), radius=0.4)","ring_3":"a Circle [blue] drawn in fig (center=(4.5, 1.9), radius=0.55)","ring_4":"a Circle [blue] drawn in fig (center=(7.4, 1.9), radius=0.55)","ring_5":"a Circle [blue] drawn in fig (center=(10.3, 1.9), radius=0.55)","rule_1":"a Text [text] that says \"One character: one arrow.\"","rule_2":"a Text [text] that says \"A choice: two arrows leaving one state.\"","rule_3":"a Text [text] that says \"A star: an arrow back to where it started.\"","rule_4":"a Text [text] that says \"Concatenation: glue the pieces end to end.\"","tag":"a Math [text] that says \"$q_0$\" drawn in fig","tag_2":"a Math [text] that says \"$a, b$\" drawn in fig","tag_3":"a Math [text] that says \"$q_1$\" drawn in fig","tag_4":"a Math [text] that says \"$q_2$\" drawn in fig","tag_5":"a Math [text] that says \"$q_3$\" drawn in fig","tape":"a Table [text] that says \"a a b b\" (rows=(('a', 'a', 'b', 'b'),))","vector":"a Vector [gray] labelled \"a\" drawn in fig (start=(2.24, 1.9), end=(3.86, 1.9))","vector_2":"a Vector [gray] labelled \"b\" drawn in fig (start=(5.14, 1.9), end=(6.760000000000001, 1.9))","vector_3":"a Vector [gray] labelled \"b\" drawn in fig (start=(8.040000000000001, 1.9), end=(9.66, 1.9))","verdict":"a Math [text] that says \"$upright(\"no match yet\")$\""},"beats":[{"start":142.29166666666666,"say":"Here is the thing nobody tells you. The machine is not guessed at, it is computed, and it follows the shape of the expression. There are only four kinds of piece in a basic regular expression, and each one has its own translation.","live":[],"does":[[142.29166666666666,"head_rules is shown on the screen, written out."]]},{"start":158.05466666666666,"say":"A single character is one arrow between two states. That is rule one and that is the whole of it.","live":["head_rules"],"does":[[160.05166666666665,"rule_1 is shown on the screen, written out."]]},{"start":165.35366666666664,"say":"A choice, written a bar b, is two arrows leaving the same state, one for each alternative. Whichever character actually turns up, one of those arrows is available to take.","live":["rule_1","head_rules"],"does":[[168.60466666666665,"rule_2 is shown on the screen, written out."]]},{"start":178.10916666666665,"say":"A star means any number of repetitions, including none at all, and that is an arrow that comes straight back to the state it left. Go round it as often as you like, or never.","live":["rule_1","rule_2","head_rules"],"does":[[184.39066666666665,"rule_3 is shown on the screen, written out."]]},{"start":190.55166666666665,"say":"And concatenation, writing one piece after another, glues the end of the first onto the start of the second. Four rules, and between them they translate anything built from characters, choice, star and concatenation.","live":["rule_1","rule_2","rule_3","head_rules"],"does":[[194.98666666666665,"rule_4 is shown on the screen, written out."]]},{"start":206.67366666666663,"say":"One honest note before we use them. The textbook construction bolts a couple of extra bookkeeping states onto every single piece, so the drawing gets enormous very fast. I am going to draw the tidied result instead. It accepts exactly the same strings, and you can actually see it.","live":["rule_1","rule_2","rule_3","rule_4","head_rules"],"does":[[224.93616666666665,"rule_1 moves to a new place on the board."],[224.93616666666665,"rule_2 moves to a new place on the board."],[224.93616666666665,"rule_3 moves to a new place on the board."],[224.93616666666665,"rule_4 moves to a new place on the board."],[224.93616666666665,"head_rules is hidden from the screen — left the board."]]},{"start":226.13616666666664,"say":"Now a pattern you might genuinely write. Bracket, a bar b, close bracket, star, then a b b. In words: any number of a's and b's, and then the string has to finish with the letters a, b, b.","live":["rule_1","rule_2","rule_3","rule_4"],"does":[[226.13616666666664,"head_build is shown on the screen, written out."],[226.82166666666666,"fig is shown on the screen, written out."],[235.05266666666665,"ring is shown on the screen, written out."],[235.05266666666665,"tag is shown on the screen, written out."],[235.05266666666665,"enter is shown on the screen, written out."]]},{"start":242.67716666666664,"say":"Take the star first. Rule two says the choice inside it is two arrows off one state, and rule three says the star sends them back where they came from. So both arrows leave q nought and both return to q nought, and I have drawn them as one loop carrying two labels.","live":["rule_1","rule_2","rule_3","rule_4","fig","head_build","ring","tag","enter"],"does":[[258.66366666666664,"ring_2 is shown on the screen, drawn."],[258.66366666666664,"head is shown on the screen, written out."],[258.66366666666664,"tag_2 is shown on the screen, written out."]]},{"start":261.09816666666666,"say":"Read that loop as: sit here and swallow whatever you like. Now rule four, concatenation, glues the rest of the pattern onto the end of it.","live":["rule_1","rule_2","rule_3","rule_4","fig","head_build","ring","tag","enter","ring_2","head","tag_2"],"does":[[263.25766666666664,"ring_2 is indicated — a transient flash."]]},{"start":270.91666666666663,"say":"The rest is three plain characters, so rule one gives us three arrows in a row. An a takes us from q nought to q one. A b takes us on to q two. And a final b takes us to q three.","live":null,"does":[[275.07366666666667,"vector is shown on the screen, drawn."],[275.07366666666667,"ring_3 is shown on the screen, written out."],[275.07366666666667,"tag_3 is shown on the screen, written out."],[275.67366666666663,"vector_2 is shown on the screen, drawn."],[275.67366666666663,"ring_4 is shown on the screen, written out."],[275.67366666666663,"tag_4 is shown on the screen, written out."],[276.8736666666666,"vector_3 is shown on the screen, drawn."],[276.8736666666666,"ring_5 is shown on the screen, written out."],[276.8736666666666,"tag_5 is shown on the screen, written out."]]},{"start":285.78566666666666,"say":"And q three is the accepting state, because arriving there is exactly what it means to have finished with a b b. Four states, five arrows, and every one of them came off a rule.","live":["rule_1","rule_2","rule_3","rule_4","fig","head_build","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5"],"does":[[287.53866666666664,"accept3 is shown on the screen, written out."]]},{"start":297.97816666666665,"say":"Now look hard at q nought, because there is something wrong with it. Two different arrows leave that state carrying the letter a. One is the loop and one goes to q one.","live":["rule_1","rule_2","rule_3","rule_4","fig","head_build","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3"],"does":[[302.1286666666666,"ring_2 is emphasized."],[306.1686666666667,"vector is emphasized."]]},{"start":309.5901666666666,"say":"So when an a arrives, the machine has no way to decide. Is this a in the middle of the string, or is it the a that starts the ending we are looking for? It cannot know until it has read what comes after. That is what nondeterministic means.","live":null,"does":[[320.6546666666667,"ring_2 is no longer emphasized."],[320.6546666666667,"vector is no longer emphasized."],[326.2156666666666,"head_build is hidden from the screen — left the board."],[326.2156666666666,"rule_1 is hidden from the screen — left the board."],[326.2156666666666,"rule_2 is hidden from the screen — left the board."],[326.2156666666666,"rule_3 is hidden from the screen — left the board."],[326.2156666666666,"rule_4 is hidden from the screen — left the board."]]},{"start":327.41566666666665,"say":"The fix is not to decide. Here is an input string, a a b b, and here is the list of states the machine could possibly be in. Before we read anything that list has exactly one entry, q nought.","live":["fig","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3"],"does":[[327.41566666666665,"head_run is shown on the screen, written out."],[330.60866666666664,"tape is shown on the screen, written out."],[340.1056666666667,"alive is shown on the screen, written out."],[340.1056666666667,"ring is emphasized."]]},{"start":342.78366666666665,"say":"Read the first a. From q nought, two arrows are labelled a: the loop, which keeps us where we are, and the one across to q one. We refuse to choose, so we take both. The machine is now in q nought and q one at the same time.","live":["fig","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3","tape","alive","head_run"],"does":[[343.60766666666666,"tape (the \"column=1\" part) is emphasized."],[348.1586666666667,"ring_2 is indicated — a transient flash."],[350.68966666666665,"vector is indicated — a transient flash."],[354.43966666666665,"ring_3 is emphasized."],[359.0376666666667,"alive becomes \"${ q_0, q_1 }$\"."]]},{"start":360.7581666666666,"say":"Read the second a. From q nought we get q nought and q one all over again. From q one there is no a arrow at all, so that possibility simply dies. The set has not changed, but the q one in it is a brand new thread, not the old one.","live":null,"does":[[361.50666666666666,"tape (the \"column=1\" part) is no longer emphasized."],[361.50666666666666,"tape (the \"column=2\" part) is emphasized."],[371.6196666666667,"ring_3 is indicated — a transient flash."]]},{"start":379.1331666666666,"say":"Read a b. The loop keeps q nought alive, and from q one the b arrow moves us on to q two. So the live set is now q nought and q two, and the thread that was in q one has moved along with it.","live":null,"does":[[380.5556666666667,"tape (the \"column=2\" part) is no longer emphasized."],[380.5556666666667,"tape (the \"column=3\" part) is emphasized."],[384.7696666666667,"vector_2 is indicated — a transient flash."],[385.53566666666666,"ring_3 is no longer emphasized."],[385.53566666666666,"ring_4 is emphasized."],[387.54466666666667,"alive becomes \"${ q_0, q_2 }$\"."]]},{"start":394.7971666666666,"say":"Suppose the string stopped right here, at a a b. We would be holding q nought and q two, and neither of those is an accepting state. So that string does not match, and we can say so without trying anything else.","live":null,"does":[[405.61766666666665,"verdict is shown on the screen, written out."]]},{"start":409.38716666666664,"say":"But it does not stop. Read the last b. The loop keeps q nought once more, and from q two the b arrow carries us into q three.","live":["fig","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3","tape","alive","verdict","head_run"],"does":[[412.12766666666664,"tape (the \"column=3\" part) is no longer emphasized."],[412.12766666666664,"tape (the \"column=4\" part) is emphasized."],[417.97866666666664,"vector_3 is indicated — a transient flash."],[418.8496666666666,"alive becomes \"${ q_0, q_3 }$\"."],[418.8496666666666,"ring_4 is no longer emphasized."],[418.8496666666666,"ring_5 is emphasized."]]},{"start":420.6106666666666,"say":"Now the string has run out and one of the states we are holding is accepting, so the pattern matched. And that is the rule in general. If any possibility in your live set is an accepting state, it is a match.","live":null,"does":[[425.32466666666664,"verdict becomes \"$upright(\"match\")$\"."],[427.3796666666666,"A box is drawn around verdict."]]},{"start":434.8061666666666,"say":"Notice what we never had to do. We never backed up and tried a different route through the pattern. We carried every possibility forward together and we read each character exactly once. Nondeterministic does not mean guessing. It means holding several answers at the same time.","live":null,"does":[[449.01666666666654,"ring is no longer emphasized."],[449.01666666666654,"ring_5 is no longer emphasized."],[449.01666666666654,"tape (the \"column=4\" part) is no longer emphasized."],[452.8061666666666,"alive is hidden from the screen — left the board."],[452.8061666666666,"fig is hidden from the screen — left the board."],[452.8061666666666,"ring is hidden from the screen — fig left the board."],[452.8061666666666,"tag is hidden from the screen — fig left the board."],[452.8061666666666,"enter is hidden from the screen — fig left the board."],[452.8061666666666,"ring_2 is hidden from the screen — fig left the board."],[452.8061666666666,"head is hidden from the screen — fig left the board."],[452.8061666666666,"tag_2 is hidden from the screen — fig left the board."],[452.8061666666666,"vector is hidden from the screen — fig left the board."],[452.8061666666666,"ring_3 is hidden from the screen — fig left the board."],[452.8061666666666,"tag_3 is hidden from the screen — fig left the board."],[452.8061666666666,"vector_2 is hidden from the screen — fig left the board."],[452.8061666666666,"ring_4 is hidden from the screen — fig left the board."],[452.8061666666666,"tag_4 is hidden from the screen — fig left the board."],[452.8061666666666,"vector_3 is hidden from the screen — fig left the board."],[452.8061666666666,"ring_5 is hidden from the screen — fig left the board."],[452.8061666666666,"tag_5 is hidden from the screen — fig left the board."],[452.8061666666666,"accept3 is hidden from the screen — fig left the board."],[452.8061666666666,"head_run is hidden from the screen — left the board."],[452.8061666666666,"tape is hidden from the screen — left the board."],[452.8061666666666,"verdict is hidden from the screen — left the board."]]}]},{"title":"Sets of States","start":453.84783333333326,"end":668.1794999999998,"objects":{"accept_d":"a Circle [blue] drawn in dfa (center=(1.7, 1.3), radius=0.42)","cost":"a Panel that says \"Carrying a set costs work on every character. Carrying one state costs a single table lookup.\"","dfa":"a Figure (x_range=(0.0, 8.3), y_range=(0.2, 5.4), aspect=(8.3, 5.2))","enter_a":"a Vector [gray] drawn in dfa (start=(0.35, 3.5), end=(1.01, 3.5))","head":"a Vector [gray] drawn in dfa (start=(1.7, 4.62), end=(2.046, 4.42))","head_2":"a Vector [gray] drawn in dfa (start=(6.5, 4.62), end=(6.846, 4.42))","head_dfa":"a Heading that says \"The Deterministic Machine\"","head_idea":"a Heading that says \"One Machine, No Choices\"","head_work":"a Heading that says \"Chasing the Sets\"","idea":"a Panel that says \"Every set of states that can be alive at once becomes a single state of a new machine. That machine has no choices left: one state, one character, one next state.\"","nfa_table":"a Table [text] that says \"from on a on b $q_0$ $q_0, q_1$ $q_0$ $q_1$ $emptyset$ $q_2$ $q_2$ $emptyset$ $q_3$ $q_3$ $emptyset$ $emptyset$\" (rows=(('from', 'on a', 'on b'), ('$q_0$', '$q_0, q_1$', '$q_0$'), ('…, header=True)","ring":"a Circle [blue] drawn in dfa (center=(1.7, 3.5), radius=0.55)","ring_2":"a Circle [blue] drawn in dfa (center=(6.5, 3.5), radius=0.55)","ring_3":"a Circle [blue] drawn in dfa (center=(6.5, 1.3), radius=0.55)","ring_4":"a Circle [blue] drawn in dfa (center=(1.7, 1.3), radius=0.55)","ring_5":"a Circle [gray] drawn in dfa (center=(1.7, 4.22), radius=0.4)","ring_6":"a Circle [gray] drawn in dfa (center=(6.5, 4.22), radius=0.4)","subsets":"a Table [text] that says \"name set on a on b $A$ ${q_0}$ $B$ $A$ $B$ ${q_0, q_1}$ $B$ $C$ $C$ ${q_0, q_2}$ $B$ $D$ $D$ ${q_0, q_3}$ $B$ $A$\" (rows=(('name', 'set', 'on a', 'on b'), ('$A$', '${q_0}$', '$B$', '$A…, header=True)","tag":"a Math [text] that says \"$A$\" drawn in dfa","tag_2":"a Math [text] that says \"$B$\" drawn in dfa","tag_3":"a Math [text] that says \"$C$\" drawn in dfa","tag_4":"a Math [text] that says \"$D$\" drawn in dfa","tag_5":"a Math [text] that says \"$b$\" drawn in dfa","tag_6":"a Math [text] that says \"$a$\" drawn in dfa","vector":"a Vector [gray] labelled \"a\" drawn in dfa (start=(2.34, 3.5), end=(5.86, 3.5))","vector_2":"a Vector [gray] labelled \"b\" drawn in dfa (start=(6.3, 2.86), end=(6.3, 1.94))","vector_3":"a Vector [gray] labelled \"a\" drawn in dfa (start=(6.7, 1.94), end=(6.7, 2.86))","vector_4":"a Vector [gray] labelled \"b\" drawn in dfa (start=(5.86, 1.3), end=(2.34, 1.3))","vector_5":"a Vector [gray] labelled \"a\" drawn in dfa (start=(2.2818014866523417, 1.5666590147156567), end=(5.918198513347658, 3.233340985284343))","vector_6":"a Vector [gray] labelled \"b\" drawn in dfa (start=(1.7, 1.94), end=(1.7, 2.86))"},"beats":[{"start":453.84783333333326,"say":"That machine works, and it is honest about not knowing. But look at what it costs. On every character we had to take a whole set of states, follow every arrow out of every one of them, and build the next set.","live":[],"does":[[453.84783333333326,"head_idea is shown on the screen, written out."],[457.99283333333324,"cost is shown on the screen, written out."]]},{"start":467.16083333333324,"say":"So here is the idea that fixes it, and it is almost embarrassingly simple. Those sets are the only thing that ever mattered. So make each set a state in its own right.","live":["cost","head_idea"],"does":[[472.58283333333327,"idea is shown on the screen, written out."]]},{"start":478.17533333333324,"say":"Then the new machine has no choices left in it anywhere. One state, one character, one next state. That is what deterministic means, and it is why the second machine can be a table you index into.","live":["idea","cost","head_idea"],"does":[[482.19183333333325,"idea (the \"one state, one\" part) is emphasized."],[489.84383333333324,"idea (the \"one state, one\" part) is no longer emphasized."],[491.60833333333323,"cost is hidden from the screen — left the board."],[491.60833333333323,"head_idea is hidden from the screen — left the board."],[491.60833333333323,"idea is hidden from the screen — left the board."]]},{"start":492.8083333333333,"say":"First write down the old machine as a table, straight off the diagram. From q nought an a goes to q nought and q one, and a b goes back to q nought.","live":[],"does":[[492.8083333333333,"head_work is shown on the screen, written out."],[494.87483333333324,"nfa_table is shown on the screen, written out."],[496.24483333333325,"nfa_table is shown on the screen, written out."],[500.63383333333326,"nfa_table (the \"row=2\" part) is emphasized."],[503.90133333333324,"nfa_table (the \"row=2\" part) is no longer emphasized."]]},{"start":504.50133333333326,"say":"From q one, an a goes nowhere and a b goes to q two. From q two, a b goes to q three. And q three has no arrows out of it at all.","live":["head_work"],"does":[[505.01883333333325,"nfa_table is shown on the screen, written out."],[508.23483333333326,"nfa_table is shown on the screen, written out."],[511.70583333333326,"nfa_table is shown on the screen, written out."]]},{"start":516.3928333333332,"say":"Now start the new machine from the only set we know, the one we begin in. Just q nought. Call it A.","live":null,"does":[[517.5418333333332,"subsets is shown on the screen, written out."],[521.4538333333333,"subsets is shown on the screen, written out."]]},{"start":524.5328333333332,"say":"From A on an a, the table says q nought and q one. That is a set we have not met before, so it earns a name of its own. Call it B. From A on a b, we get just q nought again, which is A itself.","live":null,"does":[[524.5328333333332,"subsets (the \"row=2\" part) is emphasized."],[526.3848333333333,"nfa_table (the \"row=2\" part) is emphasized."],[531.3538333333332,"nfa_table (the \"row=2\" part) is no longer emphasized."],[532.8628333333332,"subsets is shown on the screen, written out."],[537.7978333333333,"subsets (the \"row=2\" part) is no longer emphasized."]]},{"start":539.4598333333332,"say":"Now do B, which is q nought and q one. On an a, q nought gives q nought and q one, and q one gives nothing. So we land back on B. On a b, q nought gives q nought and q one gives q two, so we get q nought and q two. New set. Call it C.","live":null,"does":[[539.7908333333332,"subsets (the \"row=3\" part) is emphasized."],[555.6848333333332,"subsets is shown on the screen, written out."],[556.8808333333333,"subsets (the \"row=3\" part) is no longer emphasized."]]},{"start":558.2238333333332,"say":"Then C, which is q nought and q two. On an a we get q nought and q one, which is B again. On a b, q two gives q three, so we get q nought and q three. One more new set. Call it D.","live":null,"does":[[558.2468333333333,"subsets (the \"row=4\" part) is emphasized."],[569.9378333333333,"subsets is shown on the screen, written out."],[571.4008333333333,"subsets (the \"row=4\" part) is no longer emphasized."]]},{"start":572.9123333333332,"say":"And finally D, which is q nought and q three. On an a we get B. On a b, q three gives nothing, so we get plain q nought, which is A.","live":null,"does":[[573.3018333333332,"subsets (the \"row=5\" part) is emphasized."],[582.6708333333332,"subsets (the \"row=5\" part) is no longer emphasized."]]},{"start":583.2708333333333,"say":"And that is the moment the whole thing stops. Every set we reached was already on the list. Four sets, and the search has nothing left to find.","live":null,"does":[[586.6258333333333,"subsets (the \"column=2\" part) is emphasized."],[589.7608333333333,"subsets (the \"column=2\" part) is no longer emphasized."],[593.0468333333333,"subsets moves to a new place on the board."],[593.0468333333333,"head_work is hidden from the screen — left the board."],[593.0468333333333,"nfa_table is hidden from the screen — left the board."]]},{"start":594.2468333333333,"say":"So draw them. Four states, A, B, C and D, and now every state has exactly one arrow leaving it for a and exactly one for b.","live":[],"does":[[594.2468333333333,"head_dfa is shown on the screen, written out."],[594.8388333333332,"dfa is shown on the screen, written out."],[596.3598333333332,"ring is shown on the screen, written out."],[596.3598333333332,"tag is shown on the screen, written out."],[596.5098333333333,"ring_2 is shown on the screen, written out."],[596.5098333333333,"tag_2 is shown on the screen, written out."],[596.8098333333332,"ring_3 is shown on the screen, written out."],[596.8098333333332,"tag_3 is shown on the screen, written out."],[597.2598333333333,"ring_4 is shown on the screen, written out."],[597.2598333333333,"tag_4 is shown on the screen, written out."],[597.8598333333332,"enter_a is shown on the screen, written out."]]},{"start":604.6223333333332,"say":"From A an a goes to B, and a b comes straight back to A. From B an a stays on B, and a b drops down to C.","live":["dfa","head_dfa","ring","tag","ring_2","tag_2","ring_3","tag_3","ring_4","tag_4","enter_a"],"does":[[605.7948333333333,"vector is shown on the screen, drawn."],[608.0708333333332,"ring_5 is shown on the screen, drawn."],[608.0708333333332,"head is shown on the screen, written out."],[608.0708333333332,"tag_5 is shown on the screen, written out."],[610.2068333333332,"ring_6 is shown on the screen, drawn."],[610.2068333333332,"head_2 is shown on the screen, written out."],[610.2068333333332,"tag_6 is shown on the screen, written out."],[611.9948333333332,"vector_2 is shown on the screen, drawn."]]},{"start":613.9183333333333,"say":"From C an a climbs back to B, and a b carries on to D. And from D an a cuts across to B, while a b returns to A.","live":["dfa","head_dfa","ring","tag","ring_2","tag_2","ring_3","tag_3","ring_4","tag_4","enter_a","vector","ring_5","head","tag_5","ring_6","head_2","tag_6","vector_2"],"does":[[615.0328333333332,"vector_3 is shown on the screen, drawn."],[616.7968333333332,"vector_4 is shown on the screen, drawn."],[619.6418333333332,"vector_5 is shown on the screen, drawn."],[621.5808333333332,"vector_6 is shown on the screen, drawn."]]},{"start":623.3998333333333,"say":"Which one accepts? D does, and for one reason only. D is the set that contains q three, and q three was the accepting state of the old machine. That is the whole rule: a set accepts when anything in it accepts.","live":["dfa","head_dfa","ring","tag","ring_2","tag_2","ring_3","tag_3","ring_4","tag_4","enter_a","vector","ring_5","head","tag_5","ring_6","head_2","tag_6","vector_2","vector_3","vector_4","vector_5","vector_6"],"does":[[625.5008333333333,"accept_d is shown on the screen, written out."],[629.4828333333332,"subsets (the \"row=5\" part) is emphasized."],[635.3698333333332,"subsets (the \"row=5\" part) is no longer emphasized."]]},{"start":639.4063333333332,"say":"And now run a a b b through this one. We start in A. The first a takes us to B. The second a keeps us there. Then a b drops us down to C, and the final b carries us into D.","live":["dfa","head_dfa","ring","tag","ring_2","tag_2","ring_3","tag_3","ring_4","tag_4","enter_a","vector","ring_5","head","tag_5","ring_6","head_2","tag_6","vector_2","vector_3","vector_4","vector_5","vector_6","accept_d"],"does":[[642.5288333333333,"ring is emphasized."],[644.5028333333332,"ring is no longer emphasized."],[644.5028333333332,"ring_2 is emphasized."],[648.3228333333333,"ring_2 is no longer emphasized."],[648.3228333333333,"ring_3 is emphasized."],[650.5978333333333,"ring_3 is no longer emphasized."],[650.5978333333333,"ring_4 is emphasized."]]},{"start":652.5388333333333,"say":"One state at every moment. No sets, no threads, and a single table lookup for each character. That is about as fast as a matcher can be, and it does not care how complicated the pattern was. There is a catch, though, and it is a big one.","live":null,"does":[[654.6578333333332,"ring_4 is no longer emphasized."],[667.1378333333332,"dfa is hidden from the screen — left the board."],[667.1378333333332,"ring is hidden from the screen — dfa left the board."],[667.1378333333332,"tag is hidden from the screen — dfa left the board."],[667.1378333333332,"ring_2 is hidden from the screen — dfa left the board."],[667.1378333333332,"tag_2 is hidden from the screen — dfa left the board."],[667.1378333333332,"ring_3 is hidden from the screen — dfa left the board."],[667.1378333333332,"tag_3 is hidden from the screen — dfa left the board."],[667.1378333333332,"ring_4 is hidden from the screen — dfa left the board."],[667.1378333333332,"tag_4 is hidden from the screen — dfa left the board."],[667.1378333333332,"enter_a is hidden from the screen — dfa left the board."],[667.1378333333332,"vector is hidden from the screen — dfa left the board."],[667.1378333333332,"ring_5 is hidden from the screen — dfa left the board."],[667.1378333333332,"head is hidden from the screen — dfa left the board."],[667.1378333333332,"tag_5 is hidden from the screen — dfa left the board."],[667.1378333333332,"ring_6 is hidden from the screen — dfa left the board."],[667.1378333333332,"head_2 is hidden from the screen — dfa left the board."],[667.1378333333332,"tag_6 is hidden from the screen — dfa left the board."],[667.1378333333332,"vector_2 is hidden from the screen — dfa left the board."],[667.1378333333332,"vector_3 is hidden from the screen — dfa left the board."],[667.1378333333332,"vector_4 is hidden from the screen — dfa left the board."],[667.1378333333332,"vector_5 is hidden from the screen — dfa left the board."],[667.1378333333332,"vector_6 is hidden from the screen — dfa left the board."],[667.1378333333332,"accept_d is hidden from the screen — dfa left the board."],[667.1378333333332,"head_dfa is hidden from the screen — left the board."],[667.1378333333332,"subsets is hidden from the screen — left the board."]]}]},{"title":"When the Machine Explodes","start":668.1794999999998,"end":835.8091249999998,"objects":{"accept3":"a Circle [blue] drawn in fig (center=(10.3, 1.9), radius=0.42)","blowup":"a FunctionPlot [red] labelled \"2^n\" drawn in growth (function=<function>, x_range=(0.0, 9.0))","count":"a Math [text] that says \"$2 dot.op 2 dot.op 2 = 8$\"","counts":"a Table [text] that says \"$n$ states before states after 3 4 8 5 6 32 10 11 1024 20 21 1048576\" (rows=(('$n$', 'states before', 'states after'), ('3', '4', '8'), ('5…, header=True)","enter":"a Vector [gray] drawn in fig (start=(0.3, 1.9), end=(0.96, 1.9))","fig":"a Figure (x_range=(0.0, 11.6), y_range=(0.0, 4.4), aspect=(11.6, 4.4))","growth":"an Axes (x_range=(0.0, 10.0), y_range=(0.0, 560.0), x_ticks_every=2.0)","head":"a Vector [gray] drawn in fig (start=(1.6, 3.02), end=(1.9460000000000002, 2.8200000000000003))","head_cost":"a Heading that says \"The Price of Having No Choices\"","head_pat":"a Heading that says \"Third From the End\"","linear":"a FunctionPlot [green] labelled \"n + 1\" drawn in growth (function=<function>, x_range=(0.0, 10.0))","n_live":"a VariableNumber (initial_value=3.0, format_spec='.0f')","pattern":"a Math [text] that says \"$upright(\"(a|b)*a(a|b)(a|b)\")$\"","plain":"a Text [text] that says \"In words: the third character from the end is an $a$.\"","probe":"a PlotPoint [yellow] labelled \"3\" drawn in growth (target='blowup', x=<VariableNumber n_live = 4.0>)","remember":"a Text [text] that says \"A machine with no choices has to remember the last three characters.\"","ring":"a Circle [blue] drawn in fig (center=(1.6, 1.9), radius=0.55)","ring_2":"a Circle [gray] drawn in fig (center=(1.6, 2.62), radius=0.4)","ring_3":"a Circle [blue] drawn in fig (center=(4.5, 1.9), radius=0.55)","ring_4":"a Circle [blue] drawn in fig (center=(7.4, 1.9), radius=0.55)","ring_5":"a Circle [blue] drawn in fig (center=(10.3, 1.9), radius=0.55)","tag":"a Math [text] that says \"$p_0$\" drawn in fig","tag_2":"a Math [text] that says \"$a, b$\" drawn in fig","tag_3":"a Math [text] that says \"$p_1$\" drawn in fig","tag_4":"a Math [text] that says \"$p_2$\" drawn in fig","tag_5":"a Math [text] that says \"$p_3$\" drawn in fig","vector":"a Vector [gray] labelled \"a\" drawn in fig (start=(2.24, 1.9), end=(3.86, 1.9))","vector_2":"a Vector [gray] labelled \"a, b\" drawn in fig (start=(5.14, 1.9), end=(6.760000000000001, 1.9))","vector_3":"a Vector [gray] labelled \"a, b\" drawn in fig (start=(8.040000000000001, 1.9), end=(9.66, 1.9))"},"beats":[{"start":668.1794999999998,"say":"Here is a pattern that looks completely harmless. Any number of a's and b's, then an a, then any two characters. Read in plain English, it says the third character from the end is an a.","live":[],"does":[[668.1794999999998,"head_pat is shown on the screen, written out."],[668.6324999999998,"pattern is shown on the screen, written out."],[677.0494999999999,"plain is shown on the screen, written out."]]},{"start":681.1554999999998,"say":"Translate it with the same four rules and you get four states. A loop that swallows anything, an a that commits, and then two arrows that will take whatever they are given.","live":["pattern","plain","head_pat"],"does":[[681.6314999999998,"fig is shown on the screen, written out."],[683.9424999999999,"ring is shown on the screen, written out."],[683.9424999999999,"tag is shown on the screen, written out."],[683.9424999999999,"enter is shown on the screen, written out."],[684.1424999999998,"ring_2 is shown on the screen, drawn."],[684.1424999999998,"head is shown on the screen, written out."],[684.1424999999998,"tag_2 is shown on the screen, written out."],[688.3074999999999,"vector is shown on the screen, drawn."],[688.3074999999999,"ring_3 is shown on the screen, written out."],[688.3074999999999,"tag_3 is shown on the screen, written out."],[689.7474999999998,"vector_2 is shown on the screen, drawn."],[689.7474999999998,"ring_4 is shown on the screen, written out."],[689.7474999999998,"tag_4 is shown on the screen, written out."],[690.2474999999998,"vector_3 is shown on the screen, drawn."],[690.2474999999998,"ring_5 is shown on the screen, written out."],[690.2474999999998,"tag_5 is shown on the screen, written out."],[690.2474999999998,"accept3 is shown on the screen, written out."]]},{"start":692.4249999999998,"say":"That is a small machine, and simulating it with a live set is cheap. At most four states are ever alive at once. Now determinise it and watch what happens.","live":["pattern","plain","fig","head_pat","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3"],"does":[[697.9164999999998,"ring is indicated — a transient flash."],[697.9164999999998,"ring_3 is indicated — a transient flash."],[697.9164999999998,"ring_4 is indicated — a transient flash."],[697.9164999999998,"ring_5 is indicated — a transient flash."]]},{"start":704.2759999999998,"say":"The deterministic machine is not allowed to hold possibilities. When an a arrives it has to commit right then to whether this is the a we are looking for. But it cannot know that, because the answer depends on how many characters come afterwards.","live":null,"does":[[709.8484999999998,"ring_2 is emphasized."],[709.8484999999998,"vector is emphasized."]]},{"start":719.1679999999999,"say":"So it does the only thing left. It remembers the last three characters it has seen, and when the input finally ends it looks at the oldest of the three.","live":null,"does":[[719.1679999999999,"ring_2 is no longer emphasized."],[719.1679999999999,"vector is no longer emphasized."],[721.7564999999998,"remember is shown on the screen, written out."]]},{"start":728.7194999999998,"say":"How many things is that to remember? Two possibilities for each of three positions. Two times two times two. Eight states, where the nondeterministic machine had four.","live":["pattern","plain","remember","fig","head_pat","ring","tag","enter","ring_2","head","tag_2","vector","ring_3","tag_3","vector_2","ring_4","tag_4","vector_3","ring_5","tag_5","accept3"],"does":[[733.3284999999998,"count is shown on the screen, written out."],[736.6954999999998,"count (the \"8\" part) is emphasized."],[740.3989999999999,"count is hidden from the screen — left the board."],[740.3989999999999,"fig is hidden from the screen — left the board."],[740.3989999999999,"ring is hidden from the screen — fig left the board."],[740.3989999999999,"tag is hidden from the screen — fig left the board."],[740.3989999999999,"enter is hidden from the screen — fig left the board."],[740.3989999999999,"ring_2 is hidden from the screen — fig left the board."],[740.3989999999999,"head is hidden from the screen — fig left the board."],[740.3989999999999,"tag_2 is hidden from the screen — fig left the board."],[740.3989999999999,"vector is hidden from the screen — fig left the board."],[740.3989999999999,"ring_3 is hidden from the screen — fig left the board."],[740.3989999999999,"tag_3 is hidden from the screen — fig left the board."],[740.3989999999999,"vector_2 is hidden from the screen — fig left the board."],[740.3989999999999,"ring_4 is hidden from the screen — fig left the board."],[740.3989999999999,"tag_4 is hidden from the screen — fig left the board."],[740.3989999999999,"vector_3 is hidden from the screen — fig left the board."],[740.3989999999999,"ring_5 is hidden from the screen — fig left the board."],[740.3989999999999,"tag_5 is hidden from the screen — fig left the board."],[740.3989999999999,"accept3 is hidden from the screen — fig left the board."],[740.3989999999999,"head_pat is hidden from the screen — left the board."],[740.3989999999999,"pattern is hidden from the screen — left the board."],[740.3989999999999,"plain is hidden from the screen — left the board."],[740.3989999999999,"remember is hidden from the screen — left the board."],[740.3989999999999,"count (the \"8\" part) is no longer emphasized."]]},{"start":740.9989999999998,"say":"Now replace the three with an n. The nondeterministic machine grows very gently: it needs n plus one states, one for each position it still has to watch go by. That is the green line.","live":[],"does":[[740.9989999999998,"head_cost is shown on the screen, written out."],[746.0844999999998,"growth is shown on the screen, written out."],[752.8184999999999,"linear is shown on the screen, drawn."]]},{"start":754.3464999999999,"say":"The deterministic one has to remember the last n characters, and the number of things it might need to remember is two to the n. That is the red one.","live":["growth","head_cost","linear"],"does":[[762.0324999999998,"blowup is shown on the screen, drawn."],[762.0324999999998,"probe is shown on the screen, written out."]]},{"start":763.4914999999999,"say":"At n equals three the two are still close. Push it to five and the gap has opened. Push it to eight and the red curve has left the picture behind entirely, while the green one has crawled up to nine.","live":["growth","head_cost","linear","blowup","probe"],"does":[[767.5664999999999,"probe is redrawn as the numbers it depends on change."],[767.5664999999999,"n_live ticks to 5.0."],[770.2594999999999,"probe is redrawn as the numbers it depends on change."],[770.2594999999999,"n_live ticks to 8.0."]]},{"start":776.9439999999998,"say":"Put some numbers on it. With n of ten, eleven states have become one thousand and twenty four. Push n up to twenty and those twenty one states have become the figure on the bottom row, which is over a million.","live":null,"does":[[776.9439999999998,"growth moves to a new place on the board."],[776.9439999999998,"counts is shown on the screen, written out."],[777.1439999999999,"counts is shown on the screen, written out."],[777.5439999999999,"counts is shown on the screen, written out."],[779.8464999999999,"counts is shown on the screen, written out."],[782.0054999999999,"counts (the \"row=4\" part) is emphasized."],[784.0024999999998,"counts is shown on the screen, written out."],[787.5094999999999,"counts (the \"row=4\" part) is no longer emphasized."],[787.5094999999999,"counts (the \"row=5\" part) is emphasized."],[790.5039999999999,"counts (the \"row=5\" part) is no longer emphasized."]]},{"start":791.1039999999998,"say":"And nobody has done anything wrong here. This is not a bug in an implementation. It is the price of removing the choices. The choices were carrying information, and if you refuse to defer them, you have to store that information as states instead.","live":null,"does":[[796.8164999999999,"counts (the \"column=3\" part) is emphasized."],[804.6644999999999,"counts (the \"column=3\" part) is no longer emphasized."]]},{"start":806.3789999999999,"say":"Which is why serious engines mostly do not build the whole deterministic machine up front. Some simulate the nondeterministic one directly, carrying the live set exactly as we did by hand a few minutes ago.","live":null,"does":[]},{"start":819.5994999999998,"say":"Others build deterministic states lazily. They construct a state the first time some input actually reaches it, cache it, and throw the cache away when it grows too large. You pay for the states your data really visits and not for the ones it never does.","live":null,"does":[[830.4894999999998,"probe is redrawn as the numbers it depends on change."],[830.4894999999998,"n_live ticks to 4.0."],[834.7674583333331,"counts is hidden from the screen — left the board."],[834.7674583333331,"growth is hidden from the screen — left the board."],[834.7674583333331,"linear is hidden from the screen — growth left the board."],[834.7674583333331,"blowup is hidden from the screen — growth left the board."],[834.7674583333331,"probe is hidden from the screen — growth left the board."],[834.7674583333331,"head_cost is hidden from the screen — left the board."]]}]},{"title":"What No Regular Expression Can Match","start":835.8091249999998,"end":1060.4591666666663,"objects":{"assume":"a Text [text] that says \"Suppose some machine with four states accepts exactly these strings.\"","chain":"a Figure (x_range=(0.0, 12.0), y_range=(0.6, 4.4), aspect=(12.0, 3.8))","counters":"a Math [gray] that says \"$0$\" drawn in chain","counters_2":"a Math [gray] that says \"$1$\" drawn in chain","counters_3":"a Math [gray] that says \"$2$\" drawn in chain","counters_4":"a Math [gray] that says \"$3$\" drawn in chain","counters_5":"a Math [gray] that says \"$4$\" drawn in chain","cycle":"a Vector [red] labelled \"upright(\"two brackets\")\" drawn in chain (start=(8.1, 1.78), end=(3.9, 1.78))","engines":"a Panel that says \"Recursion and backreferences in modern engines are exactly the features that step outside this world, and their cost is that they can no longer be run as a finite machine.\"","enter":"a Vector [gray] drawn in chain (start=(0.15, 2.7), end=(0.8, 2.7))","head_arg":"a Heading that says \"Five Visits, Four States\"","head_lang":"a Heading that says \"A Language With No Pattern\"","head_moral":"a Heading that says \"Finite Memory\"","language":"a Panel that says \"Some number of opening brackets, followed by exactly the same number of closing brackets, and nothing else.\"","moral":"a Panel that says \"A machine with a fixed number of states cannot count without limit. Anything that needs to remember an unbounded quantity is beyond every regular expression.\"","original":"a Math [text] that says \"$upright(\"(((( ))))\") quad arrow.r quad upright(\"accepted\")$\"","pumped":"a Math [text] that says \"$upright(\"(((((( ))))\") quad arrow.r quad upright(\"also accepted\")$\"","ring":"a Circle [blue] drawn in chain (center=(1.4, 2.7), radius=0.55)","ring_2":"a Circle [blue] drawn in chain (center=(3.7, 2.7), radius=0.55)","ring_3":"a Circle [blue] drawn in chain (center=(6.0, 2.7), radius=0.55)","ring_4":"a Circle [blue] drawn in chain (center=(8.3, 2.7), radius=0.55)","ring_5":"a Circle [blue] drawn in chain (center=(10.6, 2.7), radius=0.55)","samples":"a Panel that says \"In the language: $upright(\"( )\")$, $upright(\"(( ))\")$, $upright(\"((( )))\")$. Out of it: $upright(\"(( )\")$ and $upright(\"( ))\")$.\"","tag":"a Math [text] that says \"$v_0$\" drawn in chain","tag_2":"a Math [text] that says \"$v_1$\" drawn in chain","tag_3":"a Math [text] that says \"$v_2$\" drawn in chain","tag_4":"a Math [text] that says \"$v_3$\" drawn in chain","tag_5":"a Math [text] that says \"$v_4$\" drawn in chain","vector":"a Vector [gray] labelled \"upright(\"(\")\" drawn in chain (start=(2.04, 2.7), end=(3.06, 2.7))","vector_2":"a Vector [gray] labelled \"upright(\"(\")\" drawn in chain (start=(4.34, 2.7), end=(5.36, 2.7))","vector_3":"a Vector [gray] labelled \"upright(\"(\")\" drawn in chain (start=(6.64, 2.7), end=(7.660000000000001, 2.7))","vector_4":"a Vector [gray] labelled \"upright(\"(\")\" drawn in chain (start=(8.940000000000001, 2.7), end=(9.959999999999999, 2.7))"},"beats":[{"start":835.8091249999998,"say":"One last limit, and this one is not about size. It is about something a regular expression cannot do at all, no matter how long you are willing to make it.","live":[],"does":[[835.8091249999998,"head_lang is shown on the screen, written out."]]},{"start":846.2891249999998,"say":"Here is the language. Some number of opening brackets, followed by exactly the same number of closing brackets, and nothing else. Two open and two closed is in. Three and three is in. Two open and one closed is not.","live":["head_lang"],"does":[[847.1951249999997,"language is shown on the screen, written out."],[855.1701249999998,"samples is shown on the screen, written out."]]},{"start":862.3766249999998,"say":"Everybody's first instinct is to reach for a star. There is no pattern that does it, and I want to show you why on the actual machine, because the argument is short and it is completely concrete.","live":["language","samples","head_lang"],"does":[[873.6386249999998,"head_lang is hidden from the screen — left the board."],[873.6386249999998,"language is hidden from the screen — left the board."],[873.6386249999998,"samples is hidden from the screen — left the board."]]},{"start":874.8386249999998,"say":"Suppose there were such a pattern. Then there is a machine for it, and that machine has some fixed number of states. It does not matter what the number is, so let us say four.","live":[],"does":[[874.8386249999998,"head_arg is shown on the screen, written out."],[877.9961249999998,"chain is shown on the screen, written out."],[884.6611249999997,"assume is shown on the screen, written out."]]},{"start":886.1081249999997,"say":"Now feed it four opening brackets and nothing else yet. It starts somewhere, and each bracket moves it along, so we can watch the states it visits.","live":["chain","assume","head_arg"],"does":[[890.3811249999998,"enter is shown on the screen, written out."],[890.3811249999998,"ring is shown on the screen, written out."],[890.3811249999998,"tag is shown on the screen, written out."],[892.4591249999997,"vector is shown on the screen, drawn."],[892.4591249999997,"ring_2 is shown on the screen, written out."],[892.4591249999997,"tag_2 is shown on the screen, written out."],[892.8591249999997,"vector_2 is shown on the screen, drawn."],[892.8591249999997,"ring_3 is shown on the screen, written out."],[892.8591249999997,"tag_3 is shown on the screen, written out."],[893.6591249999998,"vector_3 is shown on the screen, drawn."],[893.6591249999998,"ring_4 is shown on the screen, written out."],[893.6591249999998,"tag_4 is shown on the screen, written out."],[894.8591249999997,"vector_4 is shown on the screen, drawn."],[894.8591249999997,"ring_5 is shown on the screen, written out."],[894.8591249999997,"tag_5 is shown on the screen, written out."]]},{"start":896.3211249999997,"say":"Count the visits. Where it was before any bracket, then after one, after two, after three, after four. That is five visits, and I have written the count above each one.","live":["chain","assume","head_arg","enter","ring","tag","vector","ring_2","tag_2","vector_2","ring_3","tag_3","vector_3","ring_4","tag_4","vector_4","ring_5","tag_5"],"does":[[904.4021249999997,"counters is shown on the screen, written out."],[904.5221249999997,"counters_2 is shown on the screen, written out."],[904.6421249999997,"counters_3 is shown on the screen, written out."],[904.7621249999997,"counters_4 is shown on the screen, written out."],[904.8821249999997,"counters_5 is shown on the screen, written out."]]},{"start":908.6356249999998,"say":"Five visits, and the machine only has four states to be in. So two of these five have to be the very same state. There is nowhere else for them to be. That is the whole of the argument, and it is just counting.","live":["chain","assume","head_arg","enter","ring","tag","vector","ring_2","tag_2","vector_2","ring_3","tag_3","vector_3","ring_4","tag_4","vector_4","ring_5","tag_5","counters","counters_2","counters_3","counters_4","counters_5"],"does":[[913.3031249999998,"ring_2 is emphasized."],[913.3031249999998,"ring_4 is emphasized."]]},{"start":922.4481249999998,"say":"Say it is this one and this one. Then they are not two states, they are one state that the machine passed through twice. Give it a name, q.","live":null,"does":[[930.1691249999998,"tag_2 becomes \"$q$\"."],[930.1691249999998,"tag_4 becomes \"$q$\"."]]},{"start":931.6396249999998,"say":"And now look at what sits between them. The machine left q, read two opening brackets, and arrived back at q. That is a loop, and the machine has no way of knowing it went round it.","live":null,"does":[[940.1261249999998,"cycle is shown on the screen, drawn."]]},{"start":943.7566249999998,"say":"So send it round twice. It reads two more opening brackets, comes back to q again, and it is in precisely the state it would have been in anyway. Then the four closing brackets do exactly what they did before, and it accepts.","live":["chain","assume","head_arg","enter","ring","tag","vector","ring_2","tag_2","vector_2","ring_3","tag_3","vector_3","ring_4","tag_4","vector_4","ring_5","tag_5","counters","counters_2","counters_3","counters_4","counters_5","cycle"],"does":[[944.9761249999998,"cycle is indicated — a transient flash."],[952.1391249999998,"ring_2 is indicated — a transient flash."],[952.1391249999998,"ring_4 is indicated — a transient flash."]]},{"start":958.3586249999997,"say":"But count the string it has just accepted. Six opening brackets, and still only four closing ones. The machine has accepted something that is not balanced, which is the one thing we assumed it never did.","live":null,"does":[[958.9391249999998,"original is shown on the screen, written out."],[961.8301249999997,"pumped is shown on the screen, written out."],[967.9251249999998,"pumped (the \"upright(\"(((((( ))))\")\" part) is emphasized."],[971.1991249999998,"pumped (the \"upright(\"(((((( ))))\")\" part) is no longer emphasized."]]},{"start":971.7991249999998,"say":"And the four was arbitrary. Whatever number of states you name, I feed it that many opening brackets, count one more visit than it has states, and find the same loop. Every fixed number loses.","live":["chain","assume","original","pumped","head_arg","enter","ring","tag","vector","ring_2","tag_2","vector_2","ring_3","tag_3","vector_3","ring_4","tag_4","vector_4","ring_5","tag_5","counters","counters_2","counters_3","counters_4","counters_5","cycle"],"does":[[983.9191249999998,"ring_2 is no longer emphasized."],[983.9191249999998,"ring_4 is no longer emphasized."],[984.9411249999998,"assume is hidden from the screen — left the board."],[984.9411249999998,"chain is hidden from the screen — left the board."],[984.9411249999998,"enter is hidden from the screen — chain left the board."],[984.9411249999998,"ring is hidden from the screen — chain left the board."],[984.9411249999998,"tag is hidden from the screen — chain left the board."],[984.9411249999998,"vector is hidden from the screen — chain left the board."],[984.9411249999998,"ring_2 is hidden from the screen — chain left the board."],[984.9411249999998,"tag_2 is hidden from the screen — chain left the board."],[984.9411249999998,"vector_2 is hidden from the screen — chain left the board."],[984.9411249999998,"ring_3 is hidden from the screen — chain left the board."],[984.9411249999998,"tag_3 is hidden from the screen — chain left the board."],[984.9411249999998,"vector_3 is hidden from the screen — chain left the board."],[984.9411249999998,"ring_4 is hidden from the screen — chain left the board."],[984.9411249999998,"tag_4 is hidden from the screen — chain left the board."],[984.9411249999998,"vector_4 is hidden from the screen — chain left the board."],[984.9411249999998,"ring_5 is hidden from the screen — chain left the board."],[984.9411249999998,"tag_5 is hidden from the screen — chain left the board."],[984.9411249999998,"counters is hidden from the screen — chain left the board."],[984.9411249999998,"counters_2 is hidden from the screen — chain left the board."],[984.9411249999998,"counters_3 is hidden from the screen — chain left the board."],[984.9411249999998,"counters_4 is hidden from the screen — chain left the board."],[984.9411249999998,"counters_5 is hidden from the screen — chain left the board."],[984.9411249999998,"cycle is hidden from the screen — chain left the board."],[984.9411249999998,"head_arg is hidden from the screen — left the board."],[984.9411249999998,"original is hidden from the screen — left the board."],[984.9411249999998,"pumped is hidden from the screen — left the board."]]},{"start":986.1411249999998,"say":"So what actually went wrong? Not the star, and not the choice. The machine ran out of memory. To match brackets you have to remember how many are still open, and that number has no ceiling.","live":[],"does":[[986.1411249999998,"head_moral is shown on the screen, written out."],[992.7941249999998,"moral is shown on the screen, written out."]]},{"start":1000.0346249999998,"say":"A machine with a fixed number of states can remember a fixed number of things. That is the real boundary, and every language that needs to count without limit sits on the far side of it.","live":["moral","head_moral"],"does":[[1006.3161249999997,"moral (the \"cannot count without limit\" part) is emphasized."],[1011.5176249999997,"moral (the \"cannot count without limit\" part) is no longer emphasized."]]},{"start":1012.1176249999997,"say":"Which is why the recursion and the backreferences in your favourite engine are not regular expressions in this sense at all. They are the features that step outside, and the price of stepping outside is that they can no longer be run as a finite machine.","live":null,"does":[[1013.1391249999997,"engines is shown on the screen, written out."]]},{"start":1026.9161249999997,"say":"So that is the whole picture. Your pattern becomes a machine, one arrow per character, one state at a time. Nondeterminism means holding several possibilities together rather than guessing. Determinising trades those possibilities for states, sometimes at a terrible price.","live":["moral","engines","head_moral"],"does":[]},{"start":1046.4981249999996,"say":"And a machine with a fixed number of states can only ever remember a fixed amount. Next time a pattern is slow, or refuses to do what you want at all, you now know which of those four facts you are standing on.","live":null,"does":[[1059.4174999999998,"engines is hidden from the screen — left the board."],[1059.4174999999998,"head_moral is hidden from the screen — left the board."],[1059.4174999999998,"moral is hidden from the screen — left the board."]]}]}]},"durationSeconds":1060,"chapters":[{"title":"What a Pattern Really Is","startSeconds":0,"narration":"You write a pattern, you hand it to a library, and a moment later you have your matches back. I have used regular expressions almost every working day for years, and for most of that time I had no picture at all of what happens in between. It turns out to be something small and completely concrete. So that is the question for the next twenty minutes. What actually runs when a regular expression matches a string? We are going to build one of these machines by hand, feed a string through it, watch it turn into a faster machine, and then find two things it flatly cannot do. Start as small as anything can be. Here is the pattern a b. One a, then one b, and nothing else. And here is its machine. Three circles. They are called states, and the machine is sitting in exactly one of them at any moment. The little arrow on the left marks where it starts, so before it reads anything it is in q nought. The arrows between the circles are transitions, and each one carries a character. This one says: if you are in q nought and the next character is an a, move to q one. This one says: from q one, a b takes you to q two. And that second circle drawn around q two is the whole point of the picture. It means accepting. If the string runs out while the machine is sitting there, the pattern matched. If it runs out anywhere else, it did not. So feed it the string a b. We begin in q nought. The first character is an a, so we follow the a arrow and land on q one. The next character is a b, which carries us to q two. The string has run out and we are sitting in the accepting state, so this one matched. Two characters, two arrows, no searching and no going back. Now feed it a a instead. The first a is fine and puts us in q one. Then the second a arrives, and there is no arrow out of q one labelled a. The machine has nowhere to go, so it stops, and it reports no match. That is the whole vocabulary. States, transitions, one place to start, and one or more accepting states. Everything for the rest of this lecture is that same picture with more arrows on it."},{"title":"Building the Machine","startSeconds":142.29166666666666,"narration":"Here is the thing nobody tells you. The machine is not guessed at, it is computed, and it follows the shape of the expression. There are only four kinds of piece in a basic regular expression, and each one has its own translation. A single character is one arrow between two states. That is rule one and that is the whole of it. A choice, written a bar b, is two arrows leaving the same state, one for each alternative. Whichever character actually turns up, one of those arrows is available to take. A star means any number of repetitions, including none at all, and that is an arrow that comes straight back to the state it left. Go round it as often as you like, or never. And concatenation, writing one piece after another, glues the end of the first onto the start of the second. Four rules, and between them they translate anything built from characters, choice, star and concatenation. One honest note before we use them. The textbook construction bolts a couple of extra bookkeeping states onto every single piece, so the drawing gets enormous very fast. I am going to draw the tidied result instead. It accepts exactly the same strings, and you can actually see it. Now a pattern you might genuinely write. Bracket, a bar b, close bracket, star, then a b b. In words: any number of a's and b's, and then the string has to finish with the letters a, b, b. Take the star first. Rule two says the choice inside it is two arrows off one state, and rule three says the star sends them back where they came from. So both arrows leave q nought and both return to q nought, and I have drawn them as one loop carrying two labels. Read that loop as: sit here and swallow whatever you like. Now rule four, concatenation, glues the rest of the pattern onto the end of it. The rest is three plain characters, so rule one gives us three arrows in a row. An a takes us from q nought to q one. A b takes us on to q two. And a final b takes us to q three. And q three is the accepting state, because arriving there is exactly what it means to have finished with a b b. Four states, five arrows, and every one of them came off a rule. Now look hard at q nought, because there is something wrong with it. Two different arrows leave that state carrying the letter a. One is the loop and one goes to q one. So when an a arrives, the machine has no way to decide. Is this a in the middle of the string, or is it the a that starts the ending we are looking for? It cannot know until it has read what comes after. That is what nondeterministic means. The fix is not to decide. Here is an input string, a a b b, and here is the list of states the machine could possibly be in. Before we read anything that list has exactly one entry, q nought. Read the first a. From q nought, two arrows are labelled a: the loop, which keeps us where we are, and the one across to q one. We refuse to choose, so we take both. The machine is now in q nought and q one at the same time. Read the second a. From q nought we get q nought and q one all over again. From q one there is no a arrow at all, so that possibility simply dies. The set has not changed, but the q one in it is a brand new thread, not the old one. Read a b. The loop keeps q nought alive, and from q one the b arrow moves us on to q two. So the live set is now q nought and q two, and the thread that was in q one has moved along with it. Suppose the string stopped right here, at a a b. We would be holding q nought and q two, and neither of those is an accepting state. So that string does not match, and we can say so without trying anything else. But it does not stop. Read the last b. The loop keeps q nought once more, and from q two the b arrow carries us into q three. Now the string has run out and one of the states we are holding is accepting, so the pattern matched. And that is the rule in general. If any possibility in your live set is an accepting state, it is a match. Notice what we never had to do. We never backed up and tried a different route through the pattern. We carried every possibility forward together and we read each character exactly once. Nondeterministic does not mean guessing. It means holding several answers at the same time."},{"title":"Sets of States","startSeconds":453.84783333333326,"narration":"That machine works, and it is honest about not knowing. But look at what it costs. On every character we had to take a whole set of states, follow every arrow out of every one of them, and build the next set. So here is the idea that fixes it, and it is almost embarrassingly simple. Those sets are the only thing that ever mattered. So make each set a state in its own right. Then the new machine has no choices left in it anywhere. One state, one character, one next state. That is what deterministic means, and it is why the second machine can be a table you index into. First write down the old machine as a table, straight off the diagram. From q nought an a goes to q nought and q one, and a b goes back to q nought. From q one, an a goes nowhere and a b goes to q two. From q two, a b goes to q three. And q three has no arrows out of it at all. Now start the new machine from the only set we know, the one we begin in. Just q nought. Call it A. From A on an a, the table says q nought and q one. That is a set we have not met before, so it earns a name of its own. Call it B. From A on a b, we get just q nought again, which is A itself. Now do B, which is q nought and q one. On an a, q nought gives q nought and q one, and q one gives nothing. So we land back on B. On a b, q nought gives q nought and q one gives q two, so we get q nought and q two. New set. Call it C. Then C, which is q nought and q two. On an a we get q nought and q one, which is B again. On a b, q two gives q three, so we get q nought and q three. One more new set. Call it D. And finally D, which is q nought and q three. On an a we get B. On a b, q three gives nothing, so we get plain q nought, which is A. And that is the moment the whole thing stops. Every set we reached was already on the list. Four sets, and the search has nothing left to find. So draw them. Four states, A, B, C and D, and now every state has exactly one arrow leaving it for a and exactly one for b. From A an a goes to B, and a b comes straight back to A. From B an a stays on B, and a b drops down to C. From C an a climbs back to B, and a b carries on to D. And from D an a cuts across to B, while a b returns to A. Which one accepts? D does, and for one reason only. D is the set that contains q three, and q three was the accepting state of the old machine. That is the whole rule: a set accepts when anything in it accepts. And now run a a b b through this one. We start in A. The first a takes us to B. The second a keeps us there. Then a b drops us down to C, and the final b carries us into D. One state at every moment. No sets, no threads, and a single table lookup for each character. That is about as fast as a matcher can be, and it does not care how complicated the pattern was. There is a catch, though, and it is a big one."},{"title":"When the Machine Explodes","startSeconds":668.1794999999998,"narration":"Here is a pattern that looks completely harmless. Any number of a's and b's, then an a, then any two characters. Read in plain English, it says the third character from the end is an a. Translate it with the same four rules and you get four states. A loop that swallows anything, an a that commits, and then two arrows that will take whatever they are given. That is a small machine, and simulating it with a live set is cheap. At most four states are ever alive at once. Now determinise it and watch what happens. The deterministic machine is not allowed to hold possibilities. When an a arrives it has to commit right then to whether this is the a we are looking for. But it cannot know that, because the answer depends on how many characters come afterwards. So it does the only thing left. It remembers the last three characters it has seen, and when the input finally ends it looks at the oldest of the three. How many things is that to remember? Two possibilities for each of three positions. Two times two times two. Eight states, where the nondeterministic machine had four. Now replace the three with an n. The nondeterministic machine grows very gently: it needs n plus one states, one for each position it still has to watch go by. That is the green line. The deterministic one has to remember the last n characters, and the number of things it might need to remember is two to the n. That is the red one. At n equals three the two are still close. Push it to five and the gap has opened. Push it to eight and the red curve has left the picture behind entirely, while the green one has crawled up to nine. Put some numbers on it. With n of ten, eleven states have become one thousand and twenty four. Push n up to twenty and those twenty one states have become the figure on the bottom row, which is over a million. And nobody has done anything wrong here. This is not a bug in an implementation. It is the price of removing the choices. The choices were carrying information, and if you refuse to defer them, you have to store that information as states instead. Which is why serious engines mostly do not build the whole deterministic machine up front. Some simulate the nondeterministic one directly, carrying the live set exactly as we did by hand a few minutes ago. Others build deterministic states lazily. They construct a state the first time some input actually reaches it, cache it, and throw the cache away when it grows too large. You pay for the states your data really visits and not for the ones it never does."},{"title":"What No Regular Expression Can Match","startSeconds":835.8091249999998,"narration":"One last limit, and this one is not about size. It is about something a regular expression cannot do at all, no matter how long you are willing to make it. Here is the language. Some number of opening brackets, followed by exactly the same number of closing brackets, and nothing else. Two open and two closed is in. Three and three is in. Two open and one closed is not. Everybody's first instinct is to reach for a star. There is no pattern that does it, and I want to show you why on the actual machine, because the argument is short and it is completely concrete. Suppose there were such a pattern. Then there is a machine for it, and that machine has some fixed number of states. It does not matter what the number is, so let us say four. Now feed it four opening brackets and nothing else yet. It starts somewhere, and each bracket moves it along, so we can watch the states it visits. Count the visits. Where it was before any bracket, then after one, after two, after three, after four. That is five visits, and I have written the count above each one. Five visits, and the machine only has four states to be in. So two of these five have to be the very same state. There is nowhere else for them to be. That is the whole of the argument, and it is just counting. Say it is this one and this one. Then they are not two states, they are one state that the machine passed through twice. Give it a name, q. And now look at what sits between them. The machine left q, read two opening brackets, and arrived back at q. That is a loop, and the machine has no way of knowing it went round it. So send it round twice. It reads two more opening brackets, comes back to q again, and it is in precisely the state it would have been in anyway. Then the four closing brackets do exactly what they did before, and it accepts. But count the string it has just accepted. Six opening brackets, and still only four closing ones. The machine has accepted something that is not balanced, which is the one thing we assumed it never did. And the four was arbitrary. Whatever number of states you name, I feed it that many opening brackets, count one more visit than it has states, and find the same loop. Every fixed number loses. So what actually went wrong? Not the star, and not the choice. The machine ran out of memory. To match brackets you have to remember how many are still open, and that number has no ceiling. A machine with a fixed number of states can remember a fixed number of things. That is the real boundary, and every language that needs to count without limit sits on the far side of it. Which is why the recursion and the backreferences in your favourite engine are not regular expressions in this sense at all. They are the features that step outside, and the price of stepping outside is that they can no longer be run as a finite machine. So that is the whole picture. Your pattern becomes a machine, one arrow per character, one state at a time. Nondeterminism means holding several possibilities together rather than guessing. Determinising trades those possibilities for states, sometimes at a terrible price. And a machine with a fixed number of states can only ever remember a fixed amount. Next time a pattern is slow, or refuses to do what you want at all, you now know which of those four facts you are standing on."}]}}
