Graphic by Keith Ohlfs

CS111, Wellesley College, Fall 1999

Problem Set 7

Due: Friday, November 5, by 4:00 p.m.

[CS111 Home Page] [Syllabus] [Students] [Lecture Notes] [Assignments] [Programs] [Documentation] [Software Installation] [FAQ] [CS Dept.] [CWIS]


Reading Assignment:

About this Problem Set

The purpose of this problem set is to give you a better understanding of lists via drawing box-and-pointer diagrams and writing recursive lists methods. There are three problems on this assignment, the second and third of which each have several parts.

How to turn in this Problem Set

Homework Problems :

Turn in only one package of hardcopy materials. Staple your files together with the cover page, and submit your hardcopy package by placing it in the box outside of Lyn's office (SCI 121B, behind the mini-focus consultant's desk).

Reminders


Homework Problem 1: Box-and-Pointer Diagrams

Consider the following sequence of Java statements, executed in a subclass of the IntList class:


IntList L1 = prepend(1, prepend(2, prepend(3, empty())));
IntList L2 = tail(tail(L1));
IntList L3 = append(L2, L2);
IntList L4 = append(L3, L3);
IntList L5 = postpend(L4, 4);

Assume that append() and postpend() have the following definitions given in lecture:

	public static IntList append(IntList L1, IntList L2) {
		// Returns a list whose elements are those of L1 
		// followed by those of L2.
		if (isEmpty(L1)) {
			return L2;
		} else {
			return prepend (head(L1), append(tail(L1), L2));
		}
	}
	
	public static IntList postpend(IntList L, int i) {
		// Returns a list whose elements are those of L followed by i.
		return append(L, prepend(i, empty()));
	}

Draw a box-and-pointer diagram showing the list values of the variables L1, L2, L3, L4, and L5 after executing the above statements. Be sure to accurately show any sharing of list nodes. You do not need to show any execution frames, just the nodes of the lists created by executing the above statements.


Homework Problem 2: Recursive ObjectList Methods

In this problem, you are to implement the ObjectList methods specified below. Write all your code for this problem in the file PS7ObjectListOps.java within the Lists folder of ps7_programs.

The Lists folder also contains the file PS7ObjectListOpsAnswers.class, which has correct solutions for all of the methods of this problem. Because the folder contains only the compiled file, and not the source file, you cannot look at the solutions. However, you can invoke the solutions from your program if you need them.This allows decoupling the solutions to methods where one method depends on another. For instance, suppose you would like to use remove() to implement removeDuplicates(), but you have not successfully completed the definition of remove(). You can still write and test removeDuplicates(), by referring to PS7ObjectListOpsAnswers.remove() instead. Later, you can implement your own version of remove(). Indeed, the file PS7ObjectListOps.java initially contains skeletons of the methods that invoke the correct implementations of the corresponding methods in PS7ObjectListOpsAnswers.class. Of course, you should replace these skeletons with your own definitions.

Use the PS7ObjectListOpsTest.html applet to test your solutions. This applet runs your implementations of the methods on several test cases, and writes the answers to the standard output window (stdout). Study these answers to make sure that your methods have the correct behavior. If you want to print out the contents of the stdout window, you first need to save it away as a file. To do this, open the pull down menu under the apple in the upper left-hand corner of the screen, select Java Runtime, and then select Save Text Window. You will be prompted for the name of a file. Once the window is save to a file, you can open the file and print it.

When giving examples below, we will assume the existence of the following test lists, each of which is a list of strings:

L0 = [ ]
L1 = [Hello, there]
L2 = [How, are, you]
L3 = [I, am, what, I, am, you, are, what, you, are]

Part a. isMember()

public static boolean isMember (Object x, ObjectList L)
Returns true if x is in L and false otherwise. Uses .equals() to test for equality, not ==.

For example:

isMember("I", L0) = false
isMember("are", L0) = false
isMember("you", L0) = false
isMember("I", L1) = false
isMember("are", L1) = false
isMember("you", L1) = false
isMember("I", L2) = false
isMember("are", L2) = true
isMember("you", L2) = true
isMember("I", L3) = true
isMember("are", L3) = true
isMember("you", L3) = true

Part b. remove()

public static ObjectList remove (Object x, ObjectList L)
Returns a list containing all the elements of L (in the same order) except for occurrences of x, which have been removed. Uses .equals() to test for equality, not ==.

For example:

remove("I", L0) = [ ]
remove("are", L0) = [ ]
remove("you", L0) = [ ]
remove("I", L1) = [Hello, there]
remove("are", L1) = [Hello, there]
remove("you", L1) = [Hello, there]
remove("I", L2) = [How, are, you]
remove("are", L2) = [How, you]
remove("you", L2) = [How, are]
remove("I", L3) = [am, what, am, you, are, what, you, are]
remove("are", L3) = [I, am, what, I, am, you, what, you]
remove("you", L3) = [I, am, what, I, am, are, what, are]

Part c. removeDuplicates()

public static ObjectList removeDuplicates (ObjectList L)
Returns a list containing a single occurrence of each elements occuring in L. The order of the resulting elements is irrelevant. Uses .equals() to test for equality, not ==.

For example (note, the strings in the lists could be in any order, not necessary the order shown below):

removeDuplicates(L0) = [ ]
removeDuplicates(L1) = [Hello, there]
removeDuplicates(L2) = [How, are, you]
removeDuplicates(L3) = [I, am, what, you, are]

Part d. reverse()

public static ObjectList reverse (ObjectList L)
Returns a list of the elements of L in reverse order.

For example:

reverse(L0) = [ ]
reverse(L1) = [there, Hello]
reverse(L2) = [you, are, How]
reverse(L3) = [are, you, what, are, you, am, I, what, am, I]


Homework Problem 3: Doctor

In this problem, we will experiment with a very simple program that simulates a nondirective psychotherapist. This program is inspired by the celebrated Eliza program created by Joseph Weizenbaum at MIT in the early 1960s. Eliza was realistic enough to fool some people into thinking that they were conversing with another person, not a computer. Eliza has inspired a long line of programs that have grown in sophistication over the years. For example, many MUDs now include computer-based characters that attempt to fool other players into thinking that they are human.

A patient engages in a "conversation" with the doctor program by typing sentences into the input field of the Doctor.html applet. These sentences are echoed in the text area of the applet, along with the doctor's replies. Below is a sample conversation that could take place in the program. For readability, the doctor's replies have been italicized, though in the actual program all sentences are in the same font.

Doctor: What is your name?
Patient: Chandler
Doctor: Hello, Chandler. What seems to be the trouble?
Chandler: Joey moved out
Doctor: Why do you say Joey moved out
Chandler: He moved to another apartment
Doctor: You seem to think he moved to another apartment
Chandler: Yes, he moved out last week
Doctor: Please continue.
Chandler: I miss him
Doctor: Many people have the same sorts of feelings.
Chandler: Well I guess I can still see him
Doctor: Earlier you said that he moved to another apartment
Chandler: Yeah, but he doesn't live too far away
Doctor: Please continue
Chandler: And then there's the business with Janice
Doctor: Why do you say and then there's the business with Janice
Chandler: Goodbye
Doctor: Goodbye, Chandler. See you next week!

You should try out the program by executing the Doctor.html applet in the Lists folder of ps7_programs. You will quickly see that no one could be fooled into thinking that this program is a real human being. You will be working on making a few improvements to the program. Your changes will make the doctor a bit more human. A lot more work would be required to change the program to the point where it would fool people.

The doctor program uses three simple strategies to respond to a patient:

  1. Use a hedge statement like "Please continue" or "Many people have the same sorts of feelings".
  2. Attach a qualifier like "You seem to think" or "Why do you say" to the patient's most recent statement.
  3. Attach a qualifier like "Earlier you said that" or "Before you mentioned" to one of the patient's previous statements.

These strategies are embodied in the reply() method of the Doctor class, which takes the patients most recent statement and a list of all the patient's previous statements. Here is the code for reply(), along with a number of helper methods:


public String reply (String message, ObjectList history) {
  // Returns a reply to patient's message. // message is what the patient just said.
  // history is the list of patient sentences,
  // from most recent to least recent.
		if (Randomizer.Chances(1,3)) {
			return hedge();
		} else if (Randomizer.Chances(1,2) && (length(history) >= 3)) {
		     return pastQualify(history);
		} else {
		     return qualify(message);
		}
	}
public String hedge() {
	return (String) (pickRandom(hedges));
	}
	
	public String qualify(String message) {
		return pickRandom(qualifiers) + " " 
		        + process((String) message);
	}
	
	public String pastQualify(ObjectList sentences) {
		return pickRandom(pastQualifiers) + " " 
			   + process((String) pickRandom(sentences)) + ".";
	}
	
	
	public String process (String sentence) {
		return decapitalize(sentence);
	}
 
	public static String decapitalize (String s) {
	// Returns a string the same as s except the first character is lower case.
	  ObjectList exceptions = prepend("I ", 
                                prepend("I'm", 
                                 prepend("I'll", 
                                  empty())));
	  if (beginsWithOneOf(exceptions, s)) {
	  	// Special case: don't decapitalize sentences beginning with "I ..."
	  	return s;
	  } else if (s.length() > 1) {
			return Character.toLowerCase(s.charAt(0)) + s.substring(1);
	  } else if (s.length() == 1) {
			return Character.toLowerCase(s.charAt(0)) + "";
	  } else {// s is empty
			return s;
	  }
	}
	
	public static boolean beginsWithOneOf (ObjectList prefixes, String str) {
	// Returns true if str begins with one of the strings in prefixes.
		if (isEmpty(prefixes)) {
			return false;
		} else if (isPrefix((String) head(prefixes), str)) {
			return true;
		} else {
			return beginsWithOneOf(tail(prefixes), str);
		}
	}
	
	public static boolean isPrefix (String pre, String str) {
	// Returns true iff pre is a prefix of str
		return (pre.length() <= str.length())
		        && (pre.equals(str.substring(0,pre.length())));
	}


Here are a few notes on the above methods:


Part a. The way Doctor.java uses qualifiers is too simplistic to fool even the most gullible person. For example, if the patient types

I am unhappy with how they treated me

the doctor responds by prefacing the same exact string prefixed with a qualifier --- e.g.,

You feel I am unhappy with how they treated me

A better approach would be to change various words in the response to indicate the change in person. The above response would be more realistic as:

You feel you are unhappy with how they treated you

This can be accomplished by parsing the patient's string into words and making substitutions like "you" for "I" and "are" for "am". Here is a table of such substitutions:

old word

new word

I

you

me

you

my

your

mine

yours

am

are

you

I

are

am

Note that the first five substitutions are always valid, but the last two can sometimes be invalid. The word "you" could translate to either "I" or "me"; here we "guess" it should be "I", but we could be wrong. And "are" could be a verb for the third person plural, as in "Clouds are pretty", in which case we really don't want to change it to "am". We would need a more sophisticated approach to handle these cases correctly.

The doctor program contains a parseWords() method that correctly parses a string into a list of strings that are its component words and punctuation marks. For example, if sentence is the string

"You know, I really miss him!"

then parseWords(sentence) yields an ObjectList of strings with the following printed representation:

[You, know, ,, I, really, miss, him, !]

(Here ",," should be interpreted as the punctuation string "," followed by a comma delimiting the components of the ObjectList. ) The "inverse" of parseWords() is unparseWords(), which glues a list of words and punctuation marks back into a sentence that is a single string.

Using these methods, we can define a changePerson() method that applies the above person-changing substitutions to a sentence.

	public String changePerson (String sentence) {
		return unparseWords(substitute(substitutions, parseWords(sentence)));
	}

Here, substitutions is a class variable of Doctor containing a list of substitution pairs that alternates between old words and the corresponding new words:

[I, you, me, you, my, your, mine, yours, am, are, you, I, are, am]

Your goal in this part is to define the following two methods:

public static String subst1 (ObjectList substs, String word)
Assume substs is a list of strings with alternating "old"/"new" word pairs. If word is any "old" word in substs, returns the corresponding"new" word in the pair. Otherwise, returns the given word unchanged.

public static ObjectList substitute (ObjectList substs, ObjectList words)
Assume substs is a list of strings with alternating "old"/"new" word pairs, and words is a list of strings. Returns the list of strings that results from mapping the subst1 substitution process over every word in words.

For example, suppose we have the following test lists:

L0 = [ ]
L1 = [You, are, not, LISTENING, to, me, !]
L2 = [I, am, what, I, am, ;, you, are, what, you, are]
L3 = [I, am, saying, that, my, goal, is, to, get, what, is, mine, .]
substitutions = [I, you, me, you, my, your, mine, yours, am, are, you, I, are, am]

Then here are some test cases of these two methods:

subst1(substitutions, "I") = you
subst1(substitutions, "me") = you
subst1(substitutions, "my") = your
subst1(substitutions, "mine") = yours
subst1(substitutions, "am") = are
subst1(substitutions, "you") = I
subst1(substitutions, "are") = am
subst1(substitutions, "cat") = cat
 
substitute(substitutions, L0) = [ ]
substitute(substitutions, L1) = [You, am, not, LISTENING, to, you, !]
substitute(substitutions, L2) = [you, are, what, you, are, ;, I, am, what, I, am]
substitute(substitutions, L3) = [you, are, saying, that, your, goal, is, to, get, what, is, yours, .]

You should begin this problem by changing the process() method in Doctor.java to include a call to changePerson(), as follows:

	public String process (String sentence) {
		return decapitalize(changePerson(sentence));
	}

Then you should write definitions for the subst1() and substitute() methods described above. Skeletons for these methods have been included in Doctor.java. Observe the following notes:


Part b. Another way to make the doctor more sophisticated is to have the doctor notice when the patient keeps refering to the same words. For instance, if a patient says "I saw a cat in my dreams" and has mentioned "dreams" several times previously, the doctor could say "Several times now you have mentioned dreams" or "You seem to keep coming back to dreams".

Here is a method that can be used to implement this behavior:

	public static ObjectList usedBefore (int minSize, int minOccur, 
                                            String sentence, ObjectList sentences) {
	// Assume sentences is a list of sentence strings.
	// Returns a list of the words in sentence that are at least
	// minSize long and are used at least minOccur times in sentences.
		ObjectList words = mapLowerCase(filterSize(minSize, parseWords(sentence)));
		return wordsUsedBefore(minOccur, words, sentences);
	}

The usedBefore() method depends on the following methods, which you will define:

public static ObjectList mapLowerCase (ObjectList L)
Assumes L is a list of strings. Returns a list of strings whose elements are the lowercase versions of the strings in L. Use the String instance method toLowerCase() to find the lowercase version of a string.

public static ObjectList filterSize (int size, ObjectList L)
Assumes L is a list of strings. Returns a list, in the same order, of the strings in L whose lengths are at least size characters. Use the String instance method length() to measure the length of a string.

public static ObjectList wordsUsedBefore (int minOccur,
ObjectList words,
ObjectList sentences)
Assume words is a list of word strings, and sentences is a list of sentence strings. Returns the list of words in words that appear in at least minOccur sentences.

For defining wordsUsedBefore(), it is also helpful to have the following auxiliary method:

public static int occurrences (String word, ObjectList sentences)
Assume sentences is a list of sentence strings. Returns the number of sentences in which word appears.

For example, here are test cases involving mapLowerCase and filterSize, using the same test lists from the previous part:

mapLowerCase(L0) = [ ]
mapLowerCase(L1) = [you, are, not, listening, to, me, !]
mapLowerCase(L2) = [i, am, what, i, am, ;, you, are, what, you, are]
mapLowerCase(L3) = [i, am, saying, that, my, goal, is, to, get, what, is, mine, .]
 
filterSize(3, L0) = [ ]
filterSize(4, L0) = [ ]
filterSize(5, L0) = [ ]
filterSize(3, L1) = [You, are, not, LISTENING]
filterSize(4, L1) = [LISTENING]
filterSize(5, L1) = [LISTENING]
filterSize(3, L2) = [what, you, are, what, you, are]
filterSize(4, L2) = [what, what]
filterSize(5, L2) = [ ]
filterSize(3, L3) = [saying, that, goal, get, what, mine]
filterSize(4, L3) = [saying, that, goal, what, mine]
filterSize(5, L3) = [saying]

Suppose we define testSentences as list of the following sentences:

[Last night I had many dreams.,
 I pursue my dreams.,
 In my dreams, I win the lottery.,
 I like the night.,
 You are helpful.
 ]

Further suppose that we define testWords as a list of the following words:

[Last, night, I, had, many, dreams]

Then here are some test cases involving occurrences() and wordsUsedBefore():

occurrences("I", testSentences) = 4
occurrences("dreams", testSentences) = 3
occurrences("night", testSentences) = 2
occurrences("lottery", testSentences) = 1
occurrences("cat", testSentences) = 0
 
wordsUsedBefore(1, testWords, testSentences) = [Last, night, I, had, many, dreams]
wordsUsedBefore(2, testWords, testSentences) = [night, I, dreams]
wordsUsedBefore(3, testWords, testSentences) = [I, dreams]
wordsUsedBefore(4, testWords, testSentences) = [I]
wordsUsedBefore(5, testWords, testSentences) = [ ]
 

You should begin this part by modifying reply() to include a call to usedBefore() as follows:

	public String reply (String message, ObjectList history) {
		ObjectList repeats = usedBefore(5, 2, message, history);
		if (!isEmpty(repeats) && Randomizer.Chances(1,2)) {
			return repeatedWord((String) pickRandom(repeats));
		} else if (Randomizer.Chances(1,3)) {
             ... the rest of the method is unchanged ...
		}
	}

This modification refers to a repeatedWord() method, which has already been implemented for you in Doctor.java:

	public String repeatedWord (String word) {
		return pickRandom(repeatQualifiers) + " " + word + ".";
	}

Here, repeatQualifiers is an already implemented class variable holding a list of various qualifiers.

After these initial modifications, you should write definitions of the four methods specified above, fleshing out the skeltons for these methods in Doctor.java.


Optional: Further Extensions to the Doctor Program

You are encouraged to extend the doctor program so that it exhibits more interesting behavior than it now does. Here are are a few ideas, though of course you could experiment with extensions of your own design: