(defparameter *implementations*
(list
;; ideone.com/g6m1EK
(defun find-second-smallest-number-r1 (list)
(loop for n in list
for second = n then (if (< first n second) n second)
minimize n into first
finally (return (if (> second first) second nil))))
(defun find-second-smallest-number-r2-UNIQ-AND-SORT (list)
(let ((result (if (null list)
nil
(nth 1 (sort (remove-duplicates list) #'<)))))
;; sort 函数によって比較函数 < が適用されることで、数以外を含む
;; ときはエラーとなることを期待したが、要素が一つだけのときは比
;; 較されないためエラーも起きない。
(when (and (eq result nil)
(not (null list))
(not (subtypep (type-of (first list)) 'real)))
(error 'type-error :expected-type 'real :datum (first list)))
result))
(defun find-second-smallest-number-r3 (list)
(when (eq (nth 1 list) nil)
;; (nth 1 list) の値が nil のとき、LIST は空リストであるか、要
;; 素数が 1 であるか、または先頭から二つ目の要素が nil である。
;; このとき LIST の要素に大小比較可能な数以外の要素が含まれる場
;; 合はコンディション type-error を送出する。
(loop for x in list
unless (subtypep (type-of x) 'real)
do (error 'type-error :expected-type 'real :datum x)))
(loop for n in list
for 2nd = nil then (if (= n 1st)
2nd
(if (null 2nd)
(max n 1st)
(if (< n 1st)
(max n 1st)
(min n 2nd))))
minimize n into 1st
finally (return 2nd)))))
(defparameter *test-cases*
(let ((N 100000)
(type-error (make-condition 'type-error :datum nil :expected-type t)))
`(((4 5 1 7 1 2 8 9 2 7) 2)
((2 2 2 2 2 2 2 2 2 2) nil)
((1) nil)
((2 1) 2)
((1 2) 2)
((3 2 1) 2)
((1/2 1/3 1/42) 1/3)
((,pi 3.14) ,pi)
((3.141592653589793d0 ,(log #c(-1 0))) ,type-error)
((0) nil)
(() nil)
((t) ,type-error)
((1 nil) ,type-error)
((1 2 3 nil) ,type-error)
(,(cons 2 (make-list N :initial-element 1)) 2)
(,(cons 2 (make-list N :initial-element nil)) ,type-error))))
(defun run-tests (fn test-cases &optional (detailed-output-p t))
(flet ((test (expected result)
(if (and detailed-output-p (subtypep (type-of expected) 'condition))
(subtypep (type-of result) (type-of expected))
(eql result expected))))
(loop with *print-length* = 10
with *print-right-margin* = 5000
for (arg expected) in test-cases
for result = (handler-case (funcall fn arg)
(error (condition)
(if detailed-output-p
condition
(class-name (class-of condition)))))
for all-tests-passed-p = (test expected result)
then (and all-tests-passed-p (test expected result))
do (format t (if detailed-output-p
"~:[❌~;✓~] ~:S → ~S~%"
"~*~:S → ~A~%")
(test expected result)
arg
result)
finally (return all-tests-passed-p))))
(defun make-string-sink ()
(make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
(defun clear-string-sink (sink)
(setf (fill-pointer sink) 0))
(let ((debug nil))
(let ((fn (car (last *implementations*))))
(run-tests fn *test-cases* nil))
(format t "~%~50@{=~}~2%" t)
(let ((tests-output (make-string-sink))
all-tests-passed-p)
(dolist (fn *implementations*)
#+clisp (format t "~2%")
(format t "Testing ‘~(~A~)’... " fn)
(with-output-to-string (*standard-output* tests-output)
(setq all-tests-passed-p (run-tests fn *test-cases*)))
(format t "~:[FAILED~;OK~]~%" all-tests-passed-p)
(if all-tests-passed-p
;; テストを通過したら速度も計測。
(time (loop repeat 100000 do (funcall fn (caar *test-cases*))))
(if debug
(write-line tests-output)
(terpri)))
(clear-string-sink tests-output))))