MikoScript3 言語仕様
連想配列
「連想配列」は、インデックスに、整数だけでなく、文字列や、実数が使える配列です。
連想配列の各要素は、箱で、その名前が「連想名」になっています。この連想名は、その
インデックスから想起されます。連想配列の各要素は、事前の宣言によって、所定の個数
がまとめて生成されるのではなく、その都度個別に、必要分だけが、生成されます。また、
個別に破棄することもできます。さらに連想配列では、その次元数や各要素のデータ型が、
固定的ではなく、個別に動的に変化できます。このように、連想配列は、次章で述べる
「純粋配列」と比較して、かなりの柔軟性がありますが、反面、1要素あたりのメモリー
使用量は多くなります。
●1次元の連想配列
連想配列の要素は、1次元の場合、一般に、以下のように表記されます。
配列名[ インデックス ]
これは、「配列名」の箱の中にある [ インデックス ] という連想名の箱を意味します。
例えば、
A[1] は、A という名前の箱の中にある [1] という連想名の箱で、
Dog["ポチ"] は、Dog という名前の箱の中にある ["ポチ"] という連想名の箱です。
上記の表記で、「配列名」は省略できます。その場合、[ インデックス ] 単体になり
ますが、それはそれ自身で、[ インデックス ] という連想名の箱を意味します。
上記表記内の「インデックス」は、1つの式か、または、コンマで区切られた複数の式
になります。この式には、任意の式が使えますが、その評価値は通常、整数か、文字列か、
実数になるようにして使います。これ以外の特殊な使い方については、別途説明します。
以下に、[ インデックス ] の表記として、有効な例を挙げます。
[3] ["太郎"] [i] [1.23] [ 5, -6 ] [i,j] [ x + y, f(z) ]
インデックスを複数の式にすると、一見、多次元配列のように見えます。実際、それで、
多次元配列的な使い方もできますが、これはあくまで、1つの連想名に対応するだけです。
インデックスの各式は、省略しても、文法エラーにはなりません。また、インデックス
自体が、空でも構いません。以下に、その例を挙げます。
[ 8, , i + 2 ] [ "春", "サクラ", ] [ , name ] []
●多次元の連想配列
連想名の表記に使う角括弧 [ ] は、文法的には、(左項が省略可能な)後置演算子に
なります。そのため、角括弧 [ ] の後に、さらに角括弧 [ ] を連続して配置できます。
一般に、n個の角括弧 [ ] を連続して配置した場合は、n次元の連想配列になります。
例えば、2次元の連想配列の場合、その表記は、以下のようになります。
配列名[ インデックス1 ][ インデックス2 ]
これは、「配列名」の箱の中にある [ インデックス1 ] という連想名の箱の中にある
[ インデックス2 ] という連想名の箱を意味します。例えば、
A[3][4] は、
A という箱内にある [3] という連想名の箱内にある [4] という連想名の箱です。
Map["東京都"]["千代田区"]["秋葉原"] は、
Map という箱内にある["東京都"] という連想名の箱内にある ["千代田区"] と
いう連想名の箱内にある ["秋葉原"] という連想名の箱です。
多次元の場合でも、「配列名」は、省略可能です。その場合、上記の2次元では、
[ インデックス1 ][ インデックス2 ]
という表記なります。これは、[ インデックス1 ] という連想名の箱の中にある
[ インデックス2 ] という連想名の箱を意味します。
各「インデックス」に関する原則は、多次元の場合でも、1次元の場合と同じです。
●連想配列の要素
連想配列の要素が、箱であるということは、既に述べた通りです。通常の箱と違うのは、
箱名だけです。通常の箱名は、識別名で表わしますが、連想名は、上述のように、角括弧
演算子を使って表わします。それ以外の実質的な違いは、何もありません。したがって、
連想配列の要素は、通常の箱と同様の扱いになります。そのため、連想配列の要素の扱い
に関しては、特に説明するまでもありませんが、主なものだけ述べておきます。
連想名は、その前に、スコープ規定演算子を付加できます。例えば、
::Gate[8] は、グロバールスコープ内の Gate[8] という連想配列要素で、
^Mail[45] は、モジュールスコープ内の Mail[45] という連想配列要素です。
また、連想名の前に、スコープ規定演算子がなければ、デフォールトスコープになります。
連想配列の要素の中に、別の箱を入れることができます。例えば、
["花子"].Age = 17;
は、連想名が ["花子"] というの箱の中の Age という箱に 17 を代入します。
連想配列のインデックスは、整数の場合でも、0 から始める必要はなく、連続している
必要もありません。また、異なる型が混在しても、構いません。例えば、
A[-1] = "Hello";
A[39] = "Thank you";
A["太郎"].Age = 20;
連想配列の要素は個別に削除できます。また、連想配列全体を削除することもできます。
例えば、上記の連想配列の要素 A[-1] を削除するには、
delete A[-1];
とします。また、連想配列 A 全体を削除するには、
delete A;
とします。
同一連想配列内の要素をまとめて設定するには「構造体設定文」(詳細次々章参照)が
使えます。例えば、以下のようになります。
Fruit ::=
{
.["apple"] = "りんご";
.["orange"] = "みかん";
.["peach"] = "もも";
.["banana"] = "バナナ";
}
これは、以下のようにするのと同じです。(厳密には若干の違いがあります)
Fruit["apple"] = "りんご";
Fruit["orange"] = "みかん";
Fruit["peach"] = "もも";
Fruit["banana"] = "バナナ";
●初期化データの設定(1次元の場合)
連想配列の各要素に対して、初期化データを設定することができます。この表記形式は、
以下のようになります。
連想配列 = { 初期化データ列 };
「初期化データ列」は、各要素値をコンマで区切った並びになります。初期化データ列
によって設定される連想配列の各要素のインデックスは、無条件に、0, 1, 2, 3, ... の
順の整数になります。例えば、
A = { 10, 20, 30, 40, 50 };
とすると、A[0], A[1], ..., A[4] に、それぞれ、10, 20, ..., 50 が設定されます。
初期化データが連想配列に設定される時、対象の連想配列が存在していなければ、その
連想配列は、新規に生成されます。一方、既に存在している場合は、その連想配列に上書
きされます。
初期化データ列内の各要素値は、必ずしも定数である必要はなく、一般的に、式にする
ことができます。例えば、
x = 100;
function f() { return 200; }
B = { x, f(), x + f() };
を実行すると、B[0], B[1], B[2] は、それぞれ、100, 200, 300 となります。
初期化データ列内の各要素値のデータ型は、数値に限定されているわけではありません。
任意のデータ型が使えます。また、異なるデータ型を混在させることもできます。
初期化データ列の各要素値は、省略することができます。その場合には、その省略要素
に対応する連想配列の要素への代入は行なわれません。つまり、その連想配列の要素が、
不在なら不在のままで、既存なら現状のままになります。例えば、前述のように、初期化
データが設定された純粋配列 A に対して、
A = { , , -30, , -50, , -70 };
を実行すると、
A[0], A[1], A[2], A[3], A[4], A[6] は、それぞれ、
10, 20, -30, 40, -50, -70 となります。
なお、A[5] は、もともと不在で、それに対応する初期化データも省略されているために、
この設定後も不在のままです。
連想配列の要素に対しても、初期化データ列で設定できます。例えば、
C[2] = { "X", "Y", "Z" };
では、C[2][0], C[2][1], C[2][2] に、それぞれ、"X", "Y", "Z" が設定されます。
●初期化データの設定(2次元以上の場合)
前節の「初期化データ列」の波括弧の囲いは、入れ子にすることができます。例えば、
P = { 0, 1, { 20, 21, 22 }, 3 };
では、P[0], P[1], P[2], P[3] は、それぞれ、0, 1, { 20, 21, 22 }, 3 になりますが、
P[2] = { 20, 21, 22 }
なので、P[2][0], P[2][1], P[2][2] が、それぞれ、20, 21, 22 になります。
入れ子がもう1段深くなる場合も、同様です。
Q = { 0, 1, { 20, { 210, 212 }, 22 }, 3 };
では、Q[0], Q[1], Q[2], Q[3] は、それぞれ、0, 1, { 20, { 210, 212 }, 22 }, 3 に
なります。
Q[2] = { 20, { 210, 212 }, 22 }
なので、Q[2][0], Q[2][1], Q[2][2] が、それぞれ、20, { 210, 212 }, 22 になります。
Q[2][1] = { 210, 212 }
なので、Q[2][1][0], Q[2][1][1] が、それぞれ、210, 212 になります。
●連想配列へのデータの追加
連想配列にデータを追加するには、システム組み込みリレー型関数 'data を使います。
この書式は、以下のようになります。
連想配列 'data( 追加データ列 )
ここで、「追加データ列」は、各追加データをコンマで区切った並びになります。これは、
入れ子にはできません。各追加データのデータ型には、特に制約はありません。
以下に、例を示します。
A = { "春", "夏" };
で、生成生成された連想配列に対して、
A'data( "秋", "冬" );
とすると、A[0], A[1], A[2], A[3] は、それぞれ、"春","夏","秋","冬" になります。
なお、'data の対象の連想配列が存在しなかった場合、その連想配列は、新規に生成さ
れて、それに「追加データ列」が設定されます。
●連想名
連想配列の [ インデックス ] が、実際どのような文字列になっているかは、'name と
いうリレー型関数(システム組み込み)で調べることができます。例えば、
print [ 1 ] 'new! 'name ;
print [ "ABC XYZ" ] 'new! 'name ;
print [ 1.23 ] 'new! 'name ;
print [ "夏", 8,-1 ] 'new! 'name ;
を実行すると、以下のようにプリントされるので、各連想名が分かります。
+1
ABC XYZ
%DPPDKOBEHKOBEHKO
夏,+8,-1
インデックスから連想名への変換は、本言語のバージョンや実装によって、変わる場合
があります。しかし、次の事項は、保証されます。
(1) インデックスの式の個数が同じ場合、各評価値が、同じデータ型で、異なる値なら、
その連想名は、必ず異なる文字列になる。たとえば、インデックスが、[ 整数 ] の
場合、その整数値が異なれば、それに対応する連想名は、必ず異なります。
(2) インデックスの式が1つだけで、その評価値のデータ型が文字列なら、その連想名は、
その文字列自身になる。
現状では、[ 整数 ] と [ 文字列 ] の場合、両者の式の値が異なっても、それに対応
する連想名が、たまたま同じになることもあります。たとえば、[1] と ["+1"] は、同じ
連想名になります。また、[ 整数 ] と [ 実数 ] の場合、両者の式の値が同じでも、その
連想名は、同じではありません。例えば、[1] と [1.0] の連想名は、異なります。
上記事項(2) により、例えば、ABC という箱名は、["ABC"] の連想名と同じです。連想
名を使わない箱名は、識別名として有効な文字しか使えませんが、[ 文字列 ] の連想名
の場合は、任意の文字列を箱名にできます。また、この形式の連想名を使えば、対象の箱
が連想配列でなくても、インデックス的なアクセスができます。例えば、X1, X2, X3 と
いう名前の箱がある場合、
for( i = 1 ; i <= 3 ; i++ )
print [ "X" + i'd ];
で、各箱の内容をプリントできます。
連想配列のインデックスが整数の場合、その整数値と連想名は、'ixn というリレー型
関数(システム組み込み)で、相互に変換できます。例えば、
for( i = 1 ; i <= 3 ; i++ )
{
A[i] = i + 100;
print A[i], A[i]'name, A[i]'name'ixn, i'ixn, A[ i'ixn ];
}
を実行すると、以下のようにプリントされます。
101, +1, 1, +1, 101
102, +2, 2, +2, 102
103, +3, 3, +3, 103
ちなみに、次の実行でも、同じ結果がプリントされます。
for( i = 1 ; i <= 3 ; i++ )
A[i] = i + 100;
for( p := A'first ; p'ref? ; p := A'next )
print p, p'name, p'name'ixn, p'name'ixn'ixn, A[ p'name ];
'ixn は、連想配列の整数インデックスが複数ある場合にも対応しています。例えば、
[ 3, 4 ]'new!; // 連想名の箱を生成
Name = [ 3, 4 ]'name; // その連想名を取得
( i, j ) = Name'ixn; // その連想名を2つの整数インデックスに変換
name = 'ixn( i, j ); // その2つの整数インデックスを連想名に変換
print Name'quote, i, j, name'quote; // 各値をプリント
これを実行すると、「"+3,+4", 3, 4, "+3,+4"」とプリントされます。
ところで、連想ラベル は、基本的に、連想配列と同じ表記になりますが、ここでは、
その連想名と、連想配列の連想名との関連について述べます。連想ラベルの連想名は、
現状、連想配列の連想名の先頭にピリオッド「.」を付けた文字列になっています。
そのため、両者の相互変換は非常に簡単です。
さて、連想ラベルに、ラベル名演算子「:」を適用すれば、その連想名の文字列が
得られますが、これを使えば、存在していない連想配列の連想名でも取得できます。
ちなみに、'name では、その対象の連想配列が存在している必要があります。次に、
この例を示します。
A = :[ -3, -8 ]'shift(-1); // 連想ラベルから連想配列の連想名に変換
( i, j ) = A'ixn; // その連想名を2つの整数インデックスに変換
B = 'ixn( i, j ); // その2つの整数インデックスを連想名に変換
print A'quote, i, j, B'quote; // 各値をプリント
これを実行すると、「"-3,-8", -3, -8, "-3,-8"」とプリントされます。
次の例は、掛け算の九九表を連想配列に作成して、そのなかから、7の行を抜き出して
プリントします。(なお、以下で使用の 'each, 'shift, 'wcmp は当該各所参照 )
// 掛け算の九九表を作成
for( i = 1 ; i <= 9 ; i++ )
for( j = 1 ; j <= 9 ; j++ )
T[ i, j ] = i * j;
// 7の行に該当する要素を抽出してプリント
S = :[ "*", 7 ]'shift(-1);
do T'each with p
{
if( p'name'wcmp( S ) == 0 )
print "%d x %d = %2d"'fmt( p'name'ixn, p );
};
ちなみに、これを実行すると、以下の通りプリントされます。
1 x 7 = 7
2 x 7 = 14
3 x 7 = 21
4 x 7 = 28
5 x 7 = 35
6 x 7 = 42
7 x 7 = 49
8 x 7 = 56
9 x 7 = 63