前回までに配列の使い方の基本を覚えましたね。 今回は配列のメンバー関数を使った配列の応用例を紹介します。

メンバー関数を扱うと、配列の要素を並び替えたり、データをファイルに保存、読み込み…等が行えるようになります。

ストレージを知る

ファイルの入出力を行うには、そのファイルが何処にあるのか、 どこにファイルを保存するのか、それを正確に指示しなければなりません。 例えば、次のように書いても、吉里吉里は理解できません。

var path = "sample.txt";

sample.txtが何処にあるのか、吉里吉里は知らないからです。

吉里吉里では、各ファイルやフォルダを総称してストレージと呼び、 そのファイルの位置情報を示す文字列を統一ストレージ名と呼んでいます。 つまり、ファイル名を指定しなさいと言われたら、この統一ストレージ名で指定すれば良いのです。

詳しいストレージの仕様についてはリファレンスを参照頂くとして、 今回はSystemクラスのプロパティを参照して、二つのストレージ名を知ることに留めます。

System.exePath …吉里吉里本体があるフォルダ

System.dataPath…データ保存場所

吉里吉里本体とは、プログラムを実行する時にクリックしたお魚アイコン「krkr.eXe」を指します。 (ファイル名を変えている場合は読み替えてください。)

データ保存場所とは、吉里吉里本体と同じ場所に作られる、ユーザデータ保存用のフォルダを指します。 (標準で savedata という名前になり、一度でも吉里吉里を実行すると自動的に作られます。)

最初に示した sample.txtの場所を正確に指すには、次のようにします。

System.exePath+"sample.txt" …吉里吉里本体と同じ場所にあるsample.txt

System.dataPath+"sample.txt" …データ保存用フォルダと同じ場所にあるsample.txt

統一ストレージ名は文字列ですから、このように+記号で加算します。 ファイルはデータ保存場所へ保存するのが基本ですから、 今後はSystem.dataPathを原則として使用することにします。

本入門では、統一ストレージ名をストレージ名、 System.dataPathを、単にデータフォルダと書く事があります。

ファイルに書き出す

ゲームやツール類を作ると、必ず必要となるのがファイル操作です。 データの読み書きが出来ないと、データベースソフトとして開発しても、 ソフトを終了すれば全データが消えてしまい、使い物になりませんよね。

TJSでは配列のメンバー関数の呼び出しのみでこれが行えます。

save(ファイル名)

これで、配列内のデータが指定ファイル名で保存されます。 では、配列データをデータフォルダ内に保存する例を見てみましょう。 とは言っても、たった二行の簡単なものです。

var dim = [1 , 2 , 3 , 4 , 5];
dim.save(System.dataPath+"sample.txt");
				

保存されたファイルsample.txtをご覧ください。 配列のデータは次のようになっている点を確認しましょう。

1
2
3
4
5
				

このように、一行目に最初の要素、二行目以降は順番に要素が保存されます。

関数の第二引数に"c"を指定すると、内容が暗号化されます。 "z"を指定するとファイルが圧縮されます。

save(System.dataPath+"sample.txt" , "c") …暗号化

いずれの場合も、通常のテキストエディタでは読み込めませんので、 内容を知られたくないデータを保存する時に用います。

※ただし、プログラムに詳しい方なら、簡単に復号(内容を元に戻すこと)が出来ますので、 この機能に頼りきった仕様にするのはいけません。

ファイルを読み込む

データを保存するだけでは使い道が限られますので、 保存したファイルを読み込む方法も覚えましょう。

ファイルの読み込みも、配列の関数で実装されています。

load(ファイル名)

最初に次のファイルをデータフォルダに作ってください。 名前はloadtest.txtとします。 (三行目は空白にしてください。何も入力せずにリターンキーを押します。)

読み込みのテストです。
これは二行目の文字列です。

9999
void
このように、文字列以外も読み込めます。
				

では、上のファイルを配列に読み込ませましょう。

var dim = [];
dim.load(System.dataPath+"loadtest.txt");

var str="読み込み結果\n\n";
for (var i=0; i<dim.count; i++)
{
    str += dim[i]+"\n";
}
System.inform(str);
				

実行結果から、一行目が配列の要素番号0、 二行目以降は順番に配列の要素に代入されているのが分かります。

また、voidと書いた行は文字列のvoidとされている点に注意してください。 空白やvoidと書いても、全て文字列として読み込まれるという事です。

匿名配列と関数

今回の利用の場合、配列を作って変数に入れ、メンバー関数を呼び出すという、少し手間な事をしています。 これを一文にまとめると、次のように書けます。

var dim = [].load(System.dataPath+"loadtest.txt");

※[].loadは配列クラスを作り、load関数を呼び出します。 その結果を変数dimに入れているので、変数dimはloadtest.txtの内容が入った配列になります。

同様に、関数も名前をつけずに利用できます。

test( function(a){ System.inform(a); } );
function test( f )
{
    f("匿名関数の呼び出し");
}
					

このように、関数や配列といったオブジェクトは、名前を付ける事無く実物を作り、利用出来ます。 これを匿名関数匿名配列と呼ぶ事があります。

最初は扱いが難しいですが、この考え方を利用すれば、 関数や配列を宣言する事無く、関数に渡すという事も可能です。 今は分からなくても、頭に片隅にでも入れておいてください。

func( function(a){}) …関数を関数に渡す

func( [1,2,3,4,5] ) …配列を関数に渡す

ファイルの存在確認

先ほど、loadtest.txtというファイルを作り忘れたり、 名前を打ち間違えたりして、エラーが出なかったでしょうか。

プログラムを作る際は、起こり得る事象を全て考慮して作らないと、 時にはとんでもない不具合が起こったりします。 (飛躍すれば、ウィルスやOSの暴走で、ファイルが勝手に消される可能性もあります。)

よって、今回の場合は読み込むファイルが存在するか…正確に言えば、 指定したストレージが有効かどうかを確かめる必要があります。

Storages.isExistentStorage(ストレージ名)

isExistentStorageはStoragesクラスの関数で、 指定したストレージが存在するなら真、無ければ偽を返してくれます。 これを利用して、if文で処理を分岐させます。

Storagesは吉里吉里のストレージに関する操作や情報を得る事が出来るクラスです。 詳しくはリファレンスを参照してください。

var path = System.dataPath+"無効なファイル.txt";

// ストレージの存在によって分岐
if ( Storages.isExistentStorage(path) )
{
    System.inform("ストレージは存在します。");
}
else
{
    System.inform("ストレージ「"+path+"」は存在しません。");
}
				

この関数を使えば、特定ファイルが存在すれば読み込み、 存在しなければ新しくファイルを書き込んでから、それを読み込み…といった処理が実現できます。 これは、アプリケーションの設定ファイルなどに応用できますね。

配列構造を維持した入出力

loadとsaveを使った読み書きでは、配列が二次元配列、 またはそれ以上の構造になっていた場合、非常に不便です。 関数splitやjoin(当入門では説明しません。)を駆使すれば実現できますが、 今回は配列の構造をそのままに読み書きする関数を紹介します。

まず、配列のデータ構造を維持したまま保存する関数です。

saveStruct(ファイル名)

saveの後ろにStructがくっ付いている以外は、save関数と使い方は変わりません。

var dim = 
[
    [1,2,3,4,5],
    [6,7,8,9,10]
];
dim.saveStruct( System.dataPath+"struct.txt" );
				

dimは配列を二つ持った配列です。 保存したstruct.txtの中を覗いて見ると、頭に(const)がくっ付いていますが、 二次元配列の構造になっている事が確認出来ます。

この構造を読み込む方法ですが、 配列クラスのload関数では、一行ずつ読み取ってしまうので、うまく動作しません。 ここは、新しいクラスとその関数を使います。

Scripts.evalStorage(ストレージ名)

この関数について、リファレンスには次のように書かれています。

指定されたストレージを読み込み、その内容を TJS2 式として評価します。

すなわち、ファイルの内容自体をTJSとして処理して、 その結果を返してくれる関数という事ですね。 保存したstruct.txtの内容は、配列構造になっていますから、 TJSの式として有効なものなので、この関数を利用できます。

var dim = Scripts.evalStorage( System.dataPath+"struct.txt" );

				

配列の並び替え

配列は0から始まる添え字を利用して、要素を並べています。 この原理を利用して、要素の値が大きい順、または小さい順に並べ変える事が出来ます。

このような並び替えなら、反復文を利用すれば可能ですが、 ここはもっと簡単に並び替えるための関数を使います。

sort();

これだけで、データを小さい順に並びを変えられます。

// 順不同データを小さい順に並べる
var dim = [3,4,2,5,1];
dim.sort();

var str;
for (var i=0; i<dim.count; i++)
{
    str += dim[i]+"\n";
}
System.inform(str);
				

結果は1,2,3,4,5という表示になったと思います。 このような配列を並び替える事をソートと呼びます。 また、データを小さい順に並べることを昇順ソートと呼びます。 (値が大きいほうへ昇っていくから昇順と覚えましょう。)

今度は値が小さい順に並べます。 sort関数に引数を指定すれば可能ですが、今回は次の関数を使います。

reverse();

この関数で、配列の並びが後ろから頭に…つまり、逆順になります。 よって、sort()呼び出しの後にreverse();を呼べば、データは大きい順に並び替えられます。

この方法では、データが文字だった場合にうまく動作しません。 文字列を降順…Z~Aに並べたい場合は、後述のsort("z")を使用してください。

// 順不同データを大きい順に並べる
var dim = [3,4,2,5,1];
dim.sort();
dim.reverse();

var str;
for (var i=0; i<dim.count; i++)
{
    str += dim[i]+"\n";
}
System.inform(str);
				

表示が5,4,3,2,1となった事を確認しましょう。 データを大きい順に並べることを降順ソートと呼びます。 (値が小さいほうへ降りていくから降順と覚えましょう。)

sort()関数への引数

第一引数に以下の文字を指定すると、それぞれ並び替える方法を変えられます。 うまく並び替えられない場合、要素のデータ型を考えて、 以下の指定を行って見てください。

sort("+")通常の昇順ソート
sort("-")通常の降順ソート
sort("0")数値の昇順ソート( 1,2,3,4,5 )
sort("9")数値の降順ソート( 5,4,3,2,1 )
sort("a")文字の昇順ソート( a,b,c,d,e )
sort("z")文字の降順ソート( e,d,c,b,a ))

ソート関数

単なる昇順や降順ではなく、希望のソートを行う場合、 sort関数の第一引数に関数を指定することが出来ます。

ソートに使う関数をソート関数と呼び、次のような構造をしています。

// 昇順ソートを行うソート関数
// 常に引数を二つ取ります。
function sortFunc( a , b )
{
    // 関数値は真偽値
    // aがbより小さければ真を返します
    return a < b;
}
				

この関数をsort関数の第一引数に渡すと、 関数で指定した並び順で、配列がソートされます。

dim.sort(sortFunc);

※関数を呼び出すわけではないので、後ろに()を付けない点に注意しましょう。

分かりづらいと思うので、順に説明します

1.引数のaとbは配列の要素が渡される

指定配列の要素が整数だった場合、aとbには1と6や、100と90といった値が渡されます。

dim = [1,2,3,4,5]

この場合、aとbは2と5、3と4…というように、全要素が順に渡されていきます。

2.aとbを比較させる

渡された要素aとbで、希望の比較をします。 ここでは比較するための演算子「>」「<=」等が使えますね。

sortFunc関数では、「a<b」という演算式によって、 aがbよりも小さければ真、そうでなければ偽を返すという処理になっています。 この真偽値によって、配列要素aとbのどちらが前に置かれるのかが決定されます。

3.真か偽を返す

ソート関数は常に真偽値を返します。真を返すとaとbのどちらかが前に、偽を返すと後ろに置かれます。

sortFunc関数では「a<b」が返す真偽値を元に関数値としていますので、 aがbより小さければ真…すなわち、aとbの値が小さい方を前に置く、という意味になります。 結果として、このソート関数は値を小さい順にソートするという関数になるのです。

実際に動かして、昇順にソートが行われる事を確認しましょう

//昇順ソートを行うソート関数
function sortFunc( a , b )
{
    return a < b;
}

var dim = [3,4,2,5,1];
dim.sort(sortFunc);

var str;
for (var i=0; i<dim.count; i++)
{
    str += dim[i]+"\n";
}
System.inform(str);
				

ソート関数はソート専用の関数なので、他の場所で使われることはまずありません。 よって、匿名関数のコラムで紹介した方法で、 関数を直接渡す方法がよく使われます。

dim.sort(
    function ( a , b )
    {
        return a < b;
    }
);
					

尚、この関数に限っては処理がはっきりと分かるので、 一行で書いても構いません。

dim.sort( function ( a , b ){ return a < b; } );
					

配列オブジェクト

説明してきませんでしたが、配列の中にオブジェクトを入れてしまった方…例えば、 次のような配列を作った方がいるかもしれませんね。

class human
{
    var human_name;
    var human_age;
    function human( name,age )
    {
        human_name = name;
        human_age = age;
    }
    
    property name
    {
        getter()
        {
            return human_name;
        }
    }

    property age
    {
        getter()
        {
            return human_age;
        }
    }
}

var dim_h = [];
dim_h[0] = new human("山田",33);
dim_h[1] = new human("斉藤",16);
dim_h[2] = new human("田中",54);
				

humanは人間という設計図で、名前と年齢を入れるメンバー変数と、 それぞれを表示するプロパティを持つだけのクラスです。

配列でオブジェクトを管理する事で、オブジェクトの扱いを楽にしてくれるだけではなく、 全オブジェクトに対して、配列のメンバー関数が使えるというメリットが生まれます。

dim.h.count …… 全オブジェクトの数を知る

dim.h.clear() …… 全オブジェクトを消去する

今回のdim.h.clear()という使い方は、少しまずいです。 何がいけないのか…それはもう少し後に分かります。 気になる方はリファレンスを覗いて、考えて見てください。

という事は、オブジェクトのメンバーの値を元に並び替える事も可能という事です。 ここでは、各オブジェクトの年齢が高い順(降順)に並び替える例を示します。

dim_h.sort( function ( a , b ) { return a.age > b.age; } );

var str;
for (var i=0; i<dim_h.count; i++)
{
    str += dim_h[i].name+"\t"+dim_h[i].age+"\n";
}
System.inform(str);
				

着目点は、ソート関数の次の処理です。

return a.age > b.age;

引数aとbは、配列の要素が順に渡されると書きました。 ここで渡される要素とはオブジェクトなので、 そのオブジェクトのプロパティage同士を比較する…という方法を取っています。

このように、配列でオブジェクトを扱うようになると、 大量のオブジェクトを容易に管理できるようになって効率的です。 例えばカードゲームでは、全カード52枚を配列化し、ソート関数を使ってシャッフルする…というような処理が実現できそうですね。


はみ出しコラム

ここまで色々複雑で難しい仕様を覚えてきましたが、 まだウィンドウどころか、グラフィック機能すら扱えていないじゃないか、 というお声がそろそろ聞こえてきそうですね。

確かに、表示はSystem.informに頼りっきりですし、 ある程度のプログラムが作れるようになってくると、物足りないかもしれません。

ただ、今一度考えて見てください。 プログラムに初めて触れたというあなた、どれだけのプログラムを作ってきましたか? 100?200?300?きっとそれより少ないでしょう。

数多くのプログラムを自分なりに考えて作った人と、そうでない人との決定的な差は応用力です。 今楽しめている方はきっと、これまでの知識だけでも素晴らしいモノを作れる方でしょう。 少し物足りない方は、まだまだ余力が残っているはずです。

…と偉そうに書いている私ですが、プログラムの世界全体で言えば、一割くらいの知識しか持っていないと思います。 プログラミングというのは、それだけ広大で、迷宮であり砂漠であり、沼であるのです。

ただし、今は少し苦痛でも、この先きっとオアシスが見えてきます。 そして、素晴らしい世界が待っています…と自身を持って言えます。 今はとにかく、苦痛を楽しみましょう。