多次元配列を扱うクラスを作ってみた

JavaScriptで多次元配列を扱うのってめんどうですよね。例えば、10*10の二次元配列を考えて見ます。この配列は各要素の初期値として0が入っているものとします。

var array = new Array(10);
for(var i=0; i<array.length; i++){
	array[i] = new Array(10);
	for(var j=0; j<array.length; j++){
		array[i][j] = 0;
	}

}

単に配列を宣言するだけで二重ループを使ってしまいました。めんどうですね。さらに添え字のチェックも面倒です。例えばこんな場合。

var x = -4;
var y = 3;
alert(array[x][y]); // array[x] is undefined

xが範囲外なので、コメント部分のように、いちいちundefinedのエラーを吐きます。また下記のように、yについても範囲外の場合、エラーを吐きます。

var x = 4;
var y = -3;
alert(array[x][y]); // undefined

これを防ぐには、指定した値が存在するかどうか、事前にチェックを入れる必要があります。

var x = -4;
var y = -3;
if(array[x] != null && array[x][y] != null)
	alert(array[x][y]);

実にめんどうですね…。

多次元配列の扱いを楽にしよう

そこで、今回これを楽にするためのコードを書いてみました。クラス名はHyperspaceという名前にしてみました。以下のように、Hyperspaceをコンストラクタとして利用すると、10*10の二次元配列が作成されます。

var matrix = new Hyperspace([10,10],0);

第一引数で指定しているのは、配列の大きさ、第二引数で指定しているのは配列の各要素の初期値です。第一引数では、一次元配列を利用した指定が可能です。今回10*10の二次元配列を作成したかったので、この一次元配列の要素は10が二つになっています。これは二次元配列だけでなく、多次元配列を作るときにも使えます。例えば10*10*10の三次元配列を作る場合、以下のようなコードになります。

var matrix = new Hyperspace([10,10,10],0);

ただし、ここで作ったmatrixオブジェクト自体は配列ではないので注意してください。次で説明します。

値を取り出す

値を取得するにはgetメソッドを使います。このときの引数は配列の添え字を、一次元配列で指定します。

var matrix = new Hyperspace([10,10,10],0);
var value = matrix.get([0,1,9]);

[0,1,9]が添え字です。このとき、添え字が範囲外だと、nullが返ってきます。最初にやったように、各添え字ごとにチェックをしなくていいので楽ですね。ちなみに添え字の数は、ちゃんと扱う配列の大きさに合わせて指定してください。多すぎても少なすぎてもnullが返ってきます。例えば[0,1,9,4]なんて指定してもだめですよ。ちなみにこの例の場合、最初に全ての要素に0を入れているので、添え字[0,1,9]には、0が入ってます。

値をセットする

値を入れるにはsetメソッドを使います。第一引数は、添え字、第二引数は配列に入れたい値になります。この例では、[4][3][2]の位置に文字列「テスト」を代入しています。ちなみにsetに成功するとtrue、失敗するとfalseが返ってきます。

var matrix = new Hyperspace([10,10,10],"");
matrix.set([4,3,2],"テスト");

基本的な使い方はここまで。

中に入ってる配列を取得

基本的にこのクラスで出来るのは、多次元配列の作成を楽にすること、get/setによる補助だけです。もし配列に、そのほかの操作を加えたい場合は、配列を取り出す必要があります。この場合、getArrayメソッドを利用します。普通の配列が返ってきます。

var matrix = new Hyperspace([10,10,10],"");
var array = matrix.getArray();

既存の配列を、Hyperspaceの形式に適用

この場合、Hyperspace.adaptというメソッドを利用することで、既存の多次元配列から、新たにHyperspaceオブジェクトを作ることができます。

var array = [[1,2],[3,4],[5,6]];
var matrix = Hyperspace.adapt(array);
alert(matrix.get([2,0])); //5

ここで注意。adaptの引数は、長さが均一な配列だけにしておいてください。例えば、array[0].lengthは、長さ3だけど、array[1].lengthは、長さ5なんていうのはやめてください。チェックの仕組みをいれてないのでバグります。

細かい点

一次元配列を取り扱うことはまずないと思いますが、一次元配列を取り扱う場合、添え字の指定に数値を利用することができます。まず、こっちが従来のやりかた

var matrix = new Hyperspace([10],0);

一次元配列の場合にだけ使える方法

var matrix = new Hyperspace(10,0);

同様に、get/setでもこの方法による指定が可能です。

var arr = new Hyperspace([3],-1); //こっちは配列による指定
arr.set(1,"テスト"); //こっちは数値による指定
alert(arr.get(1)); //こっちも数値

ソース

Prototype.jsに依存しています。なるべくPrototype.jsとは、切り離したかったのですが、あるオブジェクトが配列かどうかチェックする関数が、標準では無いんですよね。一応一番下からダウンロードできるようにしておきます。

index.html

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>test</title>
        <script src="prototype.js"></script>        
        <script src="myutil.js"></script>
    </head>
    <body onload="init()">
    </body>
</html>

myutil.js

function init(){
	var arr = new Hyperspace([3],-1);
	arr.set(1,"テスト");
	alert(arr.get(1));

	var arr2 = new Hyperspace(3,-10);
	alert(arr2.get(1));
	
	var array = [[1,1],[2,2],[3,3]];
	array = Hyperspace.adapt(array);
	alert(array.get([2,0]));
}

/**
 * コンストラクタ
 * @param dim 配列の大きさ 数値か、数値の入った一次元配列を指定 ex.[3,4,3]  3*4*3の3次元配列を作る  
 * @param base(省略可) 配列の各要素のデフォルト値. 省略するとnull
 */
var Hyperspace = function(dim, base){
	
	//内部処理用
	Hyperspace.prototype.numToArray = function(num){
	   	if(Object.isNumber(num)){
	        var tmp = num;
	        num = new Array(1);
	        num[0] = tmp;
		}
		return num;
	};

	//内部処理用
	Hyperspace.prototype.check = function(dimension){
		for(var i = 0; i<dimension.length; i++){
			if(!Object.isNumber(dimension[i]) || !(1 <= dimension[i]) ) throw new Error("引数が異常です");
		}
	};

	//内部処理用 多次元配列を作る
	Hyperspace.prototype.make = function(dimension){
		if(dimension.length <= 0) return;
		var pointer = 0;
		var array = new Array(dimension[pointer]);
		pointer = 1;
		this.recMake(dimension, array, pointer);
		return array;
	};

	//内部処理用 makeと関連。再帰的に呼んで使う
	Hyperspace.prototype.recMake = function(dimension,arr,pointer){
		if( !(1<=pointer) || !(pointer <= dimension.length))return;
		else{
			for(var i=0;i<arr.length;i++){			
				if(pointer == dimension.length){ //最後
					arr[i]= this.base; //初期値
				}else{ //再帰
					arr[i] = new Array(dimension[pointer]);
					this.recMake(dimension,arr[i],pointer + 1);				
				}
			}
		}
	};

	/**
	 * 指定した位置にある要素を取得する
	 * 
	 * 引数は、このクラスで取り扱っている配列の添え字を一次元配列にして渡す。
	 * ただし、このクラスで取り扱っている配列の次元と、
	 * ここで指定する一次元配列の長さを一致させる必要がある。
	 * 
	 * ex.このクラスで取り扱っている配列が[3][4][3]、つまり 3*4*3の3次元配列の場合、
	 *		 var value = hyperspace.get([2,0,1]);
	 *	というような指定になる
	 * 
	 * このクラスで取り扱っている配列が一次元の場合、数値による指定が可能。
	 * ex. 例えば取り扱っている配列が[4]の場合、
	 *		 var value = hyperspace.get(3);
	 * という指定になる
	 * 
	 * @param index 
	 * @return
	 */
	Hyperspace.prototype.get = function(index){
		index = this.numToArray(index);
		if( this.dimension.length != index.length || !Object.isArray(index) ) return null;
		var a = this.array;
		for(var i=0; i < this.dimension.length; i++ ){
			if( !( 0<= index[i] && index[i]<this.dimension[i]) ){
				return null; //エラー
			}else{
				a = a[index[i]];
			}
		}
		return a;
	};

	/**
	 * 指定した位置に指定した値をセットする
	 * 成功するとtrue、失敗するとfalseが返ってくる
	 * 
	 * 引数indexはgetメソッドと同様の方法で指定できる。
	 * @param index  添え字
	 * @param value	  代入する値
	 * @return
	 */
	Hyperspace.prototype.set = function(index,value){
		index = this.numToArray(index);
		if( this.dimension.length != index.length || !Object.isArray(index) ) return false;
		var a = this.array;
		for(var i=0; i < this.dimension.length - 1; i++ ){
			if( !( 0<= index[i] && index[i]<this.dimension[i]) ){
				return false; //エラー
			}else{
				a = a[index[i]];
			}
		}
		a[index[this.dimension.length-1]] = value;
		return true;
	};

	/**
	 * 内部で取り扱っている配列を返す
	 */
	Hyperspace.prototype.getArray = function(){
		return this.array;
	};

	/**
	 * 普通の配列をHyperspaceクラスの形に適用して返す
	 * arrayには長方形な配列を渡すこと
	 */
	Hyperspace.adapt = function(array,base){
		var hyp = new Hyperspace(1);
		if(Object.isArray(array)) hyp.array=array;
		hyp.base = base;
		var a = array;
		hyp.dimension = new Array();
		var i=0;
		while( Object.isArray(a) ){
			hyp.dimension[i++] = a.length;
			a = a[0];		
		}
		return hyp;
	};

	//初期化
	this.dimension = dim;
   	this.base = base;
   	this.dimension = this.numToArray(this.dimension); //dimensionが数値なら配列に
   	if(!Object.isArray(this.dimension)) throw new Error("dimの値が異常です");
   	this.check(this.dimension);
    this.array = this.make(this.dimension);
};

ダウンロード

arrayutil.zip 直

苦労話

機能的にはたいしたことができないんですが、再帰処理の部分で、配列の参照をうまく渡せるようにいじったので、かなり読みにくいコードになっているかと思います。この部分は苦労しました…。ちょうどmakeメソッドと、makeRecメソッドあたりです。