この講義は代数学の講義であるから、代数学に強いものも扱っておこう。 GAP (Groups, Algorithms, Programming) は群などの代数学関連の計算に強く、また関連する多くのソフトウェアを統一的に扱うためのフロントエンドとしても有用である。 GAP の簡単な解説は以前行った講義で用意したので、ここではそれを見てもらおう。
GAP (Groups, Algorithms, Programming, 外部サイト) は無料で利用できる高性能な数学ソフトウェアで、特に代数学関係の計算に強い。 GAP には多くの組み込み関数が用意されておリ、単にそれを呼び出して使うだけでも十分利用価値がある。 ここでは GAP を組み込み関数を呼び出して使う方法について解説する。 この方法以外に GAP では自分でプログラムを書くことができる。
GAP のマニュアルは以下にある。(英語である。)
日本語の参考文献は多くはないが
庄司 俊明 先生 (名古屋大学) のページ (外部サイト)
などに日本語の解説がある。
GAP は常に厳密計算を行うので、通常の方法では近似計算などはできない。 また扱える数にも制限が強い。 近似計算やより柔軟な数の扱いが必要なときには、maxima の方が適しているであろう。
GAP を起動するには左下のメニューから「Math」→「GAP」とすればよい。 しかしこの方法だと作業ディレクトリの指定ができず、データの読み書きを USB メモリから行うには不便である。 以下の方法を推奨する。
端末を開き、作業ディレクトリに移動する。
コマンドラインから "gap" と入力する。
mathsci[10]$ gap
正常に起動すれば上記のような画面が出て、コマンドを受け付ける状態になる。 ウインドウサイズは自由に変更できる。
GAP では計算式の後に必ず ; (セミコロン) をつける。 セミコロンがない場合には計算式が終了していないと判断され、更なる入力を待つ状態になる。 言い換えれば、長い計算や命令はセミコロンを付けずに複数の行に分けて書くことができる。 式中のスペースは無視されるので、見やすいように適当に挿入して構わない。 また計算結果を出力する必要がない場合には ;; (セミコロン2つ) をつければ、 計算だけをして出力はされない。
以下に簡単な計算例を示す。# 以下は解説である。
gap> 123 + 456; # 加法
579
gap> 321 - 234; # 減法
87
gap> 12 * 23; # 乗法
276
gap> 12^2; # 累乗 - 指数には整数しか指定できない
144
gap> 1 + 2; 3 + 4; # 複数の計算を 1 行に書いてもよい。
3
7
gap> 13 / 4; # 除法 - 計算結果は分数で表示され近似計算は行わない。
13/4
gap> 0.1; # 小数は扱えない。
Syntax error: ; expected
0.1;
^
1
gap> 2^50; # 累乗 - PC のメモリの許す限り、いくらでも大きな数が扱える。
1125899906842624
gap> 20 mod 3; # 剰余
2
gap> Int(20 / 3); # 商 - Int で与えられた数の整数部分を返す。
6
ここで組み込み関数 Int を用いた。 関数名の大文字小文字は厳密に区別される。
いくつかの組み込み関数を試してみよう。 数値を返すものだけでなく、条件を判定するものなど、いろいろな関数が用意されている。
gap> Gcd(8,12); # 最大公約数
4
gap> Lcm(8,12); # 最小公倍数
24
gap> Binomial(6,2); # 二項係数
15
gap> IsPrime(2^11-1); # 素数判定
false
gap> FactorsInt(2^11-1); # 素因数分解
[ 23, 89 ]
gap> NextPrimeInt(2^11-1); # 与えられた自然数より大きい最小の素数を返す
2053
GAP に用意されている関数はとても多く、そのすべてを覚えることは非現実的である。 何かしたいときにはマニュアルを参照するのが普通ではあるが、GAP からでも簡単な説明を見ることができる。 例えば最大公約数について知りたければ、関数名 Gcd を想像し
gap> ?Gcd
Help: Showing `Reference: Gcd'
> Gcd( <R>, <r1>, <r2>, ... ) F
> Gcd( <R>, <list> ) F
> Gcd( <r1>, <r2>, ... ) F
> Gcd( <list> ) F
In the first two forms `Gcd' returns the greatest common divisor of the ring
... 略 ...
のようにヘルプを利用することができる。また
gap> ?Polynomial
Help: several entries match this topic - type ?2 to get match [2]
[1] Tutorial: Polynomials
[2] Reference: Polynomial Factorization
[3] Reference: Polynomial Rings
... 略 ...
のようにマニュアルの該当個所を表示する場合もある。
ヒストリー機能も便利で、一度入力したコマンドは上矢印のキーで再び呼び出すことが出来る。 類似の処理を繰り返すときなどは、これで呼び出してからそれを書き換えれば手間が省ける。
課題. コマンド Gcdex について、何をする関数であるかをヘルプまたはマニュアルで調べ、実際に試してみよ。
GAP におけるエラーでよくあるのは、以下のようなものである。
gap> a; # 未定義の変数を参照した (変数については後述する)
Variable: 'a' must have a value
gap> Gcm(2, 4); # 未定義の関数を利用しようとした
Variable: 'Gcm' must have a value
gap> Gcd(2); # 定義済みの関数であるが、引数の数や型があっていない
Error, usage: Gcd( [<R>,] <r1>, <r2>... ) called from
<function>( <arguments> ) called from read-eval-loop
Entering break read-eval-print loop ...
you can 'quit;' to quit to outer loop, or
you can 'return;' to continue
brk> quit;
gap>
エラーで停止し「brk>」を表示している場合には、ここでエラーの原因などを調べることができるが、 よく分からない時には「quit;」で抜けることができる。 次もよくあるミスである。
gap> 1 + 1 # ; (セミコロン) の付け忘れ。
> ; # 次の行に ; を入力すればよい。
2
誤ったプログラムで無限ループに陥ったときや、時間のかかりすぎるコマンドを実行してしまったときには、 Ctrl+c でプログラムを停止することができ、GAP のプロンプトに戻る。 しかし、場合によっては停止処理に時間がかかり、GAP のプロンプトがなかなか表示されない。 計算を中止してでもプログラムを停止したければ Ctrl+c を繰り返し押す。 これによって GAP 自体が強制終了され、コマンドラインに戻ることになる。
GAP を電卓のように使うとは言っても、計算結果をその後の計算に利用することぐらいはしたいであろう。 このためには変数を用意して、そこに計算結果を代入しておけばよい。 簡単な例を見る。
gap> a := 2^3;
8
gap> b := 3^2;
9
gap> a + b;
17
gap> c = 1;
Variable: 'c' must have a value
gap> a = 1;
false
変数への代入は = ではなく := で行う。 = は代入ではなく、左辺と右辺が等しいかを true または false で返す。 変数名は、アルファベットの大文字、小文字、数字、 および _ (アンダースコア)で任意の長さを持つことができる。 アルファベットの大文字と小文字は厳密に区別される。 しかし特定の文字列は GAP が利用するために予約されており、利用することができない。 例えば、true (真)、false (偽)、E (1 のべき根), ER (平方根), Z (有限体の単位元) などは利用できない。 これ以外にも GAP が利用している変数や関数を上書きしてしまうと、思わぬ不具合が生じることがあるので注意しよう。 GAP の予約語や組込み関数の多くはアルファベットの大文字で始まるので、 自分の利用する変数名はアルファベットの小文字で始めるようにするとほとんど問題は起きない。
変数への代入の際に
gap> a := 2;; b := 3;;
gap> a := a + b;
5
のように、 a の値を使って a に値を代入することも許される。
GAP では直前の計算結果を last という変数に自動的に代入する。 last は上書き可能で、自分で代入することもできるが、次の計算が行われるとその結果が代入されてしまう。
gap> last := 12;
12
gap> last;
12
gap> 1+2;
3
gap> last;
3
last という名前の変数は直前の計算結果を利用するときにだけ使えると思っていた方がよいであろう。
GAP を電卓のように利用するときであっても、 データがある程度大きいときにはそれをコマンドラインから入力するのは面倒である。 ここでは GAP に入力するデータを予めファイルに記述しておいて、それを GAP に読み込ませる方法を説明する。 これは、同じデータを繰り返し用いたいときなどにも有効な手段である。 また計算結果などをファイルに書き出す方法も説明する。
GAP にデータを読み込ませるのは簡単である。 まずコマンドラインから入力する内容を記述したファイルを用意する。 この際、説明などのコメントを記述しておくこともできる。 GAP では行の # 以降をコメントとして無視する。 例えば以下の内容のファイルを matrix.gap という名前で用意する。
mat :=
[[1,2,3],
[4,5,6],
[7,8,9]]; # 行列
vec := [1,2,3]; # ベクトル
これを GAP に読み込ませるには、GAP のコマンドラインから
gap> Read("matrix.gap");
gap> vec * mat;
[ 30, 36, 42 ]
とする。読み込むファイルが GAP を実行中のディレクトリにないときには、 ファイルを絶対パス、または相対パスで指定する。 もっと簡単な方法は、入力したい内容をエディタからコピーしてコマンドラインに貼り付けることである。 また GAP 起動時に読み込ませたいのであれば
knoppix@Microknoppix:/media/sdc1/gap$ gap matrix.gap
のようにコマンドラインから GAP のオプションとして読み込ませたいファイルを指定すればよい。 この方法では複数のファイルを読み込ませることができる。
課題. 上記の方法で GAP にファイルを読み込ませ、vec と mat にきちんと代入されていることを確認しなさい。
次に計算結果をファイルに保存する方法を説明する。 もっとも簡単な方法は画面に表示される内容をすべてファイルに保存する方法である。
gap> LogTo("logfile");
gap> 1 + 2;
3
gap> LogTo();
LogTo に保存するファイル名を指定すると保存が開始される。 このあと LogTo() とするまでの間、画面に表示されるものと同じ内容がすべてファイルに書き出される。 とりあえず、すべてを保存しておき、あとで必要な部分を整理する場合に便利であろう。
課題. LogTo を試してみなさい。
次に説明する方法は必要なデータのみをその出力形式まで指定して保存する。 工夫すれば出力を LaTeX の形式で保存するなど、いろいろなことができる。
gap> a := 4;; b := 6;;
gap> Print("Gcd(", a, ", ", b, ") = ", Gcd(a, b), "\n");
Gcd(4, 6) = 2
gap> PrintTo("logfile", "Gcd(", a, ", ", b, ") = ", Gcd(a, b), "\n");
gap> AppendTo("logfile", 1 + 2, "\n");
Print は複数の値を , (カンマ) で区切って指定でき、画面にそれを出力する。 文字列は " " でくくって指定する。 上の例では "Gcd(" を文字列として出力させた後、変数 a の値、 文字列 ", " などを指定している。 最後の "\n" は改行を表す特殊記号である。 PrintTo は Print と同じ書式で、出力をはじめの引数に指定したファイルに対して行う。 この例では logfile に書き出す。 PrintTo は指定した名前のファイルが存在するとき、すでに書き込んである内容をすべて消して書き込んでしまうので注意が必要である。 すでに書いてある内容を消さずに、出力を書き足すときには AppendTo を使う。
課題. PrintTo と AppendTo を試してみなさい。
ベクトルは [1,0,1] のように [] でくくって記述する。 すなわち、スカラーを成分とするリストを行列と見る。 加法やスカラー倍は以下のように自然な方法で記述できる。
gap> [0,1,0] + [1,2,3];
[ 1, 3, 3 ]
gap> 2 * [2,3,4];
[ 4, 6, 8 ]
gap> [1,2] + [1,0,0];
[ 2, 2, 0 ]
三つ目の例のように次元の異なるベクトルの加法も許される。 行列は 2 次元配列、すなわち行ベクトルを成分とするリスト、で表される。
gap> A := [[1,0],[0,1]];;
gap> A^2;
[ [ 1, 0 ], [ 0, 1 ] ]
gap> A^(-1);
[ [ 1, 0 ], [ 0, 1 ] ]
gap> B := [[1,1],[1,1]];;
gap> B^(-1);
fail
加法やスカラー倍はベクトルと同様である。 また行列の積や累乗も自然に計算できる。 逆行列は Inverse(A) として求めることもできるが A^(-1) でもよい。 正則でない行列 B に対しては B^(-1) は fail となる。
行列の固有値や固有ベクトルは以下のように求めることができる。
gap> A := [[3,2,0],[2,3,0],[0,0,5]];;
gap> Eigenvalues(Rationals, A);
[ 5, 1 ]
gap> Eigenvectors(Rationals, A);
[ [ 1, 1, 0 ], [ 0, 0, 1 ], [ 1, -1, 0 ] ]
ただし、どの数体の範囲で求めるのかを指定する必要がある。 上の例では Rationals (有理数体) の範囲で求めている。 また固有値の重複は求められない。 固有値と固有ベクトルの順番も対応するとは限らない。 有理数体以外の体の指定については、あとで説明する。
多項式を扱うには、まず変数を用意しないといけない。 その際、どのような数体を係数とするのかも指定する。
gap> x := Indeterminate(Rationals, "x");
x
これで多項式を扱うための準備ができたことになる。 複数の変数が必要ならば、これを繰り返す。
gap> y := Indeterminate(Rationals, "y");
y
gap> (x + y)^3;
x^3+3*x^2*y+3*x*y^2+y^3
多項式への値の代入、多項式の因数分解は以下の通りである。
gap> f := x^2 + x*y + y^2;
y^2+y*x+x^2
gap> Value(f, [x], [2]);
y^2+2*y+4
gap> Value(f, [x,y], [2,1]);
7
gap> Factors(x^2-1);
[ x-1, x+1 ]
多項式環は以下のように定義する。 x と y はすでに変数として用意されているものとする。
gap> R := PolynomialRing(Rationals, [x,y]);
Rationals[x,y]
GAP では円分体 (cyclotomic field) に含まれる数以外はうまく扱えない。 例えば 2 の 3 乗根は円分体に含まれないので扱いにくい。 円周率などの超越数も扱えない。 GAP でよく用いられる数は
1 の n 乗根 E(n)
整数 a の平方根 ER(a)
である。 ER(a) は E(n) の多項式として表示される。 もちろん、これらの数の多項式で書ける数はすべて扱うことが出来る。
gap> ER(-2)^2;
-2
gap> ER(-2);
E(8)+E(8)^3
有理数体 Rationals に 1 の n 乗根 E(n) を添加した体 (円分体) は CF(n) で表される。
gap> M := [[0,1],[2,0]];
[ [ 0, 1 ], [ 2, 0 ] ]
gap> Eigenvalues(Rationals, M);
[ ]
gap> Eigenvalues(CF(8), M);
[ E(8)-E(8)^3, -E(8)+E(8)^3 ]
平方根 E(a) を扱うために CF(n) として n をいくつにすればよいかは、 それほど簡単ではない。 これを知るためには
gap> DefaultField(ER(3));
CF(12)
などとすればよい。
q 個の元からなる有限体 (finite field, Galois field) は GF(q) と表される。 GF(q) の乗法群は巡回群となるので、その生成元の一つを Z(q) で表す。 GF(q) の 0 でない元は Z(q)^i と表すことができるので、GAP はこの形で表示する。 また 0 は有理数としての 0 特別するため 0*Z(q) と表される。 GF(q) を有理整数環上の加群と見ることができるので、GF(q) の元に整数をかけることは許される。 また q が q' の約数であるとき、自然に GF(q) は GF(q') の部分体とみなされる。
gap> Z(4)+Z(4)^2;
Z(2)^0
gap> Z(4)-Z(4);
0*Z(2)
gap> 3*Z(4);
Z(2^2)
gap> Z(4) in GF(16);
true
有限体の元を成分とする行列なども問題なく扱うことが出来る。
gap> M := [[1,1],[1,0]] * Z(4)^0;
[ [ Z(2)^0, Z(2)^0 ], [ Z(2)^0, 0*Z(2) ] ]
gap> Eigenvalues(GF(2), M);
[ ]
gap> Eigenvalues(GF(4), M);
[ Z(2^2), Z(2^2)^2 ]
GAP はそれ自身で多くの機能をもっているが、もちろん何でも出来るわけではない。 そこで多くの人が GAP 上で特定の用途に用いるためのプログラムを作成し、パッケージとして公開している。 パッケージを利用することによって GAP の利用できる範囲は大きく膨らむ。 しかしながら、今回利用している Knoppix/Math の環境では DVD から起動しているため、 新たなパッケージをインストールこることはできない。 パッケージを利用したければ Knoppix をハードディスクにインストールしたり、 あるいは UBUNTU などの Linux ディストリビューションに GAP をインストールするなどする必要がある。 GAP は MS Windows にもインストールすることができるが、いくつかのパッケージは Windows 上では利用できない。
GAP では一連の計算をファイルにまとめて書いておき、 それを読み込むことによって複数の処理を一度に行うことができる。 しかし、同じ処理の繰り返しや、条件によって異なる処理を行うなど、 単にコマンドを並べておくだけよりも、細かい処理を行うこともできる。 ここではこれらの処理の方法を解説する。
ここで述べる内容はそのすべてを覚える必要はない。 特に目的もなくプログラミングを学ぶことは楽しくないので、身につかないことが多い。 しかしどんなことが出来るのかを知っていれば、必要なときにその方法を調べて利用できるので、 説明を読みながら一通り試してみるといいだろう。
リストは数列のようなものと考えてよい。 ものの列であって、順序があり、また要素の重複も許される。 GAP では [1,3,6,2,1] のように , (カンマ) でくくって [ ] で囲んで表される。 リスト L の i 番目の要素は L[i] で表される。 要素は必ずしも連続していなくてもよいが、定義されていない要素にアクセスしようとするとエラーとなる。
gap> L := [1,2];
[ 1, 2 ]
gap> L[5] := 3;
3
gap> L;
[ 1, 2,,, 3 ]
gap> L[2];
2
gap> L[4];
List Element: [4] must have an assigned value
not in any function
Entering break read-eval-print loop ...
you can 'quit;' to quit to outer loop, or
you can 'return;' after assigning a value to continue
brk> quit;
リストの成分は同種のものである必要はない。 例えば、数字と行列が混在しても構わない。
連続する数字のリストは [1..10] のように省略することができる。 また等差数列は [1,3..11] のようにはじめの 2 項と最終項を指定することによって書くことができる。 これは後で説明するループ (繰り返し) で多く用いられる。
二つのリストをつなげるには Appen, リストの最後に要素を加えるには Add を用いる。 要素を消したいならば Unbind を用いる (Unbind は定義済みの変数を未定義に戻すときに用いられ、リスト以外にも使える)。
gap> L := [1,3];
[ 1, 3 ]
gap> M := [2,3];
[ 2, 3 ]
gap> Append(L,M);
gap> L;
[ 1, 3, 2, 3 ]
gap> Add(L,5);
gap> L;
[ 1, 3, 2, 3, 5 ]
gap> Unbind(L[3]);
gap> L;
[ 1, 3,, 3, 5]
集合はリストの特別なものとして扱われる。 重複がなく、かつ小さいものから順に並んでいるリストが集合である。 リストから集合を作るには Set を用いる。
gap> L := [1, 2, 3, 2, 1];
[ 1, 2, 3, 2, 1 ]
gap> IsSet(L);
false
gap> S := Set(L);
[ 1, 2, 3 ]
gap> IsSet(S);
true
集合の基本的な演算は Union, Intersection であろう。 また、ある物が集合の要素であるかどうかは次の例のようにして確認できる。 集合から要素を外すには RemoveSet を用いる。
gap> S := [1,2,3];
[ 1, 2, 3 ]
gap> T := [2,3,4];
[ 2, 3, 4 ]
gap> Union(S, T);
[ 1, 2, 3, 4 ]
gap> Intersection(S, T);
[ 2, 3 ]
gap> 1 in S;
true
gap> 1 in T;
false
gap> RemoveSet(S, 1);
gap> S;
[ 2, 3 ]
まず簡単な例を示す。
gap> List([1..10], IsPrimeInt);
[ false, true, true, false, true, false, true, false, false, false ]
gap> List([1..12], x -> Gcd(12,x));
[ 1, 2, 3, 4, 1, 6, 1, 4, 3, 2, 1, 12 ]
List というコマンドは、一つ目に与えたリストの要素に、二つ目の関数を働かせたものを要素とするリストを返す。 この例では 1 から 10 までの数に IsPrimeInt を働かせるので、素数でない 1 に対しては false を、 素数である 2 に対しては true を、などとなっている。 二つ目に与える関数は、一つ目の例のように簡単なものではない場合もある。 二つ目の例では 1 から 12 までの数に対して、その数と 12 の最大公約数のリストを返している。 x をリストの要素として Gcd(12, x) を要素とするリストを返すのである。 これを利用すると、かなり複雑なものも書くことが出来る。
例えば、自然数 n に関する命題 P(n) が [1..100] の範囲で正しいかどうかを知るためには
gap> Set(List([1..100], P)) = [true];
とすればよい (これは、単なる例であって効率のよい方法ではない)。
課題. 上の例の意味を理解せよ。 そして、この方法がなぜ効率が悪いのかを考察せよ。
上の例と同じ役割をもつ効率のよい方法は以下の通りである。
gap> ForAll([1..100], IsPrime);
false
gap> ForAny([1..100], IsPrime);
true
ForAll でリストの任意の要素に対して正しいかどうか (すなわち ∀) を返し、 ForAny でリストのある要素に対して正しいかどうか (すなわち ∃) を返す。
次に、与えられた集合の要素のうち、ある条件を満たすものだけを抜き出す方法を説明する。
gap> Filtered([1..50], IsPrimeInt);
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 ]
Filtered は一つ目に与えられた集合 (リストでも構わない) に対して、 二つ目に与えられた関数の戻り値が true であるものだけを抜き出した集合 (リスト) を返す。 例えば
gap> L := [1..100];;
gap> L1 := Filtered(L, 条件1);;
gap> L2 := Filtered(L1, 条件2);;
gap> L3 := Filtered(L2, 条件3);;
のように、繰り返し条件で絞り込むことも出来る。
条件に与える関数は複雑ならば自分で書くことも出来る。 これについては後で関数の説明で詳しく述べる。
例. ここで簡単な例を見ておこう。 コマンドの意味などはコメントを見れば分かるだろう。
gap> G := SymmetricGroup(4); # 4 次の対称群
Sym( [ 1 .. 4 ] )
gap> El := Elements(G); # S_4 の元をすべて求める。
[ (), (3,4), (2,3), (2,3,4), (2,4,3), (2,4), (1,2), (1,2)(3,4), (1,2,3),
(1,2,3,4), (1,2,4,3), (1,2,4), (1,3,2), (1,3,4,2), (1,3), (1,3,4),
(1,3)(2,4), (1,3,2,4), (1,4,3,2), (1,4,2), (1,4,3), (1,4), (1,4,2,3),
(1,4)(2,3) ]
gap> List(El, Order); # S_4 の元の位数のリスト
[ 1, 2, 2, 3, 3, 2, 2, 2, 3, 4, 4, 3, 3, 4, 2, 3, 2, 4, 4, 3, 3, 2, 4, 2 ]
gap> Filtered(El, x -> (Order(x)=2)); # S_4 の元のうち、位数 2 のもの
[ (3,4), (2,3), (2,4), (1,2), (1,2)(3,4), (1,3), (1,3)(2,4), (1,4),
(1,4)(2,3) ]
同じような処理を繰り返し行うときにはループを用いる。 ループにはここで説明する for を使う方法と、 あとで説明する while を用いる方法がある。 あらかじめループする回数や範囲が決まっているときには for を用い、 そうでないときには while を用いることが多い。
gap> sum := 0;
0
gap> for i in [1..10] do sum := sum + i; od;
gap> sum;
55
この例では 1 から 10 までの和を求めている。 まず sum に 0 を代入しておき、 i をリスト [1..10] の要素を動かして、 順番に sum に加えている。 for ループをインデント (行頭の空白) をつけて見やすく書くと以下のようになる。
for i in [1..10] do
sum := sum + i;
od;
for で i を動かし、do と od で囲まれた部分を繰り返し処理する。
条件分岐は以下のように行う。
gap> n := 10;
10
gap> if IsPrimeInt(n) then Print("Prime\n"); else Print("Not Prime\n"); fi;
Not Prime
ファイルに書く場合はインデントを適当につけて
if IsPrimeInt(n) then
Print("Prime\n");
else
Print("Not Prime\n");
fi;
のように書いた方が見やすい。 true でないときに何もしないのであれば else 以下を省略して fi; で終わってもよい。 また then と else のあとには複数のコマンドを書いてもよい。 また if のあとに更に条件分岐を続けたいときは、 if (条件1) ... elif (条件2) ... elif (条件3) ... else ... fi; などとして書くことができる。
ここで条件式の書き方についてまとめておこう。 まず簡単な比較は以下の通りである。
gap> 2 < 3;
true
gap> 2 <= 3;
true
gap> 2 > 3;
false
gap> 2 >= 3;
false
gap> 2 = 3;
false
gap> 2 <> 3;
true
等しいかどうかは = で、等しくないことは <> で表されることに注意しておく。 次に and (かつ) と or (または) についてであるが、これはそのまま書けばよい。
gap> (2 < 3) and (3 > 4);
false
gap> (2 < 3) or (3 > 4);
true
必要ならばカッコを自由に付けることが出来る。
for ループは、予め繰り返しの範囲が分かっている場合に用いられることが多いのに対し、 while ループは繰り返しの回数が分からないときによく使われる。 while は与えられた条件が満たされる限り、指定されたコマンドを繰り返す。 条件式などに誤りがあると無限ループに陥りやすいので注意が必要である。 プログラム例は、見やすくインデントをつけて書く。
sum := 0;; i := 1;;
while i <= 10 do
sum := sum + i;
i := i + 1;
od;
これを実行すると sum は 1 から 10 までの和となり、結果として 55 となる。 for と異なり i の値も変更してやらなければならないことに注意しておく。 i := i + 1 を書き忘れれば、i は 1 のままなので、条件が満たされ続け、無限ループになる。
条件分岐やループは、ある程度まとまったプログラムを書くときにしか利用しないかもしれないが、 次に説明する関数を書くときには多用することになるであろう。
GAP では与えられた関数を使うだけでなく、自分で新しい関数を書いてそれを利用することもできる。 もっとも簡単な例は以下のようなものである。
gap> PlusOne := function(x) return x+1; end;
function( x ) ... end
gap> PlusOne(1);
2
関数をインデントをつけて書くと以下のようになる。
PlusOne := fucntion(x)
return x + 1;
end;
例を見れば雰囲気は分かると思うので、ここではこれ以上は説明しない。 簡単な例題をやっておこう。
課題. 与えられた数の 2 乗を求める関数を書いて、その動作を確認しなさい。
いくつかの例を見ながら、いくつかのことを説明する。
例. 与えられた自然数 n に対して 1 から n までの和を返す関数。
sum := function(n)
local i, ans;
ans := 0;
for i in [1..n] do
ans := ans + i;
od;
return ans;
end;
ここで、変数 i と ans は local と宣言されている。 このような変数を局所変数という。 局所変数はこの関数内でのみ有効な変数であり、関数の外で同じ名前の変数が利用されていても、 完全に別な物として扱われ、関数内で値が書き換えられても、外の変数を書き換えたりしない。 局所変数を利用することによって関数の独立性が高まり、 その関数を利用する人が関数内で利用されている変数名を気にしなくても済む。 関数内で利用する変数は、特別な理由がない限り局所変数にした方がよいであろう。
例. 与えられた数が正ならば 1 を、負ならば -1 を、0 ならば 0 を返す関数。
sign := function(a)
if a > 0 then
return 1;
elif a < 0 then
return -1;
else
return 0;
fi;
end;
この例のように return はプログラムのどこに書いてあってもよく、 また複数書いてもよい。 ループが何重かになっているとき、それを抜けるのは面倒であるが、 関数としておけば、どこからでも return で抜けることができる。
課題. 与えられた有限群 G に対して、G の位数 2 の元の個数を返す関数を書きなさい。
プログラムを書き慣れていない人は複雑な処理を行うときに、 一つの大きなプログラムや関数を書くことが多い。 そのようにすると誤りも混入しやすく管理が煩雑になる。 大きなプログラムを書くときには簡単な機能を持った小さな関数をたくさん用意して、 それを組み合わせてプログラムを作ると考えたほうがよい。