ARM YOURSELF WITH SUFFICIENT PIZZA, CAFFEINATED SODA AND TIME BEFORE STARTING IT. SOMETIMES I REMEMBER MY CHILDHOOD FRIEND BOBBY.might cause Eliza to respond with: WHY DO YOU RECOLLECT YOUR CHILDHOOD FRIEND BOBBY JUST NOW?Pretty cool, huh? Let's see how to make this magic happen. Inside a complex patternThere are two important differences between a complex pattern and a format-free pattern:
Creating the complex pattern classWe'll create the complex pattern class by:
How a complex pattern generates a responseA FormatFreePattern generates a response by simply picking the next one from its list of responses. A ComplexPattern doesn't have a list of responses. Instead, it has a a list of "decomposition" and "reassembly" rules. Let's see what these are by examining the definition of the REMEMBER complex pattern in Eliza's script .key: remember 5 decomp: * i remember * reasmb: Do you often think of (2)? reasmb: Does thinking of (2) bring anything else to mind? reasmb: What else do you recollect? reasmb: Why do you recollect (2) just now? reasmb: What in the present situation reminds you of (2)? reasmb: What is the connection between me and (2)? decomp: * do you remember * reasmb: Did you think I would forget (2)? reasmb: Why do you think I should recall (2) now? reasmb: What about (2)? reasmb: goto what // <-- Ignore this for now reasmb: You mentioned (2)?The above definition says:
Walking through a complex pattern's response generationLet's try this out. Pretend we have a class called RememberPattern that derives from ComplexPattern, and represents the REMEMBER pattern in Eliza's script. Suppose you told Eliza:I REMEMBER WHEN I WAS A CHILD AND TASTED COCA COLA FOR THE FIRST TIME.Your input would match the RememberPattern, because it contains the word "REMEMBER".
HOW WOULD ELIZA RESPOND IF WE TOLD IT "Sometimes I remember when there was hardly any traffic
in Bombay." USE THE ABOVE ALGORITHM TO FIGURE THIS OUT.
The DecompReassemblyRule classAt the heart of a complex pattern is the process of decomposing the user's input and reassembling it into a response. In the previous example, the process was defined by this script segment:decomp: * i remember * reasmb: Do you often think of (2)? reasmb: Does thinking of (2) bring anything else to mind? reasmb: What else do you recollect? reasmb: Why do you recollect (2) just now? reasmb: What in the present situation reminds you of (2)? reasmb: What is the connection between me and (2)?A complex pattern can have many such segments. Let's create a class that represents the decomposition/reassembly bundle. We'll call it DecompReassemblyRule. This class will need to hold:
Using DecompReassemblyRule in ComplexPatternYou may recall that format free patterns (like BecausePattern) could be constructed because FormatFreePattern provided its subclasses with an Initialize() method that set up the class for use. Looking at the constructor of BecausePattern may jog your memory. We need something similar in order to construct complex patterns. In other words, we need an Initialize() method for ComplexPattern that looks like this: The parameters passed to Initialize() will need to be stored in ComplexPattern's private variables (just as is done in FormatFreePattern). So let's add these variables to ComplexPattern.Writing the ComplexPattern.GenerateResponse() methodRemember that we cloned ComplexPattern from FormatFreePattern? Obviously we can't use the cloned GenerateResponse() method we stole from FormatFreePattern because the two classes have different private variables. So let's take a stab at writing ComplexPattern.GenerateResponse()./// <summary> /// Generates a human-like response, if possible. /// </summary> /// <param name="input">The user's input.</param> /// <returns> /// The response or <see langword="null"/> if no response could be generated. /// </returns> public string GenerateResponse (string input) { // Find the first decomp/reassembly rule (if any) that can decompose the user's input. // DecompReassemblyRule doesn't yet have a CanDecompose() method, so we'll need to // write it. DecompReassemblyRule selectedRule = null; foreach(DecompReassemblyRule rule in this._decompReassemblyRules) { if (rule.CanDecompose(input)) { selectedRule = rule; break; } } // If none, return a null string if (selectedRule == null) { return null; } // Otherwise, delegate the work of generating the response to the rule. // DecompReassemblyRule doesn't yet have a GenerateResponse() method, so we'll need to // write it. string response = selectedRule.GenerateResponse(input); // Return the response return response; }Well, that was easy, because we broke down the problem of writing ComplexPattern.GenerateResponse() into 2 smaller problems that we conveniently (heh heh) delegated to DecompReassemblyRule.
DecompReassemblyRule.CanDecompose() and DecompReassemblyRule.GenerateResponse()Ready to tackle these methods? I'm not, so I'm going to be lazy and just stub them for now, like so:
Allowing Eliza to consume ComplexPatternsWe're now almost ready to give Eliza our first ComplexPattern. But if you take a look at Eliza.cs, you'll notice that Eliza contains a list of FormatFreePattern instances. How do we get this list to store instances of ComplexPattern? By refactoring (moving the common stuff out of) ComplexPattern and FormatFreePattern into a new Pattern base class, and making Eliza store a list of Pattern instances instead of a list of FormatFreePattern instances. Because ComplexPattern and FormatFreePattern both derive from Pattern, we can add instances of either class to the list. This is what the Pattern | FormatFreePattern | ComplexPattern hierarchy looks like:Pattern - FormatFreePattern - AlikePattern - AlwaysPattern ... - YesPattern - ComplexPattern - RememberPattern ...Real-world example Isaac Newton School has elementary school, middle school and high school students. Each student is an instance of one of the following C# classes:
Student - ElementarySchoolStudent - MiddleSchoolStudent - HighSchoolStudentand used a single list of the type: List<Student> allStudents = new List<Student>();to store any kind of Student, allowing the list to be populated by writing code like: allStudents.Add (new ElementarySchoolStudent("Bobby Smith")); allStudents.Add (new MiddleSchoolStudent("Mary Doe")); allStudents.Add (new HighSchoolStudent("Ishan Shastri")); allStudents.Add (new ElementarySchoolStudent("Joe Johnson")); allStudents.Add (new ElementarySchoolStudent("Melissa White")); allStudents.Add (new HighSchoolStudent("Bob Howard")); allStudents.Add (new MiddleSchoolStudent("Gary Browne")); ...
TAKE A LOOK AT Eliza.cs FROM THE PREVIOUS STEP. HOW WOULD YOU CHANGE IT SO THAT IT
CAN STORE INSTANCES OF BOTH FormatFreePattern AND ComplexPattern CLASSES?
Testing our changesWe've made quite a few changes to Eliza. Although we haven't yet created an instance of ComplexPattern, let's make sure we haven't broken anything. Run Eliza and verify that it continues to be able to conduct the following conversation. It's time to create the REMEMBER ComplexPattern and reap the benefits of all the hard work we've done in this step. We'll do that in the next step! |