MikoScript 言語仕様

 連想配列

 「連想配列」は、インデックスに、整数だけでなく、文字列や、浮動小数点数が使える
配列です。連想配列の各要素は、箱で、その名前が「連想名」になっています。この連想
名は、そのインデックスから想起されます。連想配列の各要素は、事前の宣言によって、
所定の個数がまとめて生成されるのではなく、その都度個別に、必要分だけが、生成され
ます。また、個別に破棄することもできます。さらに、連想配列では、その次元数や、各
要素のデータ型が、固定的ではなく、個別に動的に変化できます。このように、連想配列
は、次章で述べる「純粋配列」と比較して、かなりの柔軟性がありますが、反面、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