At this point, you've read all the symbols in the input. Now, some of the states in the machine are "final states," indicated by a double circle; in this machine there's just one, namely state 3. Once you've exhausted the input, you look at the state you ended up in. If it's a final state, then the machine accepts the input -- that is, it says "yes, the input is in the language". If it's not a final state, the machine rejects the input -- that is, it says "no, the input is not in the language". Try tracing through the same process as above with the input (the tall the man) and you'll wind up in state 4, which is not a final state.
Notice that no matter what state you're in, the diagram of the machine always has exactly one destination state for every symbol in the vocabulary -- never zero, and never more than one -- and also notice that that you can never just jump from one state to the other without reading in any of the input. These things are what make the machine deterministic, since at any point in time the machine's next step is completely determined.
((ALPHABET (man tall the)) (STATES (1 2 3 4)) (INITIAL-STATE 1) (FINAL-STATES (3)) (TRANSITIONS ((1 man 4) (1 tall 4) (1 the 2) (2 man 3) (2 tall 2) (2 the 4) (3 man 4) (3 tall 4) (3 the 4) (4 man 4) (4 tall 4) (4 the 4))))Note that some parts of this representation may not be needed in your program, but are included because 5-tuples are the formal way DFA's are specified.
That is, if the above DFA were the value of symbol +dfa2+ you should get:
* (accepts '(the tall man) +dfa2+) T * (accepts '(the tall tall tall man) +dfa2+) T * (accepts '(the tall the man) +dfa2+) NIL * (accepts '() +dfa2+) NIL
(defconstant +dfa1+ '((ALPHABET (a b)) (STATES (1 2 3 4 5)) (INITIAL-STATE 1) (FINAL-STATES (4)) (TRANSITIONS ((1 a 2) (1 b 5) (2 a 4) (2 b 3) (3 a 5) (3 b 2) (4 a 5) (4 b 5) (5 a 5) (5 b 5)))) "A deterministic finite-state automaton that accepts the language {(a b^n a), n is an even number}, i.e. {(a a), (a b b a), (a b b b b a), ...}." )You'll be using this DFA to test your program once you've completed writing it.
Your second step should be to break the problem down into smaller pieces. What are some smaller, easier functions you're sure you will need? Create names for them and decide roughly what their inputs and outputs will be, but don't implement them yet. You shouldn't implement anything until you've got the problem mapped out.
The next step should be to look at the top level problem you're solving. What kind of "feel" does it have -- recursive? applicative? (It might well have an iterative feel, but we haven't covered iterative programming yet so that's not an option.) If the top level seems recursive, what does the base case look like (or cases, if there's more than one)? How do you break the problem into the three pieces that Touretzky describes: base case, single step, smaller journey? (If you're of a mind to try Touretzky's recursion templates, go ahead, though if the problem is hard to squeeze into the template you've chosen it may be because you've chosen the wrong one.) If there's applicative programming involved, what functionality are you applying to what list, to get what result?
Based on your breaking down of the top level, start sketching out some function definitions, though perhaps not in full detail: even without completely specifying the insides, what arguments does a function take, and what value does it return? If there's recursion or application involved, trace manually through function calls, following the input/output behavior you've specified; start with the small, easy cases, and work your way up to something more complicated. This process may lead you to change the arguments or values or behavior of functions, or to add or get rid of functions. Ultimately go back to the top and try manually tracing through things again with a different input.
Finally, start programming. If you've got easy little helper functions, write them first, and test them before moving on, first trying the simplest cases and then doing more complex ones. Then go back and work from the top level, implementing the approach you've worked through on paper. Implement things partially and test them as you go along, always making sure that your functions do what they should on the small or easy cases before moving on. If there's recursion involved, trace the recursive function so you can see what its inputs and outputs are when it gets called.
If you get wedged (that's the technical term!), take a break. Even five minutes away from the problem doing something else (reading news, surfing the Web, sending me hate mail) is often enough to get you out of am mental rut, letting you see a different path or possibility when you return.
(mapcar #'(lambda (x) (accepts x +dfa1+)) '(() (a) (b) (a a) (a b) (b a) (b b) (a b a) (a b b) (a b b a)))Good luck, and have fun!