だいぶ前に書いたCommon Lispのプログラムを発掘しました。
まだapply
やmapcar
を知らなかったので、再帰だけを使って計算していました。たとえばベクトル(というか数値の入ったリスト)の和を計算するのにも、以下のように再帰を使っていました。
(defun sum (x) (if (null x) 0 (+ (first x) (sum (rest x))))) (sum '(1 2 3 4)) ;=> 10
apply
を使えば一発で書けます。
(defun sum (x) (apply '+ x)) (sum '(1 2 3 4)) ;=> 10
同様に幾何平均を計算するところも、以下のようにまどろっこしいことをしていました。(とはいえ(expt (* 1 2 3 4) (/ 1 4))
みたいに直接乗算を使っておらず、log
とexp
を使ってオーバーフローが生じにくいようにしてはいますね)
(defun geomean-sub (x) (if (null x) 0 (+ (log (first x)) (geomean-sub (rest x))))) (defun geomean (x) (exp (/ (geomean-sub x) (length x)))) (geomean '(1 2 3 4)) ;=> 2.213364
mapcar
を使うと次のように書き換えられます。ただ、算術平均を計算するmean
関数も欲しかったので、そちらを作って下請けとして使っています。
(defun mean (x) (/ (sum x) (length x))) (defun geomean (x) (exp (mean (mapcar 'log x)))) (geomean '(1 2 3 4)) ;=> 2.213364
長いデータ列の処理をすることを考えると、再帰でスタックを消費するのはよくありませんから(データが多いと計算できなくなってしまう)、apply
やmapcar
などを使うほうが安全そうです。こんなかんじで、プログラミング言語の勉強のために、ゆっくり車輪の再発明をしています。
Matlabのlinspace
に対応するものもCommon Lispで作ってみました。(linspace x1 x2)
とすると、x1からx2の範囲を等間隔に100個作ってくれます。個数の指定をしたいときにはオプションで個数を書いてあげるとOK。
;; linspace ;; (linspace -2.0 +1.0) ;=> (-2.0 -1.969697 -1.939394 ... 0.969697 1.0) ;; (linspace 1 5 5) ;=> (1 2 3 4 5) ;; (defun linspace (x1 x2 &optional (n 100)) (let ((x (loop for x from 0 to (1- n) collect (/ x (1- n))))) (mapcar #'(lambda (v) (+ v x1)) (mapcar #'(lambda (v) (* v (- x2 x1))) x))))
追記 データはリストではなくベクトルにしたほうがいいのかも。あるいはRのようなデータフレーム型を作ったほうがいいか。……規模が大きくなってきたら考えよう。(というより、遊びで作っているだけなので規模が大きくならないようにしよう。統計処理をするツールはLisp-Statとかがあるみたいだし。)