【JavaScript】重複のない乱数配列を作ってみた【ES6(ES2015)】

# ０．前提
　「俺はJavaScriptで重複なし乱数配列（整数）が作りたいんや！」ってことでJavaScriptで書いてますが、アルゴリズム自体は多言語に応用できます。尚、アルゴリズムそのものは筆者自身がゼロから考えました。もし他の方の考えたアルゴリズムと同じだったとしてもごめんなさい。
# １．はじめに
　ES6（ES2015）に対応した環境でないと動作しません。筆者は、[Repl.it](https://repl.it/)のJavaScript環境（Node.js v10.16.0）にて動作を確認しています。最新のメジャーブラウザでは動作しますが、IE11以前では動作しません。
　また、筆者が以前書いた記事[JavaScript基礎文法](https://qiita.com/yukuduri/items/cff4a1f9c85114600425)を軸にコード書いているので、初心者の方は先にそちらに目を通すこと推奨。
# ２．アルゴリズム
　『アルゴリズムたいそう～♪』ﾃｯﾃﾃｯﾃﾃｯﾃﾃｯﾃﾃｰ ﾁｬﾝ♪
　『こっちむいて一人で・・・ってなんでやね～ん』
　はい、これ以上やると叩かれそうなのでやめておきます。
　先にアルゴリズムを説明しておくことにしましょう。コードだけ欲しい人は読み飛ばしてください。目標とするのは、重複なしの乱数配列、例えば１～５の乱数配列ならこんな感じ。

| 添字 | 0 | 1 | 2 | 3 | 4 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 3 | 4 | 5 | 2 | 1 |

　こういう乱数が入った配列を作っていきます。いつ使うのかって？
　~~ちょっと話が外れるので最後に書くね（この記事を読むってことはそれが必要な場面があるでしょうから・・・）。~~
　→初めから配列をシャッフルすることを目的にしていたのですが、ここで考えたアルゴリズム自体が「配列をシャッフルする」ものだったので別で記事を書きました：[配列をシャッフルするアルゴリズムを思いついたが既存だった話【JavaScript/Rubyサンプルコードあり】](https://qiita.com/yukuduri/items/f1e2f00bc9f28ebe5b35)

　まずはじめに、候補となる配列を作るよ。

initAry：

| 添字 | 0 | 1 | 2 | 3 | 4 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 1 | 2 | 3 | 4 | 5 |

　０～４（`initAry`の添字の最大値）の乱数を発生させて、initAryからその乱数の位置にある要素を一つ取り出し新しい配列aryに入れるよ。

randomNumber:`3`
取り出す要素は`initAry[3]` = `4`
initAry：

| 添字 | 0 | 1 | 2 | 3 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 1 | 2 | 3 | 5 |

ary:

| 添字 | 0 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 4 |

　これを、initAryが空になるまで繰り返すよ。

randomNumber:`2`
取り出す要素は`initAry[2]` = `3`
initAry：

| 添字 | 0 | 1 | 2 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 1 | 2 | 5 |

ary:

| 添字 | 0 | 1 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 4 | 3 |

　・・・（略）・・・

randomNumber:`0`
取り出す要素は`initAry[0]` = `2`
initAry：

| 添字 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 |

ary:

| 添字 | 0 | 1 | 2 | 3 | 4 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 要素 | 4 | 3 | 5 | 1 | 2 |

　おしまい。

# ３．実装
　さて、実際にこれを実装していこう。今回、class宣言を利用している。先に全貌を提示する。

```JavaScript:RandomNumber.js
class RandomNumber{
  constructor(num, min = 0){
    this.num = num;
    this.min = min;
    this.init();
  }

  /*
   * getRandomIntメソッド
   * 処理：min以上max未満の乱数を返す。 
   * 引数
   * 　min：最小値（整数）
   * 　max：最大値（整数）
   * 返り値：生成された乱数
   */
  getRandomInt(min, max){
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
  }

  /* 
   * initメソッド
   * 処理：minを最小とするnum個の被りなしの乱数が入った配列this.aryを生成。
   * 引数
   * 　num：生成個数（整数）、初期値はthis.num
   * 　min：最小値（整数）、初期値はthis.min
   * 返り値：なし
   */
  init(num = this.num, min = this.min){
    this.index = 0;
    this.ary = [];
    let initAry = [];

    for(let i = 0; i < num; i++){
      initAry.push(min + i);
    }
    
    while(initAry.length > 0){
      this.ary.push(initAry.splice(this.getRandomInt(0,initAry.length), 1).pop());
    }
  }

  /* 
   * nextメソッド
   * 処理：実行するたびに、this.aryの要素を順に返す。
   * 　　　配列の最後までいったときははじめの要素に戻る。
   * 引数：なし
   * 返り値：this.index位置のthis.aryの要素
   */
  next(){
    if(this.index >= this.ary.length){
      this.index = 0;
    }
    return this.ary[this.index++];
  }
}
```

　初期化も行いたいため、`init`メソッドを`constructor`と別に定義している。これによって、例えば`init()`とすると乱数配列を再生成できる。
## `constructor`
<dl>
  <dt>処理</dt>
  <dd>numとminをそれぞれ同名のクラスメンバにする。</dd>
  <dd>その後、initメソッドを引数無しで実行する。</dd>
  <dt>引数</dt>
  <dd>num：生成する乱数の個数</dd>
  <dd>min：生成する乱数の最小値、初期値は0</dd>
</dl>
```JavaScript
  constructor(num, min = 0){
    this.num = num;
    this.min = min;
    this.init();
  }
```
　`constructor(num, min = 0)`のように書いて`min`の初期値を`0`にすることで、第2引数を指定せずにクラス生成すると最小値`0`の`num`個の整数乱数配列が生成されます。生成過程は後述の`init`メソッドで。

## `getRandomInt`メソッド
<dl>
  <dt>処理</dt>
  <dd>min以上max未満の乱数を返す。</dd>
  <dt>引数</dt>
  <dd>min：最小値（整数）</dd>
  <dd>max：最大値（整数）</dd>
  <dt>返り値</dt>
  <dd>生成された乱数</dd>
</dl>
```JavaScript
  getRandomInt(min, max){
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
  }
```
コードは[Math.random() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/random)のサンプルコードを転用したもの。
※[MDNのライセンス](https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses)に従い掲載。

## `init`メソッド
<dl>
  <dt>処理</dt>
  <dd>minを最小とするnum個の重複なしの乱数が入った配列this.aryを生成。</dd>
  <dt>引数</dt>
  <dd>num：生成個数（整数）、初期値はthis.num</dd>
  <dd>min：最小値（整数）、初期値はthis.min</dd>
  <dt>返り値</dt>
  <dd>なし</dd>
</dl>
```JavaScript
  init(num = this.num, min = this.min){
    this.index = 0;
    this.ary = [];
    let initAry = [];

    for(let i = 0; i < num; i++){
      initAry.push(min + i);
    }
    
    while(initAry.length > 0){
      this.ary.push(initAry.splice(this.getRandomInt(0,initAry.length), 1).pop());
    }
  }
```
引数省略時、`this.min`を最小とする`this.num`個の整数乱数配列を生成。

`this.index = 0;`：nextメソッドで使うものなので後述。

`this.ary = [];`、`let initAry = [];`：この２つが配列であることを宣言

`for`文：候補となる配列`initAry`を生成
　`initAry.push(min + i);`で最小値+iの値を`initAry`に入れていくことで生成している

`while`文：乱数配列`this.ary`を生成
　`initAry.splice(this.getRandomInt(0,initAry.length), 1).pop()`で`initAry`の要素からランダムで一つ取り出している。
　最後に`.pop()`をつけているのは、`Array`クラスの`splice`メソッドは取り出す要素が一つでも要素数`1`の配列を返してしまうためである。
　`initAry.splice(this.getRandomInt(0,initAry.length), 1)`とすると、`this.ary`の中身が「要素数`1`の配列」を`num`個格納した配列になってしまう。

## `next`メソッド
<dl>
  <dt>処理</dt>
  <dd>実行するたびに、this.aryの要素を順に返す。配列の最後までいったときははじめの要素に戻る。</dd>
  <dt>引数</dt>
  <dd>なし</dd>
  <dt>返り値</dt>
  <dd>this.index位置のthis.aryの要素</dd>
</dl>
```JavaScript
  next(){
    if(this.index >= this.ary.length){
      this.index = 0;
    }
    return this.ary[this.index++];
  }
```

なにかに使えそうと思って作成したメソッド。`forEach`などのループを使うことなく、生成された重複なしの整数乱数配列の中身を順に取り出すことができる。
# ４．使用例
```JavaScript
const r = new RandomNumber(5,1);
console.log(r.ary);
r.init();
console.log(r.ary);
for(let i= 0; i < r.ary.length*2; i++){
  console.log(r.next());
}
```

実行結果

```
[ 4, 1, 3, 5, 2 ]
[ 5, 3, 2, 4, 1 ]
5
3
2
4
1
5
3
2
4
1
```

# ５．参考文献
[(初心者向け) JavaScript のクラス (ES6 対応) - Qiita](https://qiita.com/tadnakam/items/ae8e0e95107e1427983f)
[JavaScriptのclass - Qiita](https://qiita.com/jooex/items/981824f9fb494b448a08)
[Math.random() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/random)
[Array.prototype.splice() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)

# ６．さいごに
　重複なしの乱数配列を作って配列をシャッフルしようとアルゴリズム考えたら最終的に配列をシャッフルするアルゴリズムになってた。なんだこれ。
　ちなみに当初考えていたものは、乱数作って配列に入れて、その全要素と被らない乱数が生成されるまで繰り返すものでした。処理時間めっちゃ長い。
　`class`使う必要あるの？って思った人はわざわざ`class`使う必要ないです。でも、どうやらモジュール化するなら`class`のほうがいい？
　今の所、生のJavaScriptとjQueryをHTML、CSSと併用してしか使ってないので詳しくは知りませんが。
　ではまた。

# 追記
　本記事を参考にしてモジュールとコマンドラインツールを作成しnpmで公開してくださった方がいるのでシェアしておきます（Twitter上のやり取りは約2か月前なので今更感）

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">モジュールとコマンドラインツール作って公開しました。<br><br>結局自分で使うのにプロセス再起動してもユニークにしたく乱数列シードをファイルシステムに保存するようにしたのでHTMLからは使えないものになりました。<br><br>取り急ぎルーチン参考にさせて頂いたので報告します<a href="https://t.co/S3mvkcif0p">https://t.co/S3mvkcif0p</a></p>&mdash; たかや正宗ひ○ゆき🐻@フォロバν (@kssfilo) <a href="https://twitter.com/kssfilo/status/1164434633464791040?ref_src=twsrc%5Etfw">August 22, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>