mjk LISPその2

行列の操作... 1

LSPファイルの作成... 1

関数の作成... 1

FizzBuzz. 1

ループ文... 1

関数の作成2... 1

パッケージ... 1

ファイルに保存... 1

 

 

 

行列の操作

mjk LISPでは行列の操作は独自に発展させていて、ANSI Common LISPとの共通点は殆どありません。ネットで検索してもあまりあてになりません。

ベクトルA=(1 2 3)を定義します。リストと違ってアトムが表示されないのでprint-array関数でGraph termに表示させます。

(setq A (vector 1 2 3))

#<3x1 matrix>

(type-of A)

matrix

(print-array A)

#<3x1 matrix>

Graph termで見てみます。#mは行列を示します。行列は#<x matrix>として表現され、通常はGraphのメイン画面には表示されません。

#m(

( 1.0 )

( 2.0 )

( 3.0 ))

実は*print-array*nilからtに変更するとprint-arrayを使わなくてもGraphのメイン画面でちゃんと表示されます。

(setq *print-array* t)

t

A

#m( ( 1.0 ) ( 2.0 ) ( 3.0 ))

#m(( 1.0 )( 2.0 )( 3.0 ))の間は改行なのですが、半角スペースで表示されています。copy & pasteすればちゃんと改行された状態で貼り付けられます。

因みに初期設定では*print-level* 1010行、*print-length* 5050列に制限され、それ以上はと表示されます。それ以上にする場合は*print-level**print-length*の値を変える必要があります。

初期設定の戻すには*print-array*nilにします。

(setq *print-array* nil)

nil

いちいちGraph termprint-arrayとやって確かめないといけないのは面倒なのでが*print-array*tのままでしばらく行きます。

ruler-vector関数を使うと漸増ベクトルが作成できます。MATLABlinspace関数みたいなものです。

(ruler-vector 1 10 5)

#m( ( 1.0 ) ( 3.25 ) ( 5.5 ) ( 7.75 ) ( 10.0 ))

転置するときはtranspose関数を使います。

因みに3次元配列には対応していません。

(print-array (transpose (ruler-vector 1 10 5)))

#m( ( 1.0  3.25  5.5  7.75  10.0 ))

matrix-element-sum関数で行列のアトムの総和を求めます。

(matrix-element-sum (ruler-vector 1 10 5))

27.5

matrix-extentを使うと行列の最小値と最大値が求まります。

(matrix-extent (ruler-vector 1 10 5))

( 1.0  10.0)

matrix関数を使って行列A=((1 2)(3 4))を定義します。また簡易法として#mリストのリスト形式で行列を定義します。

(setq A (matrix '((1 2)(3 4))))

#m( ( 1.0  2.0 ) ( 3.0  4.0 ))

2×2の要素1の行列を作成します。ANSI Common LISPだとmake-arrayなんですが、Mjk LISPではmake-matrix関数を使います

(setq B (make-matrix 2 2 1))

#m( ( 1.0  1.0 ) ( 1.0  1.0 ))

Steel Blank Common Lispの場合です。

* (set 'A (make-array '(2 2):initial-element 1.0))

#2A((1.0 1.0) (1.0 1.0))

 

リスト内の行列をまとめて行列にすることはできません。

(matrix '(#m(1 2) #m(3 4)))

警告音がでておしまいです。

 

行列の足し算A+B

(+ A B)

#m( ( 2.0  3.0 ) ( 4.0  5.0 ))

行列の掛け算A×B

(* A B)

#m( ( 3.0  3.0 ) ( 7.0  7.0 ))

行列に係数をかけるときは係数は後ろにします。

(print-array (* A -1))

#m( (-1.0 -2.0 ) (-3.0 -4.0 ))

nweight-matrix関数は行列と行列の要素と要素の掛け算を行います。行列Aの値の書き換えが行われます。

(nweight-matrix A #m((1 -1)(-1 1)))

#m( ( 1.0 -2.0 ) (-3.0  4.0 ))

A

#m( ( 1.0 -2.0 ) (-3.0  4.0 ))

transpose関数を使って行列Aを転置します。

(transpose A)

#m( ( 1.0  -3.0 ) ( -2.0  4.0 ))

transpose関数はAの値を書き換えません。ntranspose関数を使うとAの値が上書きされます。

一般にn〇関数は値の上書きを行います。

A

#m( ( 1.0  -2.0 ) ( -3.0  4.0 ))

(progn (ntranspose A)A)

#m( ( 1.0  -3.0 ) ( -2.0  4.0 ))

リストの計算はmapcar関数を使いましたが、行列の計算はmap-matrixnmap-matrix関数を使います。

nmap-matrix関数でA=A+2を計算します。値の上書きがなされます。因みにmap-matrix関数だと値の書き換えは行われません

(nmap-matrix 行列 (function 関数名) 行列)という構文になります。

(progn (nmap-matrix A (function +) 2)A)

#m( ( 3.0 -1.0 ) ( 0.0  6.0 ))

 

尚、空行列は作れないようです

(setq A (matrix '(()))

 

初期設定の戻すため*print-array*nilにします。

(setq *print-array* nil)

nil

行列と行列の計算は普通のリスト形式でできます。

MATLAB[1 2]+[3 4]+[5 6]

(print-array (+ #m((1 2)) #m((3 4)) #m((5 6))))

#<1x2 matrix>

#m(

( 9.0  12.0 ))

MATLAB[1; 2]*[3 4]*[5 6;7 8]

(print-array (* #m((1)(2)) #m((3 4)) #m((5 6)(7 8))))

#<2x2 matrix>

#m(

( 43.0  50.0 )

( 86.0  100.0 ))

random-matrix関数を使って2×2の乱数行列Cを作成します。値は0231の間の数となります。

(print-array (setq C (random-matrix 2 2)))

#<2x2 matrix>

#m(

( 1804289408.0  1681692800.0 )

( 846930880.0  1714636928.0 ))

231で割って値を01になるようにします。

(progn (nmap-matrix C (function /)(pow 2 31))(print-array C))

#<2x2 matrix>

#m(

( 0.840187728405  0.783099234104 )

( 0.394382923841  0.798440039158 )

inverse関数を使ってCの逆行列を表示します。因みにninverse関数だと値の上書きが行われます。

(print-array (inverse C))

#<2x2 matrix>

#m(

( 2.20564460754 -2.1632668972 )

(-1.08945989609  2.32097005844 ))

CCの逆行列を計算します。

(print-array (* C (inverse C)))

#<2x2 matrix>

#m(

( 1.00000023842 -4.26407865461e-07 )

( 1.74433495204e-07  0.999999880791 ))

精度は今一です。

random-matrix関数で3×4の乱数行列作成します。値は01の間です。

(progn (setq A (random-matrix 3 4))(nmap-matrix A (function /)(pow 2 31))(print-array A))

#<3x4 matrix>

#m(

( 0.526744961739  0.89152944088  0.807724535465  0.94932705164 )

( 0.769913852215  0.283314734697  0.919026494026  0.525995373726 )

( 0.400228619576  0.352458357811  0.0697552785277  0.0860558450222 ))

array-dimension関数は、行の数、列の数を提示します。

(array-dimension A 0)

3

(array-dimension A 1)

4

length関数だと列の数が表示されます。

(length A)

4

row関数は行を取り出します。3行目の行を表示させます。行は0行から始まりますので、3行目は2となります。

(print-array (row 2 A))

#<1x4 matrix>

#m(

( 0.400228619576  0.352458357811  0.0697552785277  0.0860558450222 ))

column関数は列を取り出します。2列目を取り出します。

(print-array (column 1 A))

#<3x1 matrix>

#m(

( 0.89152944088 )

( 0.283314734697 )

( 0.352458357811 ))

aref関数は任意の行・列のアトムを取り出します。遅く実行後に(メモリ上に)ゴミが出るそうです。行列A24列を表示します。gc関数(garbage collect)でごみ処理をします。残念ながら行列の任意の行・列のアトムを書き換えるsetf関数はMjk LISPでは用意されていません。

(aref A 1 3))

0.525995373726

(gc)

nil

Steel Blank Common Lispではsetf関数を使って以下のように行列を書き換えます。

* (defvar A #2A((1 2 3)(4 5 6)))

A

* (aref A 0 1)

2

* (setf (aref A 0 1) 100)

100

* A

#2A((1 100 3) (4 5 6))

 

因みに1行または1vref関数でもaref関数の代わりに使えます。

(vref #m((1 2 3 4 5) 0)

1.0

truncate-rows関数は行の数を減らします。

(print-array (truncate-rows A 2))

#<2x4 matrix>

#m(

( 0.526744961739  0.89152944088  0.807724535465  0.94932705164 )

( 0.769913852215  0.283314734697  0.919026494026  0.525995373726 ))

truncate-columns関数は列の数を減らします。

(print-array (truncate-columns A 2))

#<3x2 matrix>

#m(

( 0.526744961739  0.89152944088 )

( 0.769913852215  0.283314734697 )

( 0.400228619576  0.352458357811 ))

mat-append関数は行列と行列を列方向に1つにします。

  

(print-array (mat-append #m((1 2)(3 4)) #m((5 6)(7 8)) #m((9 10)(11 12))))

#<2x6 matrix>

#m(

( 1.0  2.0  5.0  6.0  9.0  10.0 )

( 3.0  4.0  7.0  8.0  11.0  12.0 ))

行方向に連結する関数は・・・ないので転置してmat-append関数を使います。

 

(print-array (transpose (mat-append (transpose #m((1 2 3)(4 5 6)))(transpose #m((7 8 9))))))

#<3x3 matrix>

#m(

( 1.0  2.0  3.0 )

( 4.0  5.0  6.0 )

( 7.0  8.0  9.0 ))

nnormalize-columns関数は行列の列方向を正則化(単位ベクトル化)し上書きします。

(progn (setq A #m((1 2 3)(6 5 4)))(nnormalize-columns A)(print-array A))

#<2x3 matrix>

#m(

( 0.164398983121  0.371390670538  0.600000023842 )

( 0.986393868923  0.928476691246  0.800000011921 ))

nremove-dc-from-columns関数は行列の列方向の平均値で各列を引き算します。

(progn (setq A #m((1 2 3)(6 5 4)))(nremove-dc-from-columns A)(print-array A))

#<2x3 matrix>

#m(

(-2.5 -1.5 -0.5 )

( 2.5  1.5  0.5 ))

 

svd関数は特異値を返します。

(progn (setq A #m((1 2 3)(6 5 4)))(setq B (svd A))(print-array B))

(#<2x1 matrix>)

(#m(

( 9.36192131042 )

( 1.83150732517 ))

)

特異値分解はUΣV*=Mとなります。Mは元の行列、Σは特異値からなる対角行列、Uは右特異ベクトルからなる単位ベクトルの行列、Vは左特異ベクトルからなる単位ベクトルの行列です。keyを指定することでUVが得られます。左特異ベクトルUを得るは:left-vectors tを追加します。

(progn (setq B (svd A :left-vectors t))(print-array B))

(#<2x1 matrix> #<2x2 matrix>)

(#m(

( 9.36192131042 )

( 1.83150732517 ))

 #m(

(-0.355380624533 -0.934721708298 )

(-0.934721708298  0.355380624533 ))

)

B(行列 行列)のリストになっています。左特異ベクトルだけを得るには以下のようにします。

(print-array (second B))

#<2x2 matrix>

#m(

(-0.355380624533 -0.934721708298 )

(-0.934721708298  0.355380624533 ))

単位行列になっているかどうかを確認します。

(matrix-element-sum (map-matrix (column  0 (second B)) #'sqr))

1.0000000596

(matrix-element-sum (map-matrix (column  1 (second B)) #'sqr))

1.0000000596

精度はこんなもんです。

右特異ベクトルVも求めてみます。

(progn (setq B (svd A :left-vectors t :right-vectors t))(print-array B))

(#<2x1 matrix> #<2x2 matrix> #<2x2 matrix>)(#<2x1 matrix> #<2x2 matrix> #<3x3 matrix>)

(#m(

( 9.36192131042 )

( 1.83150732517 ))

 #m(

(-0.355380624533 -0.934721708298 )

(-0.934721708298  0.355380624533 ))

 #m(

(-0.637017786503 -0.575135052204 -0.513252317905 )

( 0.653866827488 -0.0505269505084 -0.754920721054 )

( 0.408248484135 -0.816496610641  0.408248215914 ))

)

こちらも単位行列になっているかどうか検算してみます。

(matrix-element-sum (map-matrix (column 0 (third B)) #'sqr))

1.00000031292

(matrix-element-sum (map-matrix (column 1 (third B)) #'sqr))

0.999999986496

(matrix-element-sum (map-matrix (column 2 (third B)) #'sqr))

0.999999836087

特異値の行列を2×3の対角行列にします。

(progn (setq S (matrix (list(list(aref (first B) 0 0) 0 0)(list 0 (aref (first B) 1 0)0))))(print-array S))

#<2x3 matrix>

#m(

( 9.36192131042  0.0  0.0 )

( 0.0  1.83150732517  0.0 ))

3つの行列を掛け合わせてみます。

(print-array (* (second B) S  (third B)))

#<2x3 matrix>

#m(

( 1.0  2.0  3.00000023842 )

( 6.00000047684  5.0  3.99999952316 ))

svd関数に似た関数でorthogonalize関数というのがあります。返り値が2つあります。

(orthogonalize A)

#<2x3 matrix> #<2x1 matrix>

返り値2つをmultiple-value-list関数で確保してみてみます。

(print-array (multiple-value-list (orthogonalize A)))

(#<2x3 matrix> #<2x1 matrix>)

(#m(

(-0.637017786503 -0.575135052204 -0.513252317905 )

( 0.653866827488 -0.0505269505084 -0.754920721054 ))

 #m(

( 9.36192131042 )

( 1.83150732517 ))

)

最後の返り値はsvd関数の最初の返り値です。

最初の返り値はsvd関数の右特異ベクトルの一部のようです。

(print-array (truncate-rows (third B) 2))

#<2x3 matrix>

#m(

(-0.637017786503 -0.575135052204 -0.513252317905 )

( 0.653866827488 -0.0505269505084 -0.754920721054 ))

(progn (setq C (orthogonalize A))(print-array (- (truncate-rows (third B) 2) C)))

#<2x3 matrix>

#m(

( 0.0  0.0  0.0 )

( 0.0  0.0  0.0 ))

因みに行列にはequal関数は使えません。

(equal #m((0.0)) #m((0.0)))

nil

projection関数というのがあります。describeで調べるとprojection operator云々と書いてあります。

(print-array (multiple-value-list (projection A)))

(#<2x2 matrix> #<2x1 matrix>)

(#m(

( 0.999999940395 -1.60434424856e-07 )

(-1.60434424856e-07  1.00000011921 ))

 #m(

( 9.36192131042 )

( 1.83150732517 ))

)

projection関数は返り値2つですのでmultiple-value-list関数を使っています。射影空間のこと?最初の返り値はなんか誤差付きの単位行列のようです。最後の返り値は特異値。射影空間はと書いてあったので最初の返り値になるかどうか見てみました。

(print-array (* A (inverse(* (transpose A)A ) :if-singular :accept) (transpose A)))

#<2x2 matrix>

#m(

( 0.999999821186  2.38418579102e-07 )

( 2.08616256714e-07  0.999999761581 ))

inverse関数、そのままだとnilになってしまいますので、:if-singular :acceptとしています。MATLABpinv関数に相当します。あってないのか誤差の範囲なのかよくわかりません。

complement-projectionという関数もあります。たぶん単位行列とprojection関数の結果の差分を見ています。

(print-array (multiple-value-list (complement-projection A)))

(#<2x2 matrix> #<2x1 matrix>)

(#m(

( 5.96046447754e-08 -1.60434424856e-07 )

(-1.60434424856e-07 -1.19209289551e-07 ))

 #m(

( 9.36192131042 )

( 1.83150732517 ))

)

(print-array (+ (projection A)(complement-projection A)))

#<2x2 matrix>

#m(

( 1.0 -3.20868849712e-07 )

(-3.20868849712e-07  1.0 ))

 

LSPファイルの作成

いちいちコマンドラインで操作するのは面倒です。ファイルに記述してファイルを読むことでプログラムを実行させてみます。

端末の中に開くを選択しterminalを開きます。

テキストエディタを起動しtest.lspファイルを作成します。

以下のようなコードを作成し、保存します。MATLABだと

B=sin(linspace(0,2,91)*pi);

1行で書ける内容です。

もしEmacsがあれば、こっちを使った方がいいです。ただしショートカットキーに癖があり、慣れるのに時間がかかります。

EmacsGUIの説明は・・・省略して、端末から開きます。

Lispという項目がありますが、mjk LISP用ではないので使いません。

(setq A (ruler-vector 0 2 91)

(nmap-matrix A ‘* pi)

(nmap-matrix A ‘sin)

(ntranspose A)

デスクトップにtest.lspファイルが作成されます。

作成したtest.lspを読み込んで実行します。

(load “/home/neurosurgery/test.lsp”)

t

定義した行列Aを見てみます。

(print-array A)

#<1x91 matrix>

Graph termです。

#m(

( 0.0  0.0697564706206  0.139173105359  0.20791170001  0.275637358427  0.342020124197  0.406736642122  0.46947157383  0.529919266701  0.587785243988  0.642787575722  0.694658339024  0.743144869804  0.788010716438  0.829037547112  0.866025447845  0.898794054985  0.927183866501  0.951056480408  0.970295727253  0.984807729721  0.994521915913  0.99939084053  0.99939084053  0.994521915913  0.984807729721  0.970295727253  0.951056540012  0.927183866501  0.898794054985  0.866025388241  0.829037606716  0.788010776043  0.743144929409  0.694658458233  0.642787635326  0.587785363197  0.529919326305  0.46947157383  0.406736791134  0.342020213604  0.275637596846  0.207911849022  0.139173179865  0.0697567090392  1.50995802528e-07 -0.0697564035654 -0.139173343778 -0.207912012935 -0.275637984276 -0.342020839453 -0.40673738718 -0.469472557306 -0.52992027998 -0.587786495686 -0.642788827419 -0.694659769535 -0.743146181107 -0.788012206554 -0.829038918018 -0.866026639938 -0.898795187473 -0.927185058594 -0.951057493687 -0.970296502113 -0.984808325768 -0.99452227354 -0.99939095974 -0.999390661716 -0.994521439075 -0.984807014465 -0.970294654369 -0.951054990292 -0.927181959152 -0.898791849613 -0.866022825241 -0.829034626484 -0.788007199764 -0.743140876293 -0.694654107094 -0.642782986164 -0.587780296803 -0.529913604259 -0.469465583563 -0.406730383635 -0.342013627291 -0.275630623102 -0.207904294133 -0.139165535569 -0.0697487667203  7.80424034019e-06 ))

matrix-element-sum関数で合計をとってみます。ゼロが理想なのですが、精度が32bit浮動小数点なので小数点第5桁になっています。

(matrix-element-sum A)

7.89816209732e-05

 

関数の作成

test.lspファイルに関数を追加で書き込みながら、その都度(load “/home/neurosurgery/test.lsp”)を実行するということにします。

が、いちいち、(load云々と入力するのが面倒なので以下の関数を作成します。

 

 

Emacsだと予約された関数とか、記号、文字列が色分け表示されます。

(defun loadtest()

  "automation of (load \"/home/neurosurgery/test.lsp\")"

  (load "/home/neurosurgery/test.lsp")

)

Graph側で

(loadtest)

t

となればOKです。

 

書き方ですが

(defun 関数(引数・・・)

  “Usage (関数名 引数・・・)

   説明

  (実行文)

)

といった書式になります。

 

尚、カッコの位置ですが、Matti J Kajola氏は

(実行文

  (実行文

    )

  )

といった書き方を好まれるようです。

 

以下の関数func1を作成します。

(defun func1()

  "display  \"Hello\" on the console screen"

  (format nil "Hello")

)

(progn (loadtest)(func1))

“Hello”

describe関数を使うと注釈文を見ることができます。注釈文の「」は\”でコードしています。

(describe ‘func1)

nil

 

以下の関数func2を作成します。

(defun func2(x)

  " f(x)=x^2+3x-2"

  (+ (* x x)(* x 3) 2)

)

(func 1)を実行します。

(progn (loadtest)(func2 1))

6

関数の中身はsymbol-function関数を使います。

(symbol-function ‘関数名)または(symbol-function (function 関数名))という構文をとります。

(symbol-function (function func2))

(lambda (x) (+ (* x x) (* x 3) 2))

defunでなく、lambdaという名前になっていますが気にしないでください。

もともと組み込まれている関数の中身は見ることはできません。

(symbol-function (function rand))

#<Pascal: rand>

rand関数はコンピュータ言語Pascalを使って作成compileされたのかしらん・・・

compileされた関数かどうかはcompiled-function-p関数を使います。

(compiled-function-p (function rand))

t

この値がtだとsymbol-functionで関数の中身を見ることはできません。describeで注釈文を見るだけで我慢します。

 

func2の引数を忘れたら・・・エラーになります。

(func2)

初期値を0と設定します。&optionaを使います。l

(defun func3(&optional (x 0))

  " f(x)=x^2+3x-2"

  (func2 x)

)

(progn (loadtest)(func3 0))

2

(func3 0)

2

引数が多いときは・・・エラーになります。

(func3 0 2)

 

&restを使って余分な引数があるときにはリストにして値をそのまま追加することにしました。これでエラーが出なくなりました。

(defun func4(&optional (x 0) &rest y)

  " f(x)=x^2+3x-2"

  (if (null y)

    (func2 x)

    (if (listp y)

      (cons (func2 x)y)

      (cons (func2 x)'(y))

    )

  )

)

(progn (loadtest)(func4 0 2))

(2 2)

 

以下のようなfunc5という関数を作成しました。xy2つの引数があります。xの初期値は0yの初期値は0で、:x :yとすることでxyの順番を入れ替えてもOKです。この&keyというのはよく使われる方法です。

(defun func5(&key (x 0)(y 0))

  "f(x,y)=x^2+2xy+y^2"

  (+ (* x x)(* x y 2)(* y y))

)

(progn (loadtest)(func5 :x 2 :y 3))

25

(list (func5)(func5 :x 3)(func5 :y 3 :x 2))

(0 9 25)

 

 

FizzBuzz

以下のような/home/neurosurgery/test.lspファイルを作成します。ファイル名は何でもいいです。require パス+ファイル名で読み込みます。

FizzBuzz3の倍数ならFizz5の倍数ならBuzz15の倍数ならFizzBuzz、それ以外は数字を返します。

if関数を使うFizzBuzz1関数とcond関数を使うFizzBuzz2関数を作成しました。

x0number-1の整数です。

(defun FizzBuzz1(x)

  "FizzBuzz using if

   Usage (FizzBuzz1 integer)

   This function returns

   \"Fizz\" if multiple of 3,

   \"Buzz\" if multiple of 5,

   \"FizzBuzz\" if multiple 15,

   otherwise returns a number"

  (if (zerop (mod x 15))

    '"FizzBuzz"

    (if (zerop (mod x 3))

      '"Fizz"

      (if (zerop (mod x 5))

        '"Buzz"

              x

      )

    )

  )

)

 

(defun FizzBuzz2(x)

  "FizzBuzz using cond

   Usage (FizzBuzz2 integer)

   This function returns

   'Fizz' if multiple of 3,

   'Buzz' if multiple of 5,

   'FizzBuzz' if multiple 15,

   otherwise returns a number"

  (cond

    ((zerop (mod x 15))(quote "FizzBuzz"))

    ((zerop (mod x 3))(quote "Fizz"))

    ((zerop (mod x 5))(quote "Buzz"))

    (t x)

  )

)

 

実行します。

(progn (loadtest)(FizzBuzz1 1))

1

(list (FizzBuzz1 3)(FizzBuzz1 5)(FizzBuzz1 15)(FizzBuzz1 29))

("Fizz" "Buzz" "FizzBuzz" 29)

(list (FizzBuzz2 15)(FizzBuzz2 17)(FizzBuzz2 33)(FizzBuzz2 100))

("FizzBuzz" 17 "Fizz" "Buzz")

 

入力していくと間違えやすくなります。エディターで下図のように入力して、行を選択し、

Graphのコンソール上でマウスの真ん中ボタン(ホイール)をクリックしてコピーしたほうが楽です。

Enterキーを押すと実行されます。

(list

  (FizzBuzz2 15)

  (FizzBuzz2 17)

  (FizzBuzz2 33)

  (FizzBuzz2 100)

)

("FizzBuzz" 17 "Fizz" "Buzz")

こっちのほうがわかりやすく見やすいですね。

 

 

注意 mjk LISPでは関数の返り値としてはreturn関数は使わずに、そのもの、もしくはeval関数や文字列ではformat関数、quote関数などを使ってください。

何も使わないとき

(setq a 5)

5

(defun x2(x)(* x 2))

x2

(setq a (x2 2))

4

a

4

return関数を使ったとき。なお、LISPによっては関数の定義エラーになるかもしれませんが、mjk LISPではOKです。

(setq a 5)

5

(defun x2(x)(return (* x 2)))

x2

(setq a (x2 2))

4

a

5

最後にsetqで定義したaの値が書き換わっていません。

 

ループ文

loop関数を使ったループ文を作成しました。

以下の関数のあるLSPファイルを作成しました。

(defun FizzBuzzLoop1(number)

  "FizzBuzz using loop, if and return"

  (let ((i 1)(j 0)(result nil))

    (loop

      (if (> i number)result)

      (setq j (FizzBuzz2 i))

      (setq result (append result (list j)))

      (setq i (1+ i))

    )

  )

)

(progn (loadtest)(FizzBuzzLoop1 15))

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

(setq a (FizzBuzzLoop1 15))

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

a

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

因みにijresultlet関数で定義した局所変数なので関数の外から値を知ることはできません。

 

while関数を使ったループ文です。なお、while関数のないLISPもあり、mjk LISPwhile.lspというファイルで定義して使用しています。

(defun FizzBuzzLoop2(number)

  "FizzBuzz using while"

  (let ((i 1)(result nil))

    (while (<= i number)

      (setq result (append result (list (FizzBuzz2 i))))

      (setq i (1+ i))

    )

    result

  )

)

(progn (loadtest)(FizzBuzzLoop2 15))

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

 

do関数を使ったループ分です。

(defun FizzBuzzLoop3(number)

  "FizzBuzz using do "

  (do ((i 1 (1+ i))(result nil))

    ((> i number)result)

    (setq result (append result (list (FizzBuzz2 i))))

  )

)

(progn (loadtest)(FizzBuzzLoop3 15))

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

 

dotimes関数を使ったループ文です。dotimes0,1,2,3…と続きますので1+関数を使っています。

(defun FizzBuzzLoop4(number)

  "FizzBuzz using dotimes"

  (let ((result nil))

    (dotimes (i number result)

      (setq result (append result (list (FizzBuzz2 (1+ i)))))

    )

  )

)

(progn (loadtest)(FizzBuzzLoop4 15))

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

 

dolist関数を使ったループです。引数にリストが必要です。MakeAscendListという関数と作成し、dolist関数の中で使っています。

(defun MakeAscendList(x)

  "making (list 1,2,...x)"

  (let ((result nil))

    (dotimes (i x result)

      (setq result (append result (list (1+ i))))

    )

  )

)

 

(defun FizzBuzzLoop5(Number)

  "FizzBuzz using dolist"

  (let ((result nil))

    (dolist (i (MakeAscendList Number))

      (setq result (append result (list (FizzBuzz2 i))))

    )

    result

  )

)

(progn (loadtest)(MakeAscendList 15))

(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)

(FizzBuzzLoop5 15)

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

 

関数の作成2

関数の返り値は通常1つですが、複数にすることもできます。values関数を使います。単に返り値をリストにすればいいだけの気もしますが・・・

(defun foo(x y)(values (+ x y)(- x y)))

foo

(foo 2 3)

5 -1

残念ながらsetqでは最初の返り値しか代入されません。

(setq a (foo 2 3))

5 -1

a

5

この場合、mutiple-value-list関数を使います。

(setq a (multiple-value-list (foo 2 3)))

(5 -1) -1

a

(5 -1)

mjk LISPではどうもANSI Common LISPで使えるlambda関数が使えません。

Steel Blank Common Lispの場合です。ax+bのような計算で関数の中にlambdaを使っています。

* (defun listcall(xlist a b)(mapcar #'(lambda(x)(+ (* x a) b)) xlist))

LISTCALL

* (listcall '(1 2 3) 4 5)

(9 13 17)

mjk LISPの場合は

(defun listcal(xlist a b)(mapcar #'(lambda(x)(+ (* x a) b)) xlist))

listcal

(listcal '(1 2 3) 4 5)

でいけるはずなんですが、エラーが出ます。

(functionp #’listcal)

t

(symbol-function #’listcal)

(lambda (xlist a b) (mapcar (function (lambda (x) (+ (* x a) b))) xlist))

lambdaの中にlambdaがあるのが原因みたいです。

この場合defun関数の中にdefun関数を使って回避します。listcall関数の中にdefunを定義していますが、外でも構いません。

(defun listcal(xlist a b)

  (progn

    (defun func(x)(+ (* x a) b))

    (mapcar #'func xlist)

  )

)

(progn (loadtest)(listcal '(1 2 3) 4 5))

(9 13 17)

(symbol-function #’listcal)

(lambda (xlist a b) (progn (defun func (x) (+ (* x a) b)) (mapcar (function func) xlist)))

(symbol-function #’func)

(lambda (x) (+ (* x a) b))

 

関数の中に同じ関数を使うこともあります。再帰とよびます。以下の関数を作成しました。リスト内のリストを全部解消してひとつのリストにする関数です。flatten関数の中にflatten関数があります。

(defun flatten(x)

  (let ((result nil))

    (dolist (i x result)

      (if (atomp i)

              (setq result (append result (list i)))

        (setq result (append result (flatten i)))

      )

    )

  )

)

(progn (loadtest)(flatten '((1 2) 3 (4 (5) 6))))

(1 2 3 4 5 6)

 

defun関数に似てdefmacroという関数もあります。

(defmacro my-macro(x)(* x 2))

my-macro

(my-maro 3)

t

(symbol-function #'my-macro)

(macro (x) (* x 2))

これだけだとdefun関数と違はありません。引数がリストになると若干変わってきます。

(defun my-func(x)(apply #'+ x))

my-func

(my-func '(1 2 3 4))

10

(defmacro my-macro(x)(apply #'+ x))

my-macro

(my-macro (1 2 3 4))

10

my-funcの場合は

‘(1 2 3 4)(1 2 3 4)と評価して+をつけて(+ 1 2 3 4)としてさらに評価しています。

my-macroの場合は

素直に(1 2 3 4)+をつけて(+ 1 2 3 4)として評価しています。

 

mjk LISPでは対応していませんがdefmacroは本来backquotecommaを使って定義されています。

(defmacro マクロ名(引数)`(   ,    ,))といった感じで定義していきます。

Steel Blank Common Lispでは以下のようになります。

* (defmacro my-macro(a b)`(+ ,a (* ,b 3)))

MY-MACRO

* (my-macro 4 5)

19

 

 

パッケージ

引数やら関数をいろいろ定義していると重複したりして混乱することがあります。

そこでパッケージを使います。パッケージ名::をつけることで、パッケージ名::記号かパッケージ名::関数と区別します。

パッケージの作成はmake-package関数を使います。ANSI Common LISPdefpackage関数はありません。

またファイルを読み込んだ時、パッケージを使用するかどうかはin-package関数を使います。

(make-package "mjk")

(in-package "mjk")

(defvar a 0)

(defvar b 1)

(defun func()(print "mjk package"))

(loadtest)

t

(progn (setq a 10) a)

10

mjk::a

0

(progn (defun func()(format nil "Hello World"))(func))

“Hello World”

(mjk::func)

"mjk package"

パッケージ::が面倒な時はexport関数を使います。

(make-package "mjk")

(in-package "mjk")

(export '(a b func))

(defvar a 0)

(defvar b 1)

(defun func()(print "mjk package"))

(progn (loadtest)a)

エラーになります。この時点ではまだ使えません。import関数で使えるようにします。

(import '(mjk::a mjk::func))

t

(func)

"mjk package"

 

但し、この場合、記号や関数を再定義されると上書きされてしまいます。が、パッケージ名をつけても上書きされてしまった状態となります。

(progn (defun func()(format nil "Hello World"))(func))

“Hello World”

(mjk::func)

"Hello World"

(symbol-function (function mjk::func))

(lambda nil (format nil "Hello World"))

(progn (import ‘(mjk::func))(mjk::func))

"Hello World"

 

組み込まれているパッケージはlist-all-packages関数で確認できます。

(list-all-packages)

(#<util package> #<graph package> #<X package> #<keyword package> #<user package> #<system package> #<lisp package>)

 

ファイルに保存

ファイル操作はstreamを使います。標準では以下のstreamが用意されているそうです。

*standard-output*

#<Output stream: "stdout">

*standard-input*

#<Input stream: "stdin">

 

文字列データdataを用意します。

(setq data "abc~%def~%ghi")

"abc~%def~%ghi"

ファイル名を用意します。

(setq filename "/home/neurosurgery/sample.txt")

"/home/neurosurgery/sample.txt"

open関数を使って出力用ストリームfstreamを作成します。

(setq fstream (open filename :direction :output))

#<Output stream: "/home/neurosurgery/sample.txt">

fstreamにデータを書き込みます。

(format fstream data)

nil

fstreamを閉じます。

(close fstream)

nil

ファイル名にデータが書き込まれました。

abc

def

ghi

次は行列を保存します。3×5の行列です。

(setq A (/ (random-matrix 3 5)(pow 2 31))

#<3x5 matrix>

(print-array A)

#<3x5 matrix>

#m(

( 0.840187728405  0.798440039158  0.335222750902  0.553969979286  0.364784479141 )

( 0.394382923841  0.911647379398  0.768229603767  0.477397054434  0.513400912285 )

( 0.783099234104  0.197551369667  0.277774721384  0.628870904446  0.952229738235 ))

(setq fstream (open filename :direction :output))

そのファイル名は既にある、とエラーが出ました。

ファイル名はsample1.txtとします。

(setq filename "/home/neurosurgery/sample1.txt")

"/home/neurosurgery/sample1.txt"

(setq fstream (open filename :direction :output))

#<Output stream: "/home/neurosurgery/sample1.txt">

(format fstream A)

あらら。string、文字列でないとformat関数ではダメみたいです。print関数にします。

(print A fstream)

#<3x5 matrix>

(close fstream)

nil

保存したsample1.txtをみてみます。

 

#<3x5 matrix>

ダメですな。*print-array*tにすることで解決します。

ファイル名は同じとします。open関数に、:if-exists :supersedeを追加します。あとは一緒です。

(setq *print-array* t)

t

(setq fstream (open filename :direction :output :if-exists :supersede))

#<Output stream: "/home/neurosurgery/sample1.txt">

(print A fstream)

#m(

( 0.840187728405  0.798440039158  0.335222750902  0.553969979286  0.364784479141 )

( 0.394382923841  0.911647379398  0.768229603767  0.477397054434  0.513400912285 )

( 0.783099234104  0.197551369667  0.277774721384  0.628870904446  0.952229738235 ))

(close fstream)

nil

sample1.txtをみてみます。

 

#m(

( 0.840187728405  0.798440039158  0.335222750902  0.553969979286  0.364784479141 )

( 0.394382923841  0.911647379398  0.768229603767  0.477397054434  0.513400912285 )

( 0.783099234104  0.197551369667  0.277774721384  0.628870904446  0.952229738235 ))

#mとかカッコとか邪魔かもしれません。ナシにするにはprint関数ではなくprinc関数を使います。ファイル名をsample2.txtにします。

(setq filename "/home/neurosurgery/sample2.txt")

"/home/neurosurgery/sample2.txt"

(setq fstream (open filename :direction :output :if-exists :supersede))

:if-existが邪魔なようです。

(setq fstream (open filename :direction :output))

#<Output stream: "/home/neurosurgery/sample2.txt">

(princ fstream A)

#m(

( 0.840187728405  0.798440039158  0.335222750902  0.553969979286  0.364784479141 )

( 0.394382923841  0.911647379398  0.768229603767  0.477397054434  0.513400912285 )

( 0.783099234104  0.197551369667  0.277774721384  0.628870904446  0.952229738235 ))

(close fstream)

nil

sample2.txtをみてみます。

 

0.840187728405  0.798440039158  0.335222750902  0.553969979286  0.364784479141

 0.394382923841  0.911647379398  0.768229603767  0.477397054434  0.513400912285

 0.783099234104  0.197551369667  0.277774721384  0.628870904446  0.952229738235

10行、50列まではこれで対応できますが、それ以上の時は

(setq *print-level* (array-dimension A 0) *print-length* (array-dimension A 1))

などとしてください。