Assignment 3: Managing a Course Gradebook
Everywhere you look, data are organized into tables. In sports, tables are used to summarize athletes’ performance: rows correspond to players, and columns correspond to “stats” such as shots, penalties, and goals. In finance, bank statements are tables of transactions, each showing a debit/credit amount and the account’s remaining balance. In science labs, you might write down tables of measurements taken during an experiment. Relevant to our course, we use tables to keep track of your grades on discussion worksheets, assignments, and exams.
In this assignment, you will use nested Lists to represent tables, and you will complete two List implementations: one utilizing a dynamic array, and another using a doubly-linked chain. You will then use your linked list to build an application that can add summary statistics to a gradebook file. While nested linked lists may seem like an awkward fit for operating on tabular data, with clever use of Iterators, your application will achieve similar efficiency with both list implementations.
Learning objectives
- Use dynamic arrays and doubly-linked chains to implement the List ADT.
- Develop unit tests for an abstract data type to verify multiple implementations, including exceptional behavior.
- Implement Java
Iterator
s and leverage them to operate efficiently on both linked and array-backed structures. - Read and write CSV data.
Recommended schedule
Start early. Office hours and consulting hours are significantly quieter shortly after an assignment is released than closer to the deadline. Compared to A2, many of the TODOs here require more code to implement. We recommend spreading your work over at least 5 days. Here is an example of what that schedule could be:
- Day 1: Skim this entire handout.
Download the release code and open it in IntelliJ.
Read the class invariants in
ArraySeq
and add anassertInv()
method. Read through theSeq
interface and add unit tests toSeqTest
(TODOs 1-6), runningArraySeqTest
as you go to identify the bugs inArraySeq
’s implementation (TODO 7). - Day 2: Complete TODOs 8-16 in the
DLinkedSeq
class and verify that it passes all of your unit tests. - Day 3: Complete TODOs 17-19 in the
Gradebook
class, which add functionality to read/write tables from CSV files and manipulate the structure of these tables. Write tests to verify this functionality. - Day 4: Complete TODOs 20-22 in the
Gradebook
class, which allow you to compute summary statistics of aSeq
and appends these statistics to new rows/columns in a gradebook table. Write tests to verify this functionality. At this point, you will have a working application. - Day 5: Run the provided end-to-end test (and perhaps others you construct) to verify the functionality of your code. Answer the remaining questions in reflection.txt. Re-read your code to ensure that it conforms to our style guide and complies with all implementation constraints. Submit to CMSX.
Note that this schedule does not include challenge extensions. If you plan on tackling these, be sure to leave some time at the end of your schedule.
Remember: you can submit your code to the smoketester at any time to confirm that our autograder will be able to run it. Please don’t wait until the last minute to do this.
Collaboration policy
On this assignment you may work together with one partner. Having a partner is not needed to complete the assignment: it is definitely do-able by one person. Nonetheless, working with another person is useful because it gives you a chance to bounce ideas off each other and to get their help with fixing faults in your shared code. If you do intend to work with a partner, you must review the syllabus policies pertaining to partners under “programming assignments” and “academic integrity.”
Partnerships must be declared by forming a group on CMSX before starting work. The deadline to form a CMS partnership is Monday, Mar 3, at 11:59 PM. After that, CMSX will not allow you to form new partnerships on your own. You may still email your section TA (CCing your partner) to form a group late, but a 5-point penalty will be applied. This is to make sure you are working with your partner on the entire assignment, as required by the syllabus, rather than joining forces part way through.
As before, you may talk with others besides your partner to discuss Java syntax, debugging tips, or navigating the IntelliJ IDE, but you should refrain from discussing algorithms that might be used to solve the problems, and you must never show your in-progress or completed code to another student who is not your partner. Consulting hours are the best way to get individualized assistance at the source code level.
Frequently asked questions
Note: This assignment includes some “challenge extensions” that are worth very few points but will provide a lot of additional practice for students who choose to tackle them. Do not feel discouraged if you don’t have time to try these challenges—you can still earn an ‘A’ grade without attempting them at all. If you do attempt them, course staff will only be able to provide limited debugging assistance.
If needed, there will be a pinned post on Ed where we will collect any clarifications for this assignment. Please review it before asking a new question in case your concern has already been addressed. You should also review the FAQ before submitting to see whether there are any new ideas that might help you improve your solution.
I. Assignment overview
This assignment is focused on the List ADT, as specified in an interface named Seq
.
We call it Seq
(short for “sequence”) to avoid accidentally colliding with Java’s built-in List
interface.
In this handout, “List” and “Sequence” are synonymous.
To complete this assignment, you will need to do the following:
- Write unit tests for
Seq
methods and use them to catch bugs in the givenArraySeq
implementation. - Implement
Seq
andIterator
methods forDLinkedSeq
, which uses a doubly-linked list data structure. - Write methods to read CSV files as nested lists and to write nested lists as CSV files.
- Write a method to transpose a table represented by a nested list.
- Write methods to compute the means and standard deviations of rows and columns in a table represented by a nested list.
Setup
Download the release code from the CMSX assignment page; it is a ZIP file named “a3-release.zip”. Follow the same procedure as for A2 to extract the release files and open them as a project in IntelliJ (if it complains about standard Java classes, try “File | Repair IDE” and advance it through “step 3”).
Confirm that you can run the unit test suites in SeqTest
; you should see that all of the ArraySeq
tests pass, but most of the DLinkedSeq
tests fail.
Note that SeqTest.java actually contains three classes: SeqTest
is an abstract class containing testcases that should pass for any Seq
implementation, while ArraySeqTest
and DLinkedSeqTest
are (very small) subclasses that just override the method used to create a new Seq
object of the appropriate type.
You can try running the SeqTest
suite or any of its testcases as normal (using the green triangles), and you’ll get a popup asking which subclass you want to run it for.
You can also run the whole test suite for both Seq
implementations by right-clicking on SeqTest.java and selecting “Run ‘SeqTest and 2 more’”.
II. A test suite for the Seq
interface
A sequence (Seq
) is a collection, like a bag. But unlike a bag, a sequence maintains an ordering among its elements. For this assignment (contrary to the conventions in our textbook, but agreeing with the conventions of the Java language), we will assume that this ordering is 0-based, and we will refer to a position in this ordering as an index. A sequence’s size is dynamic: it can grow by inserting an element at the beginning, end, or even middle of the sequence (which will affect the indices of subsequent elements), and it can shrink by removing an element. Iterating over a sequence (such as with an enhanced for-loop) will yield each element in order. Sequences also support a get()
operation to retrieve an element given its index.
ArraySeq
is a class that implements the Seq
interface using a dynamic array.
Unfortunately, its author didn’t practice good defensive programming habits, and it is known to contain two bugs.
Your first task is to find and fix these bugs, and this will be much easier if ArraySeq
can detect its own invariant violations.
Your first task: Write an assertInv()
method for ArraySeq
and call it before returning from all mutating methods.
Next, read the specifications in the Seq
interface (familiarize yourself now, as these specifications will not be duplicated in the class definitions).
A couple of these methods are already tested in SeqTest
, but others are not, and ArraySeq
’s bugs are not caught by these tests.
Complete TODOs 1-5 to expand the coverage of this test suite as directed.
Your testcases must be thorough enough to flag both of the bugs in ArraySeq
.
Once you’ve caught a bug with a failing testcase, you can go ahead and fix the bug so your test suite is green.
After you have found both of the bugs, answer Reflection question 1 (TODO 6) in reflection.txt.
Note: In addition to the methods declared in Seq
, both ArraySeq
and DLinkedSeq
override the equals()
, hashCode()
and toString()
methods of Object
. We have provided sufficient tests for these methods, and we do not expect you to write any additional test coverage for them.
Finally, since Seq
is a subtype of Iterable
, implementing classes must provide an iterator()
method.
Add tests (TODO 7) to verify the functionality of the iterator, as described in the comment toward the bottom of the SeqTest.java file.
Confirm that ArraySeq
passes your tests.
Testing tips
The provided code in SeqTest.java includes two helper methods makeSeq(T[] elements)
and makeSeqOfLength(int n)
that provide easy ways to construct sequences with desired sizes and/or properties. You are welcome to use these helpers in your own tests.
Since some of the Iterator
methods promise to throw an exception under certain conditions, you should include tests that check whether an exception is thrown as specified.
The JUnit function assertThrows()
is the right tool for the job, but it works a little differently than other JUnit assertions.
You must specify the expected exception’s Class
object, and you must defer execution of the exception-throwing code using an anonymous function.
As an example, when next()
is called on an Iterator
that has no elements left, it should throw a NoSuchElementException
.
This behavior can be tested using the following code:
Iterator<String> it = emptySeq.iterator();
assertFalse(it.hasNext());
assertThrows(NoSuchElementException.class, () -> it.next());
We’ll come back to anonymous functions in more detail later in the course, but for now you can use them in this idiomatic way by just putting () ->
in front of expressions whose evaluation needs to be deferred.
III. Implementing DLinkedSeq
Now, you will complete a second implementation of the Seq
interface, the DLinkedSeq
class, that utilizes doubly-linked data.
The same test suite that you used to debug ArraySeq
will now act as “guardrails” while you work on DLinkedSeq
.
Doubly-linked chains
A “doubly-linked chain” data structure shares some similarities to the “singly-linked” structure that we discussed in lecture. This data structure also uses an auxiliary class to model each data element.
Here, we call this auxiliary class a DNode
(or doubly-linked node) since in addition to its data
field, it has two fields referencing other nodes: a next
field that points to the subsequent DNode
in the chain, and a prev
field that points to the preceding DNode
.
You will see that these “back pointers” will make some of the operations easier to implement, at the expense of making some of the rewiring more involved. The outer DLinkedSeq
class (in addition to its size
) maintains a reference to both the first (head
) and last (tail
) DNode
s in this chain to allow for both efficient prepend()
and append()
operations. This means that, whenever mutating the list, it is possible that the head pointer, tail pointer, and size will all need to be updated to maintain the class invariant.
Your tasks
- Complete the implementation of the
assertInv()
method (TODO 8) by adding in the four described checks. It is important that you are very careful here, as maintaining the invariants of linked structures can be tricky. You will want to have a workingassertInv()
method that you can rely on throughout the rest of your development. - Read through the
prepend()
method, which has been provided for you and carefully trace its execution (it may help you to draw some pictures). Use similar reasoning to develop theappend()
method (TODO 9), and use your unit tests to verify its functionality. Also implement theget()
method (TODO 10). - The next three
Seq
methods (contains()
,insertBefore()
andremove()
) all begin with the common behavior of locating a particular item. In the spirit of DRY (don’t repeat yourself), we will extract this functionality into a private helper methodfirstNodeWith()
. Implement this method (TODO 11) as per its specification. Then, use this method to complete the implementations ofcontains()
,insertBefore()
andremove()
(TODOs 12-14). Use your unit tests to verify their functionality. - Finally, complete the implementation of the
DLinkedSeqIterator
class, similar to the activity in Discussion 5. First, determine, declare, and document the fields of this inner iterator class (TODO 15). Then, implement the methods in the innerDLinkedSeqIterator
class according to their specifications in thejava.util.Iterator
interface (TODO 16). (You should ignore the iterator’sremove()
method for now. This is one of the assignment’s challenge extensions.)
Restriction.
You must implement DLinkedSeq
using only the provided fields. You may not use any data structures from the Java Collections library in your implementation of DLinkedSeq
. The release code, however, imports some interfaces and exceptions from java.util
, and that is fine.
Defensive programming.
You must assert that preconditions are satisfied in any method with parameters. You must also assert that the class invariant is satisfied at the end of the constructor and before returning from any method that mutates the list.
Efficiency requirement.
You should not call get()
in a loop over indices anywhere in your submission. That pattern is inefficient, because each call to get()
will traverse the list from the beginning instead of from where the previous call left off. Use other patterns to iterate over the list (or its nodes) instead, such as traversing next
fields with a while
loop, or using an Iterator. That being said, correctness is more important than efficiency—you might be more comfortable starting with an “array-style” loop like this, passing your tests, and then rewriting your loop later.
Iteration.
Seq
extends Iterable
and ArraySeq
and DLinkedSeq
have an iterator()
method implemented for you. This makes it possible to use ArraySeq
and DLinkedSeq
in an enhanced for-loop, which will be helpful to client code such as the latter portion of the assignment. Enhanced for-loops support easy iteration over arrays and other collections of data. For example, you can print all the integers in a Seq<Integer>
like this:
Seq<Integer> list = ...
for (Integer s : list) {
System.out.println(s);
}
IV. The Gradebook
class
The Gradebook
class collects together many useful static methods for constructing, interacting with, and writing gradebook files.
Tables as lists.
A table is organized into rows and columns. This is a two-dimensional arrangement, but it is possible to represent the structure using nested one-dimensional abstractions, like sequences. For example, you could treat the table as a “list of rows,” where each row is itself a list of the values for that row in each column. This is called a row-major representation.
Using our Seq<T>
, a table whose entries are strings would be a Seq<Seq<String>>
, aka “a list of lists of strings.” If table
is a variable of this type, then table.get(0).get(3)
represents the value in the table on the first row and in the fourth column.
A rectangular table requires that every row have the same number of columns. But our list-of-lists representation permits ragged tables that are not rectangular—that is, some rows might contain a different number of columns than other rows. Sometimes this makes sense, but it would not be desirable in applications expecting a certain number of columns, so there are times when you might need to validate a table’s shape.
The CSV file format.
One of the simplest and most common ways to represent tables is to save them as plain text files using the comma-separated values (CSV) format. These files can be read and written by any spreadsheet program. They are also easy to produce and consume from technical software platforms (like MATLAB and R) as well as from your own programs. In fact, many of your professors’ interactions with CMSX take place through CSV files.
Here is example of the contents of a CSV file:
Student Name,A1,A2
Alice,100,92
Bob,96,85
Charlie,98,97
Doug,92,75
Elsie,94,82
Each line represents a row, and columns on each row are separated by commas.
Exercise: To build your intuition for CSVs, try creating some small tables in a spreadsheet program like Microsoft Excel or Google Sheets and saving them in CSV format:
- For Microsoft Excel: File → Save As; File Format: Comma Separated Values (.csv)
- For Google Sheets: File → Download → Comma Separated Values (.csv)
Then do the reverse: using a text editor, create or modify a CSV file, then open it in a spreadsheet program to see the resulting table.
Simplified CSV format.
Commas are used to separate columns in CSVs. Likewise newlines are used to separate rows. What if you want a cell to contain a comma or a newline?
In this assignment, we disallow that. We call this restricted file format a Simplified CSV. One consequence is that spreadsheet programs will treat quotation marks and backslashes differently than this assignment, so those are best avoided in your testing. Note that spaces are accommodated in cell values, as in Student Name
above.
Section 1: Input / output
Implement the constructTable()
and outputCSV()
methods (TODOs 17-18). The constructTable()
method takes in a String
path to a CSV file (e.g., “csv/small_gradebook.csv”) and parses this file to produce a Seq<Seq<String>>
representing the table contents in row-major order.
The outputCSV()
method has the opposite responsibility, writing data from a Seq<Seq<String>>
to a CSV file. Refer back to the Discussion 4 materials for examples of how to open, parse, and write files.
Section 2: Table manipulation
When we transpose a table (a term that you may be familiar with from linear algebra), we flip it so that its rows become its columns and its columns become its rows. The order of the entries in each column/row remains the same.
Implement the transpose()
method according to its specifications (TODO 19). The work done by transpose()
should be proportional to the number of entries in the table, and you might want to consider how to use Iterator
s to meet this requirement.
Note that a pre-condition of the transpose()
method is that the table is rectangular. As we discussed above, it is possible that a CSV file can store a ragged table, but for the purposes of the main assignment we will assume that CSVs store rectangular data. We have provided an isRectangular()
method that verifies this and which is asserted at the start of the transpose()
function. One of the challenge extensions for this assignment has you implement a method to efficiently “rectangularize” a table using more advanced functionality of iterators.
Section 3: Summary statistics
In the third and final section of Gradebook
class, you’ll implement functions to add summary statistics to the the grade table.
Given a sequence of numerical data with size , which we denote in mathematical notation as an ordered -tuple (which you can envision programatically as a double[] x
with x.length == s
), the summation is the value obtained by summing up all of these numbers, or the value of sum
after executing the following block of code:
int sum = 0;
for (int i=1; i<=s; i++) {
sum += x[i-1]; // Shift "math" indices to Java array indices
}
Given some integer , we can define the “‘th power sum” of the sequence as the summation Using these ideas, we can define two summary statistics of a sequence of data.
-
The mean of , often denoted , is the average of its entries:
-
The standard deviation of , often denoted , is a measure of how spread out the entries are from each other. It is computed by the formula: Through a series of algebraic manipulations (we don’t expect you to carry this calculation out yourself), we can rewrite this as This latter expression expresses the standard deviation in terms of power sums, which will be useful in this assignment.
Tasks
-
Implement the
powerSum()
method (TODO 20), which takes as arguments aSeq<String> seq
and anint n
and returns the ‘th power sum of the numerical entries ofseq
. We say that an entry is numerical when it can be successfully parsed byDouble.parseDouble()
without throwing aNumberFormatException
. All non-numerical entries should be ignored for the purpose of this calculation. For example, callingpowerSum(seq,2)
withseq
containing the strings [Alice, 9.5, 10, 8.5, Ex, 9] should return . -
Implement the
mean()
andstdDev()
methods (TODO 21), which compute the mean and standard deviation (respectively) of the numerical entries of the argument sequence. Both of these methods should use thepowerSum()
method that you just wrote to aid with the computation. The only external methods that you may use for these calculations are those found in theMath
class. -
Implement the
addSummaryColumns()
method according to its specification (TODO 22a). This method takes in a rectangular gradebook table and appends two new columns to it which record the mean and standard deviation of each row.
As an example, calling this method on the gradebook
Name | HW1 | HW2 | HW3 | HW4 | HW5 |
---|---|---|---|---|---|
Alice | 9.5 | 10 | 8.5 | Ex | 9 |
Bob | 10 | 10 | Ex | 7.5 | 8 |
Charlie | 5.5 | 6.5 | 7.5 | 9 | 10 |
should produce
Name | HW1 | HW2 | HW3 | HW4 | HW5 | mean | standard deviation |
---|---|---|---|---|---|---|---|
Alice | 9.5 | 10 | 8.5 | Ex | 9 | 9.25 | 0.55901… |
Bob | 10 | 10 | Ex | 7.5 | 8 | 8.875 | 1.13880… |
Charlie | 5.5 | 6.5 | 7.5 | 9 | 10 | 7.7 | 1.63095… |
Note: We’ve elided the later decimal places here both to keep the handout clean and because your answers might differ around the 14th digit due to floating-point roundoff error.
Please retain the full precision of your numbers in these methods (e.g., by concatenating with an empty string, or using String.valueOf()
); we’ll apply rounding at the very end as part of main()
.
- Implement the
addSummaryRows()
method according to its specification (TODO 22b). This method takes in a rectangular gradebook table and appends two new rows to it which record the mean and standard deviation of each column. This task should not be complicated if you make effective use of the other methods that you have written.
As an example, calling this method on the gradebook
Name | HW1 | HW2 | HW3 | HW4 | HW5 |
---|---|---|---|---|---|
Alice | 9.5 | 10 | 8.5 | Ex | 9 |
Bob | 10 | 10 | Ex | 7.5 | 8 |
Charlie | 5.5 | 6.5 | 7.5 | 9 | 10 |
ß should produce
Name | HW1 | HW2 | HW3 | HW4 | HW5 |
---|---|---|---|---|---|
Alice | 9.5 | 10 | 8.5 | Ex | 9 |
Bob | 10 | 10 | Ex | 7.5 | 8 |
Charlie | 5.5 | 6.5 | 7.5 | 9 | 10 |
mean | 8.333… | 8.8333… | 8 | 8.25 | 9 |
standard deviation | 2.01384… | 1.64991… | 0.5 | 0.75 | 0.81649… |
Testing
While we will be grading your unit tests in SeqTest.java, you will neither be provided nor be expected to turn in any unit tests covering the Gradebook
class. Of course, we highly recommend that you write tests to help guide your development process; however, we leave it up to you to determine which/how many tests to write to give you the confidence that your code works as expected.
Note that, if you need help diagnosing bugs in your Gradebook
, the first thing consultants will ask to see is your GradebookTest.java, so make sure you have one.
The end-user will interact with your program through the provided main()
method, which takes as program arguments two file names, an input CSV and an output CSV. This main()
method reads in the input CSV, adds summary statistics to both its rows and columns, and then writes out the resulting table in CSV format. We have provided one sample input/output CSV pair (csv/small_gradebook.csv and csv/small_gradebook_expected_output.csv) that you can use as an end-to-end test for your Gradebook
class. We have also written a JUnit wrapper for this end-to-end test in E2ETest.java that will automatically execute it and compare its output with the expected output.
Rounding
Whenever computers perform floating-point operations, there is a risk of roundoff error, since the result often requires more digits than the operands (and a double
is only precise to about 15 decimal digits).
This error means that the associative property of arithmetic does not hold for floating-point math.
Consequently, two mathematical formulas that are algebraically equivalent will likely produce slightly different answers when evaluated on a computer.
The upshot of this is that the summary statistics in your final table might be slightly different than ours.
In unit tests, you can account for this with the three-argument version of assertEquals()
, specifying a tolerance for the comparison.
But things are trickier with end-to-end tests that only compare text output.
To make sure that these discrepancies do not affect the correctness of our end-to-end tests, we have provided a method roundEntries()
that is called by Gradescope.main()
to round all numerical entries of the final table to only a few decimal places.
After this rounding, the table that your main application produces is highly likely match the expected output exactly.
You should not do any rounding in any code that you write for this assignment. All intermediary values that you compute should be kept at full precision and converted to Strings per Java’s default behavior.
Reflecting on your work
Stepping back and thinking about how you approached an assignment (a habit called metacognition) will help you make new mental connections and better retain the skills you just practiced. Therefore, each assignment will ask you to write a brief reflection in a file called reflection.txt. This file is typically divided into three sections:
- Submitter metadata: Assert authorship over your work, and let us know how long the assignment took you to complete.
- Verification questions: These will ask for a result that is easily obtained by running your assignment. (Do not attempt to answer these using analysis; the intent is always for you to run your program, possibly provide it with some particular input, and copy its output.)
- Reflection questions: These will ask you write about your experience completing the assignment. We’re not looking for an essay, but we do generally expect a few complete sentences describing concrete details of your development process.
Respond to the TODOs in reflection.txt (you can edit this file in IntelliJ).
V. Challenge Extension
The last few points of the assignment are reserved for challenge tasks. These require some additional effort for relatively few points and are intended as additional exercise for students who complete the rest of the assignment ahead of schedule. It is okay to attempt none of the challenge tasks; they are not included in the recommended schedule. If you do not want to attempt a challenge, then you can ignore this section of the handout and skip to section VI.
The remove()
method of an iterator
[2 points]
In addition to the hasNext()
and next()
methods, the Iterator
interface declares an optional remove()
method (see the documentation). This method provides a way for the iterator to remove the last element that it visited (i.e., the most recent element to be returned by next()
) from the collection.
Implement the remove()
method in the DLinkedSeqIterator
, being sure to follow the specification in the Iterator
documentation (which, for example, tells you when remove()
is allowed to be called and what happens if this is violated). Add tests to the DLinkedSeqTest
test to verify the functionality of your remove()
method. (Note: you are adding these tests to DLinkedSeqTest
rather than SeqTest
because the provided ArraySeq
test does not override the Iterator.remove()
; it uses the default implementation which does not satisfy this specification. Thus, our ArraySeq
would fail these new tests, and so we only want to run them for DLinkedSeq
.)
Rectangularizing tables
[1 point. Depends on remove()
challenge extension.]
Add a rectangularize()
method to your Gradebook
class that takes in a Seq<Seq<T>>
table and modifies it in-place (i.e., the return type of this method should be void
). The rectangularize()
method turns a possibly-jagged table into a rectangular table by trimming entries from the rows until they all have the size of the shortest row. The amount of work that this method should do must be proportional to the number of entries in the table. As an implementation constraint, the only method that your rectangularize()
method may call on any Seq
object is iterator()
(either directly or through the use of an enhanced for loop). You must use iterators to carry out its behaviors (in particular, you must make use of the Iterator.remove()
method that you defined above).
Fail-fast iterators
[1 point. Will break “Rectangularizing tables” challenge extension if “Fail-fast iterators with remove()
” challenge extension is not also completed.]
Recall that an Iterator
guarantees to visit each element of a collection exactly once during its lifetime. This guarantee becomes untenable when modifications to the sequence are allowed in the middle of the iteration. What should be done if an element is inserted “before” the current position of the iterator? What if an element that was already visited by the iterator is moved later in the sequence? Should it be visited again or skipped over? In the assignment, we eschewed this concern in the specification of the iterator()
methods, where we required that “the sequence not be mutated while the iterator is in use.”
Unfortunately, it’s not uncommon for clients to accidentally violate this part of the contract, and the possibility of arbitrary undefined behavior is not very friendly.
Many Iterator
implementations promote this to exceptional behavior, giving rise to the concept of a fail-fast iterator.
When an iterator is fail-fast, if any* modification is made to the state of its Iterable
(i.e., the collection it is iterating over), then a subsequent call to any of the iterator’s methods should throw a ConcurrentModificationException
.
Implement fail-fast behavior for DLinkedSeqIterator
, and add unit tests to DLinkedSeqTest
to verify this functionality.
You should make these modifications in the same files that you used for the main assignment, making sure not to break any of the existing functionality. The assignment autograder will conform to the “sequence not be mutated while the iterator is in use” requirement when grading the main assignment, but it will violate this when specifically testing your fail-fast behavior.
Fail-fast iterators with remove()
[1 point. Depends on “Fail-fast iterators” challenge extension.]
In practice, we often want a way to simultaneously iterate over a collection while we are removing some of its elements; this paradigm is often referred to as filtering the collection. In fact, this was most likely a paradigm that you adopted in the rectangularize()
method, if you implemented it. To facilitate this, we make one exception to the fail-fast rule described above: if an iterator modifies the collection through its own remove()
method, then that iterator remains valid and should not throw a ConcurrentModificationException
exception. However, any other active iterators for this collection must still be invalidated. Modify your remove()
implementation to conform to this convention, and add unit tests to DLinkedSeqTest
to verify this functionality.
VI. Grading
This assignment is evaluated as follows (note: weights are approximate and may be adjusted slightly for the whole class during grading):
- Submitted and compiles (10%)
- Classes fulfill specifications (40%)
- Complies with implementation constraints, including required defensive programming measures (10%)
- Test cases are valid, well documented, and provide required coverage (20%)
- Challenge Extensions (5%)
- Exhibits good code style (5%)
- Responds to reflection questions (10%)
Formatting is a subset of style. To be on the safe side, ensure that our style scheme is installed and that you activate “Reformat Code” for each file before submitting. Graders will deduct for obvious violations that detract from readability, including improper indentation and misaligned braces.
Beyond formatting, choose meaningful local variable names, follow Java’s capitalization conventions (camelCase), and look for ways to simplify your logic. If the logic is subtle or the intent of a statement is not obvious, clarify with an implementation comment.
VII. Submission
Upload your files SeqTest.java, DLinkedSeq.java, Gradebook.java, and reflection.txt to Assignment 3 on CMSX before the deadline.
If you forgot where your project is saved on your computer, you can right-click on SeqTest.java in IntelliJ’s project browser and select “Open In”, then your file explorer (e.g. “Explorer” for Windows, “Finder” for Mac). Be careful to only submit “.java” files, not files with other extensions (e.g. “.class”). Note that your test suite will be under “tests/cs2110/”, while your other files will be under “src/cs2110/”.
After you submit, CMSX will automatically send your submission to a smoketester, which is a separate system that runs your solution against the same tests that we provided to you in the release code. The purpose of the smoketester is to give you confidence that you submitted correctly. You should receive an email from the smoketester shortly after submitting. Read it carefully, and if it doesn’t match your expectations, confirm that you uploaded the intended version of your file (it will be attached to the smoketester feedback). Be aware that these emails occasionally get misclassified as spam, so check your spam folder.