9.連想リスト


(テキスト9.5.2

今回は,連想リストとassoc という関数について説明します.


連想リスト
連想リストといっても特別なものではなく,前回のリスト
numbers のように要
素2つのリストを集めたものです.連想リストという名前は,最初の部分から後の
部分を「連想」する(引き出す)のに分かりやすい格好をしていることからきてい
ます.つまり,以下の例のように,
one -> ichi などの関係を表すときに便利な
方法です.
(setf numbers
'((one ichi)
(two ni)
(three san)
(four yon)
(five go)))

assoc
リストnumbers を基にして,英語の数字の日本語訳を答える関数translate
を作ることにします.このようなときに役に立つ関数としてassoc があります.
次のように,
assoc は第1要素がマッチしたリストを返します.
(assoc 'one numbers)-> (one ichi)
(assoc 'four numbers) -> (four yon)
(assoc 'six numbers)-> NIL

したがって,translate
(defun translate (n)
(second (assoc n numbers)))
となり,たとえば
(translate 'three) -> san
となります.


つまり,前回の課題35 find-table assoc を自分で作る問題だったの
です.


assoc の注意点
いろいろ使える関数ですが,1つ注意することがあります.
まず,次のリストを作ったとします.
(setf towns
'((shiga otsu)
(kyoto uji)
(osaka yao)
(kyoto kameoka)
(shiga nagahama)
(osaka osaka)))

assoc
(assoc 'shiga towns) -> (shiga otsu)
(assoc 'osaka towns) -> (osaka yao)
と最初にマッチしたリストを返して,他にマッチするリストがあっても答えません.


assoc が最初にマッチしたリストを返す理由
連想リストを更新するには,新しい「連想」(例:
(osaka ibaragi))をcons
で連想リストの先頭に加えていくのが簡単といえます.そうしておけば,assoc
いつも最新のデータに基づいた「連想」をしてくれるわけです.リストの中の方に

- 39 -


ある古いデータは答えない方が都合がいいわけです.


また,setf assoc を使えば連想リストの一部分を変更することができます.
p.25 setf の説明も参照してください.)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>numbers

((ONE ICHI) (TWO NI) (THREE SAN) (FOUR YON) (FIVE GO))
USER>(setf (second (assoc 'three numbers)) 'mittu)

MITTU
USER>numbers

((ONE ICHI) (TWO NI) (THREE MITTU) (FOUR YON) (FIVE GO))
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


この例では,
three でマッチする所の----------> (assoc 'three numbers)
2番目の場所を---------> (second)
mittu
---------------------------------------------> 'mittu
する-----------> (setf)
ことを意味しています.


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


40
マッチするすべてのリストを答えるfull-assoc と,対応する部分をすべて答
える
list-up を作ってください.(list-up full-assoc を少し書き換え
ればいいので,2つまとめて1つのファイルにしてください.)
(full-assoc 'shiga towns) -> ((SHIGA OTSU) (SHIGA NAGAHAMA))
(list-up 'shiga towns) -> (OTSU NAGAHAMA)
ファイル名~/Lisp/Report/report40.lisp


41
次のような品物の名前と値段を表しているリストを考えます.
(setf *price-list*
'((apple 130)
(orange 100)
(lemon 50)
(apricot 200)))
これを使って,たとえば,リンゴ2個,レモン3個の合計の値段を計算するのに
(price '((apple 2) (lemon 3))) -> 410
と答えさせる関数price を作ってください.(消費税は考えないことにしま
す.)
ファイル名
~/Lisp/Report/report41.lisp
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

10.読み書き


(テキスト5.1

今回は読み書きについて説明します.ファイルの読み書きはさしあたって使わな
いと思いますが,ついでに説明しておきます.
今までも関数の値は出力されていましたが,もう少し自由度をもった出力の仕方

- 40 -


があります.


format
Lisp
にはいろいろと出力に関する関数がありますが,1つでいろいろ使えるも
のとして
format という関数があります.format はテキストにも書いてある
print などの機能を全部含んだものになっています.
そこで,ここでは
format についてだけ説明します.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(format t "Output Function")
Output Function
NIL(関数format の値)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


format の最初の引き数「t」は画面に出力することを指定しています.ファイ
ルに出力するときは別な値にします.


「副作用」について(参考)
関数
format の「値」は3行目の「NIL」で,2行目の出力された文字は「副作
用」です.この場合は,「副作用」に方に意味・目的があるわけですが.


出力の指定の仕方
Cの
printf で「\n」を使うように,format にもいくつか指定できることが
あります.
Lisp の場合,記号は「\バックスラシュ」でなくて「~チルダ」で
始まります.


~% はCの「\n」と同じです.
~& は改行してから出力します.
~& はカーソルが現在どこにあるか分からないけれど,行の先頭から出力したい
ときに使います.)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(format t "Output Function~%~%with Return")
Output Function

with Return
NIL
USER>(format t "~&Output Function~%with Return")

Output Function
with Return
NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


Cのprintf の「\d」のようなものもあります.たとえば,数字を8進数で出
力する指定の仕方もあります.しかしここでは,リストや文字,数字を出力するの
によく使う簡単なものだけを説明しておきます.


~S はリストや文字,数字を出力する場所に使います.
~A は「~S」と同じですが「"」で文字列が囲まれません.


次の例で試してみましょう.
(defun format-test (x)
(format t "Shuturyoku ha ~S desu" x)

- 41 -


(format t "~&Output is ~A !!" x))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(format-test 10)
Shuturyoku ha 10 desu
Output is 10 !!
NIL
USER>(format-test '(a (b c)))
Shuturyoku ha (A (B C)) desu
Output is (A (B C)) !!
NIL
USER>(format-test 'symbol)
Shuturyoku ha SYMBOL desu
Output is SYMBOL !!
NIL
USER>(format-test "test string")
Shuturyoku ha "test string" desu
Output is test string !!
NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


read
キーボードから読み込むにはread を使います.read は読み込んだものの値を
計算しないので,リストを入力するときに「
'」をつける必要はありません.最初の
例を見てください.また,読み込む単位は,それが
Lisp のデータとして意味のあ
る区切りまでです.リストなら対応する閉じ括弧まで,文字列なら対応する「
"」が
出てくるまでです.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(setf a (read))
(a b (c d))

(A B (C D))
USER>a

(A B (C D))
USER>(setf a (read))
"This is READ Function"

"This is READ Function"
USER>a

"This is READ Function"
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


format と組み合わせて
(defun jijyou ()
(format t "~&Suuji wo nyuryoku -> ")
(let ((x (read)))
(format t "~S no jijyou ha ~S desu~%"
x (* x x))))
のように使います.

yes-or-no-p y-or-n-p

- 42 -


プログラムでは,ユーザーに「Yes (Y)」か「No (N)」をたずねて,それによっ
て分岐をすることがよくあります.そのための便利な関数がこれらの関数です.
それぞれ,入力されたのが「
Yes (Y)」なら「T」,「No (N)」なら「NIL」を
返します.さらに便利なことに,それ以外の入力だと受け付けずに,質問を繰り返
してくれます.引き数として,入力を促すメッセージを書くことができます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(yes-or-no-p "TEST : ")
↑この文字列がプロンプトになる
TEST : Yes/No?OKOK と入れてみても駄目)

Yes/No?yy と入れも駄目)

Yes/No?yes

T
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


y-or-n-p の方も試してください.


使用例としては,先ほどのjijyou を利用すると以下のようになります.
(defun san-suu ()
(if (y-or-n-p "Jijyou wo keisan shimasuka? ")
(jijyou)
(format t "~&Owari!!~%")))

ファイルから読み込む方法
with-open-file という関数とread(ファイルを指定して)を組み合わせて
使います.
読み込むときの
with-open-file read の使い方は,
(with-open-file (ストリームファイル名)
式...
...
(read ストリーム)
...)
です.


「ストリーム」とは,read でどこから読み込むかを指定するためのものです.
read で直接「ファイル名」を扱うことはできません.また,「ファイル名」の箇
所は,パスに注意してください.(
load と同じ.)with-open-file でファイ
ル名とストリームを結び付けているので(つまり,
with-open-file の中だけの
ローカルな変数,
let の場合と同じ),ストリームはwith-open-file の括弧
の中だけで使えます.
(read ストリーム)を使う場所に注意してください.


次の例ではLisp のワーキング・ディレクトリにあるファイルの場合です.前回
の課題の料金表などを
test.data というデータファイルにして用意します.
(defun read-price ()
(with-open-file (price-file "test.data")
(let* ((price-list (read price-file))
(my-fruit (read price-file))
(my-goods (read price-file)))
(format t "~&~S no nedan wa ~S yen desu"
my-fruit
(second (assoc my-fruit price-list))))))

- 43 -


test.data の中味────────────────────────────
((apple 130) (orange 100) (lemon 50) (apricot 200))
lemon
((apple 2) (lemon 3))
└────────────────────────────────────
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(read-price)

LEMON no nedan wa 50 yen desu
NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


ファイルに書く方法
with-open-file を使うのは同じですが,少しオプション(:direction
:output
)をつけます.また,format の出力先に「ストリーム」を指定します.
(最初の引き数の「
t」の所です.)
(with-open-file (ストリームファイル名:direction :output)
式...
...
(format ストリーム....)
...)


次のような例になります.
(defun my-output (l)
(with-open-file (my-output-file "output.data"
:direction :output)
(format my-output-file "Input List is : ~S~%"
l)
(format my-output-file "Length of Input List is : ~S~%"
(length l))
(format my-output-file "First of input is : ~S~%"
(first l))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(my-output '(a (b c) d))

NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
output.data の中味───────────────────────────
Input List is : (A (B C) D)
Length of Input List is : 3
First of input is : A
└────────────────────────────────────


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


最終的な目標は次のような関数make-graph を作ることです.そのために,以
下の課題のように小分けにして作ります.ただし,ウインドウの左端を
0 として,
文字でグラフを書くので,関数値が常に正の整数になるように注意します.
また,以下の例の
jijyou
(defun jijyou (n)
(* n n))
としてあります.make-graph を実行する前に,定義(前もってload )してお

- 44 -


きます.make-graph の中でファイルから(with-open-fileを使って)読む
ことはしないことにします.
なお,例の最後の行の
NIL の出方は気にしなくてもいいです.(プログラムの
作り方によっては出ない場合もありますが.)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(make-graph)
Function to graph? jijyou(ちゃんと尋ねてくるようにする)
Starting x value? -6
Ending x value? 6
Plotting String? "*"
*
*
*
*
*
*
*
*
*
*
*
*
*
(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL ...)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


42
まず,必要なだけスペースを出力して,カーソルを右にずらす関数n-space
作ってください.だだし,動きをテストするには次の関数
test-space を使って
ください.(スペースだけでは見えないので,うまく書いたのかどうか分からない
からです.)
(defun test-space (n)
(format t "~%...1234567890...")
(format t "~%>>>")
(n-space n)
(format t "<<<"))
ファイル名~/Lisp/Report/report42.lisp
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
(test-space 10)

...1234567890...
>>><<<(間が10個のスペース)
NIL
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


43
次のような関数plot-points を作ってください.リストの各要素の数字の分だ
n-space でスペースを書いて,そのあと記号(たとえば,***)を書きます.
(ヒント:まず,1つの数字の分のスペースと記号を書く関数を考えて,それを数
字のリストに作用させます.)最後の
NIL の出方は気にしなくてもいいです.
ファイル名
~/Lisp/Report/report43.lisp
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- 45 -


(plot-points "***" '(1 2 4 7 10 3 2))
***(スペース1個と***
***
***
***
***(スペース10個と***
***
***
(NIL NIL NIL NIL NIL NIL NIL)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


44
課題20 numlist を利用して,make-graph を作ってください.
ファイル名
~/Lisp/Report/report44.lisp
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
研究課題


jijyou の他にもいろいろな関数を作って試してみてください.


11.数式処理への応用


簡単な応用として数式処理の真似事をしてみたいと思います.


微分する関数を作る
微分は公式(アルゴリズム)があるので作るのは簡単です.積分はなかなか手間
がかかりますので,ここでは扱いません.


ここでは,演算子を+,-,*,^ とします.最後の記号はべき乗を表すことにしま
す.割り算「
/」を考えてもいいのですが,長くなるのでここでは省きました.この
他の演算(
sin, cos, log, ...)は後で考えることにしましょう.
数式を
(「数式」演算子「数式」) を繰り返して作られるものとします.(課題
26 参照.今度は変数があります.)
たとえば,
x + y--> (x + y)
x
2+ 2x --> ((x ^ 2) + (2 * x))
などです.
sin などの1変数の関数の場合,演算子が最初にくるので(たとえば,(sin x)
)後で少し考えなければいけません.ここでは,演算子が中央にある場合だけを考
えることにします.


ここで,微分の公式を思い出してみましょう.
d/dx{f(x) + g(x)} = d/dx{f(x)} + d/dx{g(x)}
d/dx{f(x) - g(x)} = d/dx{f(x)} - d/dx{g(x)}
d/dx{f(x) * g(x)} = d/dx{f(x)} * g(x) + f(x) * d/dx{g(x)}
d/dx{f(x)n} = n* {f(x)n-1} * d/dx{f(x)}
特に
d/dx{xn} = n* xn-1

今回は,今までのボトムアップ(部品を作って組み立てる)の方法から,トップ
ダウンの方法で作ります.つまり,大ざっぱに全体を作ってから,だんだん仕上げ
ていくことにします.


これから作る関数(プログラム)はdiff です.(diff formula variable)

- 46 -


の形で数式formula を変数variable で微分することを表すこことします.
まず,公式をそのままプログラムにしてみると,
(defun diff (formula variable)
(let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))
(cond ((equal func-name '+)
(list (diff form1 variable)
'+
(diff form2 variable)))
((equal func-name '-)
(list (diff form1 variable)
'-
(diff form2 variable)))
((equal func-name '*)
(list (list (diff form1 variable)
'*
form2)
'+
(list form1
'*
(diff form2 variable))))
((equal func-name '^)
(list (list form2
'*
(list form1 '^ (list form2 '- 1)))
'*
(diff form1 variable))))))

のようになります.
長いですが,公式そのままですから分かると思います.「
+」や「*」はLisp
の関数としてでなく記号として扱うので「'」をつけます.


このままでうまく動くかというと,駄目です.実際に実行してどのようなエラー
が出るか試してください.そして,なぜエラーがでるか考えてみてください.今後,
この関数を編集していきますので,ちゃんとファイルにしておく必要があります.
すぐ分かるように,この関数は再帰的であるため終了条件がないとエラーが出ま
す.
どんどん再帰的に呼び出していくと最後には,
(diff 'x 'x) のような形に行き
着くことが分かります.この形の式の値は明らかです.その部分を書いてみると
(if (symbolp formula)
(if (equal formula variable) 1 0)
......
(if (symbolp... で「x」などがもうこれ以上分解できないかどうか判定しま
す.
その次に
(if (equal...
(diff 'x 'x) --> 1
(diff 'x 'y) --> 0
となるように,式と変数が等しいかどうかを調べています.


この部分を上のdiff の最初に入れます.let の前に入れるのは,first など
を使うためには
formula が分解できる(シンボルでない)必要があるからです.
全体は以下のようになります.最初の
(if (symbolp formula)...の「else
」部分に,今までの(let ...) を入れます.長いのでインデントをうまく利
用して括弧の対応をチェックしてください.


(defun diff (formula variable)

- 47 -


(if (symbolp formula)
(if (equal formula variable) 1 0)
(let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))
(cond ((equal func-name '+)
(list (diff form1 variable)
'+
(diff form2 variable)))
((equal func-name '-)
(list (diff form1 variable)
'-
(diff form2 variable)))
((equal func-name '*)
(list (list (diff form1 variable)
'*
form2)
'+
(list form1
'*
(diff form2 variable))))
((equal func-name '^)
(list (list form2
'*
(list form1 '^ (list form2 '- 1)))
'*
(diff form1 variable)))))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(diff 'x 'x)

1
USER>(diff 'x 'y)

0
USER>(diff '(x + x) 'x)

(1 \+ 1)
USER>(diff '(x ^ 3) 'x)

((3 * (X ^ (3 \- 1))) * 1)
USER>(diff '((x ^ n) + (x ^ 3)) 'x)

(((N * (X ^ (N \- 1))) * 1) \+ ((3 * (X ^ (3 \- 1))) * 1))
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


+」「-」が「\+」「\-」と出力されていますが気にしないでください.いろい
ろな数式を微分してください.まだ,微分できない式があるかもしれません.また,
どのような結果が出てくるかをよく見ておいてください.


いろいろと試してみると,次のようなエラーが出る場合があります.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(diff '(2 * (x ^ 3)) 'x)

Error.....
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- 48 -


このように,数字が「数式」の箇所にきたときの動作がうまくいっていません.
これは,
(symbolp 'x) -> T
(symbolp 123) -> NIL
のように数字のときの判断が不十分です.数字(定数)を変数で微分したら0
す.また,今度は「
if」でなく「cond」で書くことにします.(条件が多くなった
からです.)
(defun diff (formula variable)
(cond ((numberp formula) 0)
((symbolp formula)
(if (equal formula variable) 1 0))
(t (let .....
let の部分は前と同じです.」)
)))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(diff '(2 * (x ^ 3)) 'x)

((0 * (X ^ 3)) + (2 * ((3 * (X ^ (3 - 1))) * 1)))
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


微分した結果を簡単にする関数の作成1
いろいろやってみると,簡単な数式のはずなのに長い式が出てきて,合っている
かどうかもよく分からないほどです.式の簡略化をする必要があることに気付きま
すね.
たとえば,
(diff '(x ^ 3) 'x) -> ((3 * (X ^ (3 - 1))) * 1)
のように結果が少し冗長です.これなどは,(3 * (X ^ 2)) と答えてほしいわけ
です.
つまり,
(3 - 1) --> 2
((1 * X) + (X * 1)) --> (x + x) --> (2 * x)
などの式の簡略化が必要です.
このように冗長な数式を簡略化する関数を作りましょう.


まず,
(3 - 1) -> 2
のように
(数字演算子数字) ->数字
と簡略化するものを作ってみましょう.
これは以前の課題
26 の関数value と似ていることに気づくと思います.
まず,入れ子になっていない単純な
(数字演算子数字) を計算する関数とし
calc-num-num を作ります.課題26 と違って演算子は「+,-,*,^」と種類が
多いので
p.33 funcall を使ってスマートに作ってみます.ただし.「^」は
Lisp の関数では「expt」ですから,場合分けが必要です.
(defun calc-num-num (formula)
(let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))
(if (equal func-name '^)
(setf func-name 'expt))
(funcall func-name form1 form2)))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(calc-num-num '(1 + 2))

- 49 -


3
USER>(calc-num-num '(3 - 4))

-1
USER>(calc-num-num '(5 * 6))

30
USER>(calc-num-num '(5 ^ 6))

15625
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


また,数式が(数字演算子数字) の形をしているかどうかを判定する関数と
して
num-num? を作っておきます.
(defun num-num? (formula)
(and (numberp (first formula))
(numberp (third formula))))
これは単に最初と最後(3番目)が数字かどうかを判定しているだけです.


これらの関数を使って簡略化をする関数simplify の最初のバージョンを作り
ます.
これも再帰的に作りますので,最初の部分に終了条件となる式を書きます.それ
は,単なる変数か(
symbolp),単なる数字か(numberp),(数字演算子
) の形か(num-num?)の部分です.それ以外の場合は,各部分をsimplify
たもので演算子を挟んでリストにします.
(defun simplify (formula)
(cond ((symbolp formula) formula)
((numberp formula) formula)
((num-num? formula) (calc-num-num formula))
(t (let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))
(list (simplify form1)
func-name
(simplify form2))))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(simplify '(1 + 2))

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


うまくいっているようですが,実は以下のように,もう1段の簡略化が必要です.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(simplify '(((1 + 2) + (3 ^ 4)) - 5))

((3 + 81) - 5)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


課題26 の場合には演算が本当の「足し算」なので自動的に最後まで計算が進む
のですが,今回の場合単なる記号の「
+」などですからもう少し考える必要がありま
す.ここでは次のように考えることにします.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(simplify '((3 + 81) - 5))

- 50 -



(84 - 5)
USER>(simplify '(84 - 5))

79
USER>(simplify 79)

79
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


このように,結果が変わらなくなるまで,simplify を繰り返すことにします.
これを実現する関数は次の
more-simplify です.
(defun more-simplify (formula)
(let ((one-step (simplify formula)))
(if (equal one-step formula)
formula
(more-simplify one-step))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(more-simplify '(((1 + 2) + (3 ^ 4)) - 5))

79
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


これで,微分した数式の( 数字演算子数字) の部分は簡略化されるはずです.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(more-simplify '((3 * (X ^ (3 - 1))) * 1))

((3 * (X ^ 2)) * 1)
USER>(more-simplify (diff '((x ^ 2) + 3) 'x))

(((2 * (X ^ 1)) * 1) + 0)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


微分した結果を簡単にする関数の作成2
さて,次は,
(((2 * (X ^ 1)) * 1) + 0) -> (2 * (X ^ 1)) のように「
* 1」や「+ 0」を簡単にすることを考えましょう.


まず,「0」についてのルールは
(0 + 数式) -> 数式
(0 * 数式) -> 0
(
数式+ 0) -> 数式
(数式* 0) -> 0
です.
その部分を付け加えた
simplify を書いてみると次のようになります.今まで
(list (simplify ... の部分をcond の最後の式に入れて(t (list
(simplify ...
になっている点も注意してください.
(defun simplify (formula)
(cond ((symbolp formula) formula)
((numberp formula) formula)
((num-num? formula) (calc-num-num formula))
(t (let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))

- 51 -


----- (cond ((equal form1 0)
|(cond ((equal func-name '+)
|(simplify form2))
|((equal func-name '*)
|0)))
|((equal form2 0)
|(cond ((equal func-name '+)
|(simplify form1))
|((equal func-name '*)
-----0)))
---->(t (list (simplify form1)
func-name
(simplify form2))))))))
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(more-simplify (diff '((x ^ 2) + 3) 'x))

((2 * (X ^ 1)) * 1)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


このように,「+ 0」の部分を簡略化することができました.これでうまくいっ
ているかというと,実はまだ抜けていることがあります.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(more-simplify (diff '((x ^ 2) - 3) 'x))

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


注目していない「- 0」をそのままにしておいてくれたらいいのですが,そうは
いかないわけです.そこで,考えていないルールの場合は「そのままにしておく」
部分を加えます.
(defun simplify (formula)
(cond ((symbolp formula) formula)
((numberp formula) formula)
((num-num? formula) (calc-num-num formula))
(t (let ((form1 (first formula))
(func-name (second formula))
(form2 (third formula)))
(cond ((equal form1 0)
(cond ((equal func-name '+)
(simplify form2))
((equal func-name '*)
0)
--------->(t (list 0
--------->func-name
--------->(simplify form2)))))
((equal form2 0)
(cond ((equal func-name '+)
(simplify form1))
((equal func-name '*)
0)
--------->(t (list (simplify form1)
--------->func-name
--------->0))))
(t (list (simplify form1)
func-name
(simplify form2))))))))
これでどうやら動きます.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- 52 -


USER>(more-simplify (diff '((x ^ 2) - 3) 'x))

(((2 * (X ^ 1)) * 1) - 0)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


この調子で,「- 0」「* 1」「^ 0」「^ 1」について簡略化ができます.後は
繰り返しですから括弧に注意して作ってください.
うまくいけば,次のようになります.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USER>(more-simplify (diff '((x ^ 2) - 3) 'x))

(2 * X)
USER>(more-simplify
(diff '(((3 * (x ^ 2)) - (2 * x)) + 4) 'x))

((3 * (2 * X)) - 2)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


最後の実行例を見れば,まだ簡略化すべき箇所があることが分かります.
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
提出課題


45
+ 0」,「* 0」についてのルール
(0 + 数式) -> 数式
(0 * 数式) -> 0
(
数式+ 0) -> 数式
(数式* 0) -> 0
を参考にして,「- 0」「* 1」「^ 0」「^ 1」について簡略化をできるように
simplify を改良してください.まず,ルールをはっきり書いてから取りかかった
方がいいと思います.
ただし,
(0 - 数式)
(0 - 数式) -> (0 - 数式)「そのままにしておく」
(0 - 数字) -> - 数式「符号を変える」
とすることにします.
ファイル名
~/Lisp/Report/report45.lisp
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
研究課題


((3 * (2 * X)) - 2) -> ((6 * x) - 2)
のような簡略化もできるようにsimplify を改良してください.


数式処理の改良(参考)
sin, cos, tan,...,exp, log, ln,..など新しい関数についてできるように
する必要があります.このとき,そのまま
diff に付け加えていくとだんだん長く
なってきて分かりにくくなるので,たとえば,1変数関数(
sin, exp,...など)
の処理をする関数と,2変数関数(
+,...など)の処理をする関数とにわける,な
どを考えます.
でも,本当はこのまま進めていくとどんどん複雑になって分かりにくくなります.
そこで,ルールのファイルを作って,どのルールが使えるかをパターンマッチング
を使って判定して行うことにします.そうすればルールのファイルを書き換える

- 53 -


だけで新しい処理が行えることになります.特に,簡略化をするときに重要になっ
てきます.


〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
<やり残したこと>


残りの章について簡単に紹介しておきましょう.


特に,4回生での卒業研究や大学院での研究で人工知能関係をやりたいと思って
いる人は,もう少し
Lisp をやっておくのがいいと思います.


この演習では,テキスト(これがLISPだ!)でいえば,大体10章位までの部分を
やったことになっています.繰り返し(
do を使う)の所は,この教科書はとても
力を入れて書いてあるので説明しませんでした.(その意味で少し変わった
Lisp
の本です.)


11章では,配列の所を読んでおくといいでしょう.(12章のプログラム12.3
使っています.)この他に,Cの「
struct」にあたるストラクチャもあります.他
の本で調べておくといいでしょう.


12章では,リスト構造について書いてあるので,ぜひ読んでおいてください.練
習問題の
12.3のプログラムを入力して実行してみてください.(自分で考えるのは
大変なので,本の後ろの解答例を使ってください.ただし,以下のような間違いが
あります.)


13章では,評価の所は読んでおくといい所です.難しい所なので,何となく感じ
がつかめればいいでしょう.


14章以下は,応用です.興味のある人は目を通しておくといいでしょう.


・練習問題12.3のプログラムの間違い


関数consfill の2行目の最後「\」の後にもう1個「\」を入れる.
(つまり,バックスラシュ2個)
(setf (aref consarray n1 n2) '\\))

4行目の最後の「" "」は「"["」にする.
(t (setf (aref consarray n1 n2) "[")

-----
オリジナル版大阪工業大学斉藤
1997年9月改訂小堀

- 54 -