Computer Science Logo Style volume 3: Beyond Programming 2/e Copyright (C) 1997 MIT

Automata Theory

cover photo
Brian Harvey
University of California, Berkeley

Download PDF version
Back to Table of Contents
BACK chapter thread NEXT
MIT Press web page for Computer Science Logo Style

Program file for this chapter: fsm

As I explained in the preface to the first volume, one of my purposes in writing this series of books has been to urge computer hobbyists away from the view of computer expertise as the knowledge of obscure characteristics of some particular computer--how to program it in machine language, what magic numbers can be found where in its memory, how to overcome the copy protection schemes on its disks, and so on. The trouble with this sort of machine-specific expertise is that it becomes obsolete when your favorite computer does. From my point of view, one of the virtues of Logo as a programming language is that its high level data structures direct your attention away from questions about what goes where in memory, allowing you to focus instead on a more abstract description of your problem.

Automata theory is a further step in abstracting your attention away from any particular kind of computer or particular programming language. In automata theory we consider a mathematical model of computing. Such a model strips the computational machinery--the "programming language"--down to the bare minimum, so that it's easy to manipulate these theoretical machines (there are several such models, for different purposes, as you'll soon see) mathematically to prove things about their capabilities. For the most part, these mathematical models are not used for practical programming problems. Real programming languages are much more convenient to use. But the very flexibility that makes real languages easier to use also makes them harder to talk about in a formal way. The stripped-down theoretical machines are designed to be examined mathematically.

What's a mathematical model? You'll see one shortly, called a "finite-state machine."

The point of this study is that the mathematical models are, in some important ways, equivalent to real computers and real programming languages. What this means is that any problem that can be solved on a real computer can be solved using these models, and vice versa. Anything we can prove about the models sheds light on the real problems of computer programming as well.

The questions asked in automata theory include these: Are there any problems that no computer can solve, no matter how much time and memory it has? Is it possible to prove that a particular computer program will actually solve a particular problem? If a computer can use two different external storage devices (disks or tapes) at the same time, does that extend the range of problems it can solve compared to a machine with only one such device?

There is also a larger question lurking in the background of automata theory: Does the human mind solve problems in the same way that a computer does? Are people subject to the same limitations as computers? Automata theory does not actually answer this question, but the insights of automata theory can be helpful in trying to work out an answer. We'll have more to say about this in the chapter on artificial intelligence.

What is a Computation?

What kinds of problems can we give to our abstract computers? In automata theory we want to focus our attention on computation itself, not on details of input and output devices. So we won't try creating a mathematical model of a video game.

We will play a game, though. In this game the computer has a rule in mind. You type in strings of letters, using only the letters A, B, and C. The computer tells you whether each string follows the rule or not. Your job is to guess the rule. For example, suppose you have done these experiments:

accepted     rejected
ABC    CBA
AAA    BBB
ABCABCABC    BCABCABC
A    BBBBBBB
ACCCCCCCCC    CAAAAAAAAA

You might guess, from these examples, that the rule is "The string must begin with A." Once you've made a guess you can test it out by trying more examples.

The program to play the game is called game. It takes one input, a number from 1 to 10. I've provided ten different rules. Rules 1 to 3 should be pretty easy to guess; rules 8 to 10 should be nearly impossible. (Don't feel too frustrated if you don't get them.)

A string can be any length, including length zero (the empty string). Each time you type a letter the program lets you know whether the string you've typed so far obeys the rule. The program indicates whether the string is accepted or rejected by displaying the word accept or reject on the screen. In particular, as soon as you start game the program will tell you whether or not the empty string is accepted by this rule. If you type the string ABC you'll really be testing three strings: A, AB, and ABC. You should type one letter at a time to make sure the program has a chance to respond to it before going on to the next letter. To start over again with a different string, press the Return key.

You should stop reading now and try the game. In the following paragraphs I'm going to talk about some of the answers, so this is your last chance. After you've figured out at least some of the rules, come back to the book.

Finite-State Machines

The point of studying this game is that we're going to look at a way to design a special-purpose abstract computer that understands one particular rule. We can then ask questions about how much information the computer needs to handle the job.

You've seen the word state before in connection with the Logo turtle. Its state includes its position and its heading. So one turtle state might be "position [17 82], heading 90." In principle, the turtle has an infinite number of possible states, because its position and heading don't have to be integers. Its position might be [14.142 14.142], for instance.

Anything that holds information can be in different states. As another example, an on-off light switch has two states. Some lamps have four states: off, low, medium, and high. A computer, too, has a certain number of states. The state of a computer includes all the information in its memory at some particular time.

A machine that has only a limited number of states, like the example of the light switch, is called a finite-state machine. For almost all of this chapter we'll be dealing with finite-state machines. You might think that that imposes a very severe limit on the kinds of computations we can do. But note that in the game I asked you to play, a rule can accept an infinite number of possible strings and reject an infinite number of others. The accepted or rejected strings can be of any length. (Some rules restrict the length of a string, but others allow any length at all.) In some sense, a finite-state machine can still perform infinitely varied computations.

Consider the third game as an example. The rule is "Accept any string that starts with AB." Here is a picture of a finite-state machine that implements that rule:

figure: fsm3

Each numbered circle represents a state. This machine has three states. The start arrow indicates that the machine starts out in state 1. State 3 is shown with a double circle to indicate that it is an accepting state. In other words, when the machine is in state 3 the screen says accept. The other two states are not accepting states. Every time you type a character the machine switches from one state to another. The arrow from state 1 to state 2 has an A next to its tail. This indicates that when the machine is in state 1, an input of A switches it to state 2. (The arrow from state 3 to itself has the three letters ABC at its tail. This is a shorthand notation for three separate arrows with heads and tails in the same place, one for each letter.)

This picture is actually incomplete. For a full description of the machine we have to indicate what happens on any input in any state. In other words, each circle should have three arrows coming out from it, one each for A, B, and C. I've chosen to adopt the convention that every machine has an unmarked state called reject. Any missing arrow goes to that state; once the machine is in the reject state it stays there forever. Here, then, is the complete diagram of the machine for game 3:

figure: fsm3-reject

From now on I won't draw the reject state, but you should remember that it's really part of the machine description. So this machine requires four states, not three.

If the first input letter isn't A, the machine goes to the reject state. If the first letter is A, the machine goes to state 2. Then, if the second letter is B, the machine ends up in state 3 and accepts the string AB. This is the shortest acceptable string.

Each of the three arrows from state 3 loops right back into state 3 itself. (Remember, although only one arrow appears in the picture, it is labeled with three letters, so officially it represents three arrows.) This means that once the machine is in state 3 it stays there no matter what inputs it gets. Any string that starts AB is acceptable.

Here is a machine for game number 2:

figure: fsm2

In this machine the start state is also an accepting state. (Every machine has exactly one start state, but it may have any number of accepting states.) This machine never gets into the reject state. That doesn't mean it doesn't reject any strings; all odd-length strings are rejected in state 2. But a rejected string can redeem itself by adding another input character, so state 2 allows a return to the accepting state 1.

Here is a machine for game number 5. (Notice that I'm saying "a machine" and not "the machine"; it is always possible to design other machines that would follow the same rule.)

figure: fsm5

You probably had more trouble discovering rule 5 than rule 2, and it takes longer to say the rule in English words. But the machines for the two rules are almost identical. (Remember, though, that the rule-5 machine really has a third state, the reject state, which is not shown in the diagram.)

Here are machines for rules 7 and 9. With these machines as hints, can you figure out the rules? Go back to the game program to test your hypotheses.

figure: fsm79

You should also get some practice translating in the other direction, from English rules to machine diagrams. Here are a couple to work on: Rule 4 is "To be accepted a string must be composed of doubled letters (AA, pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */

/*
 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
 * See LICENSE file for license details.
 */

#include "dwm.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

/* extern */

char stext[1024];
Bool *seltag;
int bx, by, bw, bh, bmw, master, screen, sx, sy, sw, sh;
unsigned int ntags, numlockmask;
Atom wmatom[WMLast], netatom[NetLast];
Bool running = True;
Bool issel = True;
Client *clients = NULL;
Client *sel = NULL;
Client *stack = NULL;
Cursor cursor[CurLast];
Display *dpy;
DC dc = {0};
Window root, barwin;

/* static */

static int (*xerrorxlib)(Display *, XErrorEvent *);
static Bool otherwm, readin;

static void
cleanup(void) {
	close(STDIN_FILENO);
	while(sel) {
		resize(sel, True, TopLeft);
		unmanage(sel);
	}
	if(dc.font.set)
		XFreeFontSet(dpy, dc.font.set);
	else
		XFreeFont(dpy, dc.font.xfont);
	XUngrabKey(dpy, AnyKey, AnyModifier, root);
	XFreePixmap(dpy, dc.drawable);
	XFreeGC(dpy, dc.gc);
	XDestroyWindow(dpy, barwin);
	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
	XSync(dpy, False);
	free(seltag);
}

static void
scan(void) {
	unsigned int i, num;
	Window *wins, d1, d2;
	XWindowAttributes wa;

	wins = NULL;
	if(XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
		for(i = 0; i < num; i++) {
			if(!XGetWindowAttributes(dpy, wins[i], &wa))
				continue;
			if(wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
				continue;
			if(wa.map_state == IsViewable)
				manage(wins[i], &wa);
		}
	}
	if(wins)
		XFree(wins);
}

static void
setup(void) {
	int i, j;
	unsigned int mask;
	Window w;
	XModifierKeymap *modmap;
	XSetWindowAttributes wa;

	/* init atoms */
	wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
	wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
	netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
	XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
			PropModeReplace, (unsigned char *) netatom, NetLast);

	/* init cursors */
	cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr);
	cursor[CurResize] = XCreateFontCursor(dpy, XC_sizing);
	cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);

	modmap = XGetModifierMapping(dpy);
	for (i = 0; i < 8; i++) {
		for (j = 0; j < modmap->max_keypermod; j++) {
			if(modmap->modifiermap[i * modmap->max_keypermod + j] == XKeysymToKeycode(dpy, XK_Num_Lock))
				numlockmask = (1 << i);
		}
	}
	XFree(modmap);

	wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
		| EnterWindowMask | LeaveWindowMask;
	wa.cursor = cursor[CurNormal];
	XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);

	grabkeys();
	initrregs();

	for(ntags = 0; tags[ntags]; ntags++);
	seltag = emallocz(sizeof(Bool) * ntags);
	seltag[0] = True;

	/* style */
	dc.norm[ColBG] = getcolor(NORMBGCOLOR);
	dc.norm[ColFG] = getcolor(NORMFGCOLOR);
	dc.sel[ColBG] = getcolor(SELBGCOLOR);
	dc.sel[ColFG] = getcolor(SELFGCOLOR);
	dc.status[ColBG] = getcolor(STATUSBGCOLOR);
	dc.status[ColFG] = getcolor(STATUSFGCOLOR);
	setfont(FONT);

	bmw = textw(FLOATSYMBOL) > textw(TILESYMBOL) ? textw(FLOATSYMBOL) : textw(TILESYMBOL);
	sx = sy = 0;
	sw = DisplayWidth(dpy, screen);
	sh = DisplayHeight(dpy, screen);
	updatemaster();

	bx = by = 0;
	bw = sw;
	dc.h = bh = dc.font.height + 2;
	wa.override_redirect = 1;
	wa.background_pixmap = ParentRelative;
	wa.event_mask = ButtonPressMask | ExposureMask;
	barwin = XCreateWindow(dpy, root, bx, by, bw, bh, 0, DefaultDepth(dpy, screen),
			CopyFromParent, DefaultVisual(dpy, screen),
			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
	XDefineCursor(dpy, barwin, cursor[CurNormal]);
	XMapRaised(dpy, barwin);

	dc.drawable = XCreatePixmap(dpy, root, sw, bh, DefaultDepth(dpy, screen));
	dc.gc = XCreateGC(dpy, root, 0, 0);
	XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);

	issel = XQueryPointer(dpy, root, &w, &w, &i, &i, &i, &i, &mask);
	strcpy(stext, "dwm-"VERSION);
}

/*
 * Startup Error handler to check if another window manager
 * is already running.
 */
static int
xerrorstart(Display *dsply, XErrorEvent *ee) {
	otherwm = True;
	return -1;
}

/* extern */

int
getproto(Window w) {
	int i, format, protos, status;
	unsigned long extra, res;
	Atom *protocols, real;

	protos = 0;
	status = XGetWindowProperty(dpy, w, wmatom[WMProtocols], 0L, 20L, False,
			XA_ATOM, &real, &format, &res, &extra, (unsigned char **)&protocols);
	if(status != Success || protocols == 0)
		return protos;
	for(i = 0; i < res; i++)
		if(protocols[i] == wmatom[WMDelete])
			protos |= PROTODELWIN;
	free(protocols);
	return protos;
}

void
sendevent(Window w, Atom a, long value) {
	XEvent e;

	e.type = ClientMessage;
	e.xclient.window = w;
	e.xclient.message_type = a;
	e.xclient.format = 32;
	e.xclient.data.l[0] = value;
	e.xclient.data.l[1] = CurrentTime;
	XSendEvent(dpy, w, False, NoEventMask, &e);
	XSync(dpy, False);
}

void
quit(Arg *arg) {
	readin = running = False;
}

/*
 * There's no way to check accesses to destroyed windows, thus those cases are
 * ignored (especially on UnmapNotify's).  Other types of errors call Xlibs
 * default error handler, which may call exit.
 */
int
xerror(Display *dpy, XErrorEvent *ee) {
	if(ee->error_code == BadWindow
	|| (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
	|| (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
	|| (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
	|| (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
	|| (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
		return 0;
	fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
		ee->request_code, ee->error_code);
	return xerrorxlib(dpy, ee); /* may call exit */
}

int
main(int argc, char *argv[]) {
	int r, xfd;
	fd_set rd;

	if(argc == 2 && !strncmp("-v", argv[1], 3)) {
		fputs("dwm-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
		exit(EXIT_SUCCESS);
	}
	else if(argc != 1)
		eprint("usage: dwm [-v]\n");

	dpy = XOpenDisplay(0);
	if(!dpy)
		eprint("dwm: cannot open display\n");

	xfd = ConnectionNumber(dpy);
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);

	otherwm = False;
	XSetErrorHandler(xerrorstart);
	/* this causes an error if some other window manager is running */
	XSelectInput(dpy, root, SubstructureRedirectMask);
	XSync(dpy, False);

	if(otherwm)
		eprint("dwm: another window manager is already running\n");

	XSync(dpy, False);
	XSetErrorHandler(NULL);
	xerrorxlib = XSetErrorHandler(xerror);
	XSync(dpy, False);

	setup();
	drawstatus();
	scan();

	/* main event loop, also reads status text from stdin */
	XSync(dpy, False);
	procevent();
	readin = True;
	while(running) {
		FD_ZERO(&rd);
		if(readin)
			FD_SET(STDIN_FILENO, &rd);
		FD_SET(xfd, &rd);
		r = select(xfd + 1, &rd, NULL, NULL, NULL);
		if((r == -1) && (errno == EINTR))
			continue;
		if(r > 0) {
			if(readin && FD_ISSET(STDIN_FILENO, &rd)) {
				readin = NULL != fgets(stext, sizeof(stext), stdin);
				if(readin)
					stext[strlen(stext) - 1] = 0;
				else 
					strcpy(stext, "broken pipe");
				drawstatus();
			}
		}
		else if(r < 0)
			eprint("select failed\n");
		procevent();
	}
	cleanup();
	XCloseDisplay(dpy);

	return 0;
}
cating ;; the moves from the start state of some machine (:machine). ;; Used for concatenation rule to splice two formerly separate machines; ;; used for repetition rule to "splice" a machine to itself. to splice :accepts :machine output map.se [copy.to.accepts ?] (arrows.from.start :machine) end to arrows.from.start :machine output filter [equalp startpart :machine arrowtail ?] movepart :machine end to copy.to.accepts :move output map [newtail :move ?] :accepts end to newtail :arrow :tail output make.arrow :tail (arrowtext :arrow) (arrowhead :arrow) end ;; Make a new state number to newstate make "nextstate :nextstate+1 output :nextstate end ;; Second step: Turn nondeterministic FSM into a deterministic one ;; Also eliminates "orphan" (unreachable) states. to determine :machine localmake "moves movepart :machine localmake "accepts acceptpart :machine localmake "states [] localmake "join.state.list [] localmake "newmoves nd.traverse (startpart :machine) output make.machine (startpart :machine) ~ :newmoves ~ filter [memberp ? :states] :accepts end to nd.traverse :state if memberp :state :states [output []] make "states fput :state :states localmake "newmoves (check.nd filter [equalp arrowtail ? :state] :moves) output sentence :newmoves map.se "nd.traverse (map "arrowhead :newmoves) end to check.nd :movelist if emptyp :movelist [output []] localmake "letter arrowtext first :movelist localmake "heads sort map "arrowhead ~ filter [equalp :letter arrowtext ?] :movelist if emptyp butfirst :heads ~ [output fput first :movelist check.nd filter [not equalp :letter arrowtext ?] :movelist] localmake "check.heads member :heads :join.state.list if not emptyp :check.heads ~ [output fput make.arrow :state :letter first butfirst :check.heads ~ check.nd filter [not equalp :letter arrowtext ?] :movelist] localmake "join.state newstate make "join.state.list fput :heads fput :join.state :join.state.list make "moves sentence :moves ~ map [make.arrow :join.state arrowtext ? arrowhead ?] ~ filter [memberp arrowtail ? :heads] :moves if not emptyp find [memberp ? :accepts] :heads ~ [make "accepts sentence :accepts :join.state] output fput make.arrow :state :letter :join.state ~ check.nd filter [not equalp :letter arrowtext ?] :movelist end to sort :list if emptyp :list [output []] output insert first :list sort butfirst :list end to insert :value :sorted if emptyp :sorted [output (list :value)] if :value = first :sorted [output :sorted] if :value < first :sorted [output fput :value :sorted] output fput first :sorted insert :value butfirst :sorted end ;; Third step: Combine redundant states. ;; Also combines arrows with same head and tail: ;; [1 A 2] [1 B 2] -> [1 AB 2]. to optimize :machine localmake "stubarray array :nextstate foreach (movepart :machine) "array.save localmake "states sort fput (startpart :machine) ~ map "arrowhead movepart :machine localmake "start startpart :machine foreach reverse :states [optimize.state ? ?rest] output (make.machine :start map.se [fix.arrows ? item ? :stubarray] :states filter [memberp ? :states] acceptpart :machine) end to array.save :move setitem (arrowtail :move) :stubarray ~ stub.add (arrow.stub :move) (item (arrowtail :move) :stubarray) end to stub.add :stub :stublist if emptyp :stublist [output (list :stub)] if (stub.head :stub) < (stub.head first :stublist) ~ [output fput :stub :stublist] if (stub.head :stub) = (stub.head first :stublist) ~ [output fput make.stub letter.join (stub.text :stub) (stub.text first :stublist) stub.head :stub butfirst :stublist] output fput first :stublist (stub.add :stub butfirst :stublist) end to letter.join :this :those if emptyp :those [output :this] if beforep :this first :those [output word :this :those] output word (first :those) (letter.join :this butfirst :those) end to optimize.state :state :others localmake "candidates ~ filter (ifelse memberp :state acceptpart :machine [[memberp ? acceptpart :machine]] [[not memberp ? acceptpart :machine]]) ~ :others localmake "mymoves item :state :stubarray localmake "twin find [equalp (item ? :stubarray) :mymoves] :candidates if emptyp :twin [stop] make "states remove :state :states if equalp :start :state [make "start :twin] foreach :states ~ [setitem ? :stubarray (cascade [emptyp ?2] [stub.add (change.head :state :twin first ?2) ?1] filter [not equalp stub.head ? :state] item ? :stubarray [butfirst ?2] filter [equalp stub.head ? :state] item ? :stubarray)] end to change.head :from :to :stub if not equalp (stub.head :stub) :from [output :stub] output list (stub.text :stub) :to end to fix.arrows :state :stublist output map [stub.arrow :state ?] :stublist end ;; Data abstraction for "stub" arrow (no tail) to arrow.stub :arrow output butfirst :arrow end to make.stub :text :head output list :text :head end to stub.text :stub output first :stub end to stub.head :stub output last :stub end to stub.arrow :tail :stub output fput :tail :stub end

(back to Table of Contents)

BACK chapter thread NEXT

Brian Harvey, bh@cs.berkeley.edu