- Introduction
- Set Up
- Task 1: Deque Interface
- Task 2: wordToDeque
- Task 3: isPalindrome
- Task 4: Generalized Palindrome and OffByOne
- Task 5: OffByN
- Frequently Asked Questions
- I can’t get the provided LinkedListDeque solution to compile.
- My implementation of LinkedListDeque or ArrayDeque won’t compile.
- My code passes all my tests, but fails some autograder tests.
- The Autograder doesn’t like my tests.
- Only my tests are failing. What tests am I missing?
- I’m failing the TestPalindrome or TestOffByOne tests but I feel like my tests are good.
- What’s with the weird static Palindrome palindrome = new Palindrome(); stuff?
- Deliverables
Introduction
In most of your prior programming assignments, you’ve relied on some external source of truth to tell you that your code was correct.
In this project, you’ll use your one of your deques from project 1A to solve a real world problem. Specifically, you will write a class with methods that decide if a given a word is a palindrome, a word that can be read the same forward and backward. For example, the words “noon”, “racecar”, and “mom” are palindromes. You will then expand the functionality of this class by giving it the ability to find generalized palindromes. For example, the word “flake” is an off-by-one palindrome. If we forgive differences between characters that are 1 away in the alphabet, it reads the same when read forward and backward (‘f’ and ‘e’, and ‘k’ and ‘l’ are one away from each other).
Along the way in this project, you’ll also write tests of your application to convince yourself that everything works correctly.
Unlike Project 1A, this project will be more highly scaffolded to reduce the workload in anticipation of the midterm.
Warning: The autograder output for this project is going to be terse and unhelpful. This is because we want you to use your own tests to build confidence in your code. You will not be able to rely on the autograder for correctness.
Set Up
Snaps Backups
The snaps repository you set up in lab 2 saves frequent backups of your work to your local computer. To make these backups available on Github, you can push them by following the directions below. If you’re OK with staff taking an early look at your snaps repo fill out this form. Otherwise, we won’t look at your snaps repo as described in Josh’s post.
To push your snaps backups to Github, open a new terminal window (Git Bash on Windows), and run the following commands, one by one:
cd $SNAPS_DIR
git push
That’s it! You can close the terminal window and continue working on this project!
Skeleton Files
Pull the skeleton using the command git pull skeleton master
.
In the skeleton, we have provided the following files:
PalindromeFinder.java
: A class that helps identify generalized palindromes in English.CharacterComparator.java
: An interface specifying behavior for comparing characters.TestPalindrome.java
: A class containing JUnit tests forPalindrome
(a class you will create).TestOffByOne.java
: A class containing JUnit tests forOffByOne
(a class you will create).
We also made a file for you, words.txt
, with tens of thousands of English words that will
help you test your program. To get it, run the following when in your fa20-s***
repo:
cd library-fa20
git pull origin master
If git pull origin master
didn’t work, try git submodule update --recursive --remote
from your fa20-s***
repo.
Note: We’ve set up your git repository so that it will reject words.txt
by default, since it is too large
to upload to the grader. So, don’t try to move it outside of the library-fa20
folder.
Please don’t use the -f
command to force
this large file into your repo. It will slow down the autograder.
For this assignment, you’ll also need a correct Deque
implementation. You are
welcome to use your LinkedListDeque
or your ArrayDeque
from Project 1A. To do this,
simply copy over one of your implementations to the proj1b
folder. This can be done
by running the following command, when you are in your fa20-s***
repo:
cp proj1a/ArrayDeque.java proj1b/
You can replace ArrayDeque.java
with LinkedListDeque.java
if you prefer to use
that implementation.
If you’re not
sure if your solution to Project 1A is correct, or you just didn’t finish,
you may download a correct implementation of LinkedListDeque
by left clicking on
this link. In your proj1b folder make a new file called LinkedListDeque.java
and manually copy and paste
the displayed code.
Setting up via Intellij
You can open the proj1b
folder in Intellij as normal. However, we’ve seen a few issues with students’ Intellij not recognizing the code as Java because most of it is commented out. If it seems ilke your class files have red circles near them, there are a few fixes to this issue:
- Mark the source directory (
proj1b
) as “Sources Root”. You can right click theproj1b
directory in Intellij → Mark Directory as → Sources Root. - Uncomment
TestPalindrome.java
andTestOffByOne.java
, and try reimporting again.
Task 1: Deque Interface
In this project you will find palindromes using a Double Ended Queue structure. Recall
that we defined the Deque
API, or behavior, by the following methods:
public void addFirst(T item)
public void addLast(T item)
public boolean isEmpty()
public int size()
public void printDeque()
public T removeFirst()
public T removeLast()
public T get(int index)
Since your program will rely on this behavior, it shouldn’t matter to it what Deque
implementation it is provided, ArrayDeque
or LinkedListDeque
, and should
work for both. To achieve this, we will use the power of interfaces.
This first task is going to be a little tedious, but it won’t take long.
Create an interface in a new file named Deque.java
that contains all of the
methods above.
In IntelliJ, use “New → Java Class”. IntelliJ will assume you want a class, so make
sure to replace the class
keyword with interface
.
Modify your LinkedListDeque
and/or ArrayDeque
so that they implement the Deque
interface by adding implements Deque<T>
to the line declaring the existence of the
class. If you used something other than T
for your generic type parameter, use
that instead. Add @Override
tags to each method that overrides a Deque
method.
Note: If you’re using the provided solution for LinkedListDeque
, which relies on
some inheritance black magic, your class definition should look like
public class LinkedListDeque<Item> extends LinkedList<Item> implements Deque<Item>
Now, in the Deque
interface,give isEmpty()
a default
implementation, which returns true
if the size()
is 0
.
Since your LinkedListDeque
and/or ArrayDeque
implement the Deque
interface, given the default
isEmpty()
implementation, you can remove that method from the LinkedListDeque
and/or ArrayDeque
that
you copied over to the proj1b folder.
Task 2: wordToDeque
Create a new file called Palindrome.java
. In our final program, we will use a Palindrome
object to help us decide whether certain English words are palindromes (or generalized palindromes).
But first, we will add a helper function that given a String
, wordToDeque
should return a Deque
where the characters appear
in the same order as in the String. For example, if the word is “persiflage”, then
the returned Deque
should have ‘p’ at the front, followed by ‘e’, and so forth.
In Palindrome.java
, add a method with the signature shown below:
public Deque<Character> wordToDeque(String word)
Don’t write any code yet for this method.
For now, just have it return null
so that Palindrome.java
can compile.
Uncomment the code in TestPalindrome
and run the test contained in the file. You should fail
the provided test. This makes sense. The JUnit test in this class checks that your
wordToDeque
implementation is correct, but you have not written it yet.
While it may seem strange, this is the recommended way to go about writing code.
Especially when writing big programs. You should first write a test that checks
that your implementation is correct, carefully thinking about all edge cases you need
to check for. This will help you organize your thoughts and be aware of cases
you need to account for when you actually get to writing your method.
Notice line 7 in this class:
static Palindrome palindrome = new Palindrome();
Make sure to not alter this line. If you do, the autograder may not work for you.
The first thing that may seem strange is that this code compiles even though
we never defined a Palindrome
constructor. Turns out that all classes in Java have
a default constructor. Thinking about our beloved environment diagrams, this constructor
will make an empty “object box” of type Palindrome
and save a reference to it in the static
variable
palindrome
. The Palindrome
class is not going to define any instance variables, and we really
only need Palindrome
instances to call the instance methods you will define in this class (one of which is wordToDeque
).
So, the default constructor suffices here.
You’d notice that this static
variable is missing an access modifier, such as public
or private
. When no access modifiers are specified Java gives the variable or method the default “package private” modifier. It means that any class in the same package will have access to these components. We will learn more about packages soon, and will even work with them in HW1. For now, you can assume that any class in the proj1b folder will have access to this static
variable.
Throughout this project, the testing classes will follow the same layout of this one. Do not instantiate objects (using new
) in your
JUnit test methods. Instead, instantiate objects are instantiated in static
variables of the testing class. This will ensure
you can use them across all your testing methods. This is mostly to allow our grader to test your tests. When you write JUnit tests in the future you can definitely instantiate objects inside your JUnit tests.
Your goal is to now pass this test by correctly implementing
wordToDeque
. Once you’ve passed the test, move on to the next part of this
assignment.
Tips
- Search the web to see how to get the
i
th character in a String. - Inserting
char
s into aDeque<Character>
is just like insertingint
s into aLinkedListDeque<Integer>
. - Note: The careful reader of
testWordToDeque
might wonder why we didn’t just create a correctDeque
and then callassertEquals
. The reason is that ourDeque
class does not provide anequals
method and thus it won’t work the way you expect. We’ll be talking about this in class soon. - If you’re failing your own test and can’t figure why, remember you have a debugger. Use it! Don’t just stare at your code looking for the bug. That’s too slow and tedious.
Task 3: isPalindrome
Now that you’re passing the test and getting to enjoy the nice green glow of the success bar, let’s break things again.
Modify your Palindrome.java
so that it now has a second method with the signature
below.
public boolean isPalindrome(String word)
For now, have it return a dummy value, some arbitrary thing
you select, i.e. either true or false, that will allow the code to compile. Before we write the method itself, we’re
going to write a test. Let’s start by discussing what isPalindrome
is supposed to
do.
The isPalindrome
method should return true
if the given word is a palindrome,
and false
otherwise. A palindrome is defined as a word that is the same whether
it is read forward or backward. For example, “a”, “racecar”, and “noon” are
all palindromes. “horse”, “rancor”, and “aaaaab” are not palindromes. Any word
of length 1 or 0 is a palindrome. If the argument word
is null
, the method should return false
.
‘A’ and ‘a’ should not be considered equal. However, you can assume we will only be working with lower case characters in this project.
isPalindrome Testing
Add JUnit tests to TestPalindrome
, testing the isPalindrome
method.
You’ll probably find the assertTrue
and assertFalse
methods to be useful.
You’re also welcome to use any other methods in the
JUnit documentation. It’s
ok to have multiple asserts in one test, though don’t go too wild. Make sure to
annotate your tests with @Test
, otherwise JUnit won’t run your tests.
At the very least, you should have at least one test that checks that some word is a palindrome, and one that checks that some word is not a palindrome, as well as two interesting corner cases.
When you run TestPalindrome
again, your code should fail your tests.
Tips
- As an example,
assertFalse(palindrome.isPalindrome("cat"))
tests that “cat” is not considered a palindrome. - If you’re looking for more interesting things to test, read the
isPalindrome
method description above carefully for any interesting corner cases, and ensure that your tests check those corner cases.
Writing isPalindrome
Now that you have a failing test, implement the isPalindrome
method. Use your
wordToDeque
method to make this task easier for yourself. While you can technically not
use a Deque
at all to solve this problem, we hope that this exercise will demonstrate how your choice of data structures (in this case, Deque
) will have
a profound effect on how you write your code.
Once you’ve passed your own tests, you’re ready to move on. Keep in mind that our autograder is going to be very quiet, so you’ll want to make sure your tests are thorough, so you can feel good about your code.
Tips
- Consider using recursion. There’s a really beautiful solution that uses recursion. You’ll need to create a private helper method for this to work.
- Don’t use the
get
method fromDeque
. It will just make things unnecessarily complicated. - Just for fun: When you’re done, uncomment the code in the provided
PalindromeFinder.java
class and you’ll get a list of all palindromes of length 4 or more in English (assuming you also downloaded the provided words file). - If you’re failing your own test and can’t figure why, remember you have a debugger. Use it! Don’t just stare at your code looking for the bug. That’s too slow and tedious.
Task 4: Generalized Palindrome and OffByOne
However, you’re welcome to do them in any order you choose. For this task, rather than carefully enumerating everything you’re supposed to do, as in the tasks above, we will simply give a general description of your goal.
In this task, your ultimate goal is to add a third public method to your Palindrome
class with the
following signature:
public boolean isPalindrome(String word, CharacterComparator cc)
The method will return true
if the word is a palindrome according to the
character comparison test provided by the CharacterComparator
object passed in as the
argument cc
. A CharacterComparator
is defined by the interface:
public interface CharacterComparator {
/** Returns true if characters are equal by the rules of the implementing class. */
public boolean equalChars(char x, char y);
}
For this task, you’ll create a class called OffByOne.java
, which should implement
CharacterComparator
such that equalChars
returns true
for characters that are off by one from each other. For example, the following calls to equalChars
should
return true
. Note that characters are delineated in Java by single quotes, in
contrast to Strings, which are delineated by double quotes.
OffByOne obo = new OffByOne();
obo.equalChars('a', 'b'); // true
obo.equalChars('r', 'q'); // true
However, the three calls below should return false
:
OffByOne obo = new OffByOne();
obo.equalChars('a', 'e'); // false
obo.equalChars('z', 'a'); // false
obo.equalChars('a', 'a'); // false
Note: Every char
in Java has an associated integer. For example, “under the hood” 'a'
is 97. Convince yourself
that this is true by running variants of the following (either in a main
method in a class of your choosing, or the Java Visualizer)
int x = `a`;
System.out.println(x);
For this reason, you can perform arithmetic operations on characters, so 'b'-'a'
will evaluate to 1.
Characters in Java include non-alphabetical characters. These also have integer values
associated with them. For example, '%''
is 37 and '&''
is 38. So,
'%''
and '&''
are off by one, and the following should reutrn true
:
OffByOne obo = new OffByOne();
obo.equalChars('&', '%');
You should add the method signature to OffByOne
and have it return a dummy value. Then only proceed to completing it after you have written tests in TestOffByOne
.
After you are done writing your OffByOne
class, implement the overloading isPalindrome
in your Palindrome
class and have it return a dummy value for now.
public boolean isPalindrome(String word, CharacterComparator cc)
In your TestPalindrome
add a JUnit test for this method, sending in an OffByOne
object as the CharacterComparator
argument. You are allowed to instantiate new OffByOne
objects in JUnit tests in TestPalindrome
.
Once you feel you have a high quality test, you can complete the method and verify that it is correct using your test.
To allow for odd length palindromes, we do not check the middle character for equality with itself. So “flake” is an off-by-1 palindrome, even though ‘a’ is not one character off from itself.
As with our earlier isPalindrome
method, any zero or 1 character word is considered an off-by-one palindrome.
Additionally, if word
is null, the method should return false
. If no CharacterComparator
is provided (cc
is null) but word
is provided, the method should resort to the behavior of isPalindrome(String word)
.
Tips
- Tip: Make sure to include
@Override
when implementingequalChars
. While it has no effect on the function of your program, it’s a good habit for the reasons detailed in lecture. - Tip: To calculate the difference between two chars, simply compute their
difference in java. For example
'd' - 'a';
evaluates to 3. - If you’re failing your own test and can’t figure why, remember you have a debugger. Use it! Don’t just stare at your code looking for the bug. That’s too slow and tedious.
Just for fun: Try printing out all off-by-one palindromes of length 4 or more
in English (assuming you also downloaded the provided dictionary) by modifying
PalindromeFinder.java
.
Task 5: OffByN
In this last part, you will add another class, OffByN
, that implements CharacterComparator
.
The equalChars(char x, char y)
method of OffByN
objects, will say that x
and y
are equal if they are off by N
from each other.
Each OffByN
object may have a different N
. Thus, this class should define a constructor that takes in an N
as an argument and saves it as an instance variable of the object.
OffByN offBy5 = new OffByN(5);
offBy5.equalChars('a', 'f'); // true
offBy5.equalChars('f', 'a'); // true
offBy5.equalChars('f', 'h'); // false
You are not required to write tests for this class, but hopefully by now you are convinced that you should! If you choose to write tests, you’ll need to make your own test file. Make
sure to include the appropriate import
statements, which you can copy and paste from
our two provided test files.
Just-for-fun: Try modifying PalindromeFinder.java
so that it outputs a list of
off-by-N palindromes for the N
of your choosing.
Just-for-more-fun: For what N
are there the most palindromes in English? What
is the longest off-by-N palindrome for any N
?
Frequently Asked Questions
I can’t get the provided LinkedListDeque solution to compile.
Make sure your class definition is
public class LinkedListDeque<Item> extends LinkedList<Item> implements Deque<Item>
.
My implementation of LinkedListDeque or ArrayDeque won’t compile.
Make sure your class definition ends with implements Deque<Item>
, or if your code
doesn’t use Item
, replace this with whatever generic parameter type variable you used.
My code passes all my tests, but fails some autograder tests.
Try to think about corner cases that you may not be covering. Re-read the corresponding section of the spec carefully to identify them. Then write your own tests that check these corner cases, and ensure your code passes them.
The Autograder doesn’t like my tests.
Make sure that you’re not using words.txt
anywhere in your tests. The autograder does
not have access to this file.
Only my tests are failing. What tests am I missing?
Probably corner cases.
I’m failing the TestPalindrome or TestOffByOne tests but I feel like my tests are good.
Make sure your TestPalindrome
does not say new Palindrome
anywhere other than the lines we provided. Similarly, make
sure your TestOffByOne
does not say new OffByOne
anywhere.
What’s with the weird static Palindrome palindrome = new Palindrome(); stuff?
In order to create JUnit tests for your JUnit tests, we had to resort to some clever
hacks, and the easiest way involved this weird stuff. Sorry. Luckily in TestOffByN
you
don’t have to worry about it.
Deliverables
Deque.java
(created by you)Palindrome.java
(created by you)OffByOne.java
(created by you)OffByN.java
(created by you)TestPalindrome.java
TestOffByOne.java