Lispの話(条件分岐)
こんにちは、たくさん寝太郎です。
やっと停電が治りました...。
住んでる地域の台風の被害が府内でも特に酷かったらしく、今でも市内では停電しているところがあるらしいです。私の家もベランダの屋根が吹き飛んだり瓦が飛んできたりと散々な有様です...。
とりあえず電気も復旧したので勉強再開します。今日はLand of Lisp第4章「条件と判断」の話です。
Common Lispでは条件式の評価において空のリストを偽として扱います。これによりリストを再帰で扱いやすくなります。
(defun my-length(list) (if list (1+ (my-length(cdr list))) 0))
上で定義したmy-lengthは引数にリストを与えるとcdrで先頭を落とす処理を再帰的に行うことでリストの長さを返します。
条件分岐
Lispでのifの構文は次のようになっています。
(if (条件節) then else)
thenかelseの内実際に評価されるのはどちらか一方のみです。(特殊形式)
試しに次のコードを実行してみましょう。
(if (= 0 1) (/ 1 0) (format t "0 is not 1"))
通常であれば、(/ 1 0)を実行しようとするとdivision errorを吐きますが上のコードでは後者のみが実行されるのでerrorは出ません。
ifの使い方の例としてランダムウォークを考えてみましょう。
(defun random-walk(n) (let ((point 0)) (dotimes (x n) (if (= 1 (random 2)) (setf point (1+ point)) (setf point (1- point)))) (print point)))
コインを投げて表(1)だった時は点の座標を正の方向に1ずらし、裏(0)だった時は負の方向に1ずらす試行をn回繰り返します。
(dolist (n '(10 100 10000 1000000)) (random-walk n)) 0 4 -10 52 NIL
n=10,100,10000,1000000の場合で実行すると上のように点の座標はそれぞれ0,4,-10,52となります。
(余談)
あまり処理系の話はしていませんでしたが、このコードをclispとsbclで走らせると処理速度にかなりの差が出ます。
$time clisp random_walk.lisp 0 0 -8 54 clisp random_walk.lisp 6.84s user 5.63s system 99% cpu 12.523 total $time sbcl --script random_walk.lisp 0 -6 -106 -786 sbcl --script random_walk.lisp 0.05s user 0.02s system 80% cpu 0.090 total
when, unless
(when (条件節) 式1 式2 ...) (unless (条件節) 式1 式2 ...)
whenは条件が真の時に、unlessは条件が偽の時に後述の式を全て実行します。
cond
(cond ((条件節1) 式1 ...) ((条件節2) 式2...) ... (t 式))
condフォームの条件は上から順に検査され、最初に条件を満たしたものの式を実行します。最後の条件をtとすることで他の全ての条件が満たされない時もコードが実行されることを保証します。
case
(case key (key_list1 式1 ...) (key_list2 式1 ...) ... (otherwise 式1 ...))
caseフォームは最初にkeyを受け取り、eqlコマンドでkey_listとの比較を行い等しい要素を見つけた場合にその節の式を実行します。condと同様、最後の条件にはotherwise(tでも可)を入れておきます。
and,or
(and (oddp 1) (oddp 3) (oddp 5)) (or (oddp 2) (oddp 3) (oddp 4))
oddpは引数が奇数のときtを返す関数です。
andは引数が全て真の時tを返し、orは引数のうちいずれか1つでも真であればtを返します。
and,orは条件判断にも使うことができます。次のコードを考えてみましょう。
(and (oddp 1) (oddp 2) (/ 1 0))
通常(/ 1 0)を実行するとdivision errorを吐きますが、上のコードを実行するとnilが返ってきます。andは引数の中で偽であるものが見つかった場合は後ろの式を評価せずnilを返しているということが分かります。
比較関数
eq,eql,equal,equalp,=,string-equal,char-equalなどなど多くの比較関数があります。
詳しくは省略*1しますが、eq < eql < equal < equalpの順で等しいと判定される範囲が広がっていきます。
例として次の2つのコードを考えてみましょう。
(equal 0.5 1/2) (equalp 0.5 1/2)
1つ目のコードはnilを返しますが2つ目のコードはtを返します。
ちなみに、Land of Lispの作者であるConrad Barskiは「シンボル同士は常にeqで比較し、それ以外ではequalを使うべきである」というルールを定めています。
以上で条件分岐の話は終わりです。
次回はデータの読み書きの話をしようと思います。