plot
(ql:quickload :plot/vega)
(defun plot (lst)
(plot:plot
(vega:defplot plot
`(:data ,(plot-list-to-plist lst)
:mark line
:width 480
:heigth 320
:encoding (:x (:field :x
:type :quantitative)
:y (:field :y
:type :quantitative))))))
(defun plot-list-to-plist (lst)
`(:x ,(make-array (length lst) :initial-contents (qutimes (i (length lst)) i))
:y ,(make-array (length lst) :initial-contents lst)))
(plot '(1 2 3 4 5)) ; plotが表示される
pythonで言うところのmatplotlib.pyplot
に相当する関数。plot/vega
というパッケージを利用。listをplistに変換しないといけない点に注意。シンプルなプロット以外にも、ヒストグラム等いろいろな種類がある。コード内のqutimes
も自作マクロで、後述。
vegaパッケージ内で利用できるデータ数がコード内で指定されているため、大量のデータを扱うときにsetq
等で扱えるデータ数を変更するということができない。(ql:where-is-system :plot)
で返るディレクトリ配下の、src/vega/plot.lisp
の109行目あたりにある50000
という定数を大きくすることを推奨 (例えば500000
くらいに)。
py4clからpyplotの呼び出しも試したのだけれど、プロットウィンドウが閉じなくなる等の不具合があったので、利用を諦めた。
qutimes
(defmacro qutimes (count &body body)
(let ((i (car count))
(stop (cadr count))
(acc (gensym)))
`(labels ((rev (,i ,acc)
(if (< ,i ,stop)
(rev (1+ ,i) (cons (progn ,@body) ,acc))
(reverse ,acc))))
(rev 0 nil))))
(qutimes (i 5)
(+1 i)) ; (1 2 3 4 5)
pythonのnp.arange(0, x, 1)
のような挙動をするマクロ。与えられた数まで0から1つずつ値を取っていって何かしらの処理を行い、その返り値で構成されたリストを返す。上記の例で言えば、pythonの[1 + x for x in np.arange(0, 5, 1)]
と同じ。
挙動がdotimes
と似ていて、各ステップの返り値を溜め込むという要素がqueueっぽいので、qutimes
という名前にした。
qulist
(defmacro qulist (lst &body body)
(let ((symbol (car lst))
(for (cadr lst))
(ls (gensym))
(acc (gensym)))
`(labels ((rev (,ls ,acc)
(if (consp ,ls)
(rev (cdr ,ls)
(let ((,symbol (car ,ls)))
(cons (progn ,@body) ,acc)))
(reverse ,acc))))
(rev ,for nil))))
(qulist (elm '(1 2 3 4 5))
(1+ elm)) ; (2 3 4 5 6)
pythonのリスト内包表記のような挙動をするマクロ。与えられたリストから1つずつ値を取っていって何かしらの処理を行い、その返り値で構成されたリストを返す。上記の例で言えば、pythonの[elm + 1 for elm in [1, 2, 3, 4, 5]]
と同じ。
挙動がdolist
と似ていて、かつリストの返り値を溜め込むという要素がqueueっぽいので、qulist
という名前にした。
fstr
(ql:quickload :cl-ppcre)
(defmacro fstr (str)
(labels ((peel (str) (subseq str 1 (1- (length str)))))
(let ((symbols (mapcar #'(lambda (str)
(let ((val (read-from-string str)))
(cond ((stringp val) str)
((symbolp val) val)
(t val))))
(mapcar #'peel (cl-ppcre:all-matches-as-strings "{.*?}" str))))
(format (cl-ppcre:regex-replace-all "{.*?}" str "~A")))
`(format nil ,format ,@symbols))))
(let ((hoge "hOgE")
(piyo 'pIyO)
(fuga (+ 1 2)))
(fstr "{hoge}/{piyo}/{fuga}")) ; hOgE/PIYO/3
pythonのf文字列のような挙動をするマクロ。シンボルが大文字で表示される点と、:
を用いた制御ができない点には注意。
let-if
(defmacro let-if (test symbols bindings &body body)
`(multiple-value-bind ,symbols
(if ,test
(values ,@(car bindings))
(values ,@(cadr bindings)))
,@body))
(let-if (> 10 0) (x y) (("Plus" (+ 1 2)) ("Minus" 'hoge))
(print x)
(print y)) ; "Plus" 3
bindings
のcar部分に条件が真の場合の値を、cdar部分に条件が偽の場合の値を入れる。
python等の手続き型言語であれば
if 10 > 0:
x = "Plus"
y = 1 + 2
else:
x = "Minus"
y = "hoge"
print(x)
print(y)
のように一般的に書ける処理でも、Lispだと
(if (> 10 0)
(let ((x "Plus")
(y (+ 1 2)))
(print x)
(print y))
(let ((x "Minus")
(y 'hoge))
(print x)
(print y)))
のように、if分岐ごとにletで束縛して、それぞれに処理 ((print x)
(print y)
) を書かないといけない (はず)。このlet-ifを利用すれば、条件ごとの束縛を一括して記述できる。引数の渡し方に少しクセがあるのはご愛嬌。
let-case
(defmacro let-case (keyform symbols bindings &body body)
`(multiple-value-bind ,symbols
(case ,keyform
,@(qulist (bind bindings)
`(,(car bind) (values ,@(cdr bind)))))
,@body))
(let ((hoge 10))
(let-case hoge (x y) ((5 0 0) (10 100 100))
(print (+ x y)))) ; 200
let-if
のcase
版。bindings
の各リストの最初の値が判定用。それに続く値が,@
で分解・分配されて、束縛される。
let-cond
(defmacro let-cond (symbols bindings &body body)
`(multiple-value-bind ,symbols
(cond ,@(qulist (bind bindings)
`(,(car bind) (values ,@(cdr bind)))))
,@body))
(let-cond (x y) (((= 3 5) 3 (+ 2 3))
((= 1 1) "Hoge" 'hoge))
(list x y)) ; ("Hoge" HOGE)
let-if
のcond
版。bindings
の各リストの最初の値が判定用。それに続く値が,@
で分解・分配されて、束縛される。