M.Hiroi's Home Page

JavaScript Programming

お気楽 JavaScript プログラミング超入門


Copyright (C) 2017-2025 Makoto Hiroi
All rights reserved.

配列の新機能

●ES2015 で追加されたメソッド

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, <1 empty item>, 3, <1 empty item>, 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++ の一次元配列とほとんど同じです。

  1. new TypedArray(length);
  2. new TypedArray(typedArray);
  3. new TypedArray(object);
  4. new TypedArray(buffer [, byteOffset [, length]]);

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(16) [
  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(16) [
   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

エンディアンを意識してバッファにアクセスする場合、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

Lisp ライクな連結リスト

ES2015 の機能を使った簡単な例題として Lisp ライクな連結リストを作ってみました。

●連結リストのデータ構造

今回作成する連結リストの構造を図で表すと次のようになります。

  CAR CDR       CAR CDR       CAR CDR
 ┌─┬─┐    ┌─┬─┐    ┌─┬─┐
 │・│・┼─→│・│・┼─→│・│・┼→ nil
 └┼┴─┘    └┼┴─┘    └┼┴─┘
   ↓            ↓            ↓
   1            2            3

        図 : リスト内部の構造

リストは貨物列車にたとえるとわかりやすいでしょう。車両に相当するものを「コンスセル (cons cell)」といいます。貨物列車には多数の車両が接続されて運行されるように、リストは複数のコンスセルを接続して構成されます。1 つのコンスセルには、貨物 (データ) を格納する場所 (CAR) と、連結器に相当する場所 (CDR) があります。

上図では、コンスセルを箱で表しています。コンスセルの左側がデータを格納する場所で、右側が次のコンスセルと連結しています。この例では、3 つのコンスセルが接続されています。それから、最後尾のコンスセルの CDR にはデータ nil を格納します。nil は空リストをあらわすデータとして使います。今回のライブラリでは nil を () と表示します。Lisp / Scheme では、このような連結リストを (1 2 3) と表記します。

●ドットリスト

Lisp / Scheme の場合、リストの終端は CDR 部に格納されるデータがセル以外であれば、そこがリストの終端であることがわかります。つまり、nil でなくてもかまわないのです。リストの終端が nil 以外のデータである場合、そのリストを次のように表します。

 ┌─┬─┐            ┌─┬─┐
 │・│・┼─→ ()     │・│・┼─→2
 └┼┴─┘            └┼┴─┘
   ↓                    ↓
   1                    1

 (1) ≡ (1 . nil)      (1 . 2)

 ┌─┬─┐    ┌─┬─┐    ┌─┬─┐
 │・│・┼─→│・│・┼─→│・│・┼─→ ()
 └┼┴─┘    └┼┴─┘    └┼┴─┘
   ↓            ↓            ↓
   1            2            3

     (1 2 3) ≡ (1 . (2 . (3 . ())))

        図 : ドットリスト (1)

左右の括弧の中間にドット ( . ) を置き、左側に CAR 部のデータを、右側に CDR 部のデータを書きます。つまり、リスト (a) は (a . ()) と表すことができます。このようなデータを「ドット対 (dotted pair)」と呼びます。たとえば、CAR 部が 1 で CDR 部が 2 であれば (1 . 2) となります。

それでは、リスト (1 2 3) の終端を 4 に変えてみましょう。ドット対を使った表記法では、(1 . (2 . (3 . 4))) となりますが、これは (1 2 3 . 4) と表すことができます。

 ┌─┬─┐    ┌─┬─┐    ┌─┬─┐
 │・│・┼─→│・│・┼─→│・│・┼─→4
 └┼┴─┘    └┼┴─┘    └┼┴─┘
   ↓            ↓            ↓
   1            2            3

     (1 2 3 . 4) ≡ (1 . (2 . (3 . 4)))

    図 : ドットリスト (2)

このように、nil 以外のアトムで終端されたリストを「ドットリスト (dotted list)」と呼びます。なお、ライブラリのほとんどのメソッドはドットリストの終端要素を nil と同様に扱います。たとえば、リスト (1 2 3 . 4) を for ... of 文で取り出す場合、終端の 4 は nil とみなされて取り出すことはできません。これは Lisp / Scheme の関数と同じ動作です。ご注意くださいませ。


●連結リストの仕様


●プログラムリスト

//
// list.js : Lisp ライクな連結リスト
//
// Copyright (c) 2017-2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

class List {
  constructor(x, y) {
    this._first = x;
    this._rest = y;
  }
  get first() { return this._first; }
  get rest() { return this._rest; }
  set first(x) { this._first = x; }
  set rest(x) { this._rest = x; }

  // 述語
  static isNil(xs) { return xs === nil; }
  static isList(xs) { return xs instanceof List; }
  static isCons(xs) { return List.isList(xs) && !List.isNil(xs); }

  static equal(xs, ys) {
    while (List.isCons(xs) && List.isCons(ys)) {
      if (!List.equal(xs.first, ys.first)) return false;
      xs = xs.rest;
      ys = ys.rest;
    }
    return xs == ys;
  }

  every(pred) {
    for (let x of this) {
      if (!pred(x)) return false;
    }
    return true;
  }

  some(pred) {
    for (let x of this) {
      if (pred(x)) return true;
    }
    return false;
  }

  // 空リストを返す
  static nil() { return nil; }

  // 生成
  static cons(x, y) { return new List(x, y); }

  static list(...args) {
    let xs = nil;
    for (let i = args.length - 1; i >= 0; i--) xs = new List(args[i], xs);
    return xs;
  }

  static makeList(n, x) {
    let xs = nil;
    while (n-- > 0) xs = new List(x, xs);
    return xs;
  }

  static iterate(n, a, func) {
    let xs = nil;
    while (n-- > 0) {
      xs = new List(a, xs);
      a = func(a);
    }
    return xs.nreverse();
  }

  // 文字列化
  toString() {
    let s = "(", xs = this;
    for (; List.isCons(xs); xs = xs.rest) {
      s += xs.first.toString();
      if (List.isCons(xs.rest)) s += " ";
    }
    if (!List.isNil(xs)) s += " . " + xs.toString();
    s += ")";
    return s;
  }

  // 表示
  inspect(depth) { return this.toString(); }

  // ジェネレータ
  *[Symbol.iterator]() {
	for (let xs = this; List.isCons(xs); xs = xs.rest)
      yield xs.first;
  }

  // リスト操作

  // 末尾の要素
  last() {
    let xs = this;
    while (List.isCons(xs.rest)) xs = xs.rest;
    return xs.first;
  }

  // 長さ
  length() {
    let n = 0;
    for (let x of this) n++;
    return n;
  }

  // 反転
  reverse(xs = nil) {
    for (let x of this) xs = new List(x, xs);
    return xs;
  }

  // 破壊的反転
  nreverse(ys = nil) {
    let xs = this;
    while (List.isCons(xs)) {
      let zs = xs.rest;
      xs.rest = ys;
      ys = xs;
      xs = zs;
    }
    return ys;
  }

  // 連結
  _append(ys) {
    if (List.isNil(this)) return ys;
    if (List.isNil(ys)) return this;
    let xs = this.reverse(),
        zs = xs.nreverse();
    xs.rest = ys;
    return zs;
  }

  append(...ys) {
    let n = ys.length;
    if (n == 0) return this;
    let zs = ys[--n];
    while (--n >= 0) {
      zs = ys[n]._append(zs);
    }
    return this._append(zs);
  }

  // 破壊的連結
  _nconc(ys) {
    if (List.isNil(this)) return ys;
    if (List.isNil(ys)) return this;
    let xs = this;
    while (List.isCons(xs.rest)) xs = xs.rest;
    xs.rest = ys;
    return this;
  }

  nconc(...ys) {
    let n = ys.length;
    if (n == 0) return this;
    let zs = ys[--n];
    while (--n >= 0) {
      zs = ys[n]._nconc(zs);
    }
    return this._nconc(zs);
  }

  // 先頭から n 個の要素を取り出す
  _take(n) {
    let xs = this, ys = nil;
    for (; n-- > 0 && List.isCons(xs); xs = xs.rest) {
      ys = new List(xs.first, ys);
    }
    return [ys, xs]
  }

  // 挿入 (非破壊的)
  add(n, value) {
    if (n == 0) return new List(value, this);
    let [xs, ys] = this._take(n);
    return xs.nreverse(new List(value, ys));
  }

  // 削除 (非破壊的)
  delete(n) {
    if (n == 0) return this.rest;
    let [xs, ys] = this._take(n);
    if (List.isCons(ys))
      return xs.nreverse(ys.rest);
  }

  // 先頭から n 個の要素を取り出す
  take(n) {
    let [xs, ys] = this._take(n);
    return xs.nreverse();
  }

  // 先頭から n 個の要素を取り除く
  drop(n) {
    let xs = this;
    for (; n-- > 0 && List.isCons(xs); xs = xs.rest);
    return xs;
  }

  // n 番目の要素
  get(n) {
    let xs = this.drop(n);
    if (List.isCons(xs)) return xs.first;
  }

  // n 番目の要素を更新
  set(n, value) {
    let xs = this.drop(n);
    if (List.isCons(xs)) return xs.first = value;
  }

  // 置換 (非破壊的)
  substitute(oldItem, newItem) {
    let ys = nil;
    for (let x of this) {
      ys = new List(x == oldItem ? newItem : x, ys);
    }
    return ys.nreverse();
  }

  substituteIf(pred, newItem) {
    let ys = nil;
    for (let x of this) {
      ys = new List(pred(x) ? newItem : x, ys);
    }
    return ys.nreverse();
  }

  // 破壊的
  nsubstitute(oldItem, newItem) {
    for (let xs = this; List.isCons(xs); xs = xs.rest) {
      if (xs.first == oldItem) xs.first = newItem;
    }
    return this;
  }

  nsubstituteIf(pred, newItem) {
    for (let xs = this; List.isCons(xs); xs = xs.rest) {
      if (pred(xs.first)) xs.first = newItem;
    }
    return this;
  }

  // 探索
  member(x) {
    for (let xs = this; List.isCons(xs); xs = xs.rest) {
      if (xs.first == x) return xs;
    }
    return nil;
  }

  contains(x) {
    for (let y of this) {
      if (y == x) return true;
    }
    return false;
  }

  indexOf(x) {
    let n = 0;
    for (let y of this) {
      if (y == x) return n;
      n++;
    }
    return -1;
  }

  find(pred) {
    for (let x of this) {
      if (pred(x)) return x;
    }
    return false;
  }

  count(x) {
    let c = 0;
    for (let y of this) {
      if (x == y) c++;
    }
    return c;
  }

  countIf(pred) {
    let c = 0;
    for (let y of this) {
      if (pred(y)) c++;
    }
    return c;
  }

  // 高階関数
  static _map(func, ...xs) {
    let ys = nil;
    while (xs.every(List.isCons)) {
      let zs = xs.map(x => x.first);
      ys = new List(func(...zs), ys);
      xs = xs.map(x => x.rest);
    }
    return ys;
  }

  map(func, ...ys) {
    return List._map(func, this, ...ys).nreverse();
  }

  flatMap(func, ...ys) {
    let xs = List._map(func, this, ...ys),
        zs = xs.first;
    for (let x of xs.rest) zs = x.append(zs);
    return zs;
  }

  filter(pred) {
    let ys = nil;
    for (let x of this) {
      if (pred(x)) ys = new List(x, ys);
    }
    return ys.nreverse();
  }

  reduce(func, a) {
    for (let x of this) a = func(a, x);
    return a;
  }

  reduceRight(func, a) {
    for (let x of this.reverse()) a = func(x, a);
    return a;
  }

  forEach(func) {
    for (let x of this) func(x);
  }

  takeWhile(pred) {
    let xs = nil;
    for (let x of this) {
      if (!pred(x)) break;
      xs = new List(x, xs);
    }
    return xs.nreverse();
  }

  dropWhile(pred) {
    let xs = this;
    while (List.isCons(xs) && pred(xs.first)) xs = xs.rest;
    return xs;
  }
}

// 終端の定義
const nil = new List(undefined, undefined);

// ES2015 モジュール
export default List;

●簡単なテスト

//
// testlist.js : 連結リストのテスト
//
// Copyright (c) 2017-2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

// ES2015 モジュール
import List from "./list.js";

console.log("----- cons, first, rest -----");
var a = List.cons(1, 2);
console.log('%s', a);
console.log(a.first);
console.log(a.rest);

console.log("----- isNil, isList, isCons, equal -----");
console.log(List.isNil(List.nil()));
console.log(List.isList(List.nil()));
console.log(List.isCons(List.nil()));
console.log(List.isNil(a));
console.log(List.isList(a));
console.log(List.isCons(a));
console.log(List.equal(a, a));
console.log(List.equal(a, List.cons(1, 3)));

console.log("----- list, makeList, iterate -----");
console.log('%s', List.list(1,2,3,4,5));
console.log('%s', List.makeList(10, 0));
console.log('%s', List.iterate(10, 0, x => x + 1));

console.log("--- for...of -----");
var xs = List.iterate(8, 1, x => x + 1);
for (let x of xs) console.log(x);

console.log("----- last, length, reverse, nreverse -----")
console.log(xs.last());
console.log(xs.length());
console.log('%s', xs.reverse());
var ys = xs.nreverse();
console.log('%s', xs);
console.log('%s', ys);

console.log("----- append, nconc -----");
xs = List.list(1,2,3);
ys = List.list(4,5,6);
var zs = List.list(7,8,9);
console.log('%s', xs.append(ys));
console.log('%s', xs.append(ys, zs));
xs.nconc(ys, zs);
console.log('%s', xs);
console.log('%s', ys);
console.log('%s', zs);

console.log("----- get, set -----");
console.log(xs.get(0));
console.log(xs.get(8));
console.log(xs.get(9));
console.log(xs.set(0, 10));
console.log('%s', xs);
console.log(xs.set(8, 11));
console.log('%s', xs);
console.log(xs.set(9, 12));
console.log('%s', xs);

console.log("----- add, delete -----");
console.log('%s', xs.add(0, 100));
console.log('%s', xs.add(5, 100));
console.log('%s', xs.add(9, 100));
console.log('%s', xs.add(10, 100));
console.log('%s', xs.delete(0));
console.log('%s', xs.delete(5));
console.log('%s', xs.delete(8));
console.log(xs.delete(9));

console.log("----- take, drop -----");
console.log('%s', xs.take(0));
console.log('%s', xs.take(5));
console.log('%s', xs.take(10));
console.log('%s', xs.drop(0));
console.log('%s', xs.drop(5));
console.log('%s', xs.drop(10));

console.log("----- substitute, nsubstitute -----");
xs = List.list(1,2,3,4,5,6,7,8,9,10);
console.log('%s', xs);
console.log('%s', xs.substitute(1, 100));
console.log('%s', xs.substitute(5, 100));
console.log('%s', xs.substitute(10, 100));
console.log('%s', xs.substitute(11, 100));
console.log('%s', xs.substituteIf(x => x % 2 == 0, 10));
console.log('%s', xs.nsubstitute(1, 100));
console.log('%s', xs);
console.log('%s', xs.nsubstitute(5, 100));
console.log('%s', xs);
console.log('%s', xs.nsubstitute(10, 100));
console.log('%s', xs);
console.log('%s', xs.nsubstitute(11, 100));
console.log('%s', xs);
console.log('%s', xs.nsubstituteIf(x => x % 2 == 0, 10));
console.log('%s', xs);

console.log("----- member, contains, indexOf, find -----");
xs = List.list(1,2,3,4,5,6,7,8,9,10);
console.log('%s', xs.member(1));
console.log('%s', xs.member(10));
console.log('%s', xs.member(11));
console.log(xs.contains(1));
console.log(xs.contains(10));
console.log(xs.contains(11));
console.log(xs.indexOf(1));
console.log(xs.indexOf(10));
console.log(xs.indexOf(11));
console.log(xs.find(x => x == 1));
console.log(xs.find(x => x == 10));
console.log(xs.find(x => x == 11));

console.log("----- count, countIf -----");
xs = List.list(1,2,1,2,3,4,1,2,3,4,5);
console.log(xs.count(1));
console.log(xs.count(4));
console.log(xs.count(5));
console.log(xs.count(6));
console.log(xs.countIf(x => x == 1));
console.log(xs.countIf(x => x == 4));
console.log(xs.countIf(x => x == 5));
console.log(xs.countIf(x => x == 6));

console.log("----- map, flatMap -----");
xs = List.list(1,2,3,4,5);
ys = List.list(6,7,8,9,10);
console.log('%s', xs.map(x => x * x));
console.log('%s', xs.flatMap(x => List.list(x, x)));
console.log('%s', xs.map((x, y) => List.list(x, y), ys));
console.log('%s', xs.flatMap((x, y) => List.list(x, y), ys));

console.log("----- filter, reduce, reduceRight -----");
xs = List.list(1,2,3,4,5,6,7,8);
console.log('%s', xs.filter(x => x % 2 == 0));
console.log('%s', xs.reduce((x, y) => x + y, 0));
console.log('%s', xs.reduceRight((x, y) => x + y, 0));
console.log('%s', xs.reduce((x, y) => List.cons(y, x), List.nil()));
console.log('%s', xs.reduceRight((x, y) => List.cons(x, y), List.nil()));

console.log("----- forEach, every, some -----");
xs.forEach(console.log);
console.log(List.list(1,3,5,7,9).every(x => x % 2 == 1));
console.log(List.list(1,3,5,7,8).every(x => x % 2 == 1));
console.log(List.list(1,3,5,7,8).some(x => x % 2 == 0));
console.log(List.list(1,3,5,7,9).some(x => x % 2 == 0));

console.log("----- takeWhile, dropWhile -----");
console.log('%s', xs);
console.log('%s', xs.takeWhile(x => x < 5));
console.log('%s', xs.dropWhile(x => x < 5));

●実行結果

 
$ node testlist.js
----- cons, first, rest -----
(1 . 2)
1
2
----- isNil, isList, isCons, equal -----
true
true
false
false
true
true
true
false
----- list, makeList, iterate -----
(1 2 3 4 5)
(0 0 0 0 0 0 0 0 0 0)
(0 1 2 3 4 5 6 7 8 9)
--- for...of -----
1
2
3
4
5
6
7
8
----- last, length, reverse, nreverse -----
8
8
(8 7 6 5 4 3 2 1)
(1)
(8 7 6 5 4 3 2 1)
----- append, nconc -----
(1 2 3 4 5 6)
(1 2 3 4 5 6 7 8 9)
(1 2 3 4 5 6 7 8 9)
(4 5 6 7 8 9)
(7 8 9)
----- get, set -----
1
9
undefined
10
(10 2 3 4 5 6 7 8 9)
11
(10 2 3 4 5 6 7 8 11)
undefined
(10 2 3 4 5 6 7 8 11)
----- add, delete -----
(100 10 2 3 4 5 6 7 8 11)
(10 2 3 4 5 100 6 7 8 11)
(10 2 3 4 5 6 7 8 11 100)
(10 2 3 4 5 6 7 8 11 100)
(2 3 4 5 6 7 8 11)
(10 2 3 4 5 7 8 11)
(10 2 3 4 5 6 7 8)
undefined
----- take, drop -----
()
(10 2 3 4 5)
(10 2 3 4 5 6 7 8 11)
(10 2 3 4 5 6 7 8 11)
(6 7 8 11)
()
----- substitute, nsubstitute -----
(1 2 3 4 5 6 7 8 9 10)
(100 2 3 4 5 6 7 8 9 10)
(1 2 3 4 100 6 7 8 9 10)
(1 2 3 4 5 6 7 8 9 100)
(1 2 3 4 5 6 7 8 9 10)
(1 10 3 10 5 10 7 10 9 10)
(100 2 3 4 5 6 7 8 9 10)
(100 2 3 4 5 6 7 8 9 10)
(100 2 3 4 100 6 7 8 9 10)
(100 2 3 4 100 6 7 8 9 10)
(100 2 3 4 100 6 7 8 9 100)
(100 2 3 4 100 6 7 8 9 100)
(100 2 3 4 100 6 7 8 9 100)
(100 2 3 4 100 6 7 8 9 100)
(10 10 3 10 10 10 7 10 9 10)
(10 10 3 10 10 10 7 10 9 10)
----- member, contains, indexOf, find -----
(1 2 3 4 5 6 7 8 9 10)
(10)
()
true
true
false
0
9
-1
1
10
false
----- count, countIf -----
3
2
1
0
3
2
1
0
----- map, flatMap -----
(1 4 9 16 25)
(1 1 2 2 3 3 4 4 5 5)
((1 6) (2 7) (3 8) (4 9) (5 10))
(1 6 2 7 3 8 4 9 5 10)
----- filter, reduce, reduceRight -----
(2 4 6 8)
36
36
(8 7 6 5 4 3 2 1)
(1 2 3 4 5 6 7 8)
----- forEach, every, some -----
1
2
3
4
5
6
7
8
true
false
true
false
----- takeWhile, dropWhile -----
(1 2 3 4 5 6 7 8)
(1 2 3 4)
(5 6 7 8)

改訂 2025 年 1 月 8 日