⛓️

配列だけじゃないJavaScriptのArray

2022/12/25に公開

Jitoinを開発しているitteと申します。Webエンジニア向けのサービスや記事を公開しています。

今回はJavaScriptのArrayの様々な用途を紹介します。

Arrayは直訳すると『配列』になりますが、Arrayは配列でありながら配列以外のいくつかのデータ構造の代わりとしても使えるようになっています。

用途別にArrayを説明していきます。

配列

Arrayは当然、配列として利用できます。

特定の長さの空の配列を作るには2つの書き方があります。このときの配列は、長さはあるが要素が無い状態(疎配列)になっています。

let a = new Array(3)
let a = Array(3)

配列

配列を作るときに初期値を設定する場合は次の3つの書き方があります。

let a = new Array('cat', 'dog', 'rat')
let a = Array('cat', 'dog', 'rat')
let a = ['cat', 'dog', 'rat']

配列の要素を参照するときは添字を使います。最初の要素を参照するときは添字は0になります。

let value = a[0]

配列の要素に値を代入するときも添え字を使います。

a[0] = 'cat'

配列の要素すべてに同じ値を代入するにはfillメソッドを使います。

a.fill('cat')

console.log(a) // -> ['cat', 'cat', 'cat']

fillメソッドを使えば、特定の長さの同じ値の配列を作ることができます。

let a = Array(3).fill(1)

console.log(a) // -> [1, 1, 1]

可変長配列

Arrayは配列の長さを自由に変更することができる可変長配列でもあります。

まず、長さ0の配列を作るには次の3つの書き方があります。

let a = new Array()
let a = Array()
let a = []

長さを変更するにはlengthプロパティに整数を代入します。このとき、元の長さよりも長い値を代入した場合は、増えた分の要素は無い状態になります。逆に元の長さよりも短い値を代入した場合は、はみ出た分の要素は失われます。

a.length = 3

Arrayには、配列の末尾に要素を追加するpush()メソッドが提供されています。要素を追加すると追加した分だけ長さも長くなります。

let a = []
a.push('cat', 'dog')

console.log(a.length) // -> 2
console.log(a[1]) // -> 'dog'

スタック

スタックは、先入れ後出しのデータ構造と言われます。スタックに対して行える操作は基本的には 入れる/出す だけです。
(スタックの中身を確認するために参照したり検索したりすることはあります)

スタック

例えば、ブラウザの戻る機能を実現するにはスタックを使います。WebページのURLをスタックに入れていき、戻るボタンが押されたときにスタックから最後に入れられたURLを取り出せば1つ前のページに戻ることができます。

スタックは様々な場面で活躍するデータ構造です。

スタックを作るときは長さ0のArrayを作ります。配列のときのようにArrayを使うとスタックであることが分かりにくくなるので避けています。

let stack = []

スタックに値を入れるときはpush()メソッドを使います。

stack.push('cat')
stack.push('dog')

スタックから値を取り出すときはpop()メソッドを使います。

let value = stack.pop()

console.log(value) // -> 'dog'

キュー

キューは、先入れ先出しのデータ構造と言われます。キューに対して行える操作は基本的には 入れる/出す だけです。
(キューの中身を確認するために参照したり検索したりすることはあります)

キュー

キューというのはスタックに比べると、用途が分かりにくいデータ構造です。
「先に入れたものをその順番で出すのなら、入れなくても良かったんじゃ」と思うかもしれません。
キューが活躍するのは、入れる処理と出す処理の速度(量)が異なるときです。

例えばキーボードからの入力を考えると、ユーザーは自分のペースでどんどん文字をタイピングします。普通はタイプした瞬間にPCにタイプした文字が表示されます。しかし、PC側は様々な演算を行っているため、ときには他の演算に時間がかかってユーザーのタイプをすぐに表示できないかもしれません。そんなときにキューを挟んでおけば、PCは他の処理が終わった後でキューに溜まっている文字を画面に描画すればよくなります。

他にも、画像を検索した結果をアニメーションで少しずつ画面に描画したいとします。画像検索の結果の数は検索キーワードによって異なりますし、多すぎる場合は50個ずつのように何度かに分けて結果が返ってくる場合があります。しかし、画面に描画するのは10個ずつと決まっていたとすると、検索結果をそのまま描画するわけにはいきません。そこでキューを使えば、検索結果が何個ずつだろうが何回に分かれていようが、キューから10個ずつ取り出せば10個ずつ描画できます。

キューを作るときは長さ0のArrayを作ります。Arrayを使うとキューであることが分かりにくくなるので避けています。

let queue = []

キューに値を入れるときはpush()メソッドを使います。

queue.push('cat')
queue.push('dog')

キューから値を取り出すときはshift()メソッドを使います。

let value = queue.shift()

console.log(value) // -> 'cat'

両端キュー(デック)

両端キューは、先入れ先出しだけでなく、出した側から入れて入れた側から出すこともできるデータ構造です。キューと名前がついていますが、スタックとキューを合体したようなデータ構造になっています。
行える操作は基本的にはスタックやキューと同じで 入れる/出す ですが、両側からできる点に違いがあります。

両端キュー

両端キューは、私は正直言って実務で使ったことがありません。利用するケースは少ないと思います。
例えばこんなときに使えるのではないかと想像しています。

  • 普段はスタックとして使いつつ、古い値はキューで取り出して削除したい。
  • 普段はキューとして使いつつ、値を取り出したものの、やっぱり戻したい。

両端キューを作るときは長さ0のArrayを作ります。Arrayを使うと両端キューであることが分かりにくくなるので、避けています。

let deque = []

両端キューに後から値を入れるときはpush()メソッドを使います。

deque.push('cat')
deque.push('dog')

両端キューに前から値を入れるときはunshift()メソッドを使います。

deque.unshift('rat')
deque.unshift('fox')

両端キューの後から値を取り出すときはpop()メソッドを使います。

let value = deque.pop()

console.log(value) // -> 'dog'

両端キューの前から値を取り出すときはshift()メソッドを使います。

let value = deque.shift()

console.log(value) // -> 'fox'

連結リスト

連結リストは要素が鎖のように繋がったデータ構造です。それぞれの要素が、自分の次の要素を参照しています。

連結リスト

配列との違いとして、要素の繋ぎ方を変えるだけで要素を削除したり差し込んだりすることができます。
配列だと、要素を削除したり追加したりするためには、それより後方の要素を全てずらさないといけませんが、連結リストはその必要がありませんので、追加/削除が頻繁に発生するケースに向いています。

例えば、テキストエディタだと、行削除や行追加が頻繁に発生しますので、各行を連結リストで繋ぐと効率よく処理ができます。

Arrayにはsplice()という、この追加/削除を簡単に行えるメソッドがあります。

ただし、Arrayは実際には連結リストではありませんので、本物の連結リストのように効率が良くありません。追加/削除が頻繁に発生する場合はちゃんと連結リストを作ったほうが良いです。

とはいえ、連結リストの長さが短いときや、追加/削除がたまにしか発生しないときはArrayで代用して全然問題ありません。

ちなみに、これまで説明したスタックやキューも、本来であれば連結リストで実装したほうが効率が良いデータ構造です。

連結リストを作るときは長さ0のArrayを作ります。Arrayを使うと連結リストであることが分かりにくくなるので避けています。

let linkedList = []

連結リストの前後への値の出し入れは両端キューと同じですので省略します。

連結リストの間に要素を追加するときはsplice()メソッドを使います。

  • 第一引数は要素を追加する位置です。
  • 第二引数は0にしてください。
  • 第三引数以降が追加する値になります。
let linkedList = []
linkedList.push('cat')
linkedList.push('dog')

linkedList.splice(1, 0, 'rabbit', 'bear')

console.log(linkedList) // -> ['cat', 'rabbit', 'bear', 'dog']

連結リストの任意の位置の要素を削除するときもsplice()メソッドを使います。

  • 第一引数は要素を削除する位置です。
  • 第二引数は削除する要素の個数です。
let linkedList = []
linkedList.push('cat')
linkedList.push('dog')
linkedList.push('rat')
linkedList.push('fox')

linkedList.splice(1, 2)

console.log(linkedList) // -> ['cat', 'fox']

そしてsplice()メソッドは要素を削除しつつ追加するということもできます。

「何それ、いつ使うの?」

と思うかもしれません。値を入れ替えるのであれば代入を使えばいいのですから。

具体例を挙げるとこんな計算を行うときに便利です。

計算

使い方は、第二引数で削除する個数を指定しつつ、第三引数以降に追加する要素も指定します。

let linkedList = []
linkedList.push(4)
linkedList.push('+')
linkedList.push(6)
linkedList.push('*')
linkedList.push(2)

linkedList.splice(2, 3, 12)

console.log(linkedList) // -> [4, '+', 12]

複数の連結リストを繋げるには、concat()メソッドもしくはスプレッド演算子を使います。どちらの書き方も既存の連結リストを変更せずに新しい連結リストを返します。

let newLinedList = linkedList1.concat(linkedList2, linkedList3)
let newLinedList = [...linkedList1, ...linkedList2, ...linkedList3]

連結リストから一部分を取り出すには、slice()メソッドを使います。既存の連結リストには変更を加えず、新たな連結リストを返します。
連結リストを分割するときにも利用できます。

  • 第一引数に取り出す開始位置の添字を指定します。
  • 第二引数に取り出す終了位置の添字を指定します。ただし、終了位置の要素は取り出す連結リストに含まれません。
let linkedList = []
linkedList.push('cat')
linkedList.push('dog')
linkedList.push('rat')
linkedList.push('fox')

let newLinedList = linkedList.slice(1, 3)

console.log(newLinedList) // -> ['dog', 'rat']

タプル

タプルとは複数の値を組にしたものです。

配列と大きな違いとして、
配列は一つの配列の中に同じ型(整数や文字列など)の要素が入っていて長さが任意なのに対して、
タプルは一つのタプルの中で、何番目に何の型の要素が入っているのかはっきりしています

例えば、名前と年齢のタプルだと、a[0]が文字列型の名前で、a[1]が整数型の年齢というように、はっきりと何番目が何のデータなのか決まっています。

タプルではlengthプロパティを使うことはまずありません。また、値が空ということもありません。for文で順番に処理をするといったこともありません。

タプルはオブジェクトのプロパティが添字になったものだと言えるかもしれません。タプルを使ったコードはオブジェクトに置き換えることができます。

タプルを作るときは、初期値を持つArrayを作ります。このとき、Arrayを使うとタプルであることが分かりにくくなるので避けています。

let tupple = ['hinata', 11]

タプルから値を取り出すときは、添字で取り出しても良いのですが、そうすると可読性が低くなってしまうので、分割代入を使って取り出します。

let [name, age] = tupple

console.log(name) // -> 'hinata'
console.log(age) // -> 11

タプルを関数の引数として受け取るときも分割代入を使います。

f(['hinata', 11]) // -> 'hinata'

function f([name, age]) {
  console.log(name)
}

まとめ

Arrayはいくつかのデータ構造の代用として使える便利な組み込みオブジェクトです。
各データ構造は結局すべてArrayなので、例えばスタックと配列の間でデータ変換をするといったことが必要なく、シームレスに色々な用途に活用できます。

Discussion