Lab 4 : Coding to Specifications

Objective
  • Read and interpret specifications and implementations on a larger scale.
  • Employ good programming style (everything from choice of variable names, to documentation, to “Don’t Repeat Yourself”).
  • Use checkRep methods and unit testing strategies for a larger system.
  • Utilize good debugging methodologies.
  • Continue to gain further experience with core Swift programming features (methods, classes, properties, variables, objects, loops, arithmetic, etc.)

Table Of Contents

Warning

All computer science machines, including the Mac Lab and evolene, will be offline from Sunday at 10pm to Monday at 9am due to a campus-wide power shutdown. Please plan accordingly.

Partners

  • Plan to work with a partner this week.

Before Lab

  • Read this handout completely.
  • Answer the questions in Problem 0. You can do this even before the Gitlab repos are made. The answers should go into your README.md. You can wait until the beginning of lab to do that so you can double check your answers with your partner if you like.

Quick links:

Overview

You will implement some classes that will complete the implementation of a graphing polynomial calculator, and you will answer questions about both the code you are given and the code you are going to write.

Completing this lab requires knowledge of basic algebra, including rational and polynomial arithmetic. If you need a refresher, here is how we define addition and substraction, multiplication, and division on polynomials.

Read through the whole lab first to get a sense of the overall structure. It is long but broken into reasonably-sized chunks.

Problem 0: Polynomial Arithmetic Algorithm

For this problem you will write pseudo-code algorithms for arithmetic operations applied to single-variable polynomial equations. We provide an example for polynomial addition below.

Polynomial Addition
r = p + q:
  • Set r = q by making a term-by-term copy of all terms in q to r.
  • For each term tp in p:
    • If some term tr in r has the same degree as tp,
      • then replace tr in r with the sum of tp and tr
      • else insert tp into r as a new term

You may use ordinary arithmetic operations on individual terms of polynomial equations without defining them yourself. For the above example, the algorithm uses addition on the terms tp and tr. Furthermore, after defining an algorithm, you may use it to define other algorithms. For example, if helpful, you may use polynomial addition in your algorithms for multiplication and division. Be sure your types are correct: if addition is defined over terms, and is defined over polynomials, that does not mean you can add a term to a polynomial unless you have also defined that case.

Questions

Answer the following in the README.md for your project.

  1. Write a pseudo-code algorithm for multiplication.

  2. Write a pseudo-code algorithm for division. The following is the definition of polynomial division as provided in the specification of RatPoly division operation:

    Given two polynomials u and v, with v != 0, we can divide u by v to obtain a quotient polynomial q and a remainder polynomial r satisfying the condition u = q * v + r, where the degree of r is strictly less than the degree of v, the degree of q is no greater than the degree of u, and r and q have no negative exponents.

    For the purposes of this class, the operation u / v returns q as defined above.

    The following are examples of div’s behavior:

    • \((x^3-2*x+3) / (3*x^2) = 1/3 * x\) (with \(r = -2*x+3\)).
    • \((x^2+2*x+15) / (2*x^3) = 0\) (with \(r = x^2+2*x+15\)).
    • \((x^3+x-1) / (x+1) = x^2-x+2\) (with \(r = -3\)).

    Note that this truncating behavior is similar to the behavior of integer division on computers.

    Also, see the Hints section for a diagram illustrating polynomial division. For this question, you do not need to handle division by zero; however, you will need to do so for the Swift programming exercise.

  3. Illustrate your division algorithm running on this example: \((x^3+x-1) / (x+1) = x^2-x+2\)

    Be sure to show the values of all variables in your pseudocode at the beginning of each loop iteration.

    Here is an example illustration of the addition algorithm running on \((2x^2 + 5) + (3x^2 - 4x)\):

    • p = \((2x^2 + 5)\)
    • q = \((3x^2 - 4x)\)
    • r = copy of q = \((3x^2 - 4x)\)
    • foreach term tp in p:
      • Iteration 1: tp = \(2x^2\), r = \((3x^2 - 4x)\), p = \((2x^2 + 5)\), q = \((3x^2 - 4x)\)
        • [if some term tr in r has the same degree as tp] YES: tr = \(3x^2\)
        • [then replace tr in r with the sum of tp and tr] Thus: tp + tr = \(5x^2\), so now r = \((5x^2 - 4x)\)
        • [else insert tp into r as a new term]
      • Iteration 2: tp = \(5\), r = \((5x^2 - 4x)\), p = \((2x^2 + 5)\), q = \((3x^2 - 4x)\)
        • [if some term tr in r has the same degree as tp] NO
        • [then replace tr in r with the sum of tp and tr]
        • [else insert tp into r as a new term] Thus: r = \((5x^2 - 4x + 5)\)
    • We are done! r = \((5x^2 - 4x + 5)\)

    (Notice that the values of p and q did not change throughout the execution of the algorithm. Thus, this algorithm works when p and q are required to be immutable (unchanged). That reflects how you will actually implement this)

    You can dispense with italics, and you can represent “tp” as “t_p”, for example.

Problem 1: Project Set Up

Clone your Lab 4 repository following the same steps as previous weeks. The repository contains the RatNum XCode project that you will use this week.

Problem 2: RatNum

In the problems below, you will use the provided Specifications to help fill in the missing method implementations.

Problem 2 does not involve writing code, but you do have to answer written questions about RatNum, a struct representing rational numbers. Read the specification for RatNum. Then read over the provided implementation, RatNum.swift.

You will likely want to look at the code in RatNumTests.swift to see example usages of the RatNum struct (albeit in the context of a test driver, rather than application code).

Questions

Answer the following in the README.md for your project. Two or three sentences should be enough to answer each question. Be short and to the point.

  1. Suppose the representation invariant were weakened so that we did not require that the numer and denom properties be stored in reduced form. This means that the method implementations could no longer assume this invariant held on entry to the method, but they also would no longer be required to enforce the invariant on exit. The new rep invariant would then be:

    // Rep Invariant: RI(self) :  self.denom >= 0 

    List the method or constructor implementations that would have to change. For each changed piece of code, describe the changes informally, and indicate how much more or less complex (in terms of code clarity and/or execution efficiency) the result would be. Note that the new implementations must still adhere to the given spec; in particular, RatNum.description needs to output fractions in reduced form.

  2. The +, -, *, and / methods all end with a statement of the form return RatNum(numerExpr, denomExpr). Consider an implementation of the same methods that instead end with:

    lhs.numer = numerExpr
    lhs.denom = denomExpr
    return lhs

    For this question, pretend that the numer and denom properties are declared as vars and not lets so that these assignments compile properly. How would the above changes fail to meet the specifications of the methods (hint: look at the requires and modifies clauses, or lack thereof) and fail to meet the specifications of the RatNum struct?

  3. Calls to checkRep are supposed to catch violations in the invariants of a class/struct. In general, recommended practice is to call checkRep at the beginning and end of every method. In the case of RatNum, why is it sufficient to call checkRep only at the end of the initializers? (Hint: we’ve seen this before…)

Problem 3: RatTerm

Read the specifications for the RatTerm struct, making sure you understand the overview for RatTerm and the specifications for the given methods and properties.

Read the provided skeletal implementation of RatTerm.swift, especially the comments describing how you are to use the provided properties to implement this class.

Fill in an implementation for the methods in the specification of RatTerm. You may define new private helper methods as you like. You may not add public methods; the external interface must remain the same.

You may wish to read the Hints section below before proceeding.

Throughout this assignment, if you define new methods, you must specify them completely. You can consider the specifications of existing methods (where you fill in the body) to be adequate. You should comment any code you write, as needed; please do not over-comment.

We have provided a checkRep method in RatTerm that tests whether or not a RatTerm instance violates the representation invariants. We highly recommend using checkRep where appropriate in the code you write. Think about the issues discussed in the last question of Problem 1 when deciding where to call checkRep.

We have provided a fairly rigorous test suite in RatTermTest.swift. You can run those tests to evaluate your progress and the correctness of your code.

Questions

Answer the following in the README.md for your project. Again keep your answers to 2-3 sentences.

  1. Where did you include calls to checkRep (at the beginning of methods, the end of methods, the beginning of constructors, the end of constructors, some combination)? Why?

  2. Suppose we weakened the representation invariant so that we did not require RatTerms with zero coefficients to have zero exponents. This means that the method implementations could no longer assume this invariant held on entry to the method, but they also no longer were required to enforce the invariant on exit. List which method or initializer implementations would have to change. For each changed piece of code, describe the changes informally, and indicate how much more or less complex (in terms of code clarity and/or execution efficiency) the result would be. Note that the new implementations must still adhere to the given spec; in particular, RatTerm.description still cannot produce a term with a zero coefficient (excluding 0).

  3. In the case of the zero RatTerm, we require all instances to have the same exponent (0). No such restriction was placed on NaN RatTerms. Imagine that such a restriction was enforced by changing the representation invariant to include the requirement:

    coeff.isNaN() ==> exponent == 0

    This means that the method implementations could assume this invariant held on entry to the method, but they would also be required to enforce the invariant on exit. List which method or constructor implementations would have to change. For each changed piece of code, describe the changes informally, and indicate how much more or less complex (in terms of code clarity and/or execution efficiency) the result would be. Note that the new implementations must still adhere to the given spec (except for the part where terms like NaN*x^74 are explicitly allowed).

    Which set of RatTerm invariants do you prefer?

    • coeff.isNan ==> exponent == 0
    • coeff == RatNum.zero ==> exponent == 0
    • both
    • neither

    Why?

Problem 4: RatPoly

Following the same procedure given in Problem 3, read over the specifications for the RatPoly class and its methods and fill in the blanks for RatPoly.swift. The same rules apply here (you may add private helper methods as you like). Since this problem depends on Problem 3, you should not begin it until you have completed Problem 3 (and the RatTermTest test suite runs without any errors).

You are welcome to do what you like with the provided private helper methods in RatPoly (scaleCoeff and the like); you may implement them exactly as given, implement variants with different specifications, or even delete them; and you may add your own private helper methods. However, you must make sure that every private helper method is given an accurate specification and is not still an unimplemented skeleton.

Implementing RatPoly will be quite a bit easier if you utilize a variety of array methods that are available to you. In addition to the usual count, remove(at:), insert(_:at:), and append(_:), you may find filter(_:), map(_), first(where:), and reduce(::) handy. Read the documentation, and possibly experiment with those methods a bit.

Make sure your code passes all the tests in RatPolyTest.swift.

Problem 5: RatPolyStack

Following the same procedure given in Problem 3, read over the specifications for the RatPolyStack class and its methods and fill in the blanks for RatPolyStack. The same rules apply here (you may add private helper methods as you like). Since this problem depends on Problems 3 and 4, you should not begin it until you have completed Problems 3 and 4 (and the RatTermTest and RatPolyTest test suites run without any errors).

Make sure your code passes all the tests in RatPolyStackTest.swift.

Problem 6: RatCalc

Now that RatPoly and RatPolyStack are finished, you can run the calculator app. This allows you to input polynomials and perform arithmetic operations on them. The calculator graphs the resulting polynomials as well.

Switch to the RatCalc Scheme to run this app. (Running on an iPad works better than an iPhone because of the interface design, and switch the orientation to landscape the first time you run it so you can see the full set of controls.)

When you run this app, you will see buttons and a text area to input polynomials, a stack of polynomials (with buttons to manipulate the stack), and a graphing area that shows the polynomials on the stack.

Hints

  • All the unfinished properties/methods in RatTerm and other classes have a TODO: comment, which will appear in the Jump Bar. Typically, I include a precondition statements to fail if the method is called before it is written. However, I have left the precondition lines commented out so that you can run the whole unit test suite before writing all of the methods. When you implement a method, be sure to remove the TODO:, precondition, and any other no-longer-needed code.

  • Think before you code! The polynomial arithmetic functions are not difficult, but if you begin implementation without a specific plan, it is easy to get yourself into a terrible mess.

  • The most important method in your RatPoly class will probably be sortedInsert. Take special care with this method.

  • The provided test suites in this lab are the same ones we will use to grade your implementation; you can consider the provided set of tests rigorous enough that you do not need to write your own tests.

  • Division of rational polynomials is similar to long division as taught in grade school. We draw an example here:

What To Turn In

Be sure to submit everything in the following checklist:

Submission Checklist
  1. Answers to Questions 1-9 above in your README.md.
  2. Complete implementations of:
    • RatTerm.swift
    • RatPoly.swift
    • RatPolyStack.swift

As always, verify your solution is complete and runs by cloning your repository to a temporary location and checking it.

Grading Guidelines

This class emphasizes both program correctness and best practices, including programming style, documentation, specification, sound design principles, testing methodology, etc. Grading will reflect both of these emphases. There is, of course, some subjectivity when evaluating design and specification decisions, but your choices should follow the basic philosophies and methodologies we have been exploring. Labs will be graded on the following scale:

A+: An absolutely fantastic submission of the sort that will only come along a few times during the semester. Such a submissions reflects substantial effort beyond the basic expectations of the assignment.

A: A submission that exceeds our standard expectation for the assignment. The program must reflect additional work beyond the requirements or get the job done in a particularly elegant way.

A−: A submission that satisfies all the requirements for the assignment — a job well done.

B+: A submission that meets the requirements for the assignment, possibly with a few small problems.

B: A submission that has problems serious enough to fall short of the requirements for the assignment.

C: A submission that has extremely serious problems, but nonetheless shows some effort and understanding.

D: A submission that shows little effort and does not represent passing work.