私が唯一使えるプログラミング言語は
十進 BASICだけである。
そして十進 BASIC は独自拡張として有理数計算を実装している。
実は Julia に手を出すより前に十進 BASIC の利用を検討したのだが,配列にデータを自動的に読み込ませる,とても便利な MAT READ 文というコマンドでは,有理数をデータとして扱えないという大問題があった。
もう少しちゃんと事情を説明すると,例えば -2/3 という有理数は,データとして読み込ませる際には数値データとして扱うことはできず,文字列データ扱いにしかならないのである。
そうすると,文字列 "-2/3" をどうやって数値の -2/3 として計算に使用できるようにすればよいのか。
この問題の解決法がわからず,絶望し,Julia に浮気したというわけである。
有理数の計算を行うプログラムを自分で作るというのが解決策と思えたが,それにじっくり取り組む時間も今は取れそうにない。
そもそも十進 BASIC があらかじめサポートしてくれている有理数計算をわざわざ自前でプログラミングするというのも無駄なような気がする。
なんとか十進 BASIC の機能を活かせないものか・・・?
諦めきれないまま,とりあえず掃き出し計算の基本変形のコマンドの書式を考案し,そのコマンドの parsing(構文解析)もどきを具体的にどう実装するか,プログラムを考えてみた。
プログラミングの経験はこれまでほとんどなく,特に文字列の操作はどんなプログラミング言語であろうと基本中の基本と思われるが,関数(コマンド)が何種類もあって覚えきれないと感じ,食わず嫌いで敬遠してきた。
そのツケを払うときが来たのだ。
この構文解析もどきのプログラムを文字列操作関数を用いてどう実現するか,十進 BASIC の詳しくてとても便利なヘルプを眺めて考えていたら,
あ,文字列として分数を "/" を区切りに分子と分母にバラして,改めて (分子の数値) / (分母の数値) という形で有理数として十進 BASIC に与えたらいいんじゃね?
とひらめいた。
つまり,S$="-2/3" という文字列の状態の有理数を,"/" という記号のある位置を
LET P=POS(S$,"/")
でまず突き止め,S$ の第1文字から "/" の一つ前の文字までが分子を表す文字列なので,文字列を数値に変換する VAL 関数で数値に変換し,
LET BUNSHI=VAL(S$(1:P-1))
とおく。同様に,S$ の "/" の次の文字から最後の文字までが分母を表す文字列なので,「最後の文字の番号 = 文字列 S$ の長さ」であることから,文字列の長さを返す LEN 関数も用いて
LET BUNBO=VAL(S$(P+1:LEN(S$)))
とおき,最後に
let X=BUNSHI/BUNBO
とおくと,無事に X は数値としての -2/3 として十進 BASIC に認識されるようになる。
こうして,文字列 "-2/3" を数値 -2/3 に "ロンダリング (laundering)" する方法を思い付き,まずは S$="45/9" とおいて上の手続きで X=45/9 となるようにしたところ,
PRINT X
の実行結果が約分された形の 5 となった。
これに勢いを得て,コンピュータに基本変形のコマンド・リストを与えて掃き出し計算をさせる,というここ数年来の夢を叶える大きな一歩を踏み出すに至った。
今回の件で,文字列操作アレルギーもだいぶ薄れたように思う。
あとはファイル操作アレルギーの治療が必要だな・・・。
ちなみに,三年か四年前に,LIGHTS OUT という有名なパズルゲームを行列を利用して解くプログラムをやはり十進 BASIC で作ったことがあった。
その際,確かコマンドの parsing もどきはやった記憶があるが,1 ビットの 0 と 1 の和の計算しか行わずに済むため,分数を扱う必要がなく,今回ほど深刻な困難は明るみにならなかったのである。
その時作ったプログラムは手元に残っているかどうか調べていないが,そういえば
「第 i 行に第 j 行を加える」のを add(i,j),
「第 i 行と第 j 行を入れ替える」のを exchange(i,j)
とか名付けた記憶がほうふつとしてきた。
有理係数のみの連立 1 次方程式を,こちらの与えた基本変形のコマンド・リストに従って step by step に解くプログラムを作りたい,というのはその頃からの夢だったのである。
さて,私の拙いプログラムを恥ずかしげもなく以下に公開しておく。
明らかな無駄も含まれているが,そこは大目に見ていただきたい。
私と同じようなことを夢見ていながら果たせないままでいる,この世のどこかにいるかもしれない同志にとって,いくらかでも助けになれば本望である。
REM 有理数の範囲で 3元3立1次方程式を掃き出し法で解く。
OPTION ARITHMETIC RATIONAL
REM 解きたい連立方程式の拡大係数行列 を A(m,n),
REM そのコピーを B(m,n) とする。
REM B(m,n) は正直必要なかった・・・。
REM 計算用の補助配列として行ベクトル C(n) を用意しておく。
DIM A(3,4)
DIM B(3,4)
DIM C(4)
REM プログラム中に基本変形のコマンドを入れておく。
DIM S$(5)
REM 拡大係数行列の成分は整数値で与えられているものとする。
REM ファイルから読み込むようにしたいところである。
DATA 1,2,-2,5,1,3,-2,7,-1,-2,5,-2
REM --- 方程式 ---
REM x+2y-2z=5
REM x+3y-2z=7
REM -x-2y+5z=-2
MAT READ A
PRINT "拡大係数行列は"
MAT PRINT A
REM 基本変形のコマンド・リスト。
REM 事前に手で解いてコマンドを明らかにしておく。
REM コマンドの書式は次の通り。
REM コマンドの最初の数字は基本変形の種類の識別子である。
REM "1,rI,rJ,L": 第 I 行に第 J 行の L 倍を加える。
REM "2,rI,L": 第 I 行を L 倍する。
REM "3,rI,rJ": 第 I 行と第 J 行を入れ替える。
REM こちらもファイルから読み込むようにしたいところである。
LET S$(1)="1,r2,r1,-1"
LET S$(2)="1,r3,r1,1"
LET S$(3)="2,r3,1/3"
LET S$(4)="1,r1,r3,2"
LET S$(5)="1,r1,r2,-2"
REM 拡大係数行列の成分を元データ A から計算用の配列 B へとコピーする。
REM この作業は別に必要無かった・・・。
FOR m=1 TO 3
FOR n=1 TO 4
LET B(m,n)=A(m,n)
NEXT n
NEXT m
FOR k=1 TO 5
REM 第 I 行に第 J 行の L 倍を加える
IF S$(k)(1:1)="1" THEN
LET I=VAL(S$(k)(4:4))
LET J=VAL(S$(k)(7:7))
REM 文字列ロンダリング (laundering)。
REM 係数 L を文字列から数値に変換する
LET SUBS$=S$(k)(9:LEN(S$(k)))
LET FRAC=POS(SUBS$,"/")
IF FRAC=0 THEN
LET L=VAL(SUBS$)
ELSE
LET NUMERATOR=VAL(SUBS$(1:FRAC-1))
LET DENOMINATOR=VAL(SUBS$(FRAC+1:LEN(SUBS$)))
LET L=NUMERATOR/DENOMINATOR
END IF
FOR n=1 TO 4
LET C(n)=L*B(J,n)
LET B(I,n)=B(I,n)+C(n)
NEXT n
PRINT "第";I;"行に第";J;"行の ";S$(k)(9:LEN(S$(k)));" 倍を加える;"
REM 第 I 行を L 倍する
ELSEIF S$(k)(1:1)="2" THEN
LET I=VAL(S$(k)(4:4))
REM 係数 L を文字列から数値に変換する
LET SUBS$=S$(k)(6:LEN(S$(k)))
LET FRAC=POS(SUBS$,"/")
IF FRAC=0 THEN
LET L=VAL(SUBS$)
ELSE
LET NUMERATOR=VAL(SUBS$(1:FRAC-1))
LET DENOMINATOR=VAL(SUBS$(FRAC+1:LEN(SUBS$)))
LET L=NUMERATOR/DENOMINATOR
END IF
FOR n=1 TO 4
LET B(I,n)=L*B(I,n)
NEXT n
PRINT "第";I;"行を";S$(k)(6:LEN(S$(k)));" 倍する;"
ELSEIF S$(k)(1:1)="3" THEN
LET I=VAL(S$(k)(4:4))
LET J=VAL(S$(k)(7:7))
FOR n=1 TO 4
LET C(n)=B(I,n)
LET B(I,n)=B(J,n)
LET B(J,n)=C(n)
NEXT n
PRINT "第";I;"行と第";J;"列を入れ替える;"
END IF
MAT PRINT B
NEXT k
PRINT "これですべて(方程式は)解けた!"
END
参考までにこのプログラムの実行結果も載せておく。
拡大係数行列は
1 2 -2 5
1 3 -2 7
-1 -2 5 -2
第 2 行に第 1 行の -1 倍を加える;
1 2 -2 5
0 1 0 2
-1 -2 5 -2
第 3 行に第 1 行の 1 倍を加える;
1 2 -2 5
0 1 0 2
0 0 3 3
第 3 行を1/3 倍する;
1 2 -2 5
0 1 0 2
0 0 1 1
第 1 行に第 3 行の 2 倍を加える;
1 2 0 7
0 1 0 2
0 0 1 1
第 1 行に第 2 行の -2 倍を加える;
1 0 0 3
0 1 0 2
0 0 1 1
これですべて(方程式は)解けた!