6.デバッグ法


(テキスト4.4)

今回の課題あたりから少し難しくなってきますので,デバッグのための関数を説
明しておきます.
trace という関数は
(trace関数名)
というように使います.trace で指定した関数の入出力(与えられた引き数の値,
その結果の値)をすべて教えてくれます.
現在
trace に指定されている関数は,引き数なしで
(trace)
とすれば分かります.
指定を止めるには
(untrace関数名)
とします.いつまでもtrace していると出力が長くなるので,必要がなくなった
ら,すぐに
untrace してください.特にいろいろな所で使う関数(たとえば,
second など)を指定した時は注意してください.
なお,「関数名」の部分には複数の関数を書くことも可能です.


では,p.17 で出てきたzero-in を以下のように少し変えて,わざとエラーが
出るようにして調べてみましょう.
(defun zero-in (l)
(cond ((zerop (first l)) t)
((endp l) nil)
(t (zero-in (rest l)))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(zero-in '(2 4 6 0 8))

T
USER>USER>(zero-in '(1 2 3 4))

Error: 20101 Type error. 'NIL' is not 'NUMBER'.
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


trace zero-in を指定して実行してみると,次のように,zero-in にどの
ような引き数が与えられて再帰的に呼ばれているかが分かります.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(trace zero-in)

(ZERO-IN)
USER>(zero-in '(1 2 3 4))

1> ZERO-IN : ARGUMENT = (1 2 3 4)

2> ZERO-IN : ARGUMENT = (2 3 4)

3> ZERO-IN : ARGUMENT = (3 4)

4> ZERO-IN : ARGUMENT = (4)

5> ZERO-IN : ARGUMENT = NIL
Error: 20101 Type error. 'NIL' is not 'NUMBER'.

- 19 -


┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


次に,zerop を指定してみると,zerop の引き数とその結果がよく分かります.
この
trace の結果,zerop NIL が引き数として与えられるのがエラーの原因
だと分かると思います.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(untrace zero-in)

(ZERO-IN)
USER>(trace zerop)

(ZEROP)
USER>(zero-in '(1 2 3 4))

1> ZEROP : ARGUMENT = 1

<1 ZEROP : RESULT = NIL

1> ZEROP : ARGUMENT = 2

<1 ZEROP : RESULT = NIL

1> ZEROP : ARGUMENT = 3

<1 ZEROP : RESULT = NIL

1> ZEROP : ARGUMENT = 4

<1 ZEROP : RESULT = NIL

1> ZEROP : ARGUMENT = NIL
Error: 20101 Type error. 'NIL' is not 'NUMBER'.
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


今度は,zero-in も一緒に指定すると,もっとよく分かると思います.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(trace zero-in)

(ZERO-IN)
USER>(trace)

(ZEROP ZERO-IN)
USER>(zero-in '(1 2 3 4))

1> ZERO-IN : ARGUMENT = (1 2 3 4)

2> ZEROP : ARGUMENT = 1

<2 ZEROP : RESULT = NIL

- 20 -


2> ZERO-IN : ARGUMENT = (2 3 4)

3> ZEROP : ARGUMENT = 2

<3 ZEROP : RESULT = NIL

3> ZERO-IN : ARGUMENT = (3 4)

4> ZEROP : ARGUMENT = 3

<4 ZEROP : RESULT = NIL

4> ZERO-IN : ARGUMENT = (4)

5> ZEROP : ARGUMENT = 4

<5 ZEROP : RESULT = NIL

5> ZERO-IN : ARGUMENT = NIL

6> ZEROP : ARGUMENT = NIL
Error: 20101 Type error. 'NIL' is not 'NUMBER'.
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


さらに,もう1つのデバッグのためのツールであるstep を簡単に説明します.
これは名前の通り関数の呼び出し,評価を1ステップ毎に表示してくれるものです.
表示の仕方はシステムによっていろいろです.
使い方は
(step調べたい式)
です.trace のように調べたい関数名を書くのではなく,動きを調べたい関数を引
き数を含めて普通に
Lisp に実行させるように書きます.


例では,以下のような絶対値の関数my-abs の動きを調べています.
(defun my-abs (x) (if (< 0 x) x (- x)))
trace
だと(trace my-abs) ですが,step では(step (my-abs -1))
す.
(my-abs -1)は「-1」の絶対値を求める普通の式になっています.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(step (my-abs -1))

1:(MY-ABS -1)
STEP>

2:-1
2:= -1
2:(BLOCK MY-ABS (IF (< 0 X) X (\- X)))
STEP>

3:(IF (< 0 X) X (\- X))
STEP>

- 21 -


4:(< 0 X)
STEP>

5:0
5:= 0
5:X
5:= -1
4:= NIL
4:(\- X)
STEP>

5:X
5:= -1
4:= 1
3:= 1
2:= 1
1:= 1

1
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


出力の意味は,たとえば,中ほどの部分だと,
3:(IF (< 0 X) X (\- X))この式の値を求めるには,まず
4:(< 0 X)条件の式(< 0 X) の値が問題
5:0<」の第1引き数は「0」で,
5:= 0その値は0
5:X
<」の第2引き数は「X」で,
5:= -1その値は-1
4:= NIL
したがって,4:(< 0 X) の値はNIL
のようになっています.このようにすべての式の引き数がどうなって,その結果ど
のような値が返されたのかを教えてくれます.


これでバグを見つけるには,人間の方もステップ毎にこれで正しいのかどうかよ
く見なければいけません.また,どのあたりがおかしいか見当がついていないとな
かなか難しいでしょう.
step の中で使えるコマンドは以下のようにいくつかありますが,最初のうちは
「リターン」と途中で止める「
q」ぐらいで十分でしょう.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STEP>?
nnewlineadvances to the next form.
s [N]skips the form.
ppretty-prints the form.
f symbol [n]skips until the function is called.
ggoes up to the enclosing form.
e formevalutes the form.
r formevalutes the form and returns the values.
bprint back trace.
<break command>execute the break command.
qquits the stteper.
? print this.

- 22 -


┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
提出課題


今回の問題では,再帰的な関数のプログラミングについてみっちりやってもらい
ます.
max numberp は今回が初めてですが,新しいことには何もいりません.
すべてこれまでの内容で作ることができます.
また,難しい問題は,全体を1つの関数で作ろうとせずに,いくつかに分けて作
るのがポイントです.「困難は分割せよ」です.


19
入れ子になっていない数字のリストの中の0 の数を数える関数count-zeros
を作ってください.
(count-zeros '(1 0 2 3 0 4)) -> 2
ファイル名~/Lisp/Report/report19.lisp


20
2つの整数m, n m <= n とします)を与えたとき,m 以上n 以下の整数を
並べたリストを作る関数
numlist を作ってください.ただし,再帰的な関数とし
て作ること.
(numlist 3 9) -> (3 4 5 6 7 8 9)
(numlist 1 30) -> (1 2 3 4 5 6 7 8 9 10 ...)
(システムの設定により,長いリストは途中までしか出力されません.)
ファイル名
~/Lisp/Report/report20.lisp


21
数を要素とする(入れ子になっていない)リストに対してその中で1番大きい数
を答える関数
max-in-flist を作ってください.ただし,空のリスト(nil)の
時の値は
-999999 とします.(したがって,要素の中に-999999 より小さ
いものがあるとややこしいので,そのようなリストは入力しないとします.)なお,
max という組み込み関数を使ってもいいです.(max 2 3 5 2 6) -> 6 のように
使います.引き数の個数は何個でも構いません.問題の方は引き数がリストという
条件になっていることに注意してください.
(max-in-flist '(2 4 65 2 5 7)) -> 65
(max-in-flist '()) -> -999999
ファイル名~/Lisp/Report/report21.lisp


22
21 の拡張で,数を要素とするリストが入れ子になっていても,その中で1番大
きい数を答える関数
max-in-list を作ってください.その他は21 と同じ設定
です.
(max-in-list '(1 2 (3 1 (4) ((6) 9)))) -> 9
(max-in-list '(2 4 65 2 5 7)) -> 65
(max-in-list '()) -> -999999
ファイル名~/Lisp/Report/report22.lisp


23
2番目の引き数の(入れ子になっていない)リストに,1番目の引き数と同じ要
素が含まれているかどうかを判定する関数
my-f-member を作ってください.
(my-f-member 'a '(b c d a e)) -> T
(my-f-member 'a '(b c d e)) -> NIL
ファイル名~/Lisp/Report/report23.lisp


24

- 23 -


23 と同じですが,リストが入れ子になっていてもに,1番目の要素と同じ要素
が含まれているかどうか,入れ子になっている所もすべて探して判定する関数
my-
member
を作ってください.
(my-member 'a '(b (c d b) (a b) (c d e))) -> T
(my-member 'a '(b (c d b) (c b) (c d e))) -> NIL
ファイル名~/Lisp/Report/report24.lisp


25
2つのリストを受け取り,2番目のリストから1番目のリストに現れる要素を(入
れ子になっている所もすべてを探して)すべて取り除いたリストを作る関数
my-
delete
を作ってください.ただし,1番目のリストは入れ子になっていないフラッ
トなリストとします.
一度に考えるのは難しいでしょうから,下請けの関数を考えたり,
23 24
ように段階的に難しくしていくことを考えてみてください.
(my-delete '(a b) '(b a b (c d b) ((b a)) (c d e)))
-> ((C D) (NIL) (C D E))
(1重の空のリストはNIL と表示されます.)
ファイル名
~/Lisp/Report/report25.lisp


26
以下のような数式の値を計算する関数value を作ってください.
ここでの数式の再帰的な定義は,
演算は「
+」だけ,
1)数字は「数式」
2)
(数式+ 数式) は「数式」
これを繰り返してできるものが「数式」です.
つまり,「数式」とは
1234
(12 + 3)
((1 + 2) + 3)
(0 + ((1 + 2) + (3 + 4)))
などです.
したがって,
(1 + 2 + 3 + 4) などは「数式」の条件に当てはまりません.
これらの数式に対して
(value '(0 + ((1 + 2) + (3 + 4)))) --> 10
となるような関数がvalue です.
必要なら,数字かどうかを判定するには,
numberp を使ってください.
(numberp 12345) --> T
(numberp '(a b c)) --> NIL
ファイル名~/Lisp/Report/report26.lisp


27
数字のリストの要素を大きい順に並びかえたリストを作る関数sortlist を作っ
てください.
(sortlist '(3 -8 0 2 18 -4)) -> (18 3 2 0 -4 -8)
ファイル名~/Lisp/Report/report27.lisp


28
リストの中の括弧を取り去って,「入れ子」になっていないようにする関数
make-flat を作りなさい.
(make-flat '(a b (c ((d))) e)) -> (A B C D E)
ファイル名~/Lisp/Report/report28.lisp


29

- 24 -


パスカルの3角形の各行を次々に作る関数binominal を作ってください.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1

ファイル名
~/Lisp/Report/report29.lisp
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(binominal '(1))

(1 1)
USER>(binominal '(1 1))

(1 2 1)
USER>(binominal '(1 2 1))

(1 3 3 1)
USER>(binominal '(1 3 3 1))

(1 4 6 4 1)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
研究課題


課題26 で「-, *」も含んだ,同じような数式について拡張してください.

7.変数


(テキスト1.6, 5.3, 5.5)

今回は変数を扱いたいと思います.Cでも習ったように変数にはローカルなもの
(局所変数)とグローバルなもの(大域変数)があります.ローカルな変数は関数
の中だけに現れるものです.
簡単な例としては,
(defun jijyou (n)
(* n n))
という関数を作ったとします.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(jijyou 5)

25
USER>n

Error: 20105 Variable 'N' is unbound.
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


当然のことですが,関数の外(「USER>」の出ている状態,Lisp の環境)では,
変数
n は参照することはできません.


変数への代入「setf
変数に値を代入するには,
setf を使います.
使い方は,
(setf変数) です.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- 25 -


USER>(setf jr-stations '(kyoto yamashina otsu zeze))

(KYOTO YAMASHINA OTSU ZEZE)
USER>jr-stations

(KYOTO YAMASHINA OTSU ZEZE)
USER>(length jr-stations)

4
USER>(setf start-station (first jr-stations))

KYOTO
USER>(setf new-jr-stations
(append jr-stations '(ishiyama seta)))

(KYOTO YAMASHINA OTSU ZEZE ISHIYAMA SETA)
USER>new-jr-stations

(KYOTO YAMASHINA OTSU ZEZE ISHIYAMA SETA)
USER>(setf end-station (nth 5 new-jr-stations))

SETA
USER>(list start-station end-station)

(KYOTO SETA)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


setf setq
変数への代入には伝統的にsetq を使ってきましたが,現在ではsetf を使い
ます.古い本(テキストも,原本は
1987年)などではsetq が主でsetf は特別
な場合に使うように書いてあります.しかし,
setq を拡張したのがsetf でい
ろいろな所で使えるので,今では区別せずに
setf を使います.setq は単に歴史
的な意味で存在しているだけです.(他の本などのプログラムを読んだりするとき
には思い出す必要があるでしょう.)


たとえば,setf にはsetq ではできない次のような使い方ができます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>new-jr-stations

(KYOTO YAMASHINA OTSU ZEZE ISHIYAMA SETA)
USER>(setf (first new-jr-stations) 'nishiotsu)

NISHIOTSU
USER>new-jr-stations

(NISHIOTSU YAMASHINA OTSU ZEZE ISHIYAMA SETA)
USER>(setf (third new-jr-stations) 'karasaki)

KARASAKI
USER>new-jr-stations

- 26 -



(NISHIOTSU YAMASHINA KARASAKI ZEZE ISHIYAMA SETA)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


変数は値を入れる場所と考えることができます.上の例では,setf の最初に引
き数はそれぞれ「
new-jr-stations の1番目の場所」,「new-jr-stations
の3番目の場所」になっていて,その場所に値を入れています.(「KYOTO」とい
う変数に「
nishiotsu」という値を入れようとしているのではありません.)setq
ではこのようなことはできません.
したがって,
setf の使い方は
(setf場所)
というのがより正しいいい方で,「場所」の所に「変数」が来れば普通の代入にな
ります.


setf を使った関数の例
(defun next-int (n)
(setf n (1+ n))
(list 'next 'integer 'is n))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(next-int 8)

(NEXT INTEGER IS 9)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


関数本体に複数の式を書く方法
上の例で分かるように,
(defun関数名(引き数)
式1
式2

式n
)
の形で,複数の式を書くことができます.


グローバル変数を使った例
(defun counter ()
(setf *my-count-value*
(1+ *my-count-value*)))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(setf *my-count-value* 1)

1
USER>(counter)

2
USER>(counter)

3
USER>*my-count-value*

3
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- 27 -


グローバル変数は両端を「*」で囲む習慣があります.しかし,「*」で囲んだか
らといって自動的にグローバル変数になるわけではありません.


グローバル変数の宣言
上の例の
counter をファイルにして,それをロードしただけではうまく動きま
せん.動かす前に,
(setf *my-count-value* 1)を実行する必要があります.
そこで1つのプログラムとしてグローバル変数が必要なときはファイルの先頭に,
(defvar変数名初期値)
を書いておきます.初期値はなくてもかまいません.
つまり,
counter のプログラムのファイルは,
中味───────────────────
(defvar*my-count-value* 1)

(defun counter ()
(setf *my-count-value*
(1+ *my-count-value*)))
└────────────────────
となります.これでロードするだけで使えます.


Lisp システムのグローバル変数の例
Lisp システム自体で使っているグローバル変数があります.いろいろなLisp
で共通なものもありますし,ある特定の会社のLisp だけにあるようなものもあり
ます.共通なグローバル変数でも,システムによって標準で設定してある値は異な
ることがあります.


*print-length*
現在の値は10になっていると思います.(確認してください.)この値が,リス
トを どのく らいの長 さまで 出力す るかを設 定して います .(前回 の課題 で,
(numlist 1 20) などを試した人は経験したはずです.)整数を代入することに
よって変えることができます.また,値が
NIL だと長さ無制限になります.


*print-case*
現在のLisp の出力は,大文字で出力されています.これを設定している変数で
す.設定のできる値は,
:upcase, :downcase, :capitalize です.(コロン
:」を忘れないように.)


ローカル変数の宣言
関数の引き数以外にローカル変数が必要なら,
let を使います.使い方は,
(let ((変数1初期値1)
(
変数2初期値2)

(変数n初期値n))
式1
式2

式k
)
です.
当然
let で宣言した変数は,(let ...) の中だけで使えます.


使用例は,
(defun heikin (x y)
(let ((sum (+ x y)))

- 28 -


(list x 'to y 'no 'heikin 'wa (/ sum 2.0))))


(defun replace-second-to-two (l)
(let ((my-first (first l))
(my-second 'two)
(my-rest (rest (rest l))))
(append (list my-first my-second) my-rest)))
などです.


let のローカル変数の作られ方
let で宣言した変数はパラレル(同時)に作られます.言い換えると次のような
使い方はできません.(
w を作るときには,まだmax-val min-val も存在
していません.)
(defun size-range (x y z)
(let ((max-val (max x y z))
(min-val (min x y z))
(w (- max-val min-val)))
(list 'haba 'wa w)))
実際にどのようなエラーがでるか確認しておいてください.
(ここで初めて出てきた関数
min max の逆で,使い方は同じです.)


let* の使い方
ローカル変数を逐次的に(1個ずつ)作るには
let* を使います.
(defun size-range (x y z)
(let* ((max-val (max x y z))
(min-val (min x y z))
(w (- max-val min-val)))
(list 'haba 'wa w)))
こうすれば大丈夫です.


let, let* の変数の初期値の決め方
let, let* では変数の値を最初に決めなくても構いません.つまり,
(let ((変数1初期値1)

の「初期値の部分」は省くことができます.
(defun size-range (x y z)
(let ((max-val (max x y z))
(min-val)
(w))
(setf min-val (min x y z))
(setf w (- max-val min-val))
(list 'haba 'wa w)))

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
提出課題


今回の問題はとても簡単な関数をいくつか書いてもらいます.それぞれは本当に
簡単なものですが,全体で1つのゲームの真似事ができます.問題文でやるべきこ
とが完全に指定できていないかもしれませんが,最後のような対戦ができれば,後
は各自の発想に任せます.提出ファイルについては,後のほうに指定してあります.


トランプゲームのブラックジャックの真似事をするプログラムを作ってみましょ
う.本当のブラックジャックについては,周りの知っている人に聞いてください.
ここでは,トランプを1枚ずつ配り,数の合計を
21 以内でなるべく大きい数のと
きにストップをかけて,そのときの数の大小を争うことにします.
21 を超えたら
オーバーで負けになります.プレーヤーは人間(
user)と計算機(computer)と

- 29 -


します.


((13 6) 19) のような形のリストで,カード13」と「6」を持っていて,合
計は
19 であることを表すことにします.ゲームを始める前は,(() 0) のリスト
です.


1)グローバル変数*user-hand*, *computer-hand* を初期値(() 0) で宣
言する.


2)一度実行する度に,乱数でカードを引いて変数*user-hand* を更新する関数
user-next-card を作ってください.ただし,オーバーしたら次の例のように
出力してください.(乱数を使うので次の例と同じ数字が出るとは限りませ
ん.)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>*user-hand*

(NIL 0)
USER>(user-next-card)

((7) 7)
USER>(user-next-card)

((10 7) 17)(新しいカードから順に表示される)
USER>(user-next-card)

(((11 10 7) 28) OVER!!)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


3)user-next-card とまったく同じで,*computer-hand* を更新する関数
computer-next-card を作ってください.(user-next-card をコピーし
て,関数名,変数名を変えるだけ)


4)計算機の方は,カードを引くか,(次のカードを引くとオーバーしそうだから)
ここで止めるかを判断しないといけません.ここではごく簡単に次のようなア
ルゴリズムに従うとします.そのプログラムを
computer-next とします.
4-1) 現在の計算機の合計と人間の合計を比べて,負けていたらcomputer-
next-card
で1枚カードを引く.(このままでは,どうせ負けなの
でオーバーしてもいいからカードを引く.)
4-2) カードの合計で人間に勝っていて,現在の計算機の合計が16 を超えて
いたら(これ以上引いたらオーバーしそうなので)
STOP と出力する.
4-3) そうでなければcomputer-next-card で1枚カードを引く.


5)何度もやるとき便利なように,*user-hand*, *computer-hand* を初期値
(() 0) にセットする関数clear-hands を作ってください.


乱数の生成の方法
(random 正整数) で0以上,「正整数」未満の乱数(整数)を生成します.
(defun omikuji ()
(if (> (random 101) 70)
'daikichi
'shoukichi))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- 30 -


USER>(omikuji)

SHOUKICHI
USER>(omikuji)

DAIKICHI
USER>(omikuji)

SHOUKICHI
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


............................
対戦例
以下の例では,人間と計算機が交互にカードを引いています.人間は自分自身で
次を引くかどうかを判断し,計算機には
(computer-next) を実行して判断させ
ています.


(clear-hands)
-> (NIL 0)
(user-next-card)
-> ((8) 8)
(computer-next)
-> ((9) 9)
(user-next-card)
-> ((11 8) 19)
(computer-next)
-> ((3 9) 12)
(computer-next)
(人間は19になりパスしたので,計算機が続けて引く)
-> ((9 3 9) 21)(計算機の勝ち)

(clear-hands)
-> (NIL 0)
(user-next-card)
-> ((6) 6)
(computer-next)
-> ((12) 12)
(user-next-card)
-> ((13 6) 19)
(computer-next)
-> (((11 12) 23) OVER!!)


(clear-hands)
-> (NIL 0)
(user-next-card)
-> ((2) 2)
(computer-next)
-> ((2) 2)
(user-next-card)
-> ((4 2) 6)
(computer-next)
-> ((13 2) 15)
(user-next-card)
-> ((9 4 2) 15)
(computer-next)
-> ((4 13 2) 19)
(user-next-card)
-> (((8 9 4 2) 23) OVER!!)

(人間の勝ち)


(計算機の勝ち)


- 31 -


-> (clear-hands )
(NIL 0)
-> (user-next-card)
((8) 8)
-> (computer-next)
((9) 9)
-> (user-next-card)
((10 8) 18)
-> (computer-next)
((3 8) 11)
-> (computer-next)
(人間は18になりパスしたので,計算機が続けて引く)
((9 3 8) 20)
-> (computer-next)
STOP
(計算機の勝ち)
............................


30
前半1),2)を1つのファイルにして,提出してください.
ファイル名
~/Lisp/Report/report30.lisp


31
後半3)〜5)を1つのファイルにして,提出してください.
ファイル名
~/Lisp/Report/report31.lisp
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
研究課題


上のプログラムはブラックジャックのプログラムとしては非常に不完全で
す.
たとえば,
・カードを引くのをやめることを,はっきりと示していない.
(「
NO-MORE-CARD」などを表示したりしたほうが分かりやすい.)
・勝ち負けの判定を自動的にしていない.
・乱数を引いているだけなので,同じ数のカードが(トランプなのに)5枚以上来
る可能性がある.
・すでに出たカードによって残りの数字の出る確率が変化することがない.
・本当のブラックジャックは「
1(エース)」を1 以外に使えるが,そうしていな
い.
などの問題点があります.これらを改良する方法を考えてみてください.

8.関数の作用のさせ方


(テキスト10.5〜10.6)

リストの各要素に対して一度に何か行うということは,いろいろな所にでてきま
す.今までの問題でも,各要素に同じことをやる必要があって,1つ1つ順番にや
るのは面倒だなと感じていたと思います.


数学の例では,ベクトルを2倍の大きさにしたり,平行移動したりすることにあ
たります.
たとえば,

IMAGE image/pro-pri201.gif

上の例ではベクトルの各成分に,2倍する関数「2x」や,3を加える関数「x
を作用させているわけです.

- 32 -


リストの各要素に関数を作用させるような場面はよくあるので,Lisp にはいろ
いな方法が用意してあります.つまり,いろいろな作用のさせ方があります.作用
させるほうの関数は,
Lisp に最初から組み込んである関数(+,first,...)や
defun で作った関数が使えます.それ以外に,上のベクトルの例のような「2x
や「
x+3」のような関数も使えます.


lambda 関数
lambda(ラムダ)関数とは「名前のない関数」のことです.数学では,よく関数
x2とかいって,f(x) = x2のように関数の名前「f」をつけないで使うことがあ
ります.これと同じことで,プログラムの他の所では使わない関数に,いちいち名
前をつける必要はないし,つけなくても困らないならその方が手間がかかりません.
また,1箇所でしか使われない小さな関数が,ファイルにずらずら並ぶのは格好悪
いし,プログラムの重要な所がぼやけてしまうこともあります.逆にいえば,名前
がないのでプログラムの他の所で呼ぶ(使うこと)はできません.したがって,再
帰的な呼び出しには利用できません.


数学の名前なし関数---------> x2
Lisp defun による関数--> (defun jijyou (x) (* x x))
Lisp
lambda による関数--> (lambda(x) (* x x))
つまり,lambda 関数は,defun の代わりにlambda を書いて,関数名を消し
たらでき上がりです.


次の例は,上で説明したlambda 関数(lambda (x) (* x x))を使って32
= 9 を計算しています.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>((lambda (x) (* x x)) 3)

9
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


さて,関数の作用のさせ方はいろいろあります.


funcall
まず,関数を扱う関数(関数を引き数にする関数)の最初はfuncall です.関
数を関数の引き数にするには,「
#'シャープクオート」を付けます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(funcall #'list 'a 'b 'c)

(A B C)
USER>(setf fn #'list)

#<Function LIST #x004CBD78>
USER>(funcall fn 'a 'b 'c 'd)

(A B C D)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


全然ありがたみのない,関数をダイレクトに使うより,まどろっこしいように見
えます.実は,
funcall は関数をパラメータにして関数に渡して,それによって
いろいろなことを,1つの関数でまとめて行いたいときなどに使います.(もう少

- 33 -


し後で使うことになると思います.)
簡単な例は,次のようなものです.
(defun list-func (l op)
(funcall op l))

(list-func '(a b c d) #'first) -> A
(list-func '(a b c d) #'length) -> 4

mapcar
最初のベクトルの例と同じタイプです.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(mapcar #'1+ '(1 2 3 4))

(2 3 4 5)
USER>(defun double (x) (* x 2))

DOUBLE
USER>(mapcar #'double '(1 2 3 4))

(2 4 6 8)
USER>(mapcar #'double '( ))

NIL
USER>(mapcar #'(lambda (x) (* x x)) '(1 2 3 4 5))

(1 4 9 16 25)
USER>(mapcar #'= '(1 2 3) '(3 2 1))

(NIL T NIL)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


最後の例のような使い方もできます.(どういうことを行っているかよく考えて
ください.)


リストnumbers を考えます.数字の英語,日本語の対応表になっています.
(setf numbers
'((one ichi)
(two ni)
(three san)
(four yon)
(five go)))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(mapcar #'first numbers)

(ONE TWO THREE FOUR FIVE)
USER>(mapcar #'second numbers)

(ICHI NI SAN YON GO)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


今回の課題35 find-table を使って
(defun translate (n)
(second (find-table n numbers)))

- 34 -


を作ると,次のように英語による数字のリストの日本語訳のリストを作ることがで
きます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(mapcar #'translate '(two two four))

(NI NI YON)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


条件を満たすものだけについて何かするための関数
find-if
論理関数を指定して,それを満たす(T になる)最初の要素を返す関数です.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(find-if #'numberp '(one is 1 and two is 2))

1
USER>(find-if #'(lambda (x) (> x 10)) '(1 -3 20 -6 12))

20
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


remove-if
条件を満たすものを,すべて取り除きます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(remove-if #'numberp '(one is 1 and two is 2))

(ONE IS AND TWO IS)
USER>(remove-if #'(lambda (x) (> x 10)) '(1 -3 20 -6 12))

(1 -3 -6)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


remove-if-not
条件を満たさないものを,すべて取り除きます.つまり条件を満たすものだけの
リストを作ります.
remove-if より使いやすく,役に立つ関数です.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(remove-if-not #'numberp '(one is 1 and two is 2))

(1 2)
USER>(remove-if-not #'(lambda (x) (> x 10))
'(1 -3 20 -6 12))

(20 12)
USER>(remove-if-not #'evenp '(1 3 5 7 9 11))

NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


たとえば,数字のリストの中の0 の数を数える関数はremove-if-not を使う
と簡単に作ることができます.試してみてください.使わない場合は課題
19 でや
りました.
(defun count-zeros (l)
(length (remove-if-not #'zerop l)))
- 35 -


リストをまとめて,1個にしてしまう方法
reduce は,指定された関数で与えられたリストの要素を次々に計算して,1つ
にしてしまいます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(reduce #'+ '(1 2 3 4 5))

15
USER>(reduce #'+ '( ))

0
USER>(reduce #'append '((one ichi) (two ni) (three san)))

(ONE ICHI TWO NI THREE SAN)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


リストの各要素が条件を満たしているかどうか調べる方法
every はリストのすべての要素が,指定された条件を満たしていればT,そう
でなければ
NIL を返します.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(every #'numberp '(1 2 3 4 5))

T
USER>(every #'numberp '(1 one 2 two))

NIL
USER>(every #'> '(10 20 30 40) '(1 2 3 4))

T
USER>(every #'(lambda (x y) (= x (* y 10)))
'(10 22 30 40) '(1 2 3 4))

NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


後半の例のように,every は複数のリストについても使うことができます.つま
り,各要素同士が条件を満たしているかどうかが調べられます.


今回の関数を使ったプログラムの例
(1 23 45 67) -> (1 13 25 37) のように,リストの中の偶数だけ2
で割ったものにする関数を作ってみましょう.


まず,偶数だったら2で割る関数は,
(defun waru2 (x)
(if (evenp x)
(/ x 2)
x))
です.
この関数をリストの要素にそれぞれ作用させたらいいので,目的の関数は,
(defun list-waru2 (l)
(mapcar #'waru2 l))
となります.

- 36 -


また,lambda を使って1つにまとめると
(defun list-waru2 (l)
(mapcar #'(lambda (x)
(if (evenp x)
(/ x 2)
x))
l))
となります.


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
提出課題


今回の課題は,今回出てきた関数を使うと,どれも簡単に作れます.逆に使わな
いと結構難しくなります.


32
up down を要素とするリストに対してup down を入れ替えたリストを
返す関数
up-down を作ってください.
(up-down '(up up down up down)) -> (DOWN DOWN UP DOWN UP)
ファイル名~/Lisp/Report/report32.lisp


33
リストの要素を最初から調べて,最初の空でないリスト(NIL でないリスト)を
返す関数
find-nested を作ってください.
(find-nested '(a () (b (c d)) (e) f g)) -> (B (C D))
ファイル名~/Lisp/Report/report33.lisp


34
リストの要素が全部同じものかどうかを判定する関数all-equal を作ってくだ
さい.
(all-equal '(a a a a)) -> T
(all-equal '(a a b a)) -> NIL
ファイル名~/Lisp/Report/report34.lisp


35
次のような表を表すリストmy-table を考えます.
(setf my-table
'((shiga otsu)
(kyoto uji)
(osaka yao)
(kyoto kameoka)
(shiga nagahama)
(osaka osaka)))
このリストに対して,次のような関数find-table を作ってください.
(find-table 'shiga my-table) -> (shiga otsu)
(find-table 'kyoto my-table) -> (kyoto uji)
つまり,最初の部分(shiga kyoto)がマッチする部分のリストを返しま
す.ただし先頭のほうから見ていって,最初に当てはまるものを返します.
(shiga nagahama) は返ってきません.)
ファイル名
~/Lisp/Report/report35.lisp

IMAGE image/pro-pri202.gif

を作ってくださ


を計算するのに
(vector-product '(1 2) '(3 4 5)) -> ((3 4 5) (6 8 10))
とします.
ファイル名
~/Lisp/Report/report36.lisp


37
トランプのカードを扱う関数を考えます.まず,カードは(king hearts)
(2 clubs) のように表すことにします.カードは,clubs, diamonds, hearts,
spades
ace,2, 3,4,5,6,7,8,9,jack,queen,
king
の組み合わせです.
カードの手を表すグローバル変数を
*my-hand* とします.たとえば,
(setf *my-hand* '((king hearts)
(3 spades)
(7 spades)
(jack diamonds)
(ace clubs)))
とします.
手のスーツ(カードの種類)を数える関数
count-suit を作ってください.た
とえば,
spades のカードの枚数を数えるには次のようにします.
(count-suit 'spades *my-hand*) -> 2
ファイル名~/Lisp/Report/report37.lisp


38
トランプのカードの色を表すグローバル変数を*card-colors* とします.
(setf *card-colors* '((clubs black)
(diamonds red)
(hearts red)
(spades black)))
手のカードの中で,指定された色のすべてのカードのリストを返す関数select-
color-cards
を作ってください.
(select-color-cards 'black *my-hand*)
-> ((3 SPADES) (7 SPADES)(ACE CLUBS))
(select-color-cards 'red *my-hand*)
-> ((KING HEARTS) (JACK DIAMONDS))
ファイル名~/Lisp/Report/report38.lisp


39
手がポーカーのフラッシュ(5枚のカードが全部同じスーツ)になっているかを
判定する関数
flash? を作ってください.(少なくとも2通りの作り方がありま
す.)
(flash? *my-hand*) -> NIL
(flash? '((king spades)
(3 spades)
(7 spades)
(jack spades)
(ace spades))) -> T
ファイル名~/Lisp/Report/report39.lisp
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
研究課題


今までの課題の中で,今回のいろいろな関数を使えば簡単になるものを見つけて,
作ってみましょう.
また,ポーカーのいろいろな手を判定する関数を考えてみましょう.ワンペ
ア,..,ストレートなどです.

- 38 -