ES2015 では Array にいくつか新しいメソッドが追加されました。
Array.from(arrayLike [, mapFn [, thisArg]]) Array.of(args, ...)
Array.from() の引数 arrayLike は、配列型のオブジェクトか iterable オブジェクトで、その要素を格納した配列を返します。引数 mapFn が指定された場合、arrayLike の要素に mapFn を適用して、その結果を配列に格納して返します。引数 thisArg が指定された場合、mapFn の this に thisArg がセットされます。Array.of() は可変長引数の関数で、引数を格納した配列を返します。
簡単な実行例を示します。
> var s = new Set([1, 2, 3, 4, 5]) undefined > Array.from(s) [ 1, 2, 3, 4, 5 ] > Array.from(s, x => x * x) [ 1, 4, 9, 16, 25 ] > var m = new Map([["foo", 10], ["bar", 20], ["baz", 30]]) undefined > Array.from(m) [ [ 'foo', 10 ], [ 'bar', 20 ], [ 'baz', 30 ] ] > Array.from("hello, world") [ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd' ] > Array.of() [] > Array.of(1) [ 1 ] > Array.of(1, 2, 3, 4, 5) [ 1, 2, 3, 4, 5 ]
メソッド fill() は配列を指定した値で埋めます。
fill(value [, start = 0 [, end = 配列の長さ]])
> var a = [1, 2, 3, 4, 5] undefined > a.fill(10) [ 10, 10, 10, 10, 10 ] > a [ 10, 10, 10, 10, 10 ] > a.fill(0, 1, 3) [ 10, 0, 0, 10, 10 ] > a.fill(0, 1, 4) [ 10, 0, 0, 0, 10 ]
メソッド find(), findIndex() は配列を探索する高階関数です。
find(pred [, thisArg]) => item or undefined findIndex(pred [, thisArg]) => index or -1
どちらの関数も述語 pred が真を返す要素を線形探索します。見つけた場合、find() はその要素を、findIndex() はその位置を返します。見つからない場合、find() は udefined を、findIndex() は -1 を返します。引数 thisArg が指定された場合、pred の this に thisArg がセットされます。
簡単な例を示します。
> var a = [1, 2, 3, 4, 5, 6, 7, 8] undefined > a.find(x => x == 1) 1 > a.find(x => x == 8) 8 > a.find(x => x == 9) undefined > a.findIndex(x => x == 1) 0 > a.findIndex(x => x == 8) 7 > a.findIndex(x => x == 9) -1
要素の探索であれば、一つ前の仕様 (ES5) で追加されたメソッド indexOf(), lastIndexOf() が便利です。
indexOf(item [, start]) lastIndexOf(item [, start])
indexOf() は先頭から、lastIndexOf() は末尾から探索を開始します。引数 start は探索の開始位置を指定します。item と要素は === 演算子と同等の方法で比較されます。見つけた場合はその位置を返します。見つからない場合は -1 を返します。
> [1,2,3,4,5,1,2,3,4,5].indexOf(5) 4 > [1,2,3,4,5,1,2,3,4,5].indexOf(5, 5) 9 > [1,2,3,4,5,1,2,3,4,5].indexOf(6) -1 > [1,2,3,4,5,1,2,3,4,5].lastIndexOf(5) 9 > [1,2,3,4,5,1,2,3,4,5].lastIndexOf(5, 4) 4 > [1,2,3,4,5,1,2,3,4,5].lastIndexOf(6) -1
メソッド entries() は、添字と要素を格納した配列を返すイテレータを生成します。メソッド keys() は添字を返すイテレータを生成します。
> var a = [1, 2, 3, 4, 5] undefined > for (let x of a.entries()) console.log(x) [ 0, 1 ] [ 1, 2 ] [ 2, 3 ] [ 3, 4 ] [ 4, 5 ] undefined > for (let x of a.keys()) console.log(x) 0 1 2 3 4 undefined
JavaScript の配列は要素を省略すると undefined が設定されます。
> var b = [1, , 3, , 5] undefined > b [ 1, , 3, , 5 ]
要素が undefined の場合、for...in 文で取り出されるプロパティ (添字) はスルーされます。
> for (let x in b) console.log(x) 0 2 4 undefined
for...of 文、entries()、keys() の場合、要素が undefined でもスルーされません。
> for (let x of b) console.log(x) 1 undefined 3 undefined 5 undefined > for (let x of b.entries()) console.log(x) [ 0, 1 ] [ 1, undefined ] [ 2, 3 ] [ 3, undefined ] [ 4, 5 ] undefined > for (let x of b.keys()) console.log(x) 0 1 2 3 4 undefined
メソッド copyWithin() は配列の要素を移動します。
copyWithin(target, start[, end = this.length])
start から end 未満までの要素を target 以降に移動します。
> var c = [1, 2, 3, 4, 5, 6, 7, 8] undefined > c.copyWithin(0, 4) [ 5, 6, 7, 8, 5, 6, 7, 8 ] > c [ 5, 6, 7, 8, 5, 6, 7, 8 ] > var d = [1, 2, 3, 4, 5, 6, 7, 8] undefined > d.copyWithin(4, 0, 4) [ 1, 2, 3, 4, 1, 2, 3, 4 ] > d [ 1, 2, 3, 4, 1, 2, 3, 4 ]
ES2015 の一つ前の仕様 (ES5) で、Array には関数型言語でお馴染みの便利な高階関数が追加されました。
map(func [, thisArg])
map() は配列の要素を関数 func に渡して実行し、その結果を格納した新しい配列を返します。func の仕様を示します。
func(item [, index [, array]])
item は配列の要素、index は添字、array は map() を実行している配列です。
簡単な実行例を示します。
> [1, 2, 3, 4, 5].map(x => x * x) [ 1, 4, 9, 16, 25 ] > [1, 2, 3, 4, 5].map((...args) => console.log(args)) [ 1, 0, [ 1, 2, 3, 4, 5 ] ] [ 2, 1, [ 1, 2, 3, 4, 5 ] ] [ 3, 2, [ 1, 2, 3, 4, 5 ] ] [ 4, 3, [ 1, 2, 3, 4, 5 ] ] [ 5, 4, [ 1, 2, 3, 4, 5 ] ] [ undefined, undefined, undefined, undefined, undefined ]
FizzBuzz 問題も簡単に解くことができます。
リスト : FizzBuzz 問題 function* iota(n, m, step = 1) { for (; n <= m; n += step) yield n; } function change(x) { if (x % 15 == 0) return "FizzBuzz"; if (x % 3 == 0) return "Fizz"; if (x % 5 == 0) return "Buzz"; return x; } function fizzbuzz() { return Array.from(iota(1,100)).map(change); }
> fizzbuzz().toString() '1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz, Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,31,32,Fizz,34,Buzz,Fizz,37,38,Fizz, Buzz,41,Fizz,43,44,FizzBuzz,46,47,Fizz,49,Buzz,Fizz,52,53,Fizz,Buzz,56,Fizz,58, 59,FizzBuzz,61,62,Fizz,64,Buzz,Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,76,77, Fizz,79,Buzz,Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,91,92,Fizz,94,Buzz,Fizz, 97,98,Fizz,Buzz'
filter(pred [, thisArg])
filter() は配列の要素を述語 pred に渡して実行し、真を返す要素を新しい配列に格納して返します。
> [5, 6, 4, 7, 3, 8, 2, 9, 1].filter(x => x > 4) [ 5, 6, 7, 8, 9 ] > [5, 6, 4, 7, 3, 8, 2, 9, 1].filter(x => x % 2 == 0) [ 6, 4, 8, 2 ]
簡単な例題として、n 以下の素数を求めるプログラムを作ります。
リスト ; n 以下の素数を求める function sieve(n) { let a = Array.from(iota(3, n, 2)), ps = [2]; while (a[0] * a[0] <= n) { ps.push(a[0]); a = a.filter(x => x % a[0] != 0); } return ps.concat(a); }
> sieve(100).toString() '2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97' > sieve(500).toString() '2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103, 107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211, 223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331, 337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449, 457,461,463,467,479,487,491,499'
reduce(), reduceRight() は畳み込みを行います。
reduce(func [, init]) reduceRight(func [, init])
reduce() は配列の先頭から、reduceRight() は末尾から畳み込みを行います。init は初期値で、省略された場合は先頭要素 (または末尾要素) が初期値になります。
関数 func の仕様を示します。
func(acc, item [, index [, array]])
acc は累積変数、item は配列の要素、index は要素の添字、array は畳み込みを実行中の配列です。
簡単な実行例を示します。
> [1, 2, 3, 4, 5].reduce((a, b) => a + b, 0) 15 > [1, 2, 3, 4, 5].reduce((a, b, i, ary) => { console.log(a, b, i, ary); return a + b}, 0) 0 1 0 [ 1, 2, 3, 4, 5 ] 1 2 1 [ 1, 2, 3, 4, 5 ] 3 3 2 [ 1, 2, 3, 4, 5 ] 6 4 3 [ 1, 2, 3, 4, 5 ] 10 5 4 [ 1, 2, 3, 4, 5 ] 15 > [1, 2, 3, 4, 5].reduceRight((a, b) => a + b, 0) 15 > [1, 2, 3, 4, 5].reduceRight((a, b, i, ary) => { console.log(a, b, i, ary); return a + b}, 0) 0 5 4 [ 1, 2, 3, 4, 5 ] 5 4 3 [ 1, 2, 3, 4, 5 ] 9 3 2 [ 1, 2, 3, 4, 5 ] 12 2 1 [ 1, 2, 3, 4, 5 ] 14 1 0 [ 1, 2, 3, 4, 5 ] 15
forEach(func [, thisArg])
forEach() は配列の要素を関数 func に渡して実行します。返り値は undefined です。
関数 func の仕様を示します。
func(item [, index [, array]])
item は配列の要素、index は要素の添字、array は forEach() を実行中の配列です。
簡単な実行例を示します。
> [1, 2, 3, 4, 5].forEach(console.log) 1 0 [ 1, 2, 3, 4, 5 ] 2 1 [ 1, 2, 3, 4, 5 ] 3 2 [ 1, 2, 3, 4, 5 ] 4 3 [ 1, 2, 3, 4, 5 ] 5 4 [ 1, 2, 3, 4, 5 ] undefined
every(pred [, thisArg]) some(pred [, thisArg])
every() は配列の要素を述語 pred に渡して実行し、全ての要素が真を返したとき every() の返り値は真になります。some() は一つでも真を返す要素があれば真を返します。
簡単な実行例を示します。
> [1, 3, 5, 7, 9].every(x => x % 2 == 1) true > [1, 3, 5, 7, 9, 10].every(x => x % 2 == 1) false > [1, 3, 5, 7, 9].some(x => x % 2 == 0) false > [1, 3, 5, 7, 9, 10].some(x => x % 2 == 0) true
ES2015 では、バイナリデータを効率的に扱うため、「型付き配列 (typed arrays)」が導入されました。基本的にはC/C++ の一次元配列とほとんど同じです。
TypedArray は仮名で、実際に使用するコンストラクタ名を以下に示します。
1 は要素が length 個の配列を生成します。配列の要素は 0 に初期化されます。2 は型付き配列 typeArray をコンストラクタで指定した型に変換した新しい配列を生成します。3 は Array.from() のように、object の要素を格納した新しい配列を生成します。このとき、要素はコンストラクタで指定した型に変換します。4 はあとで説明します。
配列のアクセスは今までと同様に角カッコ [ ] を使用します。また、型付き配列は iterable なオブジェクトなので、for...of 文を使うこともできます。
簡単な使用例を示しましょう。
> var a = new Int32Array(10) undefined > a Int32Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] > for (let i = 0; i < 10; i++) a[i] = i 9 > a Int32Array [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] > for (let i = 0; i < 10; i++) console.log(a[i]) 0 1 2 3 4 5 6 7 8 9 undefined > for (let x of a) console.log(x) 0 1 2 3 4 5 6 7 8 9 undefined > var b = new Int32Array([1, -2, 3, -4, 5]) undefined > b Int32Array [ 1, -2, 3, -4, 5 ] > var b = new Uint32Array([1, -2, 3, -4, 5]) undefined > b Uint32Array [ 1, 4294967294, 3, 4294967292, 5 ] > var b = new Uint16Array([1, -2, 3, -4, 5]) undefined > b Uint16Array [ 1, 65534, 3, 65532, 5 ]
型付き配列には便利なメソッドがたくさん用意されています。詳細はリファレンスマニュアル TypedArray - JavaScript | MDN をお読みくださいませ。
次は、型付き配列のコンストラクタで、4 番目の方法を説明します。引数の buffer は ArrayBuffer() で取得したメモリ領域 (バッファ) です。
var buffer = new ArrayBuffer(size)
引数 size は取得するメモリ領域の大きさで、単位はバイトです。buffer を型付き配列のコンストラクタに渡すと、その領域を型付き配列として使用することができます。byteOffset と length を指定すると、その範囲が型付き配列になります。この操作を「ビュー (view)」といいます。一つの領域を異なる型付き配列に分割して使用することもできますし、同じ領域を共有することもできます。
簡単な実行例を示します。
> var buff = new ArrayBuffer(16) undefined > var a = new Int32Array(buff) undefined > a Int32Array [ 0, 0, 0, 0 ] > for (let i = 0; i < 4; i++) a[i] = i + 1 4 > a Int32Array [ 1, 2, 3, 4 ] > var b = new Int8Array(buff) undefined > b Int8Array [ 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0 ] > for (let i = 0; i < 16; i++) b[i] = i + 1 16 > b Int8Array [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ] > a Int32Array [ 67305985, 134678021, 202050057, 269422093 ]
バッファを共有する場合、CPU の「エンディアン」に注意してください。
エンディアンとは、整数値をメモリに格納するときのバイトの並び順のことをいいます。例えば、0x12345678 のデータをメモリにセットする場合、x86 系と他の CPU (古いですが MC680x0 など) では下図に示すように順番が逆になります。
0x12345678 をメモリに格納する場合 アドレス Low High [12|34|56|78] 680x0 系の場合 アドレス Low High [78|56|34|12] x86 系の場合 図 : エンディアンの違い
大きな位から順番に詰めていく方式を「ビッグエンディアン」といい、小さな位から詰めていく方法を「リトルエンディアン」といいます。エンディアンは CPU によって異なり、モトローラの MC680x0 はビッグエンディアンで、インテル系の x86 は逆にリトルエンディアンになります。
したがって、buff に 0x01, 0x02, ..., 0x10 をセットし、それをリトルエンディアンで 32 bit 整数として扱うと、次のようになります。
0x04030201 => 67305985 0x08070605 => 134678021 0x0c0b0a09 => 202050057 0x100f0e0d => 269422093
エンディアンを意識してバッファにアクセスする場合、DataView() が役に立ちます。
new DataView(buffer [, byteOffset [, byteLength]])
引数 buffer は ArrayBuffer() で確保したメモリ領域で、byteOffset から byteLength までの範囲が DataView オブジェクトになります。
DataView オブジェクトにはデータ型ごとにアクセスメソッドが用意されていて、そのメソッドでエンディアンを指定することができます。詳細はリファレンスマニュアル DataView - JavaScript | MDN をお読みくださいませ。
簡単な例として、32 bit 符号付き整数のアクセスメソッドを示します。
getInt32(offset [, littleEndian]) setInt32(offset, value [, littleEndian])
どちらのメソッドも DataView の先頭から offset バイトを起点にしてデータのアクセスを行います。引数 littleEndian が真の場合、リトルエンディアンでアクセスします。littleEndian を省略するか偽の場合はビッグエンディアンでアクセスします。
> var buff = new ArrayBuffer(4) undefined > var a = new DataView(buff) undefined > var b = new Uint8Array(buff) undefined > b Uint8Array [ 0, 0, 0, 0 ] > a.setInt32(0, 1) undefined > b Uint8Array [ 0, 0, 0, 1 ] > a.getInt32(0) 1 > a.setInt32(0, 1, true) undefined > b Uint8Array [ 1, 0, 0, 0 ] > a.getInt32(0, true) 1
ES2015 の機能を使った簡単な例題として「連結リスト (linked list)」を作ります。
// // linkedlist.js : 連結リスト // // Copyright (C) 2017 Makoto Hiroi // // セル class Cell { constructor(item, next = null) { this._item = item; this._next = next; } get item() { return this._item; } get next() { return this._next; } set item(x) { this._item = x; } set next(x) { this._next = x; } } // 連結リスト class List { constructor(...args) { let xs = new Cell(null); // ヘッダセル this._top = xs; for (let x of args) { xs.next = new Cell(x, null); xs = xs.next; } } get top() { return this._top; } set top(x) { this._top = x; } // 作業用 _nth(n) { let xs = this.top; for (let i = -1; i < n && xs != null; i++) xs = xs.next; return xs; } // n 番目の要素を求める nth(n) { let xs = this._nth(n); return xs != null ? xs.item : null; } // n 番目に x を挿入する add(n, x) { let xs = this._nth(n - 1); if (xs != null) { xs.next = new Cell(x, xs.next); return x; } return null; } // n 番目の要素を削除 delete(n) { let xs = this._nth(n - 1); if (xs != null && xs.next != null) { let ds = xs.next; xs.next = ds.next; return ds.item; } return null; } // pred が真を返す要素を探す find(pred) { for (let x of this) { if (pred(x)) return x; } return false; } // 空リストか? isEmpty() { return this.top.next == null; } // 空にする clear() { this.top.next = null; } // 巡回 forEach(func) { for (let xs = this.top.next; xs != null; xs = xs.next) { func(xs.item); } } // ジェネレータ *[Symbol.iterator]() { for (let xs = this.top.next; xs != null; xs = xs.next) { yield xs.item; } } // 文字列 toString() { return "List(" + [...this].join(",") + ")"; } // 表示 inspect(depth) { return this.toString(); } } // 簡単なテスト var xs = new List() console.log(xs.isEmpty()); for (let x = 0; x < 10; x++) xs.add(x, x); console.log(xs.isEmpty()); console.log(xs) console.log(xs.nth(0)); console.log(xs.nth(9)); console.log(xs.nth(10)); xs.forEach(console.log); var s = ""; for (let x of xs) s += x + " "; console.log(s); xs.delete(0); console.log(xs); xs.delete(8); console.log(xs); xs.delete(4); console.log(xs); for (let i = 0; i < 10; i++) console.log(xs.find(x => x == i));
C>node linkedlist.js true false List(0,1,2,3,4,5,6,7,8,9) 0 9 null 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 List(1,2,3,4,5,6,7,8,9) List(1,2,3,4,5,6,7,8) List(1,2,3,4,6,7,8) false 1 2 3 4 false 6 7 8 false
ES2015 の機能を使った簡単な例題として「二分木 (binary tree)」を作ります。二分木の詳しい説明は拙作のページ Algorithms with Python 二分木, ヒープ をお読みください。
// // tree.js : 二分木 // // Copyright (C) 2017 Makoto Hiroi // // 節 class Node { constructor(item, left = null, right = null) { this._item = item; this._left = left; this._right = right; } get item() { return this._item; } get left() { return this._left; } get right() { return this._right; } set item(x) { this._item = x; } set left(x) { this._left = x; } set right(x) { this._right = x; } // 探索 static contains(node, x) { while (node != null) { if (x == node.item) { return true; } else if (x < node.item) { node = node.left; } else { node = node.right; } } return false; } // 追加 static add(node, x) { if (node == null) { return new Node(x); } else if (x < node.item) { node.left = Node.add(node.left, x); } else if (x > node.item) { node.right = Node.add(node.right, x); } return node; } // 最小値の探索 static min(node) { while (node.left != null) node = node.left; return node.item; } // 最小値の節を削除 static deleteMin(node) { if (node.left == null) { return node.right; } else { node.left = Node.deleteMin(node.left); return node; } } // 削除 static delete(node, x) { if (node != null) { if (x == node.item) { if (node.left == null) return node.right; if (node.right == null) return node.left; node.item = Node.min(node.right); node.right = Node.deleteMin(node.right); } else if (x < node.item) { node.left = Node.delete(node.left, x); } else { node.right = Node.delete(node.right, x); } } return node; } // 巡回 static forEach(func, node) { if (node != null) { Node.forEach(func, node.left); func(node.item); Node.forEach(func, node.right); } } // ジェネレータ static *generator(node) { if (node != null) { yield* Node.generator(node.left); yield node.item; yield* Node.generator(node.right); } } } // 二分木 class Tree { constructor() { this._root = null; } get root() { return this._root; } set root(x) { this._root = x; } // 追加 add(x) { this.root = Node.add(this.root, x); } // 探索 contains(x) { return Node.contains(this.root, x); } // 削除 delete(x) { this.root = Node.delete(this.root, x); } // 空にする clear() { this.root = null; } // 空か? isEmpty() { return this.root == null; } // 巡回 forEach(func) { Node.forEach(func, this.root); } // ジェネレータ *[Symbol.iterator]() { yield* Node.generator(this.root); } // 文字列に変換 toString() { return "Tree(" + [...this].join(",") + ")"; } // 表示 inspect(depth) { return this.toString(); } } // 簡単なテスト var a = new Tree(); console.log(a); for (let x of [5,6,4,7,3,8,2,9,1,0]) { a.add(x); } console.log(a); for (let x = -1; x <= 10; x++) { console.log(a.contains(x)); } for (let x = -1; x <= 10; x++) { a.delete(x); console.log(a); }
C>node tree.js Tree() Tree(0,1,2,3,4,5,6,7,8,9) false true true true true true true true true true true false Tree(0,1,2,3,4,5,6,7,8,9) Tree(1,2,3,4,5,6,7,8,9) Tree(2,3,4,5,6,7,8,9) Tree(3,4,5,6,7,8,9) Tree(4,5,6,7,8,9) Tree(5,6,7,8,9) Tree(6,7,8,9) Tree(7,8,9) Tree(8,9) Tree(9) Tree() Tree()