Skip to main content

CSE 331: Section 4 — Mutable Specs & Data Abstraction

This section's questions concern the following ADT:

 /**
  * Represents a **mutable** integer set, or a collection of distinct integers.
  */
  public class MutableIntSet {
    /**
     * Determines whether n is in the set.
     * @param n the number to look for in the set
     * @return true if n is in the set, false otherwise
     */
    public boolean contains(int n);

    /**
     * Adds n to the set if not already present.
     * @param n the number to add to the new set.
     * @modifies obj
     * @effects obj is unchanged if obj_0 contains n
     *          otherwise, obj contains all of obj_0 and n
     */
    public void add(int n);

    /**
     * Removes the desired int from the set.
     * @param n The int to remove
     * .... To complete in part d
     */
    public boolean remove(int n);
  }

Task 1 — Good News and Add News

Answer the following questions about the specification of MutableIntSet. Assume that T is an instance of this class whose abstract state is {1, 2, 3}.

  1. Would T.add(3) actually change obj? If not, why is that allowed when it says @modifies obj?

  2. Consider the following static method:

    /**
     * Adds n to the set if not already present.
     * @param old the set to add to
     * @param n the number to add to the new set.
     * @requires old is not null
     * @return a set with n and all of the elements of old.
     * If old.contains(n), the new set has all the same elements as 'old'.
     */
    public static MutableIntSet add(MutableIntSet old, int n);
    
    Now, consider a call T.add(4). Explain how the operation of MutableIntSet.add differs from that of a call to static add(T, 4) in terms of obj.

  3. What is the abstract state of \(T\) after the following code (This is forward reasoning.):

         T.add(4);
         T.add(2);
         T.add(0);
    
  4. Write a specification for the method remove. You should have two cases - n is in the set, and n is not. Clearly explain how the abstract state changes after the method call and what is returned.

Task 2 — Comparison is the Thief of Join

We plan to provide the following method:

    /**
     * Joins the two given MutableIntSet's into a single one.
     * @param first The first MutableIntSet
     * @param second The second MutableIntSet
     * @requires first and second are not null
     * ....
     */
    public MutableIntSet join(MutableIntSet first, MutableIntSet second);

To do so, we need to fill in the rest of the specification.

We are considering the following alternatives:

@return a set with all distinct elements from first and second           // Spec A

@modifies first                                                          // Spec B
@return a set with all distinct elements from first_0 and second

@modifies first, second                                                  // Spec C
@return a set with all distinct elements from first_0 and second_0

@modifies first                                                          // Spec D
@effects first = a set with all distinct elements from first_0 and second
@return a set with all distinct elements from first_0 and second

@modifies first, second                                                  // Spec E
@effects first = a set with all distinct elements from first_0 and second_0
@return a set
  1. Fill in the following table explaining the relationships between each pair of specifications. Write an "S" if the spec on left (the row) is stronger than the spec on top (the column), a "W" if the left spec is weaker, and "---" if the specs are incomparable.

    A B C D E
    A X
    B X
    C X
    D X
    E X
  2. Not every combination of @modifies, @effects, and @return behaviors appearing in the previous specifications would be sensible. For example, consider the following specification:

    /**
     * Joins the two given MutableIntSet's into a single one.
     * @param first The first MutableIntSet
     * @param second The second MutableIntSet
     * @requires first and second are not null
     * @effects first = a set with all unique elements from first_0 and second
     * @return a set with all unique elements from first_0 and second
     */
    

    This specification is not sensible, what is wrong with it? Why shouldn't we use it?

  3. (Bonus) If we remove the subscripts for first_0 or second_0 in the @return clause for specs B and C, they become incomparable with D/E (and likely don't mean what we want them to mean).
    @modifies first, second                                        // Spec C
    @return a set with all distinct elements from first and second
    
    Explain why.

Task 3 — RIch AF

In this problem, we will return to the original specification of MutableIntSet, whose abstract state is a set of elements. We will consider three different concrete representations for it (Note: the notation for obj = this.elems[0 : size] means that obj is the set containing the first size elements from this.elems just like how an ArrayList would work!):

    public class MutableIntSetImpl implements MutableIntSet {

(1)   // AF: obj = this.elems[0 : size]
      private int[] elems;
      private int size;

(2)   // AF: obj = this.elems[0 : size]
      // RI: this.elems contains no dups
      private int[] elems;
      private int size;

(3)   // AF: obj = this.elems[0 : size]
      // RI: this.elems is sorted
      private int[] elems;
      private int size;

      public MutableIntSetImpl() {
        this.elems = new int[10];
        this.size = 0;
      }

For each of the methods shown below, state the concrete representations (1--3) for which it would satisfy the specification of the method in MutableIntSet. In each case, briefly explain why.

  1.   public boolean contains(int n) {
        return Arrays.binarySearch(this.elems, n) >= 0;
      }
    

    Note: Binary Search is an algorithm that finds the position of a target value within a sorted array.

  2.   /**
       * Determines whether n is in the set.
       * @param n the number to look for in the set
       * @return true if n is in the set, false otherwise
       */
      public boolean contains(int n) {
        for (int i = 0; i < this.size; i++) {
          if (this.elems[i] == n)
            return true;
        }
        return false;
      }
    
  3.   /**
       * Adds n to the set if not already present.
       * @param n the number to add to the new set.
       * @modifies obj
       * @effects obj is unchanged if obj_0 contains n
       *          otherwise, obj contains all of obj_0 and n
       */
      public void add(int n) {
        if (!this.contains(n)) {
          if (size >= this.elems.length) {
            int[] temp = new int[size * 2 + 1];
            for (int i = 0; i < this.elems.length; i++) {
              temp[i] = this.elems[i];
            }
            this.elems = temp;
          }
          this.elems[size] = n;
          size++;
        }
      }
    
  4.   /**
       * Removes the desired int from the set.
       * @param n The int to remove
       * .... Based on your Task 1 part d solution
       */
      public boolean remove(int n) {
        for (int i = 0; i < size; i++) {
          if (this.elems[i] == n) {
            size--;
            for (int j = i; j < size; j++) {
              this.elems[j] = this.elems[j + 1];
            }
            return true;
          }
        }
        return false;
      }