The Common Lisp Word Scrambler, Part 2

I’ve added some stuff to my word scrambler.

I added a function that prompts you for each permutation of a word and asks you if it’s valid or not:

CL-USER> (check-word "cat")
Is "cat" a valid word? (I think it's not.) (y/n) y

Is "cta" a valid word? (I think it's not.) (y/n) n

Is "act" a valid word? (I think it's not.) (y/n) y

Is "atc" a valid word? (I think it's not.) (y/n) n

Is "tca" a valid word? (I think it's not.) (y/n) n

Is "tac" a valid word? (I think it's not.) (y/n) y

T
CL-USER>

In the process, it keeps track of valid and invalid words:

CL-USER> *valid-words*
("tac" "act" "cat")
CL-USER> *invalid-words*
("tca" "atc" "cta")
CL-USER>

Then, if I run it again, it “learns” (in a very, very rudimentary way) which words are valid and which aren’t:

CL-USER> (check-word "cat")
Is "cat" a valid word? (I think it is.) (y/n) y

Is "cta" a valid word? (I think it's not.) (y/n) n

Is "act" a valid word? (I think it is.) (y/n) y

Is "atc" a valid word? (I think it's not.) (y/n) n

Is "tca" a valid word? (I think it's not.) (y/n) n

Is "tac" a valid word? (I think it is.) (y/n) y

T
CL-USER>

The obvious missing piece is that the program can’t infer rules based on the facts you give it. I don’t really know how I might make it do that. Here’s the code for what I have so far:

(defparameter *valid-words* nil)
(defparameter *invalid-words* nil)
(defparameter *valid-words-filename* "valid-words.txt")
(defparameter *invalid-words-filename* "invalid-words.txt")

(defun all-permutations (list)
  (cond ((null list) nil)
        ((null (cdr list)) (list list))
        (t (loop for element in list
             append (mapcar (lambda (l) (cons element l))
                            (all-permutations (remove element list)))))))

(defun make-distinct (list)
  (let ((count 0)
        (result nil))
    (dolist (element list)
      (push (list element count) result)
      (incf count))
  (reverse result)))

(defun make-simple (list)
  (let ((result nil))
    (dolist (element list)
      (push (first element) result))
    (reverse result)))

(defun clean-list (list)
  (let ((result nil))
    (dolist (element list)
      (push (list-to-string (make-simple element)) result))
    (reverse result)))

(defun scramble-list (list)
  (clean-list (all-permutations (make-distinct list))))

(defun scramble (string)
  (remove-duplicates
   (scramble-list (loop for char across string collect char))
   :test #'string-equal))

(defun list-to-string (list)
  (concatenate 'string list))

(defun user-thinks-word-is-valid (word)
  (let ((opinion))
    (if (is-valid word)
        (setf opinion "I think it is.")
        (setf opinion "I think it's not."))
  (y-or-n-p (format nil "Is \"~a\" a valid word? (~a)" word opinion))))

(defun check-word (word)
  (dolist (scrambley (scramble word))
    (if (user-thinks-word-is-valid scrambley)
        (push scrambley *valid-words*)
        (push scrambley *invalid-words*)))
  (save-words))

(defun save-words ()
  (save *valid-words-filename* *valid-words*)
  (save *invalid-words-filename* *invalid-words*)
  t)

(defun save (filename expression)
  (with-open-file (out filename :direction :output :if-exists :supersede)
    (with-standard-io-syntax
      (print expression out))))

(defun load-from-file (filename)
  (with-open-file (in filename)
    (with-standard-io-syntax
      (read in))))

(defun load-words ()
  (setf *valid-words* (load-from-file *valid-words-filename*))
  (setf *invalid-words* (load-from-file *invalid-words-filename*)))

(defun is-valid (word)
  (if (find word *valid-words* :test #'string-equal) t))

Leave a Reply