TypeScript・JavaScriptでFIFOキューを作る方法

はいさい！ちゅらデータぬオースティンやいびーん！

# 概要
TypeScriptでジェネリック型を用いて、FIFOキューを作る方法を紹介します。

最初は純粋なFIFOキューを作り、それから削除ができるキューも作ってみます。

## 目次
1. 簡素なFIFOキューを実装
1. 削除できるキューを作る
1. キューに入っている実態の数を露呈する
1. まとめ

## 背景
始める前に、キューについて説明します。
### キューとは何か
キューは、配列を保って保持されているメモリー上の実体の集まりです。通常のアレイと似て非なるものです。
アレイと大きく異なるのは、キューは、後ろにしか追加できないこと、そして、前からしか取り出せないことです。
![スクリーンショット 2022-08-05 9.18.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/2d9d8445-c17b-429a-ccab-790964cf1789.png)
画像の引用：<a href="https://en.wikipedia.org/wiki/Queue_(abstract_data_type)#:~:text=In%20computer%20science%2C%20a%20queue,other%20end%20of%20the%20sequence.">Wikipedia, Queue (abstract data type)</a>

### FIFOとは
FIFOは、<strong>First-in, First-out</strong>の頭文字で、直訳すると、<strong>最初に入るものから出す</strong>。
キュー配列の後ろに実体が入ってくるのですが、出る時は、必ず配列の前から出していく、これがFIFOです。

### Enqueue
キューに実体を追加することを英語でEnqueueと言います。

### Dequeue
キューから実体を取り出すことをDequeueと言います。

# 簡素なFIFOキューを実装
最初に、教科書通りのFIFOキューを作ってみましょう。

まず、クラス構文で書きますが、TypeScriptにキューの実体の型を教えるために、クラス定義の頭に<a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">ジェネリック型</a>を使います。
```typescript
class TaskQueue<T>{
  // ここで定義するTは、new TaskQueue<{ name: string }>というように、
  // クラス・インスタンスを作成する時に、TypeScriptインタプリターにキューの値の型を教えることができます。
}
```
### キューの<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">`Map`</a>を定義します。

通常のアレイを使わないのは、今回の実装では、`Map`の方が使い勝手がいいのと、パフォーマンスがぐんといいからです。

`Map`は一つの実体に対して、キーと、その値を持ちます。通常のオブジェクトに似ています。

今回は、キーに数値の型を使い、値にジェネリック型を使います。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
}
```
ここで<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields">`#`</a>を使っているのは、デザインパターンとして、このクラスから作成されるPrototype = インスタンスでは、キューの`Map`を直接変更できるようにしたくないからです。
<hr>

### FrontとBackをクラス変数として定義します。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number = 1;
  #back: number = 0;
}
```
`#front`は次から取り出される予定の実体の、`Map`におけるキーになっています。

`#back`は、新しい実際のキーを定義するように使います。新しい実体を追加する時に、この`#back`に1を足しますので、初期値として0にします。後で`constructor`の中に入れておきましょう。
<hr>

### `contructor`で初期値を設定する
上記で定義していた値を`constructor`の中でも定義できます。

個人的には、contructorで設定する方が好きですが、統一していればいいかと思います。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number;
  #back: number;

  constructor() {
    this.#front = 1;
    this.back = 0;
    this.#queue = new Map();
  }
}
```
### キューに追加するメソッドを作成する
ここで、`Map.set`を実行する前に`#back`を+1します。また、不要ですが、`#back`の追加後の値、つまり、追加した実体のキーの値を返すようにしています。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number;
  #back: number;

  constructor() {
    this.#front = 1;
    this.back = 0;
    this.#queue = new Map();
  }

  enqueue(value: T): number {
    this.#back++;
    this.#queue.set(this.#back, value);
    return this.#back;
  }
}
```
### キューから取り出すメソッドを作成する
ここで注目していただきたいのは、
1. `#front`が`#back`より大きければ、エラーを吐かせて処理を中断すること
1. `Map.get`を実行してから、`Map.delete`で`#queue`から削除します。
1. 最後に、キューの最善の値のキーを表す`#front`を+1します。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number;
  #back: number;

  constructor() {
    this.#front = 1;
    this.back = 0;
    this.#queue = new Map();
  }

  enqueue(value: T): number {
    this.#back++;
    this.#queue.set(this.#back, value);
    return this.#back;
  }

  dequeue(): T {
    if (this.#front > this.#back) throw Error("End of Queue.");
    const poppedItem = this.#queue.get(this.#front);
    this.#queue.delete(this.#front);
    this.#front++;
    return poppedItem;
  }
}
```
### キューから取り出さずに、次の実体を「覗く」メソッドを作成する
キューには通常、キュー配列から取り出さずに、次に出てくる値があるかどうかを判断するためのメソッドがあります。
今回もそのメソッド`peek`を追加しましょう。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number;
  #back: number;

  constructor() {
    this.#front = 1;
    this.back = 0;
    this.#queue = new Map();
  }

  enqueue(value: T): number {
    this.#back++;
    this.#queue.set(this.#back, value);
    return this.#back;
  }

  dequeue(): T {
    if (this.#front > this.#back) throw Error("End of Queue.");
    const poppedItem = this.#queue.get(this.#front);
    this.#queue.delete(this.#front);
    this.#front++;
    return poppedItem;
  }

  peek(): T | undefined {
    return this.#queue.get(this.#front);
  }
}
```
ここまで来れば、単純だけど、立派なキューが作れます！
試しに`tsc`でビルドして、ブラウザのコンソールで実行してみましょう。
## ビルドしてブラウザで試してみる
上記のTypeScriptクラスを`tsc`でビルドして、ブラウザのコンソールで実行して、インスタンスを作成して試してみましょう。
![スクリーンショット 2022-08-05 10.04.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/58791306-1296-7965-7431-141e56d88597.png)
上等！

# 削除できるキューを作る
次に、キューに追加した実体を削除することができるキューの実装をします。

### 削除するメソッドを作成
まずは、指定された数値のキーに実体があるかどうかをチェックします。

あれば、削除します。
```typescript
  remove(key: number): void {
    const hasItem = this.#queue.get(key);
    if (!hasItem) throw TypeError("Item not in queue.");
    this.#queue.delete(key);
  }
```
### `dequeue`のメソッドを修正する
読者はお気づきでしょうか？この実装のキューだと、上記の`remove`を実行した後に、必ず変な問題が起きます。

そうです、削除したキーのところを`dequeue`しようとした時に、エラーが起きます。

仮に、三つの実体をキューに追加したとしましょう。
|キー|1|2|3|
|---|---|---|---|
|値|"a"|"b"|"c"|

そして、`2`を削除します。
|キー|1|3|
|---|---|---|
|値|"a"|"c"|

すると、以下のコードを実行すると
```javascript
queue.dequeue() // "a"
queue.dequeue() // エラーが起きます。
```
エラーが起きることが想像できますね。

これを防ぐために、`dequeue`のロジックを少し調整します。
```typescript
  dequeue(): T {
    let poppedItem: T | null = null;
    while (!poppedItem) {
      if (this.#front > this.#back) throw Error("End of Queue.");
      const hasItem = this.#queue.get(this.#front);
      if (hasItem) {
        poppedItem = hasItem;
        break;
      }
      this.#front++;
    }
    this.#queue.delete(this.#front);
    this.#front++;
    return poppedItem;
  }
```
このロジックでは、存在するキーに当たるか、`#front`が`#back`より大きくなるかのどちらかまで、順番を進んで処理します。

同じことを`peek`に対して実装してもいいです。
```typescript
  peek(): T | undefined {
    let peekedItem: T | undefined = undefined;
    while (!peekedItem) {
      if (this.#front > this.#back) break;
      const hasItem = this.#queue.get(this.#front);
      if (hasItem) {
        peekedItem = hasItem;
        break;
      }
      this.#front++;
    }
    return peekedItem;
  }
```

# キューに入っている実態の数を露呈する
最後に、キューの`Map`は外からアクセスできないようになっているので、キューの長さを知る方法がありません。

もし必要であれば、<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get">`getter`</a>で`TaskQueue.prototype.size`の値を露呈することができます。
```typescript
class TaskQueue<T> {
  #queue: Map<number, T>;
  #front: number;
  #back: number;

  constructor() {
    this.#front = 1;
    this.back = 0;
    this.#queue = new Map();
  }

  get size(): number {
    return this.#queue.size;
  }

  enqueue(value: T): number {
    if (this.size > this.#maxSize) throw Error("Queue is full.");
    this.#back++;
    this.#queue.set(this.#back, value);
    return this.#back;
  }

  remove(key: number): void {
    const hasItem = this.#queue.get(key);
    if (!hasItem) throw TypeError("Item not in queue.");
    this.#queue.delete(key);
  }

  dequeue(): T {
    let poppedItem: T | null = null;
    while (!poppedItem) {
      if (this.#front > this.#back) throw Error("End of Queue.");
      const hasItem = this.#queue.get(this.#front);
      if (hasItem) {
        poppedItem = hasItem;
        break;
      }
      this.#front++;
    }
    this.#queue.delete(this.#front);
    this.#front++;
    return poppedItem;
  }

  peek(): T | undefined {
    let peekedItem: T | undefined = undefined;
    while (!peekedItem) {
      if (this.#front > this.#back) break;
      const hasItem = this.#queue.get(this.#front);
      if (hasItem) {
        peekedItem = hasItem;
        break;
      }
      this.#front++;
    }
    return peekedItem;
  }
}
```
# まとめ
ここまで、TypeScript、もしくはJavaScriptでキューを実装する方法を説明してきましたが、いかがでしょうか？

TypeScriptだと、型チェックができて便利ですが、JavaScriptで使っていると、現状の実装では、キューに追加する実態の型が統一されていなくてもエラーが出ないようになっているのでご注意ください。

JavaScriptでも動く型チェック（<a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types">TypeGuard</a>）を実装することができるので、おすすめです。