0.前提
「俺はJavaScriptで重複なし乱数配列(整数)が作りたいんや!」ってことでJavaScriptで書いてますが、アルゴリズム自体は多言語に応用できます。尚、アルゴリズムそのものは筆者自身がゼロから考えました。もし他の方の考えたアルゴリズムと同じだったとしてもごめんなさい。
1.はじめに
ES6(ES2015)に対応した環境でないと動作しません。筆者は、Repl.itのJavaScript環境(Node.js v10.16.0)にて動作を確認しています。最新のメジャーブラウザでは動作しますが、IE11以前では動作しません。
また、筆者が以前書いた記事JavaScript基礎文法を軸にコード書いているので、初心者の方は先にそちらに目を通すこと推奨。
2.アルゴリズム
『アルゴリズムたいそう~♪』テッテテッテテッテテッテテー チャン♪
『こっちむいて一人で・・・ってなんでやね~ん』
はい、これ以上やると叩かれそうなのでやめておきます。
先にアルゴリズムを説明しておくことにしましょう。コードだけ欲しい人は読み飛ばしてください。目標とするのは、重複なしの乱数配列、例えば1~5の乱数配列ならこんな感じ。
添字 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
要素 | 3 | 4 | 5 | 2 | 1 |
こういう乱数が入った配列を作っていきます。いつ使うのかって?
ちょっと話が外れるので最後に書くね(この記事を読むってことはそれが必要な場面があるでしょうから・・・)。
→初めから配列をシャッフルすることを目的にしていたのですが、ここで考えたアルゴリズム自体が「配列をシャッフルする」ものだったので別で記事を書きました:配列をシャッフルするアルゴリズムを思いついたが既存だった話【JavaScript/Rubyサンプルコードあり】
まずはじめに、候補となる配列を作るよ。
initAry:
添字 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
要素 | 1 | 2 | 3 | 4 | 5 |
0~4(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 |
おしまい。
3.実装
さて、実際にこれを実装していこう。今回、class宣言を利用している。先に全貌を提示する。
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
- 処理
- numとminをそれぞれ同名のクラスメンバにする。
- その後、initメソッドを引数無しで実行する。
- 引数
- num:生成する乱数の個数
- min:生成する乱数の最小値、初期値は0
getRandomInt
メソッド
- 処理
- min以上max未満の乱数を返す。
- 引数
- min:最小値(整数)
- max:最大値(整数)
- 返り値
- 生成された乱数
init
メソッド
- 処理
- minを最小とするnum個の重複なしの乱数が入った配列this.aryを生成。
- 引数
- num:生成個数(整数)、初期値はthis.num
- min:最小値(整数)、初期値はthis.min
- 返り値
- なし
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 = [];`:この2つが配列であることを宣言
`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
などのループを使うことなく、生成された重複なしの整数乱数配列の中身を順に取り出すことができる。
4.使用例
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
5.参考文献
(初心者向け) JavaScript のクラス (ES6 対応) - Qiita
JavaScriptのclass - Qiita
Math.random() - JavaScript | MDN
Array.prototype.splice() - JavaScript | MDN
6.さいごに
重複なしの乱数配列を作って配列をシャッフルしようとアルゴリズム考えたら最終的に配列をシャッフルするアルゴリズムになってた。なんだこれ。
ちなみに当初考えていたものは、乱数作って配列に入れて、その全要素と被らない乱数が生成されるまで繰り返すものでした。処理時間めっちゃ長い。
class
使う必要あるの?って思った人はわざわざclass
使う必要ないです。でも、どうやらモジュール化するならclass
のほうがいい?
今の所、生のJavaScriptとjQueryをHTML、CSSと併用してしか使ってないので詳しくは知りませんが。
ではまた。
追記
本記事を参考にしてモジュールとコマンドラインツールを作成しnpmで公開してくださった方がいるのでシェアしておきます(Twitter上のやり取りは約2か月前なので今更感)
モジュールとコマンドラインツール作って公開しました。
— たかや正宗ひ○ゆき🐻@フォロバν (@kssfilo) August 22, 2019
結局自分で使うのにプロセス再起動してもユニークにしたく乱数列シードをファイルシステムに保存するようにしたのでHTMLからは使えないものになりました。
取り急ぎルーチン参考にさせて頂いたので報告しますhttps://t.co/S3mvkcif0p