Contents:

Introduction

Part 1: Verifying Correctness of Methods

Part 2: Writing Unit Tests for FiniteSet

Part 3: Choosing a Representation for SimpleSet

Part 4: Implementing and Testing Short Methods

Part 5: Implementing More Complex Methods

Part 6: Implementing and Testing toString

How to Turn In Your Homework

Introduction

In this assignment, you will practice writing code in an ADT, choosing how to represent an ADT, and verifying your code by reasoning as well as writing tests. The two ADTs you will be working with are FiniteSet and SimpleSet. Here, FiniteSet represents a finite set of real numbers, while SimpleSet represents either a finite set of real numbers or its complement (everything but finitely many points). By the end of assignment, you will have implemented methods that help display these sets or perform set operations, as well as unit tests for some of those methods.

Before starting this assignment, be sure that you are up-to-date on our lecture material on ADTs and testing. (You can review the same material by reading the resource pages on Writing Representation Invariants and Abstraction Functions and Testing .) These instructions assume that you are familiar with this material.

Part 1: Verifying Correctness of Methods

Like HW3, we recommend working on this part before starting the rest of the homework.

Complete the code reasoning problem given in this worksheet.

On the worksheet, you will verify the correctness of the union and intersection operations with finite sets of real numbers. Before you start the problems, make sure you fully understand the background given in the first page.

Feel free to print out the worksheet or rewrite the problems on a separate sheet. You may submit a scanned copy of any hand-written document with the problems and your solutions as long as it is legible.

Submit your solution as a PDF in Gradescope. Be sure to indicate in Gradescope which pages of your PDF solve each problem.

Part 2: Writing Unit Tests for FiniteSet

When we release each homework assignment, we push the new starter files to your repository in GitLab, so you should obtain these new files by pulling from git.

The rest of this assignment is going to involve working with

  • FiniteSet.java and SimpleSet.java in the directory hw-sets/src/main/java/sets,

  • The test files FiniteSetTest.java and SimpleSetTest.java in the directory hw-sets/src/test/java/sets.

To start, read FiniteSet.java, understand how a FiniteSet is represented, and what each method does. Note that the union and intersection methods are the ones given in Part 1, and there is a provided method for the set difference operation.

Then, go to FiniteSetTest.java, and see the tests written for constructing FiniteSet objects or checking the equals method.

Note the subdomains being tested: empty sets, sets with one item, multiple items, out-of-order, etc.

One approach used to write these tests is the “0, 1, 2” heuristic. We look at an empty object (0), an object with only one element (1) and an object with multiple elements (2).

The "0, 1, 2" heuristic is a good place to start for writing tests, but it shouldn't be the only appraoch you use. Consider other heuristics, as mentioned in lecture, when deciding which tests to write. One such heuristic is looking at the internal representation of an ADT, and giving inputs that might break the representation invariant if not implemented. The out-of-order case in the testCreation method here is an example to this approach.

With these in mind, write unit tests for the union, intersection, and difference methods in FiniteSetTest.java.

Your test suite should cover the simple cases mentioned above, as well as other, more complex cases specific to potential errors for these methods. As you are writing unit tests, each test method should test a specific case, such that failing a test would tell you as much as possible about why the code is incorrect.

We give you some starter methods: testUnion, testIntersection and testDifference in FiniteSetTest.java to give you a base for writing tests, but feel free to replace or add new methods beyond these in order to follow our unit testing standards.

You can refer to the testing guide for more hints on the test cases you can include.

Part 3: Implement the Representation of SimpleSet

Read the specification in SimpleSet.java to understand what SimpleSet represents, as well as behavior of each method.

Note that a SimpleSet can represent either a set of finitely many real numbers (like FiniteSet) or all real numbers except finitely many of them (the "complement" of a finite set).

As you can see, the ADT is fully documented but missing its representation: there are no private fields defined in the SimpleSet class and no Abstraction Function or Representation Invariant.

A SimpleSet either represents a finite set, or a set only excluding a finite set, however it is not possible to store an infinite number of points in a set. To do this, you should include exactly one field representing a FiniteSet of points and one boolean field representing whether or not this SimpleSet represents the complement of its stored FiniteSet. In other words, the boolean field determines whether the abstract state includes or excludes the points in the representation.

If a set is finite, the FiniteSet will be used to contain all of the points IN the set, and the boolean field might be false.

If the set is infinite, the FiniteSet will be used to contain all of the points NOT in the set, and the boolean field might be true.

Additionally, write a suitable Representation Invariant (defining the rules of the state of the representation) and Abstraction Function (translating the abstract representation to the actual representation) above the private fields. Refer to FiniteSet.java, lecture/section slides, or the Writing Rep Invariants and Abstraction Functions handout for more help on representation invariants and abstraction functions.

Once your representation is complete, implement the SimpleSet constructors and the equals method. The SimpleSets that we create in the test suite rely on the complement method which we do not implement until part 4, so the tests provided for the equals method will not all pass yet. To get some reassurance that you're on the right track testEqualsEmptySet, testEqualsSingleton, and testEqualsComplexSet in SimpleSetTest.java should fail on lines 82, 93, and 108 respectively.

Part 4: Implementing and Testing Short Methods

Now that you have a good representation for the SimpleSet ADT, you can implement its methods as described in the specifications above them. Specifically, implement size and complement to start. As a reference, the staff solution for these methods were very short.

While implementing these methods, you should also include explanations as to why your code is correct. These are not meant to be formal assertions, they are supposed to be easily readable by someone looking at your code. Explain your solution thoroughly, so that it's easy to understand why the code works. As a minimum, you should explain how your answer is calculated based on the type of SimpleSet you are working with (finite, or complement of finite), even if you don't use any if/else statements. To get full credit, your code should be not just correct, but also easy to understand.

Once you have implemented complement, the testEquals and testComplement tests provided in SimpleSetTest.java should now pass.

However, for size, we have provided no tests, so you will need to write them yourself in SimpleSetTest.java. We have given you an empty method testSize to fill out, but add additional methods to best test a single subdomain at a time. You should choose your set of test inputs carefully, applying the heuristics taught in class. (The approaches mentioned in Part 2 are useful here also, but keep in mind that SimpleSet will likely require more cases due to the fact that it can represent more types of sets than FiniteSet.) Once you have tested a good set of inputs and the tests pass, you can move on to the next problem.

Part 5: Implementing More Complex Methods

Next, continue on to implement the union, intersection, and difference methods. As a reference, the staff solution for each of these is ~10-15 lines.

Start by implementing the union method. You should take advantage of the methods of FiniteSet that are already available. These will allow you to form the union of two finite sets. However, you must also handle the cases where one or both of the SimpleSets involved are not finite but rather the complement of finite sets.

Similar to the previous part, you should include comments explaining why the code is correct. As a minimum, you should explain how your method behaves with every combination of SimpleSet types. Each of the two sets can be either finite or finite complement, so there are four possible combinations. We recommend that you handle each of these cases separately in your implementation.

To get full credit, your code should be not just correct but also easy to understand. When you are done with your implementation, you should check your solution by running the tests for union provided in SimpleSetTest.java.

Although they share a name, your implementation for SimpleSet.union will look very different from the implemenation of FiniteSet.union that you saw in the reasoning worksheet. Your implementation should not need any loops.

Next, implement the methods intersection and difference.

As with union, you should include comments explaining why the code is correct for each combination of SimpleSet types. Even if your implementation handles some combinations with the same code, your comments should explain each of these cases. To get full credit, your code should be not just correct but also easy to understand.

When you are done with your implementation, you should check your solution by running the provided tests for these methods in SimpleSetTest.java. At this point, all provided tests and all tests you have written so far should be passing.

Note: You can actually use set theory to minimize the work needed for intersection and difference. As a hint, consider how to implement these last two functions using only the operations you have already finished.

Part 6: Implementing and Testing toString

For the last part of the assignment, we work with toString. Read the specification, and understand what the output should be in each case.

Then, write unit tests for it in SimpleSetTest.java. Again, we give you a starter method, testToString, but add methods as you see fit so each method tests a single subdomain. Make sure that these tests fail (as it should since toString is not yet implemented). You can refer to Part 2 and 5 for hints on which cases you can test. Keep in mind that you should not expect your method to round floating point numbers (to a number of decimal places other than the default).

After your test suite is finished, implement toString based on the specification. For any loop in your implementation, make sure to include a loop invariant. Additionally, you should include a comment explaining why the code works, similar to what was asked in previous parts. To do this, you can use a StringBuilder, which is helpful for building a string. You should not round floating point numbers, you can just add them to your string with default formatting.

After both your tests and implementation are finished, you can check your answer by running your tests. While passing tests is not a guarantee that your code works correctly, writing tests before the implementation makes it less likely for you to repeat the same mistakes in both.

How to Turn In Your Homework

Your answers in Part 1 should be submitted in Gradescope, as a PDF file. This should be the only part you are submitting in Gradescope.

At the end of each assignment, you must refer to the Assignment Submission Handout and closely follow the steps listed to submit the rest of your assignment. Do not forget to double check your submission as described in that handout - you are responsible for any issues if your code does not run when we try to grade it.

Use the tag name hw4-final for this assignment.

We should be able to find the following in your repository:

  • SimpleSet.java in hw-sets/src/main/java/sets, with all functionality correctly implemented and necessary comments included (for example, the representation invariant and abstraction function).

  • FiniteSetTest.javain hw-sets/src/test/java/sets, containing unit tests for the provided union, intersection and difference operations in FiniteSet.java.

  • SimpleSetTest.java in hw-sets/src/test/java/sets, containing unit tests for the size and toString methods in SimpleSet.java.