Insertion sort (ACL2)

From LiteratePrograms

Jump to: navigation, search
Other implementations: ACL2 | C | C, simple | Eiffel | Erlang | Forth | Haskell | Io | Java | Lisp | OCaml | Python | Python, arrays | Ruby | Scala, list | Smalltalk | Standard ML | Visual Basic .NET

This article describes an implementation of insertion sort in ACL2, along with the theorems necessary to enable ACL2 to verify its correctness. The overall structure is:

(in-package "ACL2")
insertion sort functions
correctness property functions
proof of correctness

Insertion sort functions

Since ACL2 is a Lisp variant with very limited array support, it's easiest to write a list-based implementation of insertion sort. We separate it into two methods. The first, insert-sorted, takes an atom and inserts it into a sorted list in its correct sorted position:

<<insertion sort functions>>=
(defun insert-sorted (a lst)
  (if (or (endp lst)
          (<= a (car lst)))
      (cons a lst)
    (cons (car lst) (insert-sorted a (cdr lst)))))

In English, the function says:

  • If the list is empty, or the first element is no smaller than a, just prepend a to the list.
  • Otherwise, the result is the first element of the list followed by a inserted into the remainder of the list.

ACL2 has no difficulty proving that the function terminates, since the list becomes shorter in each recursive call and we do not recurse of the list of length zero.

Next, we write insertion-sort, which simply inserts the first element of the list into the insertion-sort of the remainder of the list:

<<insertion sort functions>>=
(defun insertion-sort (lst)
  (if (endp lst)
    (insert-sorted (car lst) (insertion-sort (cdr lst)))))

If the list is empty, we just return it. Again, termination is trivial. In the interactive shell we can try sorting some lists to see that it seems to work:

ACL2 !>(insertion-sort '(4 2 1 3))
(1 2 3 4)
ACL2 !>(insertion-sort '(9 2 5 0 1 4 8 3 7))
(0 1 2 3 4 5 7 8 9)
ACL2 !>(insertion-sort nil)

Correctness property functions

The most obvious property that a sorting function must have is that its result must be in sorted order. We define a function that enables easy testing of this:

<<correctness property functions>>=
(defun is-ordered (lst)
  (or (endp lst)
      (endp (cdr lst))
      (and (<= (car lst) (car (cdr lst)))
           (is-ordered (cdr lst)))))

In the base case, lists of length zero or one are ordered; otherwise, we use recursion to step through the list, comparing each adjacent pair of elements.

Another more subtle, but equally important property is that the output of a sort must be a rearrangement (permutation) of its input. Otherwise, a function that always produces the empty list would be a correct sort! To test whether one list is a permutation of another, we first need an auxilary function to delete the first occurence of a given value from a list, if it is present:

<<correctness property functions>>=
(defun delete-element (a lst)
  (if (endp lst)
    (if (equal a (car lst))
        (cdr lst)
      (cons (car lst) (delete-element a (cdr lst))))))

We can now define is-permutation by stating that a list is a permutation of another if either both are empty, or if the first element of the first list is in both lists (according to member), and deleting it from both lists leaves two lists that are permutations of one another:

<<correctness property functions>>=
(defun is-permutation (left right)
  (or (and (endp left) (endp right))
      (and (not (endp left))
           (member (car left) right)
           (is-permutation (cdr left) (delete-element (car left) right)))))

Trying examples show that both desired properties seem to hold for a variety of inputs:

ACL2 !>(is-permutation (insertion-sort '(9 2 5 0 1 4 8 3 7)) '(9 2 5 0 1 4 8 3 7))
ACL2 !>(is-ordered (insertion-sort '(9 2 5 0 1 4 8 3 7)))
ACL2 !>(is-permutation (insertion-sort '()) '())
ACL2 !>(is-ordered (insertion-sort '()))

This is great testing, but we don't yet have a formal proof of correctness.

Proof of correctness

Our main theorem is that insertion sort's output is both ordered and a permutation of its input:

<<main theorem>>=
(defthm insertion-sort-correct
  (and (is-ordered (insertion-sort x))
       (is-permutation x (insertion-sort x))))

If we simply give this to ACL2 to prove, without loading any books, it is not able to prove the result. The first simplification checkpoint looks like this:

Subgoal *1/3''
         (MEMBER (CAR X)
                 (INSERT-SORTED (CAR X)
                                (INSERTION-SORT (CDR X))))).

Essentially, it wants to prove that the result of insert-sorted has the supplied element as a member. We state this as a lemma:

(defthm insert-sorted-has-input-as-member
  (member a (insert-sorted a x)))

ACL2 proves this successfully using induction. When we attempt the main theorem again, it proves Subgoal *1/3'' and continues to a new simplification checkpoint:

Subgoal *1/2.1
              (IS-PERMUTATION (CDR X)
                              (INSERTION-SORT (CDR X))))
                                    (INSERTION-SORT (CDR X))))).

The last hypothesis is irrelevant; what we need to show here is the other fundamental defining property of insert-sorted, that if its input list is ordered, its output list will be ordered too:

(defthm insert-sorted-preserves-ordered
  (implies (is-ordered x)
           (is-ordered (insert-sorted a x))))

ACL2 proves this successfully. Returning to the main theorem, we now get stuck at this simplification checkpoint:

Subgoal *1/2''
              (IS-PERMUTATION (CDR X)
                              (INSERTION-SORT (CDR X))))
                         (INSERT-SORTED (CAR X)
                                        (INSERTION-SORT (CDR X))))).

This checkpoint suggests a lemma relating insert-sorted to is-permutation. It's helpful to note that insert-sorted always inserts a somewhere, even if its input list is not sorted. As such, if its second argument is a permutation of some list x, then its result is a permutation of (cons a x), the list x with a prepended. The theorem looks like this:

(defthm insert-sorted-is-permutation-of-cons
  (implies (is-permutation x y)
           (is-permutation (cons a x)
                           (insert-sorted a y))))

This theorem is a bit too difficult for ACL2. It reaches a checkpoint that looks like this:

Subgoal *1/4''
              (NOT (MEMBER (CAR X)
                           (DELETE-ELEMENT A (INSERT-SORTED A Y))))
              (MEMBER (CAR X) Y))
                              (DELETE-ELEMENT (CAR X) Y)))).

The form (DELETE-ELEMENT A (INSERT-SORTED A Y)) is suggestive; this clearly should reduce to Y, and if it did, it would contradict the last hypothesis. Any theorem with a false hypothesis is true. So we add a theorem:

(defthm delete-element-undoes-insert-sorted
  (equal (delete-element a (insert-sorted a x))

ACL2 proves this. We then return to our main theorem, which ACL2 proves successfully, without even requiring a proof of insert-sorted-is-permutation-of-cons:

That completes the proof of *1.
Warnings:  None
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)

We put together all our lemmas in the correct order and our job is done:

<<proof of correctness>>=
main theorem
Download code