質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

21回答

11795閲覧

「-3, -2, -1, 1, 2, 3」の6つの整数をランダムで得る「いい」方法

miu_ras

総合スコア902

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

4グッド

4クリップ

投稿2017/02/02 23:50

編集2017/02/04 03:09

最初は「-3~3の整数を得る」という仕様でよく、こんな感じでした。※本当の最初のコードはコメントアウトの物でしたが、ごくまれに-4が出るというバグがあるので修正しました。

javascript

1var v = (Math.floor(Math.random() * 7) - 3); 2//var v = (Math.ceil(Math.random() * 7) - 4);

ただ、0は除外しようと思いここからコードを変更しようと思ったのですが、シンプルな方法が思いつきませんでした。

一応、以下の2案は思いついたのですが、

  • 候補の値を配列で持つ
  • 「1, 2, 3」をランダムで得た後、「+ or -」をランダムで得てかける

あまりエレガントではないと思い、採用を躊躇しています。
この程度の規則なら数学的なテクニックを駆使して導き出るのではないかと思っています。

「-3, -2, -1, 1, 2, 3」の6つの整数をランダムで得る「いい」方法、
これについて教えてください。よろしくお願いします。

補足追記

2017/02/04 12:00頃、質問内容を「数学的」から「いい方法」に変更しました。

私がもともと考えていた「数学的な方法」とは以下の条件です。

  • Math関数と算術演算子・ビット演算子だけを使う
  • Math.randomの使用は1回のみ
  • 条件分岐・配列(あるいは配列に類するもの)を使わない
  • 出来れば、関数・変数を使わない、1行で書けるコード

ただ、「数学的な方法」にこだわりすぎたり、他の選択肢を捨ててしまうのも違うなと思い、「いい方法」に変更しました。

最初に考えていた「数学的な方法」を満たす回答も引き続き歓迎しますが、同時にシンプルな方法や、意外な方法もお待ちしています。

Lhankor_Mhy, ShoheiTai, magf, Y.H.👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2017/02/03 12:43

「数学的なテクニック」と「エレガント」について具体的な指標を下さい。 私なりに数学的でエレガントなコードを書いてみましたが、miu_ras さんの求めるものになっているのか、自信がありません…。
guest

回答21

0

0-5をマッピングする式だけ書きますが

(x+4)%7-3

スマホからなので同じ回答見落としていたらごめんなさい。


追記。ちゃんとJSで書きます。

return (Math.floor(Math.random() * 6) + 4) % 7 - 3

投稿2017/02/09 23:55

編集2017/02/10 03:41
yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/10 00:55

すみません。どういう意味ですか? 全くわかりません。 最低限、JavaScriptで何かしらの結果が得られるコードを書いていただきたいです。
退会済みユーザー

退会済みユーザー

2017/02/10 01:36 編集

これ、きれいですね。というか巧い!
ozwk

2017/02/10 01:32

なるほどー
Zuishin

2017/02/10 01:39

0 から 5 まで x が変化するとき、計算結果は 1, 2, 3, -3, -2, -1 となりますね。 つまり 0 から 5 までの乱数を求めてこの計算式に当てはめれば目的の乱数が得られると。
Lhankor_Mhy

2017/02/10 02:12

ああ、個人的にはこれがベストですね。一般化してカリー化するなら n => x => (x => (x+n+1)%(2*n+1)-n )(Math.floor( Math.random() * 2 * n ))
yuba

2017/02/10 03:44

miu_rasさん、失礼しました。コードの意図はZuishinさんの補足していただいたとおり、0〜5の乱数をこの式に突っ込めば-3〜-1,1〜3の乱数になるというものです。 ただ、言われてJSで清書して気づいたのが、JSの乱数ってそういえば最初から整数でなく実数だったんですよね… 0.5とかの実数が登場するのが気持ち悪いと思ってひねり出した式だったのですが、もともと実数なのならそっちでもいいかとなったところです。 整数で乱数を発生させる処理系だったらもっと鮮やかにきまったのですが。
miu_ras

2017/02/11 01:03

なるほど。「xが0-5」だったのですね。 シンプルでいいですね。余りをうまく使うのが鍵ですね。 ありがとうございました
guest

0

「数学的」という言葉の定義が不明なので確かな回答はできませんが、

javascript

1var v = Math.floor(Math.random() * 6); 2v -= (v < 3) ? 3 : 2;

または

javascript

1const map = [-3, -2, -1, 1, 2, 3]; 2var v = map[Math.floor(Math.random() * 6)];

ちなみに、

javascript

1var v = (Math.ceil(Math.random() * 7) - 4);

と書くと、v は -4, -3, -2, -1, 0, 1, 2, 3 のいずれかの整数となります。

なぜなら、0 <= Math.random() < 1である(※)ため、Math.random() が 0 を返したとき、

Math.ceil(0 * 7) - 4 == 0 - 4 == -4;

となるからです。

https://www.ecma-international.org/ecma-262/5.1/#sec-15.8.2.14

Returns a Number value with positive sign, greater than or equal to 0 but less than 1

正の符号で、0 以上 1 未満の数を返します

投稿2017/02/03 00:42

KiyoshiMotoki

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:27

ceilの件はそうですよね。質問を投稿して出勤途中に気付きました。誰も気づかないことを願っていたのですが、teratailでJavaScriptでは無理だったようですね。
miu_ras

2017/02/04 02:28

回答も参考になりました。ありがとうございました
guest

0

2ビットと符号ビットで足りることに着目し、000100を排除すればいいことに気づきました。
1~6をランダムに生成し、下位2ビットを残し、3桁目ビットで全ビットそろえてXOR取ってます。

javascript

1var v = ( x => 2 ( x & 0b11 ) ^ ( 0- ( ( x & 0b100 ) >> 2 ) ) 3)( Math.random() * 6 + 1 )
追記

think49さんの「汎用性が高いコードがエレガント」という言葉と、raccyさんのカリー化に刺激を受けまして、一般化してみました。

javascript

1var f = n => 2 x => ( x => 3 x + (( x & 0b1000000000000000000000000000000) >> 30) - 0b1000000000000000000000000000000 4 )( Math.floor( Math.random() * 2 * n + ( 0b1000000000000000000000000000000 - n ) ) ); 5var v = f(3)();

f(n)は[n, n-1, ... 1,-1, ... -(n-1), -n]の乱数を返す関数を返します。たぶん。
ただし、javascriptで扱える整数に上限があるため、nには上限があります。おそらく。

投稿2017/02/03 12:24

編集2017/02/04 11:54
Lhankor_Mhy

総合スコア36074

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 06:55

これは少し難しいのですが、かっこいいですね。 「000と100を排除すればいい」この発想がすごいですね。 勉強になります。ありがとうございました
Lhankor_Mhy

2017/02/04 11:52

ありがとうございます。かっこいいと言っていただけてとてもうれしいです。 調子に乗って一般化してみました。 でも実際、現場でこんな可読性の悪いコードを書いてたら怒られるんでしょうね。
guest

0

ベストアンサー

var v = (Math.floor(Math.random() * 3) + 1) * ((Math.floor(Math.random() * 2) + 1) * 2 - 3);

random の仕様がわかっていませんでした。例題を見て 0 は出ないと思い込みました。書き直しました。

もう一つ。

var a = Math.floor(Math.random() * 6); var v = a - 3 + Math.floor(a / 3);

もう一つ。

var v = Math.round((Math.floor(Math.random() * 6) - 2.5) * 1.1);

追記
三番目のものについて修正しました。
また、検証しました。
以下のコードにて

<!DOCTYPE html> <html> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script> $(function(){ var data = {}; for (var i = 0; i < 1000; i++) { var v = Math.round((Math.floor(Math.random() * 6) - 2.5) * 1.1); if (!(v in data)) { data[v] = 0; } data[v]++; } for (var key in data) { $('table').append(`<tr><th>${key}</th><td>${data[key]}</td></tr>`); } }); </script> </head> <body> <table> <tr> <th>数値</th><td>出現回数</td> </tr> </table> </body> </html>

次の結果となりました。

数値 出現回数
1 139
2 179
3 167
-1 177
-2 162
-3 176

0 は発生せず、仕様を満たしているようです。

投稿2017/02/03 00:26

編集2017/02/05 12:36
Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2017/02/03 00:37

マイナスの理由を教えて下さい。
ozwk

2017/02/03 00:55 編集

一項目のMath.random()が0のときv=0では? ほぼありえないと思いますが。
Zuishin

2017/02/03 00:59 編集

ozwk さんありがとうございます。了解しました。お題を見て仕様を誤解していたので書き直しました。
miu_ras

2017/02/04 02:19

そうなんですよね。ceilだとマレにしか出ないバグがあるんですよね。質問を投稿して出勤途中に気付きました。誰も気づかないことを願っていたのですが、teratailでJavaScriptでは無理だったようですね。
miu_ras

2017/02/04 02:25

私にとっての理想的な形は3つめです。ただ、3つめは「-2. -1, 0, 1, 2, 3」になっていました…。 1つめ2つめも参考になりました。ありがとうございました
Zuishin

2017/02/04 11:18

やっぱり頭の中だけで考えてもうまくいかないものですね。 と言いつつ確かめてませんが、修正しました。
miu_ras

2017/02/05 00:42

修正後の3つ目は、実行するとできているように見えますね。 ただ、ごくまれに0になるバグ込みだと思います。
Zuishin

2017/02/05 02:06

まだ 0 になりますか? Math.floor(Math.random() * 6) これが 0,1,2,3,4,5 2.5 を引いて -2.5,-1.5,-0.5,0.5,1.5,2.5 1.1 を乗じて -2.75,-1.65,-0.55,0.55,1.65,2.75 四捨五入して -3,-2,-1,1,2,3 こうなるつもりだったんですが。 どこが悪いかわからないのでやはり後で実際に確かめます。
miu_ras

2017/02/05 02:20

すみません。勘違いでした。問題ないと思います。 とすると、randomが1回で四則演算とMathだけで完結しているのでかなりいいですね。
Zuishin

2017/02/05 12:36

そうですね。検証してみましたが、うまく動きました。
guest

0

「-3, -2, -1, 1, 2, 3」を配列として捉えて、ランダムに選ぶのは、ワリとやる手だと思います。
シンプルだと思いますが。

数学的な方法って、どんなものをイメージしていますか?

補足
シンプルな方法ということで、乱数の元は組み込まれた関数を使用するものとします。
すると乱数は連続する数値の範囲を指定することになります。

その前提で考えると、「-3, -2, -1, 1, 2, 3」を連続した数値に直すか、連続した順番で捉えるかの2択になるかと。

前者は多段な処理を行わなければならないのであまりシンプルな発想ではないと思います。
シンプルさで言えば、後者の配列として捉える方法が適解かと。

重要な追記
途中から、識者が指摘を諦めてますがw

Math.random() の取る範囲が、0以上1未満なので、一様性を確保しようとするとMath.floor()と組み合わせるのがセオリーなんですね。今回は勉強になりました。

Math.ceil() 使っているヒトォー、0 問題クリアできていませんよぉー。
0を有効数字として扱うと、一様ではなくなるので組み合わせとしては最悪ですね。

Math.round() はコツがいりますね。発生した乱数を0.5ずらしてやらなければ一様にならない。

投稿2017/02/02 23:55

編集2017/02/04 00:55
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/03 00:37

十分にシンプルな方法があるのに質問するから大喜利みたいになってる。。。w面白いですね。
KSwordOfHaste

2017/02/03 03:25 編集

撃沈しました!やはりte2jiさんのおっしゃるとおりなのかな。ありそうに思えるのでなんか悔しいですw; みごとに与太郎を演じてしまいました。
退会済みユーザー

退会済みユーザー

2017/02/03 03:52

みんな発想が柔軟ですよね。私にはこんなにたくさんのやり方は思い浮かばないです。 単純な結果を求めるアルゴリズムの質問は盛り上がって面白いです。
think49

2017/02/03 13:39 編集

私見ですが、この手法の問題は [-3, -2, -1, 1, 2, 3] を生成するアルゴリズムが掲示されていない事にあると考えています。 任意の数値反意で乱数を得る事を考えた場合、動的に配列を生成するエレガントなコードが必要になりますが、私が書いたところではエレガントとは呼べないコードになってしまいました。 ただ、te2jiさんは「決め打ちで良い」の前提で配列を用意する手法を提案されていると思いますし、miu_rasさんがどうお考えになっているかで回答の方向性が変わってくると思います。
退会済みユーザー

退会済みユーザー

2017/02/03 14:16

たしかに、元の数列の生成アルゴリズム(というか選定理由?)が提示されると、アルゴリズムの拡張性を考え、適正回答は変わるかもしれないですね。 ただ、連続性は今以上に無くなると思うので、やはり配列が有利だと思います。 せっかく think49 さんがコメントくれたんで教えてほしいのですが、配列を生成するコストっていうのがよく理解できませんでした。べた書きがコストってりかいでただしいですか?
think49

2017/02/03 14:27

To: te2ji さん 私の回答から createIntArray2 でページ検索してみて下さい。 私の頭が固いだけかもしれませんが、3行もコードがあって洗練されていないと感じています。 1行かつメソッドチェーン数をもっと減らしたいのです。
退会済みユーザー

退会済みユーザー

2017/02/03 14:46

文字通り、配列を作っていたんですね。 (関数の中身が理解できていなかったもので^^;) 文章の方は理解できました!w
退会済みユーザー

退会済みユーザー

2017/02/04 00:56

みなさんの回答を改めて確認して、気がついたことがあったので、追記しました。今回は勉強になりました。
miu_ras

2017/02/04 02:01

私が最初にあげた配列を使う方法でやった方がいいということですね。ありがとうございました
退会済みユーザー

退会済みユーザー

2017/02/04 02:35

配列作るコストを考えなくていいのであれば、配列最強かとw think49 さんの指摘通り、 [-3, -2, -1, 1, 2, 3] を生成するアルゴリズム次第かと思います。 個人的には、katoy さんのが好きですね。
catsforepaw

2017/02/04 02:52

横から失礼します。 質問では『「-3, -2, -1, 1, 2, 3」の6つの整数』と明確に要件を提示しているので、配列を使うなら単純に`var v = [-3, -2, -1, 1, 2, 3][Math.floor(Math.random() * 6)];`と1行でできます。おそらく配列の生成コストはかからないと思います。
退会済みユーザー

退会済みユーザー

2017/02/04 03:17

今回、与えられた前提条件としては、配列の生成コストを考えないくていいので、配列が最強だと思ってますよ。そう書いてますし。 ただ、「-3, -2, -1, 1, 2, 3」の6つの整数がなんらかのアルゴリズムで生成されている場合、話は変わります。 わかりやすい例で言うと、「-3, -2, -1, 1, 2, 3」の6つの整数が、「1 から 6 までの整数を選び、それが 4 以上であれば 7 引く」ってアルゴリズムによる生成だったとすると、それをそのまま採用して、乱数発生後の数字を加工したほうが良いケースもあるかと。分かりやすいし。 また、今回の質問を簡略化しているだけで、実際に使う数列は、「-3, -2, -1, 1, 2, 3」でない場合、その選択要件次第では、配列生成コストが大きくなる可能性もあります。 結果、配列を生成するアルゴリズム次第じゃないかと。
catsforepaw

2017/02/04 04:19

te2ji さん すみません。名前を書き忘れたため誤解されてしまったようです。think49さんの「1行かつメソッドチェーン数をもっと減らしたいのです。」というコメントに対して反応したつもりでした。 それと、 > ただ、「-3, -2, -1, 1, 2, 3」の6つの整数がなんらかのアルゴリズムで生成されている場合、話は変わります。 そうなると質問の趣旨から外れてしまうのではないでしょうか。「この程度の規則」とも書かれているので、質問に書かれている以外のアルゴリズムを持ち出すのは適当ではないと考えます。
退会済みユーザー

退会済みユーザー

2017/02/04 04:52

書いているとおり、配列作るコストを考えなくていいのであれば、配列最強と思ってます。 ただ、選択した数字群がなんらかのアルゴリズムに基づいているなら、それを選択したアルゴリズムをトレースするほうが良いケースもあると思います。提示されていないので、それが提示されれば、最適解が変わるかもしれないということです。 think49 さんの 【この手法の問題は [-3, -2, -1, 1, 2, 3] を生成するアルゴリズムが掲示されていない事にあると考えています。】というコメントのとおりですね。 ちょっと指摘内容が頭に入ってきません。なにかスレ違いがあるのだと思いますが。
catsforepaw

2017/02/04 07:25

繰り返しになってしまいますが、質問には『「-3, -2, -1, 1, 2, 3」の6つの整数』と明示されていて、その6つの整数をランダムで取得するにはどうするかというのが質問の趣旨であって、6つ(もしくはそれ以上)の数列を生成するアルゴリズムまで話を膨らませる必要はないと言いたかったのです。 混乱させて申し訳ありません。私のコメントは無視してもらってもかまいません。
think49

2017/02/04 10:06 編集

To: catsforepaw さん 命題の捉え方の問題だと思います。 エレガントの定義がなかったので、私が考えるエレガントなコード(汎用性の高いコード)を書いてみました。 「エレガント」に対するイメージは万人共通ではないので、質問者の要件によって答えが変わりますね、と。
guest

0

node

1$ node 2> function f(x) { return (x - 2.5) + Math.sign(x - 2.5) * 0.5; } 3undefined 4> function rand() { return Math.floor(Math.random() * 6); } 5undefined 6 7> [f(0), f(1), f(2), f(3), f(4), f(5)] 8[ -3, -2, -1, 1, 2, 3 ] 9 10> [f(rand()), f(rand()), f(rand()), f(rand())] 11[ 3, -2, 1, -1 ] 12> [f(rand()), f(rand()), f(rand()), f(rand())] 13[ -3, 3, 1, 3 ] 14> [f(rand()), f(rand()), f(rand()), f(rand())] 15[ -2, -2, -2, 1 ] 16> [f(rand()), f(rand()), f(rand()), f(rand())] 17[ -1, 3, -3, 1 ] 18> [f(rand()), f(rand()), f(rand()), f(rand())]

数学的には、x に対する f(x) が次のようになる関数 f を作ればよいことになります。
x: 0 1 2 3 4 5
f(x): -3 -2 -1 1 2 3

f(x) = (x - 2.5) + sign(x - 2.5) * 0.5 は、その 1 つの例になります。
ここで sign(x)は
x < 0 なら -1 を返す
0 < x なら 1 を返す
ような関数とします。

f を javascript (node.js) で書き、動作を試してみたのが↑です。

追記:
数学的なことをだらだらと述べます。

f(n) = m が n 個与えられたとき、それを満たす x の多項式はいくつも存在します。
一般的には n 個に対しては (n - 1) 次多項式で、それが可能です。
(2点なら、1次式で可能になる)

参考

ここでの質問の場合は、6つの f(x) = m があたえられているので 5 次多項式の答えが存在するはずです。

しかし、ここでの質問のケースでは、 ほとんど直線であり、その直線を途中でちょっとずらすだけで条件をみたすことができます。
ずらす操作を if での条件分岐で記載することも可能です。
普通の多項式はすべてなだらかなグラフになりますが、なだらかにならない式として
|x| とか sgn(x) とか floor(x) とか ディリクレ関数 ... があります。

ここでは、 sgn(x) (x が正なら 1, 負なら -1, 0 なら 0 を返す) を利用することで、直線をずらす処理を数式で
表現してみたのです。
floor(x) をつかっても、直線をずらすことを表現できる気もします。

投稿2017/02/03 16:36

編集2017/02/04 05:38
katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 03:15

Math.signを使うのは数学的でかっこいいですね。 ありがとうございました
miu_ras

2017/02/04 07:21

…Math.signを三角関数と間違えていました。 ようやく理解しました。 「{}」を乱数のとりうるパターンとすると、 「{0, 1, 2, 3, 4, 5} - 2.5」で {-2.5, -1.5, -0.5, 0.5, 1.5, 2.5}をつくり それをsignにかけその結果に0.5をかけて{-0.5, 0.5}にして加算。 数学的でかっこいいですね。ありがとうございました
think49

2017/02/04 11:38

0.5ずらす発想が秀逸ですね。単純な四則演算だけで構成されている点も分かりやすくて良いと思いました。 Simple is best!
guest

0

整数の商と剰余を使えばできますね。

出来れば、関数・変数を使わない、1行で書けるコード

には反しますが...

JavaScript

1function randomValue() { 2 return (function(x) { 3 return (x % 3 + 1) * (Math.floor(x / 3) * 2 - 1); 4 })(Math.floor(Math.random() * 6)); 5}

もうひとつ、全然エレガントではない方法。
結局、[0, 1, 2, 3, 4, 5] => [-3, -2, -1, 1, 2, 3] の単射が欲しいわけです。
長さnの数値配列は、n-1次多項式で表現できます。
5次式だと大変なので、[0, 1, 2, 3] => [-2, -1, 1, 2] で考えます。
(x, y) = (0, -2), (1, -1), (2, 1), (3, 2)
の4点を通る3次関数 y = ax^3 + bx^2 + cx + d の係数は、線形代数だけで求められ、

y = -1/3x^3 + 3/2x^2 - 1/6x - 2

となります。

JavaScript

1function randomPoly() { 2 return (function(x) { 3 return -1/3*x*x*x + 3/2*x*x - 1/6*x - 2; 4 })(Math.floor(Math.random() * 4)); 5}

いかがでしょうか?

投稿2017/02/04 19:14

編集2017/02/04 19:40
quietk

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/05 02:32

1つめは、意外なことに「% 3」を使うアプローチはこれが初のようです。 2つめは、実用性は低いかもしれませんが、アプローチが面白いですね。 ありがとうございました
guest

0

無理やりやってみました。
引き直しなど条件分岐は行わず、
[0,5]の整数の一様分布に対してちゃんと均等な出現確率になるようになっています(多分)

javascript

1function f(t){ 2 return Math.ceil(Math.abs(t-2.5))*Math.sign(t-2.5) 3} 4 5function g(){ 6 r = Math.floor(Math.random() * 6) 7 return f(r) 8} 9 10console.log([0,1,2,3,4,5].map(f)) 11 12histogram=[0,0,0,0,0,0,0] 13for(i=0;i<1000;i++){ 14 histogram[g()+3] ++ 15} 16console.log(histogram)

投稿2017/02/03 00:20

編集2017/02/03 00:32
ozwk

総合スコア13521

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:11

私が最初に使っていた言葉の「数学的な考え」はこんな感じです。 ただそこにこだわりすぎると複雑になってしまうようですね。 勉強になります。ありがとうございました
guest

0

miu_ras さんご本人が think49 さんの回答に対してのコメントの中でしている式です。

var v = (Math.floor(Math.random() * 6) - 3) || 3;

非常にシンプルで面白いと思います。分かりやすいし。
コメントに埋もれさせておくのがもったいなかったので、パクリ回答してみましたw

投稿2017/02/05 02:41

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

lazex

2017/02/05 11:29

書き込もうとしたらすでにでてたので+いれときました
guest

0

数学的なテクニックっぽい計算式

javascript

1var v = Math.round(Math.sin((Math.floor(Math.random() * 6) - 2.5) * 36 * Math.PI / 180) * 3);

エレガントにはほど遠いですね。パフォーマンスも最悪だと思います。
配列を使うのが一番シンプルで無難かと思います。

投稿2017/02/03 01:54

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:33

むずいですね…。でも私が最初に求めていた要件を満たすロジックです。 勉強になります。ありがとうございました
catsforepaw

2017/02/04 02:43

おそらく質問者さんはrandomを2回使うことがエレガントではないと考えたのだろうと思い、1回で済ませる方法を考えたのですが、線形なスケーリングでは無理だと判っていたので、前半は急で後半はなだらかになるような関数に当てはめれば良かろうと思いつき、真っ先に浮かんだのがsin関数でした。
guest

0

ゼロが出たら再発行
While( (v = (Math.ceil(Math.random() * 7) - 4) === 0 ){}

投稿2017/02/03 00:25

hikochang

総合スコア648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:12

ループですかー。ありがとうございました
guest

0

【条件】

  1. 乱数生成は1度のみ行う。
  2. 任意の整数nに対して、-n, -n+1, ... -2, -1, 1, 2, ... n-1, nの計n*2個の整数の内一つを返す。ランダム性は1.で生成した乱数を用いること。ただし、n1以上2**52-1以下とする。(2**52以上の場合は2*nNumber.MAX_SAFE_INTEGERを越えるため)

【考察】
Math.random() [0,1)の乱数
Math.random()*2*n [0,2n)の乱数
Math.floor(Math.random()*2*n) 0, 1, 2, ... 2n-2, 2n-1 の乱数
それぞれnでわると前半は0以上1未満、後半は1以上2未満となる。
fn = i=>Math.floor(i/n) 0, 1, ... n-1, n ... 2n-2, 2n-1 => 0, 0, ... 0, 1, ... 1, 1
そこで次のような関数を考える。
gn = x=>x+1-(2*n+1)*fn(x) 0, 1, ... n-1, n ... 2n-2, 2n-1 => 1, 2, ..., n, -n, -n+1, .. -2, -1
よって
gn(Math.floor(Math.random()*2*n))
(x=>x+1-(2*n+1)*fn(x))(Math.floor(Math.random()*2*n))
(x=>x+1-(2*n+1)*(i=>Math.floor(i/n))(x))(Math.floor(Math.random()*2*n))
関数の中の関数は、引数に副作用が無いため、そのまま評価できるので
(x=>x+1-(2*n+1)*Math.floor(x/n))(Math.floor(Math.random()*2*n))
副作用があるMath.random()を引数にとるように式を変形すると
(x=>Math.floor(2*n*x)+1-(2*n+1)*Math.floor(2*x))(Math.random()) 1, 2, 3, ... n-1, n, -n, -n+1, ... -2, -1 の乱数
求めるのはn=3の時なので、
(x=>Math.floor(6*x)+1-7*Math.floor(2*x))(Math.random()) 1, 2, 3, -3, -2, -1 の乱数

【解】

JavaScript

1let v = (x => Math.floor(6 * x) + 1 - 7 * Math.floor(2 * x))(Math.random());

数学っぽく考えたつもりですが、Math.random()に副作用があるのでまったく数学っぽくない時点でなんとも言えないです。


【別解】
[0,1) => -n, -n+1, ... -2, -1, 1, 2, ... n-1, n
となる写像関数を直接考える。

  1. 2倍すれば[0,2)となるため、[0,1), [1,2)に分離でき、floorをとれば0, 1になる。
  2. -1を底、1.の値を冪指数とする冪を求めると、1, -1になる。
  3. 2n倍すれば[0,2n)となるため、[0,1), [1,2), ... [n-1, n), [n, n+1), ... [2n-2,2n-1), [2n-1,2n)に分離でき、floorをとれば0, 1, ... n-1, n, ... 2n-2, 2n-1となる。

このうちの前半は1.の0(2.の1)に、後半は1.の1(2.の-1)に相当する。
4. さらにnでの余りを求めると0, 1, ... n-1, 0, ... n-2, n-1と前半と後半が同じである。
5. さらに1足せば1, 2, ... n, 1, ... n-1, nとなる。
6. 2.と5.を組み合わせれば、目的の写像が得られる。
f(n)(x) = (-1)^[2x]*([2nx]%n+1)
※ fはカリー化されている。^ ... 冪乗。[z] ... ガウス記号、床関数。% ... 剰余。

これをJavaScriptで表すと下記になる。

JavaScript

1const f = n => x => (-1)**Math.floor(2 * x) * (Math.floor(2 * n * x) % n + 1)

写像関数が得られたので、n=3の時にxが[0,1)分布の乱数を与えることで答えは

JavaScript

1let v = (n => x => (-1)**Math.floor(2 * x) * (Math.floor(2 * n * x) % n + 1))(3)(Math.random());

となる。


【別解2】
※ 途中の演算でMath.MAX_SAFE_INTEGERを越える場合があるため修正。

Math.floorを一回だけにする方法を考える。他にも演算はなるべく1回とする。

2n倍したものにfloorをとると0, 1, ... 2n-2, 2n-1になる。
これを2を除数とした商と剰余を考えると、下記になる。
商: 0, 0, 1, 1, ... 2n-2, 2n-2, 2n-1, 2n-1
剰余: 0, 1, 0, 1, ... 0, 1, 0, 1
つまり、剰余部分は-1を底にしてその値を冪指数にした冪を求めれば
剰余: 0, 1, 0, 1, ... 0, 1, 0, 1 => 1, -1, 1, -1 ... 1, -1, 1, -1
が得られるため、後は商に1足した物と掛ければ、
1, -1, 2, -2, ... 2n-1, -2n+1, 2n, -2n
が得られる。

JavaScriptにすると次のような関数になる。

JavaScript

1// q 商、r 剰余 2const g = r => q => (-1)**r * (q + 1);

JavaScriptには商を直接求める演算子や関数がないため剰余から求める関数を考える。

JavaScript

1// d 被除数、m 除数 2const h = d => m => r => (d - r) / m;

除数は2で固定であるため、これを用いると次のようになり、展開しながら変形していく。

JavaScript

1const f = x => g(x % 2)(h(x)(2)(x % 2)); 2// `x % 2`は冗長のためさらに書き替える。 3const f = x => (r => g(r)(h(x)(2)(r)))(x % 2); 4// 関数を展開する。 5const f = x => (r_ => (r => q => (-1)**r * (q + 1))(r)((d => m => r => (d - r) / m)(x)(2)(r_)))(x % 2); 6// rはr_と同じ、xとdは同じであり、mは2固定で冗長のためまとめる。 7const f = x => (r => (q => (-1)**r * (q + 1))((m => (x - r) / 2)()))(x % 2); 8// 固定の所は評価をしてしまって。qもなくす。 9const f = x => (r => ((-1)**r * ((x - r) / 2 + 1)))(x % 2);

これに最初の2nを掛けて床関数で整数化したものを与えると[0,1)を均等に配分できる。

JavaScript

1const f_ = n => y => (x => (r => ((-1)**r * ((x - r) / 2 + 1)))(x % 2))(Math.floor(2 * n * y));

n=3のときのため、最終的に次のようになる。

JavaScript

1let val = (n => y => (x => (r => ((-1)**r * ((x - r) / 2 + 1)))(x % 2))(Math.floor(2 * n * y)))(3)(Math.random());

【別解3】
-1は奇数乗か偶数乗かだけで切り替わることに気付いた。

JavaScript

1let val = (x => ((-1)**x * ((x - x % 2) / 2 + 1)))(Math.floor(6 * Math.random()));

IE等の古いブラウザ用に書き直すと

JavaScript

1var val = function (x) {return Math.pow(-1, x) * ((x - x % 2) / 2 + 1);}(Math.floor(6 * Math.random()));

投稿2017/02/03 13:55

編集2017/02/04 13:06
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/05 01:41 編集

1つ目は、少しわかりにくかったのですが、数学的っぽいと感じます。いいですね。 2つ目(別解)は「**」があってひるんだのですが、ES7proposalなんですね。ES7も勉強しないと。よく見ると、商とべき乗で済んでいて、考え方はシンプルでいいですね。見た目はごちゃっとしていますが…。 3つ目(別解2)は2つ目とさほど意味合い的には変わらないのだと思いますが「=>」と小カッコが多くてわかりにくかったです…。 4つ目(別解3)は急にシンプルになった印象でした。 {0, -1, 0, -1, 0, -1}と{0, 0, 2, 2, 4, 4}を作るあたりは、なるほどと勉強になりました。他にも応用できるかもしれませんし。ありがとうございました
miu_ras

2017/02/05 02:00

気付いたのでメモしておきます。 「(x - x % 2) / 2 + 1」の部分を 「(x % 3) + 1」にしても行けますね。演算が減ってシンプルになります。
guest

0

数学的にエレガントとは

「miu_ras さんがエレガントと思うコードの定義」が不明なので、私がエレガントと思うコードの定義を書きます。

(1) [-3,-2,-1,0,1,2,3] からランダムに値を得てから 0 を例外処理するコードはエレガントではない

0 を除外するコードは2種類考えられます。

  • 0 が返された場合、1 を返す
  • 0 が返された場合、もう一度、[-3,-2,-1,0,1,2,3] からランダムに値を得る(0 が返されなくなるまで繰り返す)

前者は 1 が返される確率が上がる為、エレガントではありません。
後者は 0 が返された場合の処理コストが上がる為、エレガントではありません。

(2) 配列からランダムに値を得る方法はエレガントではない

[-3,-2,-1,0,1,2,3] からランダムに値を得る方法はシンプルですが、配列を生成するコストがかかるのでエレガントではありません。

配列からランダムに値を得るコード

[1,2,3] を元に [-3,-2,-1,1,2,3] を生成するコード。

JavaScript

1function createIntArray1 (minInt, maxInt) { 2 var array = [...Array(maxInt + 1).keys()].slice(minInt); 3 4 return array.slice().reverse().map(value => value * -1).concat(array); 5} 6 7function createRandomInt1 (minInt, maxInt) { 8 var array = [...Array(maxInt + 1).keys()].slice(minInt); 9 10 array = array.slice().reverse().map(value => value * -1).concat(array); 11 return array[Math.floor(Math.random() * array.length)]; 12} 13 14console.log(JSON.stringify(createIntArray1(1, 3))); // [-3,-2,-1,1,2,3] 15console.log(createRandomInt1(1, 3)); 16console.log(createRandomInt1(1, 3)); 17console.log(createRandomInt1(1, 3)); 18console.log(createRandomInt1(1, 3)); 19console.log(createRandomInt1(1, 3)); 20console.log(createRandomInt1(1, 3)); 21console.log(createRandomInt1(1, 3)); 22console.log(createRandomInt1(1, 3)); 23console.log(createRandomInt1(1, 3)); 24console.log(createRandomInt1(1, 3));

[0,1,2,3,4,5,6,7] を元に [-3,-2,-1,1,2,3] を生成するコード。

JavaScript

1function createIntArray2 (minInt, maxInt) { 2 var halfLength = maxInt - minInt + 1, 3 array = [...Array(halfLength * 2 + 1).keys()].map(value => value - halfLength); 4 5 return array.splice(halfLength, 1), array; 6} 7 8function createRandomInt2 (minInt, maxInt) { 9 var halfLength = maxInt - minInt + 1, 10 array = [...Array(halfLength * 2 + 1).keys()].map(value => value - halfLength); 11 12 return array.splice(halfLength, 1), array[Math.floor(Math.random() * array.length)]; 13} 14 15console.log(JSON.stringify(createIntArray2(1, 3))); // [-3,-2,-1,1,2,3] 16console.log(createRandomInt2(1, 3)); 17console.log(createRandomInt2(1, 3)); 18console.log(createRandomInt2(1, 3)); 19console.log(createRandomInt2(1, 3)); 20console.log(createRandomInt2(1, 3)); 21console.log(createRandomInt2(1, 3)); 22console.log(createRandomInt2(1, 3)); 23console.log(createRandomInt2(1, 3)); 24console.log(createRandomInt2(1, 3)); 25console.log(createRandomInt2(1, 3));

数学的にエレガントなアルゴリズム

下記アルゴリズムは miu_ras さんが質問文中で「エレガントではない」としていますが、数学的に解決する方法の一つだと私は思います。

  1. Math.random() で 1,2,3 の乱数を得る
  2. Math.random() で -1,1 の乱数を得る
    1. と 2. の積を求める

数学的にエレガントなコード

私が考える数学的にエレガントなコードは「Math.xxxx メソッド、算術演算子だけで完結するコード」です。

JavaScript

1function createRandomInt (minInt, maxInt) { 2 return (Math.round(Math.random()) * 2 - 1) * (Math.floor(Math.random() * (maxInt - minInt + 1)) + minInt); 3} 4 5console.log(createRandomInt(1, 3)); 6console.log(createRandomInt(1, 3)); 7console.log(createRandomInt(1, 3)); 8console.log(createRandomInt(1, 3)); 9console.log(createRandomInt(1, 3)); 10console.log(createRandomInt(1, 3)); 11console.log(createRandomInt(1, 3)); 12console.log(createRandomInt(1, 3)); 13console.log(createRandomInt(1, 3)); 14console.log(createRandomInt(1, 3));

Math.random() に偏りがない前提で考えるならば、

  • 「正の数/負の数」からランダムに偏りなく選びます
  • 「1,2,3」からランダムに偏りなく選びます

更新履歴

  • 2017/02/03 19:44 算術演算子型コードを追記
  • 2017/02/03 21:30 「数学的にエレガント」の定義に言及
  • 2017/02/03 21:41 配列からランダムに値を得るコードを追記
  • 2017/02/03 22:21 配列からランダムに値を得るコードの別解を追記

Re: miu_ras さん

投稿2017/02/03 04:49

編集2017/02/03 13:29
think49

総合スコア18162

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 05:01

「0 が返された場合、1 を返す」この言葉で、もう一つ思いついきました。 var v = (Math.floor(Math.random() * 6) - 3) || 3; 論理演算子を使っているので、最初の「数学的な方法」は満たさないのですが、 これでもまぁまぁシンプルでいい感じですよね。JavaScriptっぽいし。 > 私が考える数学的にエレガントなコードは > 「Math.xxxx メソッド、算術演算子だけで完結するコード」です。 これは私も同じです。ただ、Math.randomを使うのは 1回でもどうにかできるはずではないかと思ったことがきっかけです。 ただそこにこだわりすぎて複雑になりすぎても問題なので ご提示のようなコードがいいかもしれませんね。 ありがとうございました
退会済みユーザー

退会済みユーザー

2017/02/04 13:24

> var v = (Math.floor(Math.random() * 6) - 3) || 3; これ面白い。一票入れたいw
think49

2017/02/06 02:22

今回、正の最小値を任意の数値にした事で考え方が束縛してしまった感がありました。 皆さんのように1に固定する柔軟な発想が足りませんでした。 > var v = (Math.floor(Math.random() * 6) - 3) || 3; JavaScript 的なコードという意味ではこんなコードも考えたのですが、miu_ras さんのコードの方がすっきりしていいですね。 function createRandomInt (length) { var number = Math.floor(Math.random() * length * 2) - length; return number + (number > -1); }
guest

0

数学的かはわかりませんが、その他の方法として、、、

// 1〜6を取得して3より大きければ7を引く var v = Math.ceil(Math.random() * 6); if(v > 3) { v -= 7 } // 1〜3を取得して、ランダムに符号を変える var v = Math.ceil(Math.random() * 3); if(Math.random() < 0.5) { v *= -1 }

投稿2017/02/03 00:17

Kapustin

総合スコア1186

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:08

なるほど。条件分岐を使っている点が少しに来なりますが、 結構シンプルでいいですね。ありがとうございました
guest

0

「-3≦x≦3(端を含むかはそこまで気にしなくてもいい)の一様乱数を生成して、出た小数を無限大に丸める」という方法も考えられます。

ただ、運悪く(?)ちょうど0が出たときはどうするかは、ちょっと検討が必要かもしれません。

投稿2017/02/03 00:10

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/03 00:22

これ、面白いですね。実用の範囲で一様かと。 それでも、私は3回連続ちょうど0を引く自信があります!
miu_ras

2017/02/04 02:03

「無限大に丸める」というのはコードとしてはどうなるのでしょうか?
guest

0

  • MDNのソースそのままの引用で済む。
  • ランダムで出したい数値の範囲が変更されても、配列内の数値を変更するだけでよくメンテナンスしやすい。

配列を使わない理由がないですね。
大量の数列を用意する必要があり、配列に規則性がある場合は、その配列を生成するための関数を作ればいいと思います。今回の場合はそこまでする必要はないですね。

javascript

1function getRandomInt(min, max) { 2 return Math.floor( Math.random() * (max - min + 1) ) + min; 3}; 4 5const arr = [-3, -2, -1, 1, 2, 3]; 6 7let i = getRandomInt(0, arr.length-1); 8 9console.log(arr[i]);

規則性のある配列をまず作り、そこから必要ない数値を除外するというアプローチも簡単にできます。

javascript

1// [-3, -2, -1, 0, 1, 2, 3] の配列を作り、0を除外 2const arr = [...Array(7).keys()].map(v => v - 3).filter(v => v); 3 4console.log(arr) // [-3, -2, -1, 1, 2, 3]

javascript

1// 1~10の配列を作り、3、9を除外 2const arr = [...Array(10).keys()].map(v => v + 1).filter(v => v !==3 && v !==9); 3 4console.log(arr) // [1, 2, 4, 5, 6, 7, 8, 10]

投稿2017/02/04 03:14

編集2017/02/04 03:30
yamato_hikawa

総合スコア2092

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 06:33

配列方式は柔軟性がある点がいいですよね。 ありがとうございました
guest

0

こんなのはどうでしょうか?ちょっと長いですが、、

javascript

1function rdm() { 2 var num = parseInt(Math.random() * 7) - 3; 3 return num == 0 ? rdm() : num; 4}

投稿2017/02/03 16:39

kiritsugu

総合スコア127

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 03:10

再帰ですか…。ありがとうございました
guest

0

まず前提として「数学的な方法」とはどのようなものであるか定義しないことには具体的な答えはでません。

コンピュータで作る乱数は基本的に疑似乱数に過ぎません(例外はハードウェア乱数生成)。また、乱数の生成過程には様々なものがあります。一様分布、正規分布などモデルがわからないことには確定しません。そして、これが決まれば「候補を配列に持つ」というのは非常にスマートだと思います。

ちなみに、私の出身研究室では、「教授が不確定な外乱要素を乱数で補正するという発明をした」とされており、巻き添え食らわせらかけました。なんか、この発明で特許を取得し、大金を動かしたらしいです。シミュレートするならともかく補正できるのはおかしいだろう、と思います。ただし、万が一にこの外乱要素と疑似乱数の生成過程が同一であるなら可能性もあるかもしれません。もっとも、私は別の可能性を考えましたがね。

投稿2017/02/03 14:11

HogeAnimalLover

総合スコア4830

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 03:13

一応、私の考えていた「数学的な方法」の定義を書きました。その条件に基づき回答をお願いします。 必ずしも条件にこだわらないと変更しましたが、「数学的な方法」に沿った回答も引き続きお待ちしています。
HogeAnimalLover

2017/02/04 13:37

「プログラム言語で記述しやすい」ということが「いい方法」の要件であると仮定します。であれば、既出の「候補を配列にもつ」というデータ構造と「添え字を乱数で取得する」というアルゴリズムがもっとも素直だと思います。なお、乱数分布、生成過程は既存のMathクラスに丸投げするとします(自分が当初考えていたことは深読みだったようですね)
guest

0

すみません、全然ダメでした orz

randomを一回だけの呼び出しにして1ラインで、ややこしくない方法でというのは何かありそうな気がしますが以外に難しいですね ><


シンプル(1ライン)かつややこしくないというあたりを狙うならこんなんでもいいんでしょうか

var result = Math.ceil(Math.random()*6-2.5)

追記:ちなみにjavaなどの感覚でceilの結果が浮動小数だと整数として使えないかなと思って
browserのconsoleを使って
var a = [1, 2, 3]
なんてしてからrが1になったときに
alert(a[r])
とやったら2が表示されたので「こんなんでもいいのかな」と思いました。
テキトーな回答で恐縮ですが。

投稿2017/02/03 02:57

編集2017/02/03 03:19
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ozwk

2017/02/03 03:09

random()が(1.5/6, 2.5/6]の範囲内で0です
KSwordOfHaste

2017/02/03 03:13

負の方向ではfloorにしないと・・・全然ダメでしたorz 大変失礼しました
miu_ras

2017/02/04 02:34

ありがとうございました
guest

0

1~6の乱数を取得して、3引くってのはどうでしょう?相当シンプルだと思いますが。

投稿2017/02/03 01:02

mugicya

総合スコア1046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mugicya

2017/02/03 01:07

勝手に0がでる仕様なのかな…? 0~5までを取得して、+1 したあと -3 …
maisumakun

2017/02/03 01:14

「0を抜かす」という要件を満たせませんね。
mugicya

2017/02/04 00:48

そういや、そうですね。(酔っ払い) 得たい数列を配列に仕込んで、その範囲で乱数取得してしまったほうがいいですね。 数列の順番も初期化時にシャッフルすれば尚いいですね。 数学的にという質問内容から推測するに、おそらくこんな短い範囲ではないのだろうなあ…と思っていますが。
miu_ras

2017/02/04 02:29

それは質問のスタート地点ですよね…。ありがとうございました
guest

0

https://teratail.com/questions/64398
上記質問に対するalgさんの回答のパクリですが、、、

c#

1List<int> source = new List<int>() {-3,-2,-1,1,2,3}; 2 3Random random = new Random(); 4int randomIndex; 5int randomValue; 6 7// 確認 8for ( int i = 0; i < 100; i++ ) 9{ 10 randomIndex = random.Next(source.Count); 11 randomValue = source[randomIndex]; 12 Console.WriteLine(randomValue); 13}

投稿2017/02/03 00:15

hikochang

総合スコア648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miu_ras

2017/02/04 02:05

いちおうJavaScriptでお願いしたいです。 回答の方向性としては、私が最初にあげている配列を使う方法がいいということですね。 ありがとうございました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問