Exercises from Programming praxis
Table of Contents
Some solutions for problems founds on programming praxis
Tribonacci Numbers
;; tribonacci sequence
;; Copyright (C) 2012 cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(defmacro matrix-elt (mat row col)
`(nth ,col (nth ,row ,mat)))
(defun column@ (mat at)
(mapcar #'(lambda (r) (nth at r)) mat))
(defun m-mult (a b)
(let ((res (loop for i from 0 below (length a) collect
(make-list (length (first b))))))
(loop for i from 0 below (length a) do
(loop for j from 0 below (length (first b)) do
(setf (matrix-elt res i j)
(reduce #'+ (mapcar #'* (nth i a) (column@ b j)) :initial-value 0))))
res))
(defun m-pow (a exp &optional (base (copy-tree a)))
(if (> exp 2)
(m-pow (m-mult a base) (1- exp) base)
(m-mult a base)))
(defun nth-fibonacci (n)
(if (= n 0)
0
(matrix-elt (m-pow '((1 1) (1 0)) (- n 1)) 1 0)))
(defun nth-tribonacci (n)
(cond
((= n 0)
0)
((= n 1)
1)
(t
(matrix-elt (m-pow '((1 1 0) (1 0 1) (1 0 0)) (1- n)) 0 2))))
(defun tribonacci-real (n &optional (res (list 1 0 0)))
(if (= 0 n)
(reverse res)
(tribonacci-real (- n 1) (push (+ (first res) (second res) (third res)) res))))
(defun tribonacci (n)
(cond
((= n 0)
nil)
((= n 1)
(list 0))
((= n 2)
(list 0 1))
((= n 3)
(list 0 0 1))
(t
(tribonacci-real (- n 3)))))
(defun tribonacci-ratio (n)
(float (/ (nth-tribonacci n) (nth-tribonacci (1- n)))))
Random Access Lists
From the excellent: Purely Functional Data Structures, Chris Okasaki Cambrige Press 1998
;; skew binary number random access list
;; Copyright (C) 2012 cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(defclass btree ()
((value
:initarg :value
:accessor value)
(left
:initarg :left
:initform nil
:accessor left)
(right
:initarg :right
:initform nil
:accessor right)))
(defmethod print-object ((object btree) stream)
(with-slots (value left right) object
(format stream " <value ~a left ~a right ~a> " value left right)))
(defgeneric leaf-p (object))
(defgeneric lookup-tree (object w pos))
(defgeneric update-tree (object w pos value))
(defmethod leaf-p ((object btree))
(and (null (left object))
(null (right object))))
(defmethod lookup-tree ((object btree) (w integer) (pos integer))
(cond
((= pos 0)
(value object))
(t
(if (< pos (/ w 2))
(lookup-tree (left object) (truncate (/ w 2)) (- pos 1))
(lookup-tree (right object) (truncate (/ w 2)) (- pos 1 (truncate (/ w 2))))))))
(defmethod update-tree ((object btree) (w integer) (pos integer) value)
(cond
((= pos 0)
(make-instance 'btree :value value :left (left object) :right (right object)))
(t
(if (< pos (/ w 2))
(make-instance 'btree :value (value object)
:left (update-tree (left object) (truncate (/ w 2)) (- pos 1) value)
:right (right object))
(make-instance 'btree :value (value object)
:left (left object)
:right (update-tree (right object) (truncate (/ w 2)) (- pos 1 (truncate (/ w 2))) value))))))
(defun scons (val slist)
(cond
((null slist)
(list (list 1 (make-instance 'btree :value val))))
((= (length slist) 1)
(cons (list 1 (make-instance 'btree :value val)) slist))
((= (first (first slist)) (first (second slist)))
(let ((w1 (first (first slist)))
(w2 (first (second slist)))
(tree-left (second (first slist)))
(tree-right (second (second slist))))
(cons (list (+ w1 w2 1)
(make-instance 'btree :value val :left tree-left :right tree-right))
(subseq slist 2))))
(t
(cons (list 1 (make-instance 'btree :value val)) slist))))
(defun shead (slist)
(if (null slist)
nil
(value (second (first slist)))))
(defmacro with-weight-and-node ((w node slist) &body body)
`(let ((,node (second (first ,slist)))
(,w (first (first ,slist))))
,@body))
(defun stail (slist)
(with-weight-and-node (w node slist)
(if (leaf-p node)
(rest slist)
(cons (list (truncate (/ w 2)) (left node))
(cons (list (truncate (/ w 2)) (right node))
(rest slist))))))
(defun lookup (pos slist)
(if (null slist)
nil
(with-weight-and-node (w node slist)
(if (< pos w)
(lookup-tree node w pos)
(lookup (- pos w) (rest slist))))))
(defun update (pos value slist)
(if (null slist)
nil
(with-weight-and-node (w node slist)
(if (< pos w)
(cons (update-tree node w pos value) (rest slist))
(cons (first slist)
(update (- pos w) value (rest slist)))))))
Hash Tables With Open Addressing
;; simple open addressing hashtable
;; Copyright (C) 2012 cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(asdf:load-system :alexandria)
(alexandria:define-constant +hashtable-size+ 10 :test #'=)
(defun hash (string)
(if (string/= string "")
(let ((first-char (char-code (elt string 0))))
(mod (+ first-char (hash (subseq string 1))) +hashtable-size+))
0))
(defclass hashnode ()
((key
:initform nil
:accessor key
:initarg :key
:type 'string)
(datum
:initform nil
:accessor datum
:initarg :datum)
(status
:initform :empty
:accessor status
:initarg :status)))
(defmethod print-object ((object hashnode) stream)
(with-slots (key datum status) object
(print-unreadable-object (object stream :type nil :identity t)
(format stream "key: ~s datum: ~a status: ~a" key datum status))))
(defparameter *hashtable* '())
(defun h-initialize ()
(setf *hashtable*
(loop for i from 0 below +hashtable-size+ collect
(make-instance 'hashnode))))
(defun h-add-datum (key datum &optional (address (hash key)) (count 0))
(if (< count +hashtable-size+)
(if (or
(eq (status (nth address *hashtable*)) :empty)
(eq (status (nth address *hashtable*)) :deleted)
(string= key (key (nth address *hashtable*))))
(progn
(format t "address ~a (key ~a) empty~%"
address key)
(setf (datum (nth address *hashtable*)) datum)
(setf (key (nth address *hashtable*)) key)
(setf (status (nth address *hashtable*)) :occupied))
(progn
(format t "collision for address ~a (key ~a) collide with ~a~%"
address key (nth address *hashtable*))
(h-add-datum key datum (mod (1+ address) +hashtable-size+) (1+ count))))
(error "hashtable full for key ~s" key)))
(defun h-lookup (key &optional (address (hash key)) (count 0))
(if (< count +hashtable-size+)
(let ((node@address (nth address *hashtable*)))
(if (not (eq (status node@address) :empty))
(if (string= (key node@address) key)
(values (datum node@address) address)
(h-lookup key (mod (1+ address) +hashtable-size+) (1+ count)))
nil))
nil))
(defun h-delete (key)
(multiple-value-bind (found address)
(h-lookup key)
(if found
(let ((node@address (nth address *hashtable*)))
(setf (datum node@address) nil)
(setf (key node@address) nil)
(setf (status node@address) :deleted))
nil)))
(defun test-hashtable ()
(h-initialize)
(loop
for i in '("a" "b" "c" "d" "d" "e" "f" "g" "h" "i" "m")
and d = 0 then (1+ d) do
(format t "add ~a -> ~a~%" i d)
(h-add-datum i d)
(format t "~a~%" *hashtable*))
(format t "delete m key ~a ~%" (hash "m"))
(h-delete "m")
(format t "~a~%" *hashtable*)
(format t "re add m key ~a ~%" (hash "m"))
(h-add-datum "m" 11)
(format t "~a~%" *hashtable*)
(format t "delete c key ~a ~%" (hash "c"))
(h-delete "c")
(format t "~a~%" *hashtable*)
(format t "delete m key ~a ~%" (hash "m"))
(h-delete "m")
(format t "~a~%" *hashtable*))
4SUM
An inefficient solution.
(defun n-tuple (input &optional (count (length input)))
(let ((res '()))
(labels ((actual-n-tuple (input &optional (count (length input)) (accum '()))
(if (> count 0)
(mapcar #'(lambda (a)
(actual-n-tuple input (1- count) (append accum (list a))))
input)
(push accum res))))
(actual-n-tuple input count)
res)))
(defun 4sum (input)
(if (or (every #'(lambda (a) (< a 0)) input)
(every #'(lambda (a) (> a 0)) input))
nil
(remove-if #'(lambda (v) (not (= (reduce #'+ v) 0)))
(n-tuple input 4))))
Minimum Scalar Product
(defun min-scalar (a b)
(reduce #'+ (mapcar #'(lambda (v1 v2) (* v1 v2))
(sort a #'<)
(sort b #'>))))
Make
;; simplicistic build system
;; Copyright (C) 2012 cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(asdf:load-system :alexandria)
(asdf:load-system :osicat)
(asdf:load-system :cl-lex)
(asdf:load-system :yacc)
(asdf:load-system :trivial-shell)
(defparameter *debug* t)
(defmacro define-stat-time (slot-name)
(alexandria:with-gensyms (stat)
`(defun ,(alexandria:format-symbol t "~:@(get-stat-~a~)" slot-name) (file)
(restart-case
(let ((,stat (nix:stat file)))
(if ,stat
(,(alexandria:format-symbol :nix "~:@(stat-~a~)" slot-name)
,stat)))
(use-value (value) value)))))
(define-stat-time mtime)
(define-stat-time ctime)
(define-stat-time atime)
(defun file-outdated-p (file &rest dependencies)
(handler-bind ((nix:enoent #'(lambda (c)
(declare (ignore c))
(invoke-restart 'use-value nil))))
(let ((atime (get-stat-atime file))
(mtimes (remove-if #'null (mapcar #'get-stat-mtime dependencies))))
(when *debug*
(format t "file ~a ~a ~a ~a~%" file atime dependencies mtimes))
(if atime
(remove-if #'(lambda (mtime) (<= mtime atime)) mtimes)
t))))
(defclass rule ()
((name
:initform nil
:accessor name
:initarg :name)
(dependencies
:initform '()
:accessor dependencies
:initarg :dependencies)
(commands
:initform '()
:accessor commands
:initarg :commands)))
(defparameter *all-rules* '())
(defun find-dependency (rule-name &optional (all-rules *all-rules*))
(find-if #'(lambda (dep) (string= (name dep) rule-name)) all-rules))
(defmethod print-object ((object rule) stream)
(format stream "name ~a dep ~a cmds ~a"
(name object) (dependencies object) (commands object)))
(defgeneric build (object))
(defmethod build ((object rule))
(let ((commands-trigger (cond
((not (osicat:file-exists-p (name object)))
t)
((dependencies object)
nil)
(t
t))))
(loop for i in (dependencies object) do
(let ((dep (find-dependency i)))
(if dep
(let ((trigger (build dep)))
(when trigger
(setf commands-trigger trigger)))
(when (file-outdated-p (name object) i)
(setf commands-trigger t)))))
(when commands-trigger
(if *debug*
(format t "rule: ~a~%~{~a~%~}~%~%" (name object) (commands object))
(mapc #'(lambda (cmd)
(format t "~a~%~{~a~}~%" cmd (subseq (multiple-value-list
(metashell:shell-command cmd))
0 2)))
(commands object))))
commands-trigger))
(defmethod build ((rule string))
(build (find-dependency rule)))
(cl-lex:define-string-lexer lexer
("\\t(.*)" (return (values 'command $1)))
("([a-z,0-9,_,\\-,\\.]+):" (return (values 'name $1)))
("([a-z,0-9,_,\\-,\\.]+) " (return (values 'dependency $1)))
("([a-z,0-9,_,\\-,\\.]+)\\n" (return (values 'terminal-dependency $1))))
;; make := rule*
;; rule := name dependencies* terminal-dependency command*
(yacc:define-parser *make-parser*
(:start-symbol make)
(:terminals (name dependency terminal-dependency command))
(make rules nil)
(rules
(rule rules)
rule)
(rule
(name dep stopper commands
#'(lambda (name dependency terminal-dependency command)
(push (make-instance 'rule
:name name
:dependencies (alexandria:flatten
(append dependency (list terminal-dependency)))
:commands (alexandria:flatten command))
*all-rules*))))
(dep
(dependency dep)
dependency
nil)
(stopper
terminal-dependency
nil)
(commands
(command commands)
command)
(dependency)
(command)
(name)
(terminal-dependency))
(defun read-file (file)
(with-open-file (stream file :direction :input)
(do ((lines ""))
(nil)
(handler-case
(setf lines (concatenate 'string lines (format nil "~%") (read-line stream)))
(end-of-file (c)
(declare (ignore c))
(return lines))))))
(defun make (root-rule &optional (file "Makefile"))
(yacc:parse-with-lexer (lexer (read-file file))
*make-parser*)
(build root-rule))
SEND + MORE = MONEY, Part 2
;; cryptarithm solver with hill-climbing algorithm
;; Copyright (C) 2012 cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(eval-when (:execute)
(setf *random-state* (make-random-state t)))
(defparameter *all-letters-values* (make-hash-table :test 'equal))
(defparameter *all-letters* (coerce "abcdefghijklmnopqrstuvwuxyz" 'list))
(defparameter *use-jump* t)
(defparameter *jump-count* 100)
(defun dump-hashtable (table)
(maphash (lambda (k v) (format t "~s -> ~s~%" k v)) table))
(defun reverse-hashtable (table)
(let ((res (make-hash-table :test 'equal)))
(maphash (lambda (k v)
(setf (gethash v res) k))
table)
res))
(defun copy-hashtable (table)
(let ((res (make-hash-table :test 'equal)))
(maphash (lambda (k v)
(setf (gethash k res) v))
table)
res))
(defun random-permutation (vals)
(if vals
(let ((random-pos (random (length vals))))
(append (list (nth random-pos vals))
(random-permutation (append
(subseq vals 0 random-pos)
(if (< (1+ random-pos) (length vals))
(subseq vals (1+ random-pos))
nil)))))
nil))
(defun initialize-hashtable (&optional (letters *all-letters*)
(hashtable *all-letters-values*)
(max-val 10))
(loop for i from 0 below max-val do
(if (nth i letters)
(setf (gethash i hashtable) (format nil "~a" (nth i letters)))
(setf (gethash i hashtable) nil)))
hashtable)
(defun string->number (str &optional (hashtable *all-letters-values*))
(let ((reverse-hashtable (reverse-hashtable hashtable)))
(parse-integer
(format nil "~{~a~}"
(loop for i across str collect (gethash (string i) reverse-hashtable))))))
(defun fitness (first second res hashtable &optional (operator :+))
(ecase operator
(:+
(abs (- (+ (string->number first hashtable)
(string->number second hashtable))
(string->number res hashtable))))))
(defun swap (&optional
(hashtable *all-letters-values*)
(max-number 10))
(let* ((first-random-number (random max-number))
(second-random-number (random max-number))
(first-random-val (gethash first-random-number hashtable))
(second-random-val (gethash second-random-number hashtable)))
(setf (gethash first-random-number hashtable) second-random-val
(gethash second-random-number hashtable) first-random-val)
hashtable))
(defun get-letter-set (the-set &rest str)
(if str
(apply #'get-letter-set (union the-set (remove-duplicates
(coerce (first str) 'list)
:test #'equal)
:test #'equal)
(rest str))
the-set))
(defun first-letter-0-p (hash-table &rest words)
(loop for i in words do
(if (= (gethash (string (char i 0)) (reverse-hashtable hash-table)) 0)
(return-from first-letter-0-p t)))
nil)
(defun solve (first second res &optional (hashtable (initialize-hashtable
(get-letter-set nil first second res)
(make-hash-table :test 'equal)))
(operator :+) (ct 0))
(let ((current-fitness (fitness first second res hashtable operator)))
(if (and (= current-fitness 0)
(not (first-letter-0-p hashtable first second res)))
(progn
(format t "~a ~a ~a = ~a => ~a ~a ~a = ~a~%" first operator second res
(string->number first hashtable)
operator
(string->number second hashtable)
(string->number res hashtable))
(dump-hashtable (reverse-hashtable hashtable)))
(let* ((swapped-hashtable (swap (copy-hashtable hashtable)))
(new-fitness (fitness first second res swapped-hashtable operator)))
(if (or
(and *use-jump*
(= (mod ct *jump-count*) 0))
(< new-fitness current-fitness))
(solve first second res swapped-hashtable operator (1+ ct))
(solve first second res hashtable operator (1+ ct)))))))
Min Stack
(defparameter *stack* nil)
(defparameter *minimum* nil)
(defparameter *compare-fun* #'<=)
(defun push-min (val)
(when (or (null *stack*)
(funcall *compare-fun* val (first *minimum*)))
(push val *minimum*))
(push val *stack*))
(defun pop-min ()
(let ((popped (pop *stack*)))
(when (and popped
(funcall *compare-fun* popped (first *minimum*)))
(pop *minimum*))
popped))
(defun minimum ()
(first *minimum*))