javascriptの配列とリスト

配列の生成

要素の値をコンマ区切りで書いたものから配列を生成できます。

var names = ["mike","tama","kuro"];
function arrayTest01(){
   var tbl01 = document.getElementById("test01");
   var tds = tbl01.getElementsByTagName("td");
   var names = ["mike","tama","kuro"];
   tds[1].firstChild.nodeValue = names[2];
   tds[2].firstChild.nodeValue = names[0];
   tds[3].firstChild.nodeValue = names;
   tds[4].firstChild.nodeValue = "names.length "+names.length;
}

tds[0]tds[1]tds[2]tds[3]tds[4]

各要素への参照は角括弧に番号を入れたもの([0]のような)を使って行います。

要素の数は、.length というプロパティで得られます。

この他の書き方もあります。次の3つは同じ配列を作ります。

var names = ["mike","tama","kuro"];
var names = new Array("mike","tama","kuro");
var names = Array("mike","tama","kuro");

確実には知りませんが、長年の観察によると配列(Array)が、いわゆる連想配列に拡張(あるいはマップ様のものと融合)して、オブジェクト指向をとりこんでオブジェクトの一つとなったものと思います。配列とオブジェクトとは厳密には別という時代がありました。現在(2018年)は同じもののようですが、歴史的な理由から生成方法がいくつもあるようです。本当に同一かは実装によるのでしょうが、今回の使用範囲では使い方に差はありませんでした。ただし、後述の要素数を指定した生成では文法上の違いがあります。

特筆すべきは、要素の種類が混在でも構わないことです。

function arrayTest02(){
   var tbl02 = document.getElementById("test02");
   var tds = tbl02.getElementsByTagName("td");
   var iroiros = ["mike",365,true,tbl02,tds];
   tds[0].firstChild.nodeValue = iroiros[0];
   tds[1].firstChild.nodeValue = iroiros[1];
   tds[2].firstChild.nodeValue = iroiros[2];
   tds[3].firstChild.nodeValue = iroiros[3];
   tds[4].firstChild.nodeValue = iroiros[4];
   var ofs = 5;
   tds[0+ofs].firstChild.nodeValue = typeof iroiros[0];
   tds[1+ofs].firstChild.nodeValue = typeof iroiros[1];
   tds[2+ofs].firstChild.nodeValue = typeof iroiros[2];
   tds[3+ofs].firstChild.nodeValue = typeof iroiros[3];
   tds[4+ofs].firstChild.nodeValue = typeof iroiros[4];
}

tds[0]tds[1]tds[2]tds[3]tds[4]

"mike"は文字列、365は数値などですが、typeof で確認することができます。

javaでもObjectを入れるぞと宣言すればできないことはないですが、普通、文字列をいれる配列とか整数を入れる配列とか決めて使います。それに慣れているとちょっと驚きです。

空の配列をつくる

生成の時に要素が空であれば、要素数0の配列ができます。

var karas = [];

要素数が0でも、番号を指定して代入すると要素数が勝手に増えてくれます。

番号が飛んでも許されますが、代入されないところは undefined になります。

function arrayTest03(){
   var tbl03 = document.getElementById("test03");
   var tds = tbl03.getElementsByTagName("td");
   var karas = [];
   tds[4].firstChild.nodeValue = "代入前は "+karas.length;
   karas[0] = tds[0].firstChild.nodeValue;
   karas[2] = tds[2].firstChild.nodeValue;
   karas[3] = tds[3].firstChild.nodeValue;
   var ofs = 5;
   tds[0+ofs].firstChild.nodeValue = karas[0];
   tds[1+ofs].firstChild.nodeValue = karas[1];
   tds[2+ofs].firstChild.nodeValue = karas[2];
   tds[3+ofs].firstChild.nodeValue = karas[3];
   ofs = 10;
   tds[0+ofs].firstChild.nodeValue = typeof karas[0];
   tds[1+ofs].firstChild.nodeValue = typeof karas[1];
   tds[2+ofs].firstChild.nodeValue = typeof karas[2];
   tds[3+ofs].firstChild.nodeValue = typeof karas[3];
   tds[4+ofs].firstChild.nodeValue = "代入後は "+karas.length;
}

tds[0]tds[1]tds[2]tds[3]tds[4]
000111222333⇦この行からcopyする
copy ⇧配列の要素数⇩
type

表の一行目の"000","222","333" を読んで二行目に書き写しています。"111"は読まないので、kara[1]には値が代入されません。

htmlの特質と考えていいでしょうが、テキストノードから読んだものは数字だけでも「文字列」として扱われます。"000"が0にならないことからわかりますし、typeof で string となることから確信できます。

結果から、次の事柄が確認できます。(1)空の配列を作ったときの長さは0だが、[3]に代入すると長さは4になること、(2)代入されない要素はundefinedになること、(3)テキストノードから読んだデータは文字列になること

この他の書き方もあります。次の3つは同じ配列を作ります。

var karas = [];
var karas = new Array();
var karas = Array();

要素数を指定する配列

お勧めが[]だったので

var arr = [];
arr.length = 256;

他の言語では配列は先に使う分だけ要素数を確保するもの。Javascriptでは特に必要ではない。初期値をundefined以外にしたい場合は、次項。

function arrayTest04(){
   var tbl04 = document.getElementById("test04");
   var tds = tbl04.getElementsByTagName("td");
   var shiteiA = [];
   shiteiA.length = 256;
   var shiteiB = new Array(256);
   var shiteiC = Array(256);
   var shiteiD = [256];
   tds[1].firstChild.nodeValue = shiteiA.length;
   tds[2].firstChild.nodeValue = shiteiB.length;
   tds[3].firstChild.nodeValue = shiteiC.length;
   tds[4].firstChild.nodeValue = shiteiD.length;
}

作成方法A length=256B new Array(256)C Array(256)D [256]
要素数

A,B,Cの方法は同等で、生成の後に .length プロパティで要素数が256になっていることを確認しています。

Dの[256]は要素が256という数値であるような要素数1の配列ができてしまうので、間違いです。

要素数と初期値を指定する配列

たくさんのカウンタを配列に用意して、見つかった数を数えたい場合、最初に全部を0クリアして、+1していくことがあります。このような時配列の要素の初期値を設定する必要があります。

function arrayTest05(){
   var tbl05 = document.getElementById("test05");
   var tds = tbl05.getElementsByTagName("td");
   var shiteiA = [];
   shiteiA.length = 256;
   for(var i=0;shiteiA.length>i;i++){
      shiteiA[i] = 0;
   }
   tds[1].firstChild.nodeValue = shiteiA[255];
   var shiteiB = [];
   for(var i=0;256>i;i++){
      shiteiB[i] = 0;
   }
   tds[2].firstChild.nodeValue = shiteiB[255];
   shiteiA.fill(2);
   tds[3].firstChild.nodeValue = shiteiA[255];
}

設定方法A length指定後forでB いきなりforでA2 fill()メソッドを使って
最後の要素の値

Aは配列の要素数を指定してから、配列の長さ分だけforで0を代入します。Bは要素数の指定をせずにforで0を代入します。forの中で指定した数が要素数になります。A2はfill()というメソッドを使って2を全部の要素に代入しています。表にはすべて最後の要素の値を表示しています。0,0,2が正解です。

fill()メソッドが使えない環境もありますので公開するならforがお勧めです。

他の言語を使っているとまだ入れ物のない要素に代入するのには抵抗がありますし、将来的にfill()を使うことを考えると.lengthで長さ指定の後、.fill()ということになります。

.length はプロパティなので()なし、.fill()はメソッドなので()ありです。内部事情を知っていると納得はできますが、値を設定してリ引き出したりするメソッドも作れるので、違いを気にしなくていい言語設計になっていかないかなと思っています。

繰り返し機構

for文は多くの言語で使える王道です。

for(var i=0;shiteiA.length>i;i++)
for(var i=0;256>i;i++)

もう一つ、メソッドとして .forEach() があります。昔あった for each...in というものは廃止されています。古い情報には注意(といっても昔から使用には要注意でした)。

今回、昔使えない for each...in というのがあったなと調べたら、新しい .forEach() ができていたということなので、ちょっと使ってみます。

function arrayTest06(){
   var tbl06 = document.getElementById("test06");
   var tds = tbl06.getElementsByTagName("td");
   var names = ["mike","tama","kuro"];
   for(var i=0;names.length>i;i++){
      tds[i+1].firstChild.nodeValue = names[i];
   }
   var ofs = 5;
   names.forEach(function(element,index) {
      tds[index+ofs].firstChild.nodeValue = element;
   });
}

tds[0]tds[1]tds[2]tds[3]

for文と並べて同じことをさせてみました。forEachの第一引数のelementは配列の要素ひとつ,第二引数indexは番号です。第一引数だけでも呼べます。

2015年あたりに標準化されているので、まだ未対応のブラウザが残っている可能性があります。.fill()に未対応なブラウザもこれは大丈夫でしたが、まだ使わないほうが無難かもしれません。

.forEach()メソッドの引数はコールバック関数と呼ばれるものですが、関数に渡される引数は配列の要素が一つだけなので、次の要素を関数が知るためには問い合わせなければならないというイメージなのだと思います。実際にはマルチコアCPUで並行処理をするための機能だと思います。そうすると関数側では自分の仕事が終わって値を返せば、次のデータは気にしないことになりますから、コールバックしませんね。

forEachメソッド

前項のforEachメソッドは、});が美しくないと思います。コールバック関数を引数に取るということなので、この関数を別に書けば美しくなると思って書いてみたのが次です。

function arrayTest07(){
   var tbl07 = document.getElementById("test07");
   var tds = tbl07.getElementsByTagName("td");
   var names = ["mike","tama","kuro"];
   names.forEach(outputNamesToTD,tds);
}
function outputNamesToTD(element,index) {
   this[index+1].firstChild.nodeValue = element;
}

tds[0]tds[1]tds[2]tds[3]

outputNamesToTDというのが、ここでのコールバック関数名です。この関数は引数が(要素[,番号[,配列]])と決まっていますから、仮引数も書きませんし、他の引数を与えることもできません。

forEachメソッドにoutputNamesToTDという名前を与えて、「この名前の関数を配列要素などを引数にして呼んでね」という指示をしているのです。こちらがコールバックの名前の由来でしょうか。でも「バック」ではないような気がします。

ここで問題が生じます。outputNamesToTDと名前をつけずに、forEachの引数内に直接関数を書いていたときには、tdsつまり出力先のtd要素の配列を直接参照できたのですが、別のfunctionを書いたために参照できなくなります。そこで登場するのがforEachの第二引数(ここではtds)です。コールバック関数内で this として参照できます。

この仕組みがないとtdsはグローバル変数にするか、outputNamesToTD内にgetElementById("test07")部分から全部書いて、呼ばれるたびに算出することになってしまいます。

ありがたい仕組みですが、一つしか指定できませんし、苦し紛れのような気もします。

リストとしての配列

Javaなどでは配列とは別にListがありますが、Javascriptでは配列をリストの様に使うことができます。

pushは配列の最後に要素を追加し、配列の長さを返します。

popは配列の最後の要素を削除し、削除した要素の値を返します。

saba,hokke,iwashi,shishamo をpushで配列に取り込んでpopで取り出して書き出します。

仕組みを理解するために、一回のpushごとに戻値と配列を書いていき、4つ読んだら一回のpopのごとに戻値と配列を書いています。

function arrayTest08(){
   var tbl08s = document.getElementById("test08s");
   var stds = tbl08s.getElementsByTagName("td");
   var tbl08d = document.getElementById("test08d");
   var dtrs = tbl08d.getElementsByTagName("tr");
   var fishes = [];
   var k = 0;
   for(var i=0;stds.length>i;i++){
      k = fishes.push(stds[i].firstChild.nodeValue);
      var dtds = dtrs[k].getElementsByTagName("td");
      dtds[0].firstChild.nodeValue = k;
      dtds[1].firstChild.nodeValue = fishes;
   }
   while(fishes.length>0){
      k++;
      var fish = fishes.pop();
      var dtds = dtrs[k].getElementsByTagName("td");
      dtds[0].firstChild.nodeValue = fish;
      dtds[1].firstChild.nodeValue = fishes;
   }
}

sabahokkeiwashishishamo
戻値配列

pushとpopを組み合わせていますが、通常の配列として角括弧の記法を使って代入したり、参照することもできます。

push,pop,shift,unshiftの組み合わせ

前項と同様に、saba,hokke,iwashi,shishamo を配列に取り込んでから取り出して書き出します。

push,pop に加えて、shift,unshiftを使います。

unshiftは配列の最初に要素を追加し、配列の長さを返します。

shiftは配列の最初の要素を削除し、削除した要素の値を返します。

push,pop,shift,unshiftは任意に組み合わせて使用できます。全部の組み合わせでやってみます。

function arrayTest09(src,dist){
   var tbl09s = document.getElementById("test09s");
   var stds = tbl09s.getElementsByTagName("td");
   var tbl09d = document.getElementById("test09d");
   var dtrs = tbl09d.getElementsByTagName("tr");
   var fishes = [];
   var k = 0;
   for(var i=0;stds.length>i;i++){
      k = src=="push"
         ? fishes.push(stds[i].firstChild.nodeValue)
         : fishes.unshift(stds[i].firstChild.nodeValue);
      var dtds = dtrs[k].getElementsByTagName("td");
      dtds[0].firstChild.nodeValue = k;
      dtds[1].firstChild.nodeValue = fishes;
      dtds[2].firstChild.nodeValue = src;
   }
   while(fishes.length>0){
      k++;
      var fish = dist=="pop"
         ? fishes.pop() :fishes.shift();
      var dtds = dtrs[k].getElementsByTagName("td");
      dtds[0].firstChild.nodeValue = fish;
      dtds[1].firstChild.nodeValue = fishes;
      dtds[2].firstChild.nodeValue = dist;
   }
}

sabahokkeiwashishishamo
戻値配列操作

ここで、三項演算子(条件?trueの時の式:falseの時の式)も使いました。javascriptでは条件演算子と呼ぶようです。ifの中で変数を定義すると外ではアクセスできませんから、これは便利です。

配列中の値を探して削除

indexOf("検索値")は配列中の要素で"検索値"と一致するものを探して位置(index)を返します。見つからない時は-1です。indexOf("検索値",開始位置)という使い方もできます。一つ見つかったらそれ以上は探しません。

splice(開始index,長さ)は、開始indexから、長さだけの要素を削除して、削除した要素の配列を返します。元の配列は書き換えられて要素数が減ります。ここのプログラムでは3項演算子の中に書かれているのでわかりにくいかもしれません。

split("区切り文字")は文字列を区切り文字で区切って、分けたものを要素にした配列を生成して返します。使用例を作る都合で加えました。

function arrayTest10(name){
   var tbl10 = document.getElementById("test10");
   var tds = tbl10.getElementsByTagName("td");
   var history = tds[1].firstChild.nodeValue;
   var ofs = 3;
   var names = tds[history*ofs+2].firstChild.nodeValue;
   var namelist = names.split(",");
   var index = namelist.indexOf(name);
   var rmname = 0>index ?name+":not found" :namelist.splice(index,1);
   history++;
   if(tds.length > history*ofs){
      tds[history*ofs+0].firstChild.nodeValue = index;
      tds[history*ofs+1].firstChild.nodeValue = rmname;
      tds[history*ofs+2].firstChild.nodeValue = namelist;
      tds[1].firstChild.nodeValue = history;
   }
}

テスト10 削除する要素を指定してください。

indexNo./削除要素リスト
0mike,tama,kuro,tora,tama

nodeValueとして配列を指定するとコンマ区切りにして結合してくれるので、楽にプログラムできています。もしこうならない実装があれば、.join(",") を使わなくてはなりません。

spliceは「切り貼り」の意味なので、貼ることもできます。

リストのリスト

リストは関数を呼び出す時の引数や戻値にすると便利なことが多いですが、リストのリストを必要とする場面がありました。後にJavaで同様のことをしようとして困ったことがあります。Javascriptは柔軟にあっさりできてしまいます。

必要があってというのはこんな場面です。3つのリストがあります。要素数が増減するかもしれないのでリストです。このリストが3つあって、これを別の関数に引数として渡します。現在はリスト3つですが将来的に増える可能性もあるので、こちらもリストにします。リストのリストです。

var basename = ["アイス","ヨーグルト"];
var sauce = ["ブルーベリー","黒蜜","練乳","チョコ"];
var topping = ["イチゴ","バナナ","小倉餡"];
var dessertmenu = [basename,sauce,topping];

basename,sauce,toppingという分類名は配列やリストでは失われて番号になりますが、実際に作成したのは数学関係のプログラムでしたので、0,1,2という連番で構いませんでした。必要なら配列の代わりにオブジェクトを使います。

ver example1 = dessertmenu[2][0]+dessertmenu[1][2]+dessertmenu[0][0];
// イチゴ練乳アイス

basenameに"かき氷"を加えたければ、

dessertmenu[0].push("かき氷");

drinkメニューを加えたければ、

dessertmenu.push(["コーヒー","紅茶","ソーダ"]);

で可能です。

function arrayTest11(){
   var tbl11 = document.getElementById("test11");
   var tds = tbl11.getElementsByTagName("td");
   var basename = ["アイス","ヨーグルト"];
   var sauce = ["ブルーベリー","黒蜜","練乳","チョコ"];
   var topping = ["イチゴ","バナナ","小倉餡"];
   var dessertmenu = [basename,sauce,topping];
   tds[0].firstChild.nodeValue = "参照例";
   var example1 = dessertmenu[2][0]+dessertmenu[1][2]+dessertmenu[0][0];
   tds[1].firstChild.nodeValue = example1;
   tds[2].firstChild.nodeValue = "追加前";
   tds[3].firstChild.nodeValue = dessertmenu[0];
   tds[4].firstChild.nodeValue = "追加後";
   dessertmenu[0].push("かき氷");
   tds[5].firstChild.nodeValue = dessertmenu[0];
   tds[6].firstChild.nodeValue = "飲物追加前";
   tds[7].firstChild.nodeValue = dessertmenu;
   tds[8].firstChild.nodeValue = "飲物追加前";
   dessertmenu.push(["コーヒー","紅茶","ソーダ"]);
   tds[9].firstChild.nodeValue = dessertmenu;
   basename.push("パスタ");
   tds[10].firstChild.nodeValue = "!!";
   tds[11].firstChild.nodeValue = dessertmenu;
}

やってみます。

作業名出力

最後の例(!!)で、配列に入るのは参照であることがわかります。