![]() |
Problem Set 7 Due on Friday 10 November at the start of class |
IntList
Contract
StringList
ContractStringListOps.java
in the ps07_programs/Unjumbler
folder.LabOps.java
in the ps07_programs/Unjumbler
folder.The purpose of this problem set is to give you experience with strings, lists, and
iteration (while
loops and for
loops).
In Task 1, you get a clearer picture of how lists and recursive
methods on lists work by drawing an invocation tree and box-and-pointer diagrams.
In Task 2, you will get a better
understanding of lists by writing recursive list methods. You will
need to write several methods that manipulate lists of strings as part
of implementing an "unjumbling assistant" that will help you solve
word jumble problems.
In Task 3, you will use iteration to partition a list.
All code for this assignment is available in the
ps07_programs
folder in the cs111d
download
directory on the cs server.
Unjumbler.java
file.
Partition.java
file from Tasks 3b, 3c, 3d, and 3e;
ps08_programs
folder. In particular,
Unjumbler
subfolder should
contain your final version of Unjumbler.java
.
Partition
subfolder should
contain your final version of Partition.java
.
In this problem, we first present some notational conventions for using invocation trees together with box-and- pointer diagrams for lists, and then ask you to use these conventions to show the execution of a list manipulation program.
Recall that an invocation tree is an abbreviation for a collection of Java Execution Model frames. Each node of the tree shows the arguments and the result of a method invocation; such a node abbreviates a frame in ExecutionLand. Also recall that a box-and-pointer diagram is an abbreviation for interconnected linked list nodes in ObjectLand.
It is possible to combine invocation trees and box-and-pointer
diagrams to summarize the execution of a list manipulation program.
For instance, consider the following definition of a list reversal
method (which is assumed to be in a subclass of IntListOps
,
so that it can use postpend()
):
public static IntList reverse (IntList L) { if (isEmpty(L)) return L; else return postpend(reverse(tail(L)), head(L)); // could also write: // return append(reverse(tail(L)), prepend(head(L), empty())); }
Suppose that L1
is a list whose
printed representation is [1,2,3]. Then the execution of the
invocation reverse(L1)
is summarized by
the following diagram:
A boxed element of the form reverse(X):Y
is the root node of an invocation tree for the invocation of the
reverse()
method on list X
that returns result Y
. In the
invocation tree, particular list nodes are denoted by object
references, which are letters inside circles. These object
references name nodes appearing in the box-and-pointer portion of the
diagram. The object references could have instead been denoted by
pointers in the diagram, but this would lead to a spaghetti of
pointers. We follow the convention that the empty list is always
written as a distinct solid circle. (We are not interested in
modelling sharing involving the empty list.)
In the above diagram we treated postpend()
as a "primitive" by not showing nodes for each invocation of
postpend()
. However, the nodes created by
the prepend()
s performed by the hidden
postpend()
s are shown in the
box-and-pointer diagrams.
An important feature of the box-and-pointer list diagram portion
of the picture is that it accurately depicts sharing. In the case of
reverse()
, the result returned by one
call to reverse does not share any list nodes with the results of any
other call. As an example of a diagram with more sharing, consider
the following method, which is also assumed to be defined in a
subclass of the IntListOps
class:
public static IntList appendTails (IntList L) { if (isEmpty(L)) return L; else return append(L, appendTails(tail(L))) }
Below is a diagram showing an invocation tree and box-and-pointer
list structure for the invocation appendTails(L1)
,
where L1
is defined as above. Note how
the final result (the node labelled"f") includes as subparts the
results to the recursive calls (the nodes labelled "e" and "d").
Suppose that the following methods are defined in a subclass of
the IntList
class:
public static IntList F (IntList L) { return tail(G(0, L)); } public static IntList G (int n, IntList L) { if (isEmpty(L)) return prepend(0, L); else { IntList subResult = G(n + head(L), tail(L)); return prepend(head(L) + head(subResult), prepend(head(L) * (n + head(subResult)), tail(subResult))); } }
Suppose that L2
is an integer list
whose printed representation is [1,2,3,4]. Draw a combined
invocation tree/box-and-pointer diagram that illustrates the
invocation F(L2)
. Use the same
conventions used in the reverse()
and
appendTails()
examples from above in your
diagram.
The game involves "unjumbling" English words whose letters have been
reordered. For instance, the jumbled word ytikt
can be unjumbled to kitty
. The game can be challenging;
even relatively short jumbles can be tricky to unjumble.
For instance, here are the words that appeared on the
April 2, 2004, version of the on-line game; can you unjumble them?:
catue, hybus, nyflod, degurt
In this problem, you will create a Java program that acts
as an "unjumbling assistant". Given a string, your program
will first generate all possible reorderings of the letters in the
string. Such reorderings are called permutations.
For example, there are six reorderings of the letters in
the string tra
:
[tra,rta,rat,tar,atr,art]
In general, a string with n distinct letters has n! (pronounced "n factorial") permutations. For instance, a 4-letter string has 4! = 24 permutations, a 5-letter string has 5! = 120 permutations, a 6-letter string has 6! = 720 permutations, and so on.
Next, the assistant will determine which of the permutations
is an English word by looking them up in a dictionary.
You do not have to worry about how to construct such a dictionary;
this has been done for you. Notes on how to use the dictionary as
a "black box" can be found later in this problem description.
In the case of "tra"
, filtering out the English words
leaves:
[rat,tar,art]
When the string you are unjumbling contains duplicate letters, a
simple permutations generator will yield some duplicate
permutations. For instance, the permutations of "dda"
will generate the 3! = 6 permutations:
[dda,dda,dad,dad,add,add]
Filtering out the English words yields:
[dad,dad,add,add]
In such cases, the unjumbling assistant should also filter out duplicates to yield the final list:
[dad,add]
To get a feel for what the unjumbling assistant does,
you should experiment with the UnjumblerAnswers.class
file
in the Unjumbler
folder which contains working methods for unjumbling.
For this assignment, we have provided this file (instead of a test
folder),
as an example of how your solution should execute. For example, if you want to see how
your program should behave, you can run the unjumbleInteractively()
method
from the UnjumblerAnswers.class
file in either of two different ways:
main()
method of Unjumbler.java
, you can un-comment the lineUnjumblerAnswers.unjumbleInteractively();
Unjumbler
folders needs to have both
the UnjumblerAnswers.class
and Stdin.class
files. If you have accidentally deleted them, you'll need to retrieve them
again from the ps07_programs
folder.)UnjumblerAnswers.unjumbleInteractively();
You can use this method to help you solve the on-line Word Jumble puzzles!
Important Note: With its default settings,
DrJava is only able to unjumble words of at most 6 characters.
Attempts to unjumble words longer than 6 characters will encounter a StackOverflowError
,
indicating that there is a chain of execution frames that is too long for
Java Virtual Machine to handle.
Longer words can be handled by modifying the default
stack size used by DrJava for program execution.
This can be done via the following steps:
Edit>Preferenences
to display the Preferences window.
-Xss4000k
(no spaces). This tells the
Java Virtual Machine (JVM) to use a stack with size 4000 kilobytes
(about 4 megabytes), which is sufficient for unjumbling
8-character words.
Tools>Reset Interactions
to make the change take effect.
Here are some things you need to know:
Unjumbler
class in the file Unjumbler.java
in the
Unjumbler
folder. Carefully read the comments at the top of the
Unjumbler.java
file; these tell you which class methods
you can use in the file without an explicit class name as a
prefix.
main()
method of Unjumbler.java
.
main()
method of the
Unjumbler
class, just as you did in Lab.
Recall that the fromString()
class method for
the StringList
class is a very convenient way to
generate a string list. For instance fromString("[I,am,Sam]")
yields a three element list containing the strings "I"
,
"am"
, and "Sam"
.
UnjumblerAnswers
that contains working versions
of each of the nine methods. If you don't have a working
method meth1()
, but need
meth1()
to define
meth2()
, you can use
UnjumblerAnswers.meth1()
in your
definition of meth2()
. This allows
you to work on the nine methods below in any order,
while still being able to test each method along the way.
(For this to work, your Unjumbler
folder must
have the file UnjumblerAnswers.class
.
If you have accidentally deleted this file, you will need
to retrieve it again from the ps07_programs
download folder --- you do not have the .java
file to generate it.)
public static StringList remove (String s, StringList L)
s
in L
have been removed. The other strings in the list should have the same relative order
in the resulting list as in the given list.
Examples:
remove("I", fromString("[I,know,that,I,said,that,I,did]"))
returns the string list [know,that,said,that,did]
.
remove("that", fromString("[I,know,that,I,said,that,I,did]"))
returns the string list [I,know,I,said,I,did]
.
remove("said", fromString("[I,know,that,I,said,that,I,did]"))
returns the string list [I,know,that,I,that,I,did]
.
remove("you", fromString("[I,know,that,I,said,that,I,did]"))
returns the string list [I,know,that,I,said,that,I,did]
.
Note: Use the equals()
instance method from the
String
class to compare two strings. For instance,
"cat".equals("cat")
returns true
but
"cat".equals("dog")
returns false
.
You should not use ==
to compare two strings
because it may not return what you expect. For instance,
while "cat" == "dog"
is guaranteed to return
false
, "cat" == "cat"
and "cat" == ("c" + "at")
are not guaranteed
to return true
. They may return true in
some implementations and some circumstances, but you cannot
rely on this behavior.
public static StringList removeDuplicates (StringList L)
L
exactly once.
The order of the elements in the returned list should be the relative
order of the first occurrence of each element in L
.
Examples:
removeDuplicates(fromString("[I,know,that,I,said,that,I,did]"))
returns the string list [I,know,that,said,did]
.
removeDuplicates(fromString("[you,say,what,you,mean,and,mean,what,you,say]"))
returns the string list [you,say,what,mean,and]
.
removeDuplicates(fromString("[lists,are,cool]"))
returns the string list [lists,are,cool]
.
Note: The remove
method from above is helpful here!
public static StringList mapConcat (String s, StringList L)
L
with n strings, returns a new list with
n strings in which the ith string of the resulting list is
the result of concatenating s
to the ith element of
L
.
Examples:
mapConcat("com", fromString("[puter,plain,municate,pile]"))
returns the string list [computer,complain,communicate,compile]
.
mapConcat("I ", fromString("[came,saw,conquered]"))
returns the string list [I came,I saw,I conquered]
.
public static StringList insertions (String s1, String s2)
s1
and s2
, where s2
has n
characters, returns a list of n + 1 strings that result from inserting
s1
at all possible positions within s2
,
from left to right.
Examples:
insertions("*", "split")
returns the string list [*split,s*plit,sp*lit,spl*it,spli*t,split*]
insertions("a", "bcd")
returns the string list [abcd,bacd,bcad,bcda]
insertions("com", "pile")
returns the string list [compile,pcomile,picomle,pilcome,pilecom]
insertions("abc", "")
returns the string list [abc]
Note: The LabOps
class contains two helper
methods that are useful for defining insertions
:
public static String first (String s)
s
.
For example, first("computer")
returns the string "c"
.
public static String butFirst (String s)
s
.
For example, butFirst("computer")
returns the string "omputer"
.
public static StringList insertionsList (String s, StringList L)
s
at all possible positions in all the strings of L
.
Examples:
insertionsList("a", fromString("[bc,cb]"))
returns the string list [abc,bac,bca,acb,cab,cba]
insertionsList("*", fromString"[I,am,Sam]"])
returns the string list [*I,I*,*am,a*m,am*,*Sam,S*am,Sa*m,Sam*]
insertionsList("abc", fromString("[]"))
returns the string list []
Note: The StringListOps
class contains a helper
method append
that is useful for defining insertionsList
:
public static StringList append (StringList L1, StringList L2)
Returns a new string list containing all the elements ofL1
followed by all of the elements ofL2
. For example,
append(fromString("[I,do]"), fromString("[not,like,green,eggs]"))
returns[I,do,not,like,green,eggs]
append(fromString("[I,do]"), fromString("[]"))
returns[I,do]
append(fromString("[]"), fromString("[not,like,green,eggs]"))
returns[not,like,green,eggs]
public static StringList permutations (String s)
s
. A permutation of a string s
is any string that is formed by reordering the letters in the string
s
(without duplicating or deleting any letters).
For a string with n distinct characters, there are
exactly n! (i.e., "n factorial") permutations.
If some characters in s
are repeated, there
are still n! permutations, but the permutations
contain duplicates. The elements in the list returned by
permutations
may be in any order.
Examples:
permutations("a")
returns the string list [a]
.
permutations("ab")
returns the string list [ab,ba]
or the string list [ba,ab]
.
permutations("abc")
returns (any permutation of)
the string list [abc,bac,bca,acb,cab,cba]
.
permutations("abcd")
returns (any permutation of) the string list
[abcd,bacd,bcad,bcda, acbd,cabd,cbad,cbda, acdb,cadb,cdab,cdba abdc,badc,bdac,bdca, adbc,dabc,dbac,dbca, adcb,dacb,dcab,dcba].
permutations("121")
returns (any permutation of) the string list
[121,211,211,112,112,121]
. Note that when the given string contains
duplicate characters, the permutation list will contain duplicates.
permutations("1231")
returns (any permutation of) the string list
[1231,2131,2311,2311, 1321,3121,3211,3211, 1312,3112,3112,3121, 1213,2113,2113,2131, 1123,1123,1213,1231, 1132,1132,1312,1321].
Note: There are many ways to define the permutations
method, but a particularly elegant way uses the
first
, butFirst
, and insertionsList
methods from above. Be very careful in defining your base case!
public static StringList filterWords (StringList L)
L
that are English words.
The resulting strings should be in the same relative order as in L
.
Examples:
filterWords(fromString("[the,dog,barked,at,the,cat]"))
returns the string list [the,dog,barked,at,the,cat]
.
filterWords(fromString("[the,dog,barkd,ate,hte,cat]"))
returns the string list [the,dog,ate,cat]
.
filterWords(fromString("[tra,rta,rat,tar,atr,art]"))
returns the string list [rat,tar,art]
.
Note: To determine if a string is an English word, you should
use the class method isWord()
that is already defined for you
in the Unjumbler
class:
public static boolean isWord (String s)
Returnstrue
ifs
is a word in the default English dictionary, andfalse
otherwise.
The default dictionary (which can be changed within the
Unjumbler
class) contains 22641 English
words (all lower case, no proper nouns) of up to 8 characters
in length. It is not a "perfect"
dictionary: there are some perfectly acceptable English
words that are not in the dictionary.
You can change the
default dictionary to one that contains 45425 English words
without a length restriction, but this takes longer to load.
For details on how to do this, see the comments near the
end of Unjumbler.java
.
public static StringList unjumble (String s)
s
that are English
words (as determined by the default dictionary). The order of elements in
the resulting list does not matter, but each word in the resulting
list should be listed only once.
Examples:
unjumble("tra")
returns the string list [rat,tar,art]
.
unjumble("tras")
returns the string list [rats,arts,star]
(the default dictionary doesn't recognize tars
or tsar
as words).
unjumble("argle")
returns the string list [glare,large,lager,regal]
.
unjumble("sbso")
returns the string list [sobs,boss]
.
unjumble("xzzy")
returns the string list []
.
Note: You should only remove duplicates after
performing filterWords
. It turns out that performing
removeDuplicates
on a large lists (such as the
output of permutations
) can take a very
long time. (To understand why this is so, take CS230!)
public static void unjumbleInteractively()
Example: Below is a sample session of unjumbleInteractively()
.
The text typed by the program is in italics, while the text typed
by the user is in bold:
Enter a string to unjumble and press Return (enter the empty string to quit):
argle
Constructing dictionary from file dicts/dict8.bin.
This may take a little while...
Done! Dictionary constructed with 22641 words.
[glare,large,lager,regal]
Enter a string to unjumble and press Return (enter the empty string to quit):
eshou
[house]
Enter a string to unjumble and press Return (enter the empty string to quit):
sbos
[boss,sobs]
Enter a string to unjumble and press Return (enter the empty string to quit):
Thank you for using the unjumbler!
Notes:
Stdin
class:
public static String readLine (String s)
Displays the prompts
in the Java Console window, and waits for the user to type a line of text. When the user presses the return key, returns the line of text that has been typed as a string.
isWord()
method is invoked for the
first time.
Constructing dictionary from file dicts/dict8.bin.
This may take a little while...
Done! Dictionary constructed with 22641 words.
The following partitionIter()
method takes an integer named
pivot
and a list of integers named L
and
partitions the list into two lists:
L
less than pivot
.
L
greater than or equal to pivot
.
The two resulting lists are packaged together and returned in a
PairOfIntLists
object.
public static PairOfIntLists partitionIter (int pivot, IntList L) { return partitionTail(pivot, L, IL.empty(), IL.empty()); } public static IntListList partitionTail (int pivot, IntList list, IntList lesses, IntList greaters) { if (IL.isEmpty(list)) { return twoLists(lesses, greaters); } else if (IL.head(list) < pivot) { return partitionTail(pivot, IL.tail(list), IL.prepend(IL.head(list), lesses), greaters); } else { return partitionTail(pivot, IL.tail(list), lesses, IL.prepend(IL.head(list), greaters)); } }
We will use IL.
to prefix IntList
operations
(and the testing code given to you will use ILL.
to
prefix IntListList
operations, which we will learn about
soon).
We use the PairOfIntLists
class to glue two IntLists together.
Here is the contract for this class:
public static PairOfIntLists mkpair (IntList L1, IntList L2);
GluesL1
andL2
together into aPairOfIntLists
object.
public static IntList first (PairOfIntLists p);
Returns the first list inPairOfIntLists
object.
public static IntList second (PairOfIntLists p);
Returns the second list inPairOfIntLists
object.
To make the code more readable, the Partition.java
file uses the following helper
method, which takes two lists and glues them together into a
PairOfIntLists
:
public static PairOfIntLists twoLists (IntList L1, IntList L2) { return PairOfIntLists.mkpair(L1, L2); }
partitionTail()
method is a tail recursive method that
specifies an iteration in four state variables named
pivot
, list
, lesses
, and
greaters
. Any iteration can be characterized by how the
values of the state variables change over time. Below is a table with
four columns, one for each state variable of the iteration described
by partitionTail()
. Each row represents the values of the
parameters to a particular invocation of partitionTail()
.
pivot | list | lesses | greaters |
Suppose that the list A
has the printed representation
[7,2,3,5,8,6,1]
. Fill in the above table to show
the parameters passed to successive calls to partitionTail()
in the computation that begins with the
invocation partitionIter(5, A)
. There are more rows shown here than you need, so you should draw only
as many rows as you need to in your table.
partitionWhile()
while
loop.
Flesh out the following code skeleton of a partitionWhile()
method that
behaves just like the above partitionIter()
method except that it uses a
while
loop rather than tail recursion to express the same iteration
as partitionTail()
.
public static PairOfIntLists partitionWhile (int pivot, IntList L) { // Replace this stub: return twoLists(IL.empty(), IL.empty()); }
Notes:
partitionWhile()
method in the file Partition.java
in the
Partition
directory.
IntList
operations with IL.
while
loop has the syntax
while (test-expression) { body-statements }
while
loop
to implement the iteration implied by the iteration table.
The body of this loop may contain any Java statements, including
conditionals, but should not contain other while
loops.
Partition
application invokes the main()
method of the Partition
class, which will run the methods of
this task on various test cases. You should study the results of
the test cases in the Java console or DrJava interactions pane to verify
that the results are what you expect.
partitionFor()
partitionTail()
and
partitionWhile()
can also be expressed via a
for
loop whose index variable is the list
state variable. Flesh out the skeleton of the following method
so that it expresses the partitioning iteration with a
for
loop:
public static PairOfIntLists partitionFor (int pivot, IntList L) { // Replace this stub: return twoLists(IL.empty(), IL.empty()); }
Recall that a Java for
loop has the syntax
for (init-statement; test-expression; update-statement) { body-statements }
The above for
loop is
equivalent to the following
while
loop:
init-statement; while (test-expression) { body-statements; update-statement; }
partitionRec()
partitionIter()
method, elements in the two
returned lists are in a relative order opposite to their relative
order in the original lists. For instance, partitioning the list
[1, 4, 8, 3, 6, 7, 5, 2]
about the pivot 6 yields the lists
[2, 3, 4, 1]
and [7, 6, 8]
.
Suppose that we want the resulting lists to have the same relative
order as in the original list. If we are provided with an IntList
reversal
method IL.reverse()
, we can easily accomplish this by reversing the two
lists before gluing them together. That is, we can
change the line
return twoLists(lesses, greaters);within
partitionTail
to be
return twoLists(IL.reverse(lesses), IL.reverse(greaters));
An alternative to using IL.reverse()
to achieve this behavior is
to define a non-tail-recursive version of partitionIter()
that we will call partitionRec()
. Flesh out the
following skeleton of partitionRec()
, which partitions the elements
of a list about the pivot but maintains the relative order of the
elements in the resulting lists:
public static PairOfIntLists partitionRec (int pivot, IntList L) { if (IL.isEmpty(L)) { return twoLists(IL.empty(), IL.empty()); } else { PairOfIntLists subresult = partitionRec(pivot, IL.tail(L)); // Replace the following stub: return twoLists(IL.empty(), IL.empty()); } }
Notes:
partitionRec()
should be a self-contained non-tail-recursive method
that should not invoke any of the other partitioning methods
mentioned in this problem.
The "Rec" in the name partitionRec()
stands for "Recursive",
and is intended to suggest a non-tail-recursive method.
partitionRec()
method is tested by the main()
method of the Partition
class.
quicksort()
To sort the elements of a list, partition the elements of the tail of the list around its head into result lists that we'll call lesses and greaters. Then result of sorting the whole list can be obtained by appending the result of sorting lesses to the result of prepending the head of the list to the result of sorting greaters.
For example, if the initial list is [5, 2, 8, 3, 6, 7, 1, 4]
,
then partitioning the tail of the list around the head (5) yields:
lesses = [4, 1, 3, 2] greaters = [7, 6, 8]
By wishful thinking, sorting lesses
will yield [1, 2, 3, 4]
and sorting greaters will yield [6, 7, 8]
.
The result of sorting the original list is the result of appending
[1, 2, 3, 4]
to the result of prepending 5
to
[6, 7, 8]
.
Flesh out a method with header public static IntList quicksort (IntList L)
that uses this idea to sort the elements of a list. You may use IL.append()
to append two lists and any of the above partitioning methods to partition a list.
The quicksort()
method is tested by the main()
method of the Partition
class.
It turns out that quicksort, true to its name, is one of the fastest algorithms for sorting a list. If you take CS230 and CS231, you will learn (among other things) much more about various sorting algorithms and how to compare them in terms of efficiency.