Tiny Examples With Assertions

Prabhaker Mateti

These are notes on the usage of assertions in small programs. I used pseudo-code that is almost a real PL. Assertions are either enclosed in braces (following CS literature tradition, not to be confused with blocks in C++ or Java), or given using the notation /*@ assertion */.

Overview

  1. Mathematical Logic Quick Overview Boolean expressions v Assertions; The notion of assertion A is weaker than assertion B
  2. javadoc, JML
  3. Correctness by proof, not testing
  4. Design by Contract

Dutch National Flag Problem [Dijkstra 1976]

This is a Correct by Design example from the book by Dijkstra, Edsger Wybe, A Discipline of Programming, Prentice-Hall, 1976. A classic. Has many examples of Correct by Design. Dijkstra is a Turning Award Winner 1972.

Problem: An array a[1..n] is filled with elements colored red, white or blue. Design an algorithm that rearranges the elements so that all the red-elements are at the left, all the blues are at the right, and the whites are in the middle.

Non-functional Requirements: Do not assume that red < white < blue. We are expecting an O(n) algorithm. Focus on assertions.

Answer: We use a[x .. y] to stand for all elements a[i] such that x <= i <= y. When x > y, a[x..y] is empty and we can ascribe any property P wish to it. The location of LI is immediately after the "while" token.

r = w = 0;
b = n+1;
while
 /*@ LI is a[1..r] is red and a[r+1 .. w] is white and a[b..n] is blue */
 (w+1 < b) {
  switch (a[w+1]) {
    case white:
      w ++;
      break;
    case red:
      w ++;
      exchange a[w] with a[r+1];
      break;
    case blue:
      b --;
      exchange a[w+1] with a[b];
      break;
  }
}
/*@ w+1 == b and a[1..r] is red and
    a[r+1 .. w] is white and a[b..n] is blue */

The while loop terminates because the gap (b - w) guaranteedly decreases in every iteration.

You should also explain why/how the LI remains valid after each of the switch-cases.

Bubble Sort

Exercise: Write down the two loop invariants. Their location is immediately after the first semicolon of the for loop. Before the termination test.

There are many variations in the implementation.

template < typename T>
void bubbleSort(std::vector < T > & v) {
  const int size = v.size();
  for(int i = 0; i < size; i++) {
    for(int j = 0; j < size - i; j++) {
      if (v[j] > v[j+1])
        std::swap(v[j], v[j+1]);
    }
  }
}

Variation #2: Better suited to the name of the algorithm. [Spot any errors?]

template < typename T>
void bubbleSort(std::vector < T > & v) {
  const int size = v.size();
  for(int i = 0; i < size; i++) {
    for(int j = size - 1; j >= i ; j--) {
      if (v[j] > v[j+1])
        std::swap(v[j], v[j+1]);
    }
  }
}

Quick Sort

Quick Sort

Game of Stanley Gill

Q. The following is known as the game of Stanley Gill (see Abacus, Vol 4, No. 4, 1987, page 31). State (1) the proof rule for repeat-until, (2) a strong-enough loop invariant at the bottom of the loop body, and (3) prove the correctness of the algorithm.

LCM stands for the 'least common multiple'; GCD stands for greatest common divisor.

{a > 0 and b > 0}
u := a; x := a;
v := b; y := b;
repeat
 if x < y then y := y - x; v := v + u fi;
 if y < x then x := x - y; u := u + v fi;
 {loop invariant}
until x = y
{x = y and ((x+y) div 2) = gcd(a, b)
       and ((u+v) div 2) = lcm(a, b)}

A: (1) Proof rule for repeat-until:

{P} S {Q}, Q and ~B -> P
------------------------------
{P} repeat S until B {Q and B}

(2) A strong-enough loop invariant is given below.

a >= x > 0 and b >= y > 0 and u > 0 and v > 0 gcd(a, b) = gcd(x, y) and xv + yu = 2ab (i)

(3) We show that (i) is a loop invariant. Referring to the rule of repeat- until above, both P, and Q are being taken as (i), and B as x != y. It is trivial to see that Q and ~B -> Q.

{(i)} if x < y then y := y - x; v := v + u fi; {(i)} and you can check that

{(i)} if y < x then x := x - y; u := u + v fi; {(i)}

follows similarly. If x >= y, the first of these assertions immediately follows, so we need only worry about the case when x < y. So, we need to show

{(i) and x < y} y := y - x; v := v + u fi; {(i)}.

From the axiom of assignment, and the rule of consequence, this is equivalent to showing that

(i) and x < y implies a >= x > 0 and b >= y-x > 0 and u > 0 and v+u > 0 gcd(a, b) = gcd(x, y-x) and x(v+u) + (y-x)u = 2ab

This simplifies to

(i) and x < y implies a >= x > 0 and b >= y-x > 0 and u > 0 and v+u > 0 gcd(a, b) = gcd(x, y-x) and xv + yu = 2ab

We know that gcd(x, y) = gcd(x, y-x) if x < y, so the above implication follows easily.

From the repeat-until rule, we can therefore conclude that Q and B, which is

a >= x > 0 and b >= y > 0 and u > 0 and v > 0 gcd(a, b) = gcd(x, y) and xv + yu = 2ab and x = y

holds at the end of the loop. gcd(a, b) = gcd(x, x) = x = (x + y) div 2. The harder part is to show that (u+v) div 2 = lcm(a, b). This part of the proof is independent of the above algorithm. It only depends on the number-theoretic properties of lcm and gcd, and here it goes:

Let g = gcd(a, b). Then a = gi, for some i, and b = gj, for some j, such that i and j do not have any common factors. (Otherwise, g would not be the gcd.) Therefore, lcm(a, b) = lcm(gi, gj) = g*lcm(i, j) = gij.

xv + yu = 2ab = 2gigj = 2ggij = g(v + u), because x = y = gcd(a,b) = g

Therefore, u + v = 2gij and lcm(a, b) = gij = (u+v) div 2.

Exercise: Show that u+v is always even.

GCD(a, b) where odd(a) or odd(b)

Q. Give all three loop invariants of the following algorithm, with the given pre- and post-conditions.
{a > 0 & b > 0 & (odd(a) or odd(b)}
x := a; y := b;
loop
  while even(y) do y := y/2 od;
  while even(x) do x := x/2 od;
  if x = y then exit loop;
  if x < y then y := (y - x)/2
  else
    if x > y then x := (x - y)/2
end
{x = y = gcd(a, b)}

A: LI == loop invariant. The algorithm uses a loop construct with a middle exit.

LI0 between lines 5 and 6: gcd(x, y) = gcd(a, b).
LI1 for the first while loop: gcd(x, y) = gcd(a, b).
LI2 for the second while loop: gcd(x, y) = gcd(a, b).

(N.B. That all three have the same loop invariant is highly unusual.)

Exit assertion at 10 follows from LI0 because gcd(x, x) = x.

The proof rules for the middle-exit loop are given below. If the following three conditions hold

(1) {A} S1 {LI}
(2) {LI & not BX} S2; S1 {LI}
(3) LI & BX => B

then we can conclude that the following holds.

	{A}
	loop
		S1;
		{LI}
		if BX then exit loop;
		S2;
	end
	{B}

Exercise: Prove the correctness: See the Game of Stanley Gill for the rest.

Exercise: Does {odd(a) or odd(b)} imply {a > 0 & b > 0} ?

Longest Ascending Subsequence

The function nlass(s) returns the length of longest ascending subsequences of the string s. Design an algorithm for it. Convince us, preferably through a correctness proof, that your algorithm 'works'.

The following solution is a mild adaptation of the solution and discussion of the above problem from the book "The Science of Computer Programming," Section 20.2 by David Gries.

Let s[1..n] be the given string, lass(x) stand for the set of all longest ascending subsequences of x, and let len(x) stand for the length of a longest ascending subsequence of x. Suppose we have so far examined s[1..i], and found k to be the length of longest asc subseq, i.e.,

P(d) is k = len(s[1..i]) & 1 <= i <= n

We would like to extend the above to hold for P(d+delta), where delta is going to be the element s[i+1]. It is clear that, for i = 1, k will be 1.

Consider lass(s[1..i]).

Previous Permutation

Q: Systematically develop an algorithm for the `previous permutation' problem. That is, given a sequence s of numbers, your algorithm should generate a t, also a sequence of numbers, such that s is the next permutation of t (as defined in class). Describe precisely all intermediate steps in the development.

A: The problem can be solved only if s is not sorted. Let n == length of s.

(a) Find i:1..n such that s[i] > s[i+1], and s[i+1..n] is sorted.

(b) Find j:i+1..n such that s[j] < s[i] and s[j] is the highest such element.

(c) Exchange s[i] with s[j], and invert the tail s[i+1..n]

Exercise: We leave the implementation details to you.

Print Error Marks

[TBD make start index 0. URL to cpp] Q: In a printed line of length L, certain characters are marked. The marks are represented by a specific character (say, '*') printed below each such character. The positions (indices) of the marked characters are given in an array p[1..n], n >= 0. These elements of p are such that for all i: 1 <= p[i] <= L, and p[1] <= p[2] <= p[3] <= ... <= p[n]. This example is from Niklaus Wirth, Systematic Programming, 1973 (book).

        Example:  p = <3, 6, 9, 19, 22>, n = 5
        line of text :Thes broplem is peenuds.
        line of marks:  *  *  *         *  *

Investigate the following proposals (written in C++, but with array index starting at 1) to print the line of marks. The line of text is assumed to have been printed already.

  1. Give the strongest loop invariant for each of the loops in each `solution.'
  2. Which ones are correct? Indicate the conditions under which the incorrect ones fail.

To save some vertical space, we violate a few well accepted pretty printing conventions.

  #define n       whatever
  #define L       whatever

  int p[n+1];
  int i, j, k;

  // (a)
  k = 1;
  for (i = 1; i <= L; i++)  {
    if (i == p[k])  {
      putchar('*');
      k ++;
    } else  putchar(' ');
  }

  // (b)
  k = 1; p[n+1] = 0;
  for (i = 1; i <= L; i++) {
    if (i == p[k]) {
      putchar('*');
      do { k++; } while (i == p[k]);
    } else  putchar(' ');
  }

  // (c)
  i = 1;
  for (k = 1; k <= n; k++) {
    do {  putchar(' ');
          i++;
    } while (i != p[k]);
    putchar('*');
    i++;
  }

  // (d)
  i = 1; k = 1;
  do {
    while (i <  p[k]) { putchar(' '); i++; }
    if    (i == p[k]) { putchar('*'); i++; }
    k++;
  } while (k <= n);

  // (e)
  i = 1; k = 1;
  while (k <= n) {
    while (i <  p[k]) { putchar(' '); i++; }
    while (i == p[k]) k++;
    putchar('*');
  }
Answer:

The loop invariants of the various proposals are similar; so to save space we use the following abbreviations.

Let I stand for 1 <= i <= L+1.
Let K stand for 1 <= k <= n+1
Let P stand for (for all i:1..n-1(1 <= p[i] <= p[i+1] <= L))
Let IKP stand for I & K & P.
Let s stand for the seq of chars printed so far; #s denotes the length of s.

Let a[1..y] denote the correct seq that needs to be printed; i.e., for all j:1..y, a[j] == '*' iff p[k] == j for some k in 1..x; a[j] == ' ', otherwise. This seq a is clearly a function of p, x and y; so we write a = es(p, x, y), es for expected seq.

Note that n may or may not be <= L. It is in establishing the proper relationship between s and a that most of the proposals failed. The relation should have been
s == a[1..i] == es(p, k, i) at all times, and
s == a[1..L] == es(p, n, L) eventually.

(a.i) IKP & s == es(p, k-1, p[k-1]) | blank ^ (i - p[k-1]), where blank ^ x stands for x-blanks when x > 0, for empty seq when x <= 0.

(a.ii) The p[k]'s are not guaranteed to be distinct. If they are not, once an i equals p[k], k will get incremented and then onwards it will remain unchanged. Also, k can become equal to n+1.

(b.i) IKP & s == es(p, k-1, i-1) for the for-loop.

IKP & s == es(p, k-1, i) for the do-loop at the boolean exp. (b.ii) correct except for reference to p[n+1], which is non existent.

(c.i) IKP & s == es(p, k-1, i-1) & i-1 <= p[k-1] for the for-loop, and

K&P & s == es(p, k-1, i) for the do-loop at the boolean exp. Note that I is dropped from the second loop invariant. (c.ii) Suppose p[1] == p[2] >= 2. We then get an infinite sequence of blanks after the '*'. Also, if p[1] == 1, we get an inf seq of blanks.

(d.i) IKP & s == es(p, k, i) & i-1 == p[k-1] at bool exp for the do-loop.

IKP & s == es(p, k-1, i-1) & (k <= n -> i <= p[k]) for the while-loop. (d.ii) n can be 0, so that p[1] is undefined, and yet (d) accesses p[1]. The informal stmt of the problem does not quite say it, but don't we want L chars (blanks/stars) printed? (d) does not do that.

(e.i) IKP & s == esb(p, k-1) & #s <= i <= p[k] for the outer while-loop. IKP & s == esb(p, k-1) | blank^x & #s <= i <= p[k] for the inner while-loops.
(e.ii) forgot to increment i after printing '*'; so fails, e.g., on p[1] = 1, p[2] = 2.

Exercises

  1. [01/10] Almost all introductory programming books have code for sorting and searching. It is likely that these are without assertions. Provide assertions and verify that the code is correct. Focus on: Sorting {bubble sort, quicksort, merge sort}; Searching {linear search, binary search}.
  2. [02/10] Introduce a primitive: exchange(i, j) whose pseudo-code is shown below:
    typedef ordered but opaque is item;
    typedef 0 .. n-1 is index;
    global array a[index] of item;
    
    exchange(index i, index j)
    {
      item temp := a[i];
      a[i] := a[j];
      a[j] := temp
    }
    
    Specify exchange(). Rewrite the code of quick sort using this primitive and with the additional constraint of using no variables of type item other than the a[]. Verify the correctness of this revised version of quick sort.
  3. [10++] Proving correctness is almost always hard. Proving termination is almost always easy. In rare cases it can be very difficult.
    /*@ x is the given input integer, x > 0 */
    while (x > 1) {
        if (x % 2 == 0)
            x /= 2;
        else
            x = 3*x + 1;
    }
    

    Does this terminate for arbitrary integers x > 0? Ignore arithmetic overflow issues. Read http://en.wikipedia.org/wiki/ Collatz_conjecture.

  4. [03/10] Write the Bubble Sort in the best possible way. Some books have code for it using boolean flags that aim to terminate the iterations sooner. Do these help?
  5. [03/10] Write linearSearch.cpp, the complete file including a test harness, in the best possible way.

Further Readings

  1. CodeGuru.com, Visual C++ Debugging: How to use 'ASSERT' and deal with assertions failures? http://www.codeguru.com/forum/showthread.php?t=315371 Recommended Reading.
  2. C++ assert http:// www.cplusplus.com/ reference/ clibrary/ cassert/ assert/ Required Reading.
  3. Donald E Knuth, "A Literate Program [Most Common Words]," Communications of the ACM, Vol. 29, No. 6, 471-483. Required Reading.
  4. Prabhaker Mateti, Practical Advice on Writing Pre- Post-Conditions for Real Programs, http://www.cs.wright.edu/~pmateti/ Courses/433/Notes/prepost-notes.html Required Reading.
  5. Prabhaker Mateti, Two Specifications of Tabulated Equations. tabeqn.pdf (Later) Required Reading.
  6. Microsoft.com, MFC Assertions http://msdn.microsoft.com/ en-us/ library/ c72tsd3t (VS.80) .aspx Recommended Reading.
  7. Oracle.com, Programming With Assertions in Java, http://docs.oracle.com/ javase/ 8/ docs/ technotes/ guides/ language/assert.html Required Reading.

Copyright © 2013 Prabhaker Mateti