配列

作成日 : 2013-01-26
最終更新日 :

オライリーの JavaScript 第6版を図書館から借りて、 いろいろ調べている。 7 章の配列は知らないので、少し調べてみた。 以下の例で、配列の要素に使われているの名称はエスペラントである。

メソッド

sort メソッド

sort は、配列で表現された複数の値を破壊的に整列させる汎用メソッドである。次はその一例である


let a = new Array("pomo", "banano", "ĉerizo"); // リンゴ, バナナ, サクランボ
a.sort();
let s = a.join(","); // s == "banano, ĉerizo, pomo"  バナナ, サクランボ, リンゴ

無名関数式も使える。特に sort は順番を決めるうえで大切だ。


let a = new Array(800, 108, 1003, 50000); 
a.sort();       // 1003, 108, 50000, 800 辞書式。
a.sort(function(a, b) {
  return a - b;
});      // 108, 800, 1003, 50000 数値順

大文字と小文字を区別せずにソートしたいときは、toLowerCaseを使う。


a = ['formiko', 'Besteto', 'kato', 'Hundo'] // それぞれ、アリ、虫、ネコ、イヌ 
a.sort();               // ['Besteto', 'Hundo', 'formiko', 'kato'] 大文字小文字を区別 
a.sort(function(s, t) { 
  let a = s.toLowerCase(); 
  let b = t.toLowerCase(); 
  if (a < b) return -1; 
  if (a > b) return  1; 
  return 0;
});             // ['Besteto', 'formiko', 'Hundo', 'kato'] 

reduce メソッド

reduce は、配列で表現された複数の値から単一の値を得るための汎用メソッドである。以下は、
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
に基づく情報が多い。

配列の総和を得る


	a = [1, 2, 3, 4]  
	const sumo = a.reduce((akum, val) => akum + val, 0) // 10 ( = 0 + 1 + 2 + 3 + 4)

これは次のように書いても同じだ。


	a = [1, 2, 3, 4]  
	const sumo = a.reduce((akum, val) => akum + val) // 10 ( = 1 + 2 + 3 + 4)

配列の最大値を得る


	a = [3, 1, 4, 1, 5, 9, 2]  
	const maksimumo = a.reduce((a, b) => a > b ? a : b) // 9  

配列の最大値を得る添字を求める


	a = [3, 1, 4, 1, 5, 9, 2]  
	const maks_indekso = a.reduce((av, kv, ki, aro) => a[av] > kv ? ki : av, 0) // 

上記の例では、配列の最大値自体は 9 だから、求める添字は 9 を値にとる 5 である(つまり、a[5] = 9 である)。 この最後の例はわかりにくいので説明する。まず、a.reduce のアロー関数に出ている引数は一般的に次で与えられる:

reduce((antaŭaValoro, komencaValoro, kurantaIndekso, aro) => { /* … */ }, komencaValoro)

これと照らしながら動作を見る。まず、komencaValoro が初期値として設定され、av の値となる。具体的には添字の値としての 0 である。 そして、aro[av] と kv の値が比較される。av は最大値を得る添字だから、aro[av] は前までに走査された配列のなかでの最大値そのものである。 それと現在の値 kv (= aro[ki]) が条件演算子(三項演算子)によって比較される。その結果、kv が aro[av] より大きいならば ki が次の動作の比較対象となる。 そうでないならば、つまり kv が aro[av] と等しいか aro[kv] より小さいならば、av がそのまま残り、次の動作の比較対象となる。 この動作が最終要素まで進んで配列の最大値が求められる。

join メソッド

join は、配列の全要素を区切り文字を挟んで文字列を順に連結し、連結した文字列を新たに作成して返すメソッドである。

要素のない配列

要素のない配列も可能である。

  let a = [];
  console.log(a.length) ; // 0
  let b = [3];
  console.log(b.length) ; // 1
  b.pop();          // 3
  console.log(a.length) ; // 0

localeCompare の利用

配列を整列するときには、sort 関数と、sort 関数と一緒に使われる比較計算関数がある。 ここでは、String オブジェクトにある localCompare メソッドを使ってみた。 s に HTML の文字列が入る。

    let z = [{n:1, c:'治'}, {n:2, c:'鷗外'}, {n:3, c:'百閒'}, {n:4, c:'漱石'}, {n:5, c:'弴'}, 
        {n:6, c:'ばなな'}, {n:7, c:'オリザ'}];
    let s = "";

    for (let i = 0; i < z.length; i++){
        s += z[i].n + ':' + z[i].c;
    }
    s += "<br>"; 
    z.sort(function(x, y) {return x.c.localeCompare(y.c);});

    for (let i = 0; i < z.length; i++){
        s += z[i].n + ':' + z[i].c;
    }

実行結果は次の通り。

二重配列

二重配列も可能である。


    let a = [
        ['formiko', 'Besteto', 'kato', 'Hundo'],
         ['pomo', 'banano', 'ĉerizo','']
        ];

この項、2020-02-25 追加。

要素の追加と削除

要素を末尾に追加するには push() メソッドを用いる。戻り値は新しい配列の長さである。

    let fruktoj = ['frago'];
    fruktoj.push('banano', 'pomo', 'persiko');
    console.log(fruktoj.length); // 4

要素を先頭に追加するには unshift() メソッドを用いる。戻り値は新しい配列の長さである。

	// 続き
    fruktoj.unshift('oranĝo');
    console.log(fruktoj.length); // 5

要素の末尾を削除するには pop() メソッドを用いる。戻り値は削除された要素である。

	// 続き
    fruktoj.pop(); // 'frago'

要素の先頭を削除するには shift() メソッドを用いる。戻り値は削除された要素である。

	// 続き
    fruktoj.shift(); // 'oranĝo'

map と reduce

map メソッドと reduce メソッドの使い方を、多次元配列、特に 2 次元配列を例にとって練習する。 これは、2 次元配列を数学の行列のように扱う場合に有効な方法である。

const a = [[1,2,3],[4,5,6],[7,8,9]];

行方向への加算

b[i]=a[i][0]+a[i][1]+a[i][2] となる配列 b[i] (i=0, 1, 2)を作る

const a=[[1,2,3],[4,5,6],[7,8,9]]
const b = a.map(h => h.reduce((is, as) => is + as, 0) ) // is = prev, as = current 
console.log(b) // => [6, 15, 24]

列の抜き出し

c[i]=a[0][i] となる配列 c[i] (i=0, 1, 2)を作る

const a=[[1,2,3],[4,5,6],[7,8,9]]
const c = a.map(v => v[0])   
console.log(c) // => [1, 4, 7]

列方向への加算

d[i]=a[0][i]+a[1][i]+a[2][i] となる配列 d[i] (i=0, 1, 2)を作る

const a=[[1,2,3],[4,5,6],[7,8,9]]
const d = a.reduce((is, as) => is.map((v,i) => v+as[i]), a[0].slice().fill(0) );
console.log(d) // => [12, 15, 18]

この d を作る例は私には難しい。 [1,2,3]+[4,5,6] ができるようなコールバック関数を reduce に与えなければならないので、 このように map を使っている。reduce で使える引数を is, as で与えているが、 これはエスペラントの過去時制を表わす接尾辞 -is と現在時制を表わす接尾辞 -as である。 英語ならばそれぞれ previousValue, currentValue を使うだろう。 もう一つ注意すべき点は、reduce に与える初期値を a[0].slice().fill(0) としたことである。 最初 a[0].fill(0) としていたが、これではもとの a[0]の値を上書きしてしまうので、 誤った値 [11, 13, 15] になってしまう。そこで slice を使ってコピーを作るようにした。 2022-04-06

行列の転置

転置行列、すなわち e[i][j]=a[i][j] となる配列 e[i] (i, j=0, 1, 2)を作る。

const a=[[1,2,3],[4,5,6],[7,8,9]]
const e = a[0].map((_,v) => a.map(h => h[v]))
console.log(e) // => [[1,4,7],[2,5,8],[3,6,9]]

この例も私には難しい。最初の map メソッドの引数 (_, v) は、第1の引数は捨て、 第2の引数 v のみを使うことを意味する。v はエスペラントの veritikalo (縦列)の意味である。 第 v 列に対して再度 a.map で横(行)horizontaloごとに h[v] を呼び出してベクトルを作る。 なお、この操作を関数とすることもできる。仮に trans という名前をつけよう。

const a=[[1,2,3],[4,5,6],[7,8,9]]
const trans = m => m[0].map((_, v) => m.map(h => h[v]));
console.log(trans(e)) // => [[1,4,7],[2,5,8],[3,6,9]]

ベクトルの内積

i と j を固定してa[i] と a[j] をそれぞれベクトルとみなしたときの内積を作る。次は i = 1, j = 2 とした場合だ。

const a=[[1,2,3],[4,5,6],[7,8,9]]
const f = a[1].reduce((is, as, i) => is + as * a[2][i], 0)
console.log(f) // => 122 (=28+40+54)

内積を作る関数に ip (エス : interna produto) という名前をつけよう。

const a=[[1,2,3],[4,5,6],[7,8,9]]
const ip = (v, w) => v.reduce((is, as, i) => is + as * w[i], 0);
console.log(a[1], a[2]) // => 122 (=28+40+54)

次に、2 次元配列と内積を発展させる。2 次元配列 a に対し、1 次元配列 a[0] と同じく配列 a[0], a[1], a[2] の内積をそれぞれ求める。

const a=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
const g = a.map(v => ip(v, a[0]));
console.log(g) // => [30, 70, 110] ([1*1+2*2+3*3+4*4,1*5+2*6+3*7+4*8,1*9+2*10+3*11+4*12])

行列の積

さらに発展させて、行列の積を作ることができる。

const a=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
const b=[[3,4],[5,6],[7,8],[9,10]];
const h = a.map(v => trans(b).map(w => ip(v, w)));
console.log(h) // => [[70, 80],[166,192],[262,304]] 

特に、行列が 1 つの場合、a を行列として a と trans(a) の積を作る。

const a=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
const k = a.map(v => a.map(w => ip(v, w)));
console.log(k) // => [[30, 70, 110],[70, 174, 278],[110, 270, 446]] 

この結果を見るとわかる通り、計算には無駄がある。積は対称行列だから、計算は上三角部分だけでいい。 a[i]とa[j]の内積を a[i]*a[j] のように書くとき、次のような二次元配列が作れるか。
[[a[0]*a[0],a[0]*a[1],a[0]*a[2]],[a[1]*a[1],a[1]*a[2]],[a[2]*a[2]]]
妥協して次の書き方にした。もう少しスマートな書き方があるはずだ。

const a=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
const l = a.map(v, i) => a.map((w, j) => (i <= j ? ip(v, w) : undefined));
console.log(l) // => [[30, 70, 110],[, 174, 278],[,, 446]] 

拡大行列

二つの与えられた行列の列を連結させて得られる行列を拡大行列という。 拡大行列は、二つの行列に対し同じ行基本変形を施すことを目的として構成される。列の数を合わせるために、 下記の例では b = [4,3,1] ではないことに注意。

const a=[[1,3,2],[2,0,1],[5,2,2]], b = [[4],[3],[1]];
const c = a.map((v, i) => v.concat(b[i]));
console.log(c); // [[1,2,3,4],[2,0,1,3],[5,2,2,1]]

配列もどき

HTML を扱う場合、一見すると配列でありながら、実は配列でない構造がある。 たとえば、getElementsByTagName で取得した DOMエレメントのオブジェクト HTMLCollection や、 querySelectorAll で取得した DOM エレメントのオブジェクト NodeList は、配列によるアクセスができるので配列と思いがちだが、 実は配列ではない。というのも、配列のクラスに備わっているソートや検索、列挙などが使えないからだ。

	// link 要素列を取得(HTMLCollection)
	const elements = document.getElementsByTagName('link');
	// 列挙したいがエラー
	elements.forEach(element => console.log(element));	

この場合は、ArrayObject = Array.prototype.slice.call(HTMLCollectionObject) のようにすれば、HTMLCollection が Array に変換できる。 Array.prototype.slice() (developer.mozilla.org) 参照。

参考文献

関連

まりんきょ学問所JavaScript 手習い > 配列


MARUYAMA Satosi