Lisp, One ( At a Time

Some small programs in Lisp.

The Idea

I've be on and off Lisp for more than two decades now, and it is perhaps time that I grow up :-) This page documents my re-learning steps through a non trivial example.

Crosswords generator

You're given a 100,000 words dictionary, as a sorted text file of ASCII characters, a grid from 15x15 to 25x25, and it is up to you now to fill the grid with existing words. Finding suitable definitions is left as an exercise for the reader.

The Arc Consistency Crossword Compiler is a good source to get a few sample grids and an initial dictionary to play with. ArCCC is a C program whose author claims that it solves any grid in his example in less than a minute. My experience tells me otherwise.

My first attempt at a solution is in Java. There I provided all the debugging infrastructure that I needed to see how the various algorithms that I had or would come up with would be performing. I'm glad to report that I can solve a 15 x 15 grid at about 92% in less than 30 seconds. I'm very sad however, to have to acknowledge that hours after having reached the 92% mark, my best algorithm seems to be stuck in some local optimum it can't break away from.

On To Lisp

While Richard Gabriel argues that objects have failed, even though Guy Steele seems to disagree, I'm willing, at least as a first experiment, to not replicate either ArCCC procedural design, nor my own Object Oriented Java design. After all if Paul Graham seems to side with Peter Gabriel, then there surely must be some other alternative to object orientedness, and so I'm going to avoid CLOS.

Problem's Scope

At least for now, the program will not attempt to provide any user interface other than the traditional Lisp REPL, and will have to store each solved grid as a file, in a format similar to that of an input grid.

Some degree of randomness is expected of the solver, simply because we want to be able to offer more than one solution for any given grid. There might be other ways than pure randomness to choose a particular solution, though.

Needed Libraries

I'm using Peter Siebel excellent Unit Test Framework that must be loaded first.

I have also borrowed the ppmx macro from David S. Touretzky Common Lisp: A Gentle Introduction to Symbolic Computation

Bottom Up Design

Even though, I'm not going to use any "oo-ness", I still must define the bits that I want to operate on.
I will need to manipulate a grid, a dictionary, and will need a solver to apply a subset of the dictionary to the grid.

Since the idiomatic way to program in Lisp (and in Forth, BTW :-) is to use Lisp to create a language in which the application will be expressed, I'm going to try to respect this, as much as I can.

But there's probably no way around the facts that the "program" will need to access the grid to

Likewise, the "program" will need to access the dictionary to

  • create it first, from some input file
  • populate it, in a way that will make it easy (and preferably efficient) for the solver to ask questions such as

    So, shamelessly forging ahead, I'm defining four global (Yuck!) variables:

    ;;; The four main pieces of data the crossword solver is composed of  
    (defvar *io-handler*)
    (defvar *grid*)
    (defvar *dict*)
    (defvar *solver*)
    

    The Input Output Handler

    (to be filled as the design progresses)

    The Grid

    (to be filled as the design progresses)

    The Dictionary

    Thanks to Pascal Bourguignon, jayessay and drewc on comp.lang.lisp I was set straight: the previous week code was simply trying to reimplement what already exists in common-lisp!

    So, here is a much simpler approach, so far, wich creates both the word storage and the index, in less code than I needed last week to only perform half of it!

    I'm now using lisp-unit for the unit tests, and colorize to pretty print to HTML.

      20051127
    ( defstruct dict
      "Holds the dictionary"
      (words (make-array 0
                         :element-type 'character
                         :adjustable t
                         :fill-pointer 0
    )
    )

      ;; We decide to waste the first element of the index. It cannot
      ;; possibly be anything but zero, but having it that way simplifies
      ;; all index calculations since we don't have to special case the
      ;; first word index.
      ;;
      ;; The net result is that the index array always has one more
      ;; element than the words array.
      (index (make-array 1
                         :element-type 'integer
                         :adjustable t
                         :fill-pointer 1
                         :initial-element 0
    )
    )
    )



    (defun reset-dict (dict)
      (adjust-array (dict-words dict) 0)
      (adjust-array (dict-index dict) 1)
    )


    (defun trim-dict (dict)
      (let ((wlen (length (dict-words dict)))
            (ilen (length (dict-index dict)))
    )

        (adjust-array (dict-words dict) wlen)
        (adjust-array (dict-index dict) ilen)
    )
    )


    (defun append-dict (dict word)
      (let*
          ((word-length (length word))
           (next-offset (+ word-length (length (dict-words dict))))
    )

        ;; adjust the index
        (vector-push-extend next-offset (dict-index dict))
        ;; store the new word
        (dotimes (i word-length (= i word-length))
          (vector-push-extend (aref word i) (dict-words dict))
    )
    )
    )

    Here are the tests

    (use-package :lisp-unit)

    (remove-all-tests)

    (defvar *test-dict* nil) ; for testing purposes only

    (define-test test-00-basic
      (assert-true t)
      (assert-false nil)
    )


    (defun fresh-dict ()
      (setf *test-dict* (make-dict))
    #+nil  (format t "fresh-dict: *test-dict* ~S~%" *test-dict*)
      *test-dict*
    )


    (define-test test-01-array-create
      (let
          ((dict (fresh-dict)))
        (assert-true (dict-words dict))
        (assert-true (dict-index dict))
        (assert-true (= (length (dict-words dict)) 0))
        (assert-true (= (length (dict-index dict)) 1))
    )
    )


    (define-test test-02-array-reset
      (let
          ((dict (fresh-dict)))
        (reset-dict dict)
        (assert-true (= (length (dict-words dict)) 0))
        (assert-true (= (length (dict-index dict)) 1))
    )
    )


    (define-test test-03-append-one
      (let
          ((dict (fresh-dict))
           (word "four")
    )

        (append-dict dict word)
    #+nil    (format t "append-one: *test-dict* ~S~%" *test-dict*)
        (assert-true (= (length (dict-words dict)) (length word)))
        (assert-true (= (length (dict-index dict)) 2))
    )
    )


    (define-test test-04-append-list
      (let ((dict (fresh-dict))
            (count 1)
            (offset 0)
    )

        (dolist (w '("one" "two" "three" "four"))
          (append-dict dict w)
          (setf offset (+ offset (length w)))
          (assert-true (= offset (length (dict-words dict))))
          (setf count (+ 1 count))
          (assert-true (= count (length (dict-index dict))))
    )
    )
    )

          

    and the results

    CL-USER 1 > (run-tests)
    TEST-00-BASIC: 2 assertions passed, 0 failed.
    TEST-01-ARRAY-CREATE: 4 assertions passed, 0 failed.
    TEST-02-ARRAY-RESET: 2 assertions passed, 0 failed.
    TEST-03-APPEND-ONE: 2 assertions passed, 0 failed.
    TEST-04-APPEND-LIST: 8 assertions passed, 0 failed.
    TOTAL: 18 assertions passed, 0 failed, 0 execution errors.
    
    CL-USER 2 >
    

    The Solver

    (to be filled as the design progresses)

    To Do List

    Various "nice to have" that I will consider adding, as time allows

    About This Site

    Acknowledgement

    Many Thanks to Will McCutchen for his early support, and Fred Gilham for his constructive comments

    Web site layout and css shamelessly copied and pasted from Tim Bradshaw's excellent Lisp Hacks pages.

    Release History

        20051127    JFB     last update -- rewrite through advice of some common lisp community members.
        20051120    JFB     the appender side works
        20051120    JFB     added link to the archive, improved version of the dictionary appender, though still not complete
        20051113    JFB     added link to the From Java to Lisp page
        20051112    JFB     first cut of half finished and buggy dictionary building code
        20051030    JFB     setting the stage
        20051029    JFB     first release
    

    Providing feed back

    The main reason for this site

    I would love to hear from you!