find-min/find-max
列の要素にキー関数を適用して、その結果が最大(or 最小)となる要素を返す、という関数。
loopマクロのmin/max辺りでやってくれるかなと思ったが、無理そうなので自作した。
マクロをふんだんに使用。
(defmacro find-xxx-helper (empty? first loop compare key-fn seq) `(if (,empty? ,seq) nil ;; 最初の要素とその値(key-fn適用結果)を取得 (let* ((top-elem (,first ,seq)) (top-value (funcall ,key-fn top-elem))) ;; 二番目以降の要素に対するループ (,@loop (let ((value (funcall ,key-fn elem))) (when (,compare value top-value) ;; top-elemの値と比較 (setf top-elem elem ;; top-xxx更新 top-value value)))) (values top-elem top-value)))) (defmacro find-xxx (compare key-fn seq) (let ((list-loop '(dolist (elem (cdr seq)))) (vector-loop '(loop for i from 1 below (length seq) for elem = (aref seq i) do)) (#1=vector-empty? '(lambda (v) (zerop (length v)))) (#2=vector-first '(lambda (v) (aref v 0)))) `(etypecase ,seq (list (find-xxx-helper null car ,list-loop ,compare ,key-fn ,seq)) (vector (find-xxx-helper ,#1# ,#2# ,vector-loop ,compare ,key-fn ,seq))))) ;; 列の各要素にkey-fnを適用して、その値が最小/最大となった要素を返す ;; 値が最小/最大となる要素が複数ある場合は、その内の最初のものが返される => TODO: :from-endフラグをつける? ;; 返り値は、(values 最小/最大要素 key-fnの適用結果)となる ;; ただし、渡されたseqが空の場合は、nilを返す (defun find-min (key-fn seq) (find-xxx < key-fn seq)) (defun find-max (key-fn seq) (find-xxx > key-fn seq))
使用例:
> (find-min #'second '((:a 10) (:b 30) (:c 2) (:d 5))) --> (:C 2) 2 > (find-max #'char-code "列の各要素にkey-fnを適用して、その値が最小/最大となった要素を返す") --> #\U9069 ; #\適 36969 > (find-min #'char-code "") --> NIL