辞書配列とは、配列の各要素に名前をつけた配列で、連想配列とも呼ばれます。

配列が小規模のデータベースに向いているのに対し、辞書配列は複数のデータをまとめて管理する用途に使われます。

辞書配列クラスの使い方

今回使うクラスの名前はDictionaryです。 よって、実物を作るのは次のようにします。

var dic = new Dictionary();
				

また、次のように書く事も出来ます。

var dic = %[];
				

配列の時と違い、[]の頭に%が付いている事に注意してください。

辞書配列の基本

辞書配列というのは、簡単に言うと名前の付いた配列です。

配列

このように、辞書配列は箱に名前を付ける必要があります。 よって、配列とは違い、順番に箱が並べられるわけでは無い(順不同)事に注意してください。

辞書配列の各箱を参照するには次のように書きます。

辞書配列名["箱の名前"]

dicという辞書配列のaという名前の箱を使うなら次のように書きます。

dic["a"]

これで辞書配列dicの箱aを参照出来ます。 この時の箱の名前をキーと呼びます。 辞書配列は「キーと値」というペアの組み合わせで成り立っているのです。

辞書配列

辞書配列の要素には名前があるので、次のように参照することも出来ます。

dic.a

オブジェクトのメンバーへの参照と同じ形ですね。

しかし、基本的に辞書配列と通常のオブジェクトは少し扱いが異なりますから、この書き方は推奨しません。 辞書配列の名前を分かりやすくしたり(例えば先頭にdicと付けるなど)、dic[""]の書き方にしておく事をお勧めします。

配列のキーの名前は変数の命名規則に則りますが、 システムの予約語を指定することが出来ます。

dic['return'] = "戻る";

ただし、ピリオド( . )を使った参照は出来ません。

dic.return = "戻る"; ←エラー

出来るだけ[]を使った指定を推奨する理由の一つです。

要素に代入する

配列の時のように、辞書配列の中に箱を作ります。 辞書配列は名前を付けますので、配列のように順序を気にする必要はありません。

var dic = new Dictionary();
dic["first"]=1;
dic["second"]=2;
dic["third"]=3;
				
辞書配列への代入

これでfirstという箱に1、secondという箱に2、thirdという箱に3がそれぞれ代入出来ました。 この使い方は、普通の変数と全く同じです。

辞書配列は、配列のように数値で変数を管理しませんので、for文を利用した繰り返し処理は出来ません。 よって、後からメンバー変数を追加できるだけのクラスという考え方も出来ます。

ある複数の変数があり、それが同じグループに属していた場合、 それらの変数を辞書配列で管理する、という使い方が辞書の基本ですので、あまり繰り返し処理の必要性は無いのかもしれません。

使い方としては、KAGで使われているように、ゲームのセーブデータを辞書配列として保存する、というような方法が良い例です。

簡単な代入方法

辞書配列も、簡単に代入する記述が複数ありますので、 新しい記述と併せて紹介しておきます。

//辞書配列の初期化(要素の数は0)
var dic = new Dictionary();
				
//辞書配列の初期化(要素の数は0)
var dic = %[];
				
//辞書配列の初期化と代入
var dic = new Dictionary();
dic["fst"] = 1;
dic["snd"] = 2;
dic["thd"] = 3;
:
				
//辞書配列の初期化と代入
var dic = %["fst"=>1,"snd"=>2,"thd"=>3];
				

"箱の名前" => 代入する値

これを一つのペアとして、複数ある場合は配列と同じようにカンマ( , )で区切ります。 当然、代入する値が文字列の場合はダブルクォート("")で囲む必要があります。

//辞書配列の初期化と代入
var dic = %[
    "fst"=>1,
    "snd"=>2,
    "thd"=>3
];
				

配列の時と全く同じ考え方です。 自由な書式で書けることを利用しています。

//辞書配列の初期化と代入
var dic = %[
    "fst",1,
    "snd",2,
    "thd",3
];
				

「=>」の代わりにカンマが利用できます。この書き方は配列と全く同じですが

キー,値,キー,値...

という並びになる点に注意してください。 これは少しややこしいので、素直に「=>」を使う方がスマートに書けますね。

二次元辞書配列

前回作ったテスト結果を表示するプログラムですが、どうも使いづらいですね。 例えば、算数のテスト結果を知るのに、次のように書く必要があります。

pupil[1][2]

最初の1は配列の順序なのですが、2番目の2は算数の点数が入っている箱です。 従って、各テスト結果を導き出すには、箱の順序を覚えておく必要があります。これは使いづらいですね。

こんな時、各点数が何の科目なのかを分かりやすくする為、名前が付けられると便利です。 ここで辞書配列が活躍するわけです。

辞書配列も、配列と同じように二次元辞書配列にする事が出来ます。辞書配列の辞書配列…というモノですね。

しかし、一般に辞書配列を多重に使う事は多くありません。 繰り返し処理が行えない為、あまり膨大なデータベースのようにしてしまうと、その管理が難しくなるからです。

今回は配列の辞書配列という方法を紹介します。 配列の要素の中に辞書配列を入れるという書き方です。

//配列に辞書配列を入れる
var dim = 
[
  //これらは辞書配列
  %["name"=>"山田","point"=>100],
  %["name"=>"田中","point"=>90]
];
				

これを利用し、前回作ったテスト結果を表示するプログラムを改良しましょう。

var pupil =
[
    %[
      "name"=>"山田",
      "jpn"=>100,"calc"=>90,"science"=>88,"society"=>95
    ],

    %[
      "name"=>"斉藤",
      "jpn"=>65,"calc"=>75,"science"=>80,"society"=>99
    ],

    %[
      "name"=>"田中",
      "jpn"=>90,"calc"=>92,"science"=>74,"society"=>100
    ]
];

var result="名前\t国語\t算数\t理科\t社会\n";

//配列pupilに対して全て
for ( var i=0; i<pupil.count; i++)
{
    result += pupil[i]["name"]+"\t"+
              pupil[i]["jpn"]+"\t"+
              pupil[i]["calc"]+"\t"+
              pupil[i]["science"]+"\t"+
              pupil[i]["society"]+"\n";
}
System.inform(result);
				

この方法では二つのメリットがあります。

一つは生徒の名前と各科目の点数を見つけやすい事。 nameやscienceという単語を使っていますので、名前かな?科学かな?などと、キーの名前から値が連想できます。 通常の配列だと、数字ですから何を指しているのか分かりづらかったですよね。

このようにキーの名前から値が連想できる配列という点から、 辞書配列は連想配列とも呼ばれます。

もう一つは多重ループを必要としない事。 辞書配列のキーは、使う側がしっかり管理している事を前提として使うものなので、一つのループ内で済みます。 これで読みづらい事は無いでしょうし、分かり易いという点でも良いです。

辞書配列のループ

通常、辞書配列は普通の配列のようにfor文などでループさせる事は出来ません。 「キー」と「値」という特殊な配列になっている為、 辞書配列全てに対して処理を行うには、辞書配列のキーを全て知っておかなければならないからです。

逆に言えば、辞書配列のキーさえ知る事が出来れば、for文の繰り返し処理を行う事が出来ますね。 例えば、キーの名前を全て配列に保存し、その要素をキーとして辞書配列を操作します。

var dim = ["id1","id2","id3"];
for ( var i=0; i<dim.count; i++)
{
    dic[dim[i]] = "新しい要素";
}
				

問題は、どうやって辞書配列のキーを配列に保存するかですが、 配列クラスのメンバー関数である「assign」を使うと解決出来ます。

var dim = [];
dim.assign( 辞書配列 );
				

assignは他の配列をコピーする関数ですが、辞書配列も指定できる事を利用します。 リファレンスには次のように書かれています。

辞書配列 (Dictionaryクラスのオブジェクト) をコピー元配列に指定した場合は、その辞書配列の各メンバが、名前、値の順に交互に現れる配列となります。メンバの順番は不定となります。

つまり、こういう事です。

dim = ["キー","値","キー","値"];

配列の中に保存されているキーは、箱番号で言うと「0,2,4,6...」というように現れます。 すなわち、二つ置きに現れる事を覚えておきましょう。

では、これを踏まえて実際に動かす例を見てください。

// 操作対象の辞書配列
var dic = %[
    "fst"=>1,
    "snd"=>2,
    "thd"=>3
];

// 辞書dicのコピー配列
var dim = [];
// 辞書dicのキーを保存する配列
var key = [];
dim.assign(dic);

// キーを配列keyに保存する
for (var i=0; i<dim.count; i+=2)
{
    key[key.count] = dim[i];
}

// 辞書dicの要素全てを操作
for (var j=0; j<key.count; j++)
{
    // この例では各要素に1を加算する
    dic[key[j]] += 1;
    System.inform(key[j]+"="+dic[key[j]]);
}
				

※実行結果からも分かるとおり、辞書配列の順序はソートされません。 希望の並びにしたい場合は、配列クラスの「sort」関数を使ってください。

この手法を利用すると、様々なデータに応用できますので、是非うまく使ってください。 また、辞書配列を受け取り、キー配列を返すような関数を作っておくと、いろいろな場面で使い回せますのでお勧めです。

コンテキスト

TJS2リファレンスを見ると、辞書配列クラスもいくつかのメンバー関数を持っているようです。 辞書配列をコピーするassign、全要素を消去するclear等。 ところが、次のプログラムを書くとエラーになってしまいます。どういう事でしょうか?

var dic = %[];
dic.clear();
				

エラー内容は「メンバーclearが見つかりません」とあります。 これはつまり、オブジェクトdicのメンバーとして、clear関数が存在しないという事です。 リファレンスに目を凝らしましょう。

Dictionary クラスのオブジェクトは、作成された状態ではメンバを何一つ持っていません。 たとえば、assign メソッドを使おうと思って、Dictionary クラスのオブジェクト dict に対してdict.assign(src) のように記述しても、dict が assign というメソッドを持っていないためにエラーになります。

つまり、Dictionaryクラスはメンバー関数を持っているけど、Dictionaryクラスのオブジェクトは持っていないという事になります。 設計図であるクラス自体がメンバーを持っていても、オブジェクトが持っていないのでは使い道が無いですね。 ここで、これまでの知識を総動員して考えてください。

  • メンバー関数は、どのクラスに属しているかの情報を持っている
  • クラス(設計図)自体のメンバーを参照出来る
  • クロージャーという機能を使えば、メンバーを外部に持ち出せる

これに加えて、メンバー関数やプロパティはどのオブジェクトに属しているのかという情報も持っているのです。 この時、メンバーが属しているオブジェクトの事をコンテキストと呼びます。 難しい呼び名で申し訳ないのですが、簡単な説明が出来ませんので、出来れば覚えてください。

class myclass
{
    function show(str)
    {
        System.inform(str);
    }
}
var obj1 = new myclass();
var obj2 = new myclass();
				

この時、myclassが持つshow()と、obj1、obj2が持つshow()は同一関数ですが、 それを実行すべきオブジェクトが違います。

myclass.show()のコンテキストは myclass。 obj1.show()のコンテキストはobj1、同じくobj2.show()はobj2がコンテキストです。

これで、辞書配列クラスの謎が解けました。 次回は辞書配列オブジェクトをコンテキストとして、メンバー関数を呼び出す方法を覚えましょう。

incontextof 演算子

辞書配列オブジェクトはメンバーを何一つ持っていませんでした。 よって、dic.clear()という指定は誤りであり、その理由も分かりました。

辞書配列オブジェクトからメンバー関数を呼び出すには、 辞書配列クラスのメンバー関数を持ち出して、コンテキストを変更するという方法しかありません。 ここで、再度リファレンスを頼りにしましょう。

incontextof 演算子は、左にあるものを先に評価し、次に右にあるものを評価します。左にあるものを評価した結果をオブジェクトとして、そのオブジェクトのコンテキスト部分を、右にあるものを評価した結果と置き換えたものが演算全体の結果となります

これを簡単に言うと、左側のオブジェクトのコンテキストを、右側のオブジェクトへ置き換えたものを返す、という事です。

Dictionary.clear incontextof dic

これでDictionary.clear関数のコンテキストがdicに移され、その結果が得られます。

この結果が何者なのかを説明するのは難しいのですが、 簡潔に言うと「コンテキストがオブジェクトdicに移された関数Dictionary.clear自身」と言えます。

この得られた結果を元に変数に代入し、括弧を付けて呼び出せば、 dicをコンテキストとしてDictionary.clearを呼び出すことが出来ます。

var dic = %[];
var p = Dictionary.clear incontextof dic;
// 次の呼び出しはDictionary.clear
// ただ、実行するオブジェクトはdicに置き換えている
p();
				

ただし、通常はこのような書き方をせず、得られた結果を直接実行します。

Dictionary.clear incontextof dic();

このままでは正しい結果が得られませんので、演算式全体を括弧でくくります。 (incontextofの左側がDictionary.clear、右側がdic()という意味になってしまいます。)

(Dictionary.clear incontextof dic)();

では、辞書配列の内容をファイルに保存する関数saveStructを使ってみましょう。

// 操作対象の辞書配列
var dic = %["fst"=>1, "snd"=>2, "thd"=>3 ];

// コンテキストをdicに変更して呼び出す
(Dictionary.saveStruct incontextof dic)(System.exePath+"sample.txt");
				

これを実行して、krkr.eXeと同じ場所にsample.txtが入っている事を確認してください。 また、ファイルの内容がdicと同一になっている点も確認してください。