独学Common Lisp

My Practical Utilities2

概要

ここでは私が用いた関数やマクロなどをメモしていきます。My Practical Utilitiesと似た問題領域でしたが、計算速度が重要だったので、ベクトル単位で計算することを前提にしたユーティリティになっています。

大半が既存の第三者ライブラリにあるものですが、不幸にもネットワークに接続されていないマシンでの作業だったため、自分で実装しました。

Reader Macro: [ , ]

行列、ベクトルへのアクセスが非常に多い問題だったため、リーダーマクロを使って配列へのアクセスを容易にしています。ベクトルの場合はsvref、行列の場合はarefに変換します。

(set-macro-character #\[
  (lambda (stream char)
    (let ((array (read stream t nil t))
          (row (read stream t nil t))
          (col (read stream t nil t)))
      (if (eq col '])
        `(svref ,array ,row)
        (let ((end (read stream t nil t))) `(aref ,array ,row ,col))))))

以下のように使います。

[ a 1 2 ]
; => (aref a 1 2)
[ b 1 ]
; => (svref b 1)

Macro: mx

cl-num-utilsmxマクロを参考に、非常に簡易的に実装した行列作成マクロです。ここでは型の指定を行わず、その代わり型変換も省略しています。

(defmacro mx (&body rows)
  (let ((nrow (length rows))
        (ncol (length (car rows))))
    `(make-array '(,nrow ,ncol)
                 :initial-contents ',rows)))

以下のように使います。

(mx (1 2 3) (4 5 6))
; => #2A((1 2 3) (4 5 6))

Macro: mk

mxマクロの別バージョンです。簡易行列計算ライブラリで紹介したものと同じです。

(defmacro mk (list)
  (cond ((listp list) `(mx ,@list))
        ((symbolp list) `(mx ,@(symbol-value list)))))

Function: mksv

n個の要素を持つベクトルを作成します。

(defun mksv (n)
  (make-array n :initial-element 0.0d0))

Function: file-list

cl-csvの簡易版ですが、標準で数字を数として読み込みます。My Practical Utilsで紹介したstring-splitを使います。

(defun file-list (file)
  (with-open-file (in file)
    (loop
      for line = (read-line in nil)
      while line
      unless (string= line "")
      collect (string-split line))))

Function: mload

CSVファイルから行列を読み込むマクロです。簡易行列計算ライブラリで紹介したものとほぼ同じです。

(defmacro mload (file)
  (let ((temp (file-list file)))
    `(mk ,temp)))

Function: nrow

行列の行数を取得します。

(defun nrow (mat)
  (array-dimension mat 0))

Function: ncol

行列の列数を取得します。

(defun ncol (mat)
  (array-dimension mat 1))

Function: mcol

行列の特定の列を取得します。特定の行を取得することはなかったので、そちらは実装していません。このページの一番上で紹介したリーダーマクロを使用します。以後も同様です。

(defun mcol (mat col)
  (let* ((n (nrow mat))
         (v (mksv n)))
    (dotimes (i n) (declare (fixnum i)) (setf [ v i ] [ mat i col ]))
    v))

Macro: i-

地味なアナフォリックマクロで、(1- i)と書くところを(i-)と書けるだけのものです。6文字が4文字になるので、短縮化効率性は33%です。

(defmacro i- ()
  `(1- i))

Macro: if-math-i

非常に変わったマクロで、あまり利用は推奨されません。インデックスにより3分岐でかつベクトルへの代入を伴う処理が多かったので、その場限りとして作ったアナフォリックマクロです。推奨はされませんが、ソースコードは大幅に簡潔になりました。ループの際の変数としてiを、3分岐の分岐点となるインデックスにstという変数を使います。この時はあるショックの分析をしていたので、Shock-Timeでstにしていました。アナフォリックマクロです。

(defmacro if-math-i (vec val1 val2 val3)
  `(cond ((< i st) (setf [ ,vec i ] ,val1))
         ((= i st) (setf [ ,vec i ] ,val2))
         (t        (setf [ ,vec i ] ,val3))))

Macro: def

defparameterが長いと思ったので、エイリアスを作りました。

(defmacro def (name value)
  `(defparameter ,name ,value))

Macro: defvars

複数の変数を同時に初期化しておく際に使いました。

(defmacro defvars (&body vars)
  (cons 'progn (mapcar (lambda (v) `(def ,v nil)) vars)))

Macro: e+, e-, e*, e/

ベクトル単位で計算するため、要素ごとの計算を行うマクロを作りました。除算は0の場合にエラーではなく、0を返すようにしています。

(defmacro e+ (&body vs)
  `(map 'vector #'+ ,@vs))
(defmacro e- (&body vs)
  `(map 'vector #'- ,@vs))
(defmacro e* (&body vs)
  `(map 'vector #'* ,@vs))
(defmacro e/ (&body vs)
  `(map 'vector (lambda (x y) (if (zerop y) 0.0d0 (/ x y))) ,@vs))

Macro: vs+, vs-, vs*, vs/

ベクトルとスカラーの計算を行うために作りました。

(defmacro vs+ (v s)
  `(map 'vector (lambda (x) (+ x ,s)) ,v))
(defmacro vs- (v s)
  `(map 'vector (lambda (x) (- x ,s)) ,v))
(defmacro vs* (v s)
  `(map 'vector (lambda (x) (* x ,s)) ,v))
(defmacro vs/ (v s)
  `(map 'vector (lambda (x) (/ x ,s)) ,v))

Function: elog

ベクトルの要素ごとのlogを取る関数です。0対応してあります。

(defun elog (v)
  (map 'vector (lambda (x) (if (zerop x) 0.0d0 (log x))) v))

Function: lag

時系列データのラグを取る関数です。今回はベクトル版です。

(defun lag (v)
  (let* ((n (length v))
         (nv (mksv n)))
    (loop for i fixnum from 1 to (1- n) do (setf [ nv i ] [ v (i-) ]))
    nv))

Function: change

時系列データの変化率を取る関数です。変化率は対数で求めます。

(defun change (v)
  (e- (elog v) (elog (lag v))))

なお、この変化率は、

$$ \frac{V_t - V_{t-1}}{V_{t-1}} = \frac{V_t}{V_{t-1}} - 1 $$

に相当しますから(変化分のみに対応)、

$$ \frac{V_t}{V_{t-1}} $$

を使いたい場合はvs+で要素に1を加算する必要があります(二時点間の比に対応)。

Macro: save

変数のデータを保存するマクロです。ファイルに変数を保存すると、loadで読み込めます。

(defmacro save (file &body vars)
  (with-open-file (out file :direction :output :if-exists :supersede)
    (dolist (v vars)
      (format out "(defparameter ~a" v)
      (print (symbol-value v) out)
      (format out ")~2%")))
  T))

以下のように使います。

(save "dataset.lisp" var1 var2 var3 var4 var5)
; => T

(load "dataset")

完全に副作用だけで動作するマクロですが、変数名を記録するため、関数では実装できません。関数で実装する場合は、変数名に'を付けて渡す必要があります。


Copyright © 2017- satoshiweb.net All rights reserved.