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

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

新規登録して質問してみよう
ただいま回答率
85.46%
アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

4回答

1531閲覧

ダイスの出目によるポイントの合計値の出現率を求めたい

angeltime

総合スコア1

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2021/09/07 07:06

前提

  1. 10面ダイスをX個振る
  2. それぞれのダイスについて、出目が1だったら2、10だったら-1、それ以外の場合はY以下なら1、Yより大きいなら0をポイントとする
  3. Yが1~9のそれぞれの場合において、ポイントの合計の出現率を求めたい

例えば、

X=1, Y=5の場合

ポイント出現率
-110%
040%
140%
210%

X=3, Y=7の場合

ポイント出現率
-30.1%
-20.6%
-13%
08.3%
119.2%
226.4%
329.1%
411.4%
51.8%
60.1%

となります。
Xを1~30程度、Yを1~9の範囲での全ての結果を求めたいと思っています。

試したこと

まずは高速に動作するであろうC++で愚直に実装してみました。

C++

1#include <iostream> 2#include <array> 3#include <cmath> 4 5int main() 6{ 7 // ダイスの出目によるポイントのテーブル 8 const char point_table[10][10] = { 9 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 10 {2, 0, 0, 0, 0, 0, 0, 0, 0, -1}, 11 {2, 1, 0, 0, 0, 0, 0, 0, 0, -1}, 12 {2, 1, 1, 0, 0, 0, 0, 0, 0, -1}, 13 {2, 1, 1, 1, 0, 0, 0, 0, 0, -1}, 14 {2, 1, 1, 1, 1, 0, 0, 0, 0, -1}, 15 {2, 1, 1, 1, 1, 1, 0, 0, 0, -1}, 16 {2, 1, 1, 1, 1, 1, 1, 0, 0, -1}, 17 {2, 1, 1, 1, 1, 1, 1, 1, 0, -1}, 18 {2, 1, 1, 1, 1, 1, 1, 1, 1, -1}, 19 }; 20 21 const int X = 8; 22 23 // X個のダイスを振った時の全ての組み合わせにおいて、合計ポイントが出現する回数を記録する配列 24 // ポイントの合計は-X~2Xの範囲で出現するのでそれが収まる範囲 25 // point_sum[X]が合計ポイント0の出現回数で、X-1なら合計ポイント-1、X+2なら合計ポイント2の出現回数を指す 26 std::array<int, X*3+1> point_sum = {0}; 27 28 for (int Y = 1; Y <= 9; Y++) { 29 for (size_t i = 0; i < point_sum.size(); i++) { 30 point_sum[i] = 0; 31 } 32 for (int i = 0; i < 10; i++) { 33 int p1 = point_table[Y][i]; 34 for (int j = 0; j < 10; j++) { 35 int p2 = point_table[Y][j]; 36 for (int k = 0; k < 10; k++) { 37 int p3 = point_table[Y][k]; 38 for (int l = 0; l < 10; l++) { 39 int p4 = point_table[Y][l]; 40 for (int m = 0; m < 10; m++) { 41 int p5 = point_table[Y][m]; 42 for (int n = 0; n < 10; n++) { 43 int p6 = point_table[Y][n]; 44 for (int o = 0; o < 10; o++) { 45 int p7 = point_table[Y][o]; 46 for (int p = 0; p < 10; p++) { 47 int p8 = point_table[Y][p]; 48 // p1~p8はそのダイスによるポイント 49 // その合計は-X~2Xの範囲で出現するので、配列に収まるようそれにXを加算したものを配列のインデックスにしている 50 point_sum[p1+p2+p3+p4+p5+p6+p7+p8+X]++; 51 } 52 } 53 } 54 } 55 } 56 } 57 } 58 } 59 for (size_t i = 0; i < point_sum.size(); i++) { 60 std::cout << "X = " << X << " / Y = " << Y << " / " << (int)i-X << " = " << point_sum[i]/std::pow(10, X-2) << "%" << std::endl; 61 } 62 } 63 64 return 0; 65}

発生している問題

このプログラムはX=8の場合の、出現する全てのダイス目をカウントする事で確率を求めています。
この愚直な方法でも確かに確率は求められるのですが、当然ですがダイスの数が1つ増えるごとに実行時間がおよそ10倍になり、こちらの環境ではX=10の段階で計算終了まで20分ほどかかってしまいました。

また、あるXの時に計算した内容を保持しておき、X+1の計算に流用する事で計算時間を減らせないかとも検討しました。
上記のX=8の例であれば、i, j, k, l, m, n, o, pの全ての組み合わせにおいてp1+p2+p3+p4+p5+p6+p7+p8の値を記録しておき、X=9の計算の際にその値を利用する方法も試したのですが、僅かに実行時間は減らせるものの結局はXが増えるたびに指数的に実行時間が伸びるのであまり意味はなく、メモリ的な限界もあり難しいと判断しました。

実現したいこと

ダイスの数が30個程度(できれば、少なくとも20個)までの結果は求めたいと思っているのですが、この方法で求められるのはせいぜい12個程度までで、それ以上は実行時間が非現実的です。
なにか良い方法はありますでしょうか?
目的は確率を求める事なので、実行時間は現実的な範囲であれば長くなっても問題ありませんし、プログラミング以外の手段でそれが求められるのならそれでも構いません。
言語、ライブラリ等も問いません。

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

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

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

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

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

guest

回答4

0

ベストアンサー

とりあえず頭空っぽにして考えると,
1個のダイスで加算されるポイントのパターンは4パターンしかないので,
X個のダイスを考えたときには,4^X パターンの結果を総当たりすれば済む,すなわち

ダイスの数が1つ増えるごとに実行時間がおよそ10倍になり

の「10倍」のところを,4倍にはできるのではないでしょうか.

要は,「出目の確率が偏った4面ダイス」を扱うのだと考えて良かろう,と.
(この「4面ダイス」の各目の出る確率はYで定まる)


で,愚直にX回の繰り返しをするとしたら,
各回の時点で同じ得点のパターンがあればそいつらを統合してパターンを減らしてやればいいのでは.

例えば,X=3のとき,愚直にやると

  1. 1個目のダイスの結果が4パターン
  2. 2個目のダイスでさらに4パターンだから,ここまでの組み合わせは 4*4=16 パターン
  3. 3個目のダイスでさらに4パターンだから,組み合わせは 16*4 = 64 パターン

となるかもしれないけども,実際には2.の時点で取り得る総ポイントのパターンは -2 ~ 4 の7パターンしかないので,ここでデータをまとめてしまえば,3.での組み合わせを 7*4 = 28 パターンに減らせる.


愚直に実装してみた.
(「早い」かどうかは謎だけど20分とかはかからないのではないかと)

C++

1//Yによって定まる,1個のサイコロから得られるポイントの確率データを作る作業用関数. 2//(めんどくさいのでYはまともな値が入ってくる前提) 3void SetupPointRate( int Y, double (&Rates)[4] ) 4{ 5 Rates[0] = 0.1; //-1 point の確率 6 Rates[2] = (Y-1) * 0.1; //1 point 7 Rates[3] = 0.1; // 2 point 8 9 Rates[1] = 0.8 - Rates[2]; //0 point 10} 11 12//処理実装 13struct Data 14{ 15public: 16 Data() : m_MinPoint(0), m_P{1.0} {} 17public: 18 //サイコロ1回分のデータ更新作業 19 void Update( const double (&Rates)[4] ) 20 { 21 const std::vector<double> PrevP = m_P; 22 const int PrevMinPoint = m_MinPoint; 23 --m_MinPoint; 24 m_P.assign( m_P.size()+3, 0 ); 25 26 for( int iPrev=0; iPrev<PrevP.size(); ++iPrev ) 27 { 28 int Point = PrevMinPoint + iPrev; 29 for( int iRate=0; iRate<4; ++iRate ) 30 { 31 int deltaPoint = iRate - 1; 32 m_P[ Point+deltaPoint-m_MinPoint ] += ( PrevP[iPrev] * Rates[iRate] ); 33 } 34 } 35 } 36 37 //結果表示 38 void Show() const 39 { 40 int Point = m_MinPoint; 41 for( const auto &P : m_P ){ std::cout << Point++ << " : " << P*100 << "%\n"; } 42 } 43private: 44 int m_MinPoint; //現時点での最低ポイント 45 std::vector<double> m_P; //各ポイントの確率 46}; 47 48//main 49int main(void) 50{ 51 const int X=3; 52 const int Y=7; 53 54 double Rates[4]; 55 SetupPointRate( Y, Rates ); 56 Data D; 57 for( int i=0; i<X; ++i ){ D.Update( Rates ); } 58 D.Show(); 59 60 return 0; 61}

投稿2021/09/07 07:21

編集2021/09/07 08:13
fana

総合スコア11708

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

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

fana

2021/09/07 07:39

ダイス1個増やす度に総ポイントのパターンが3パターン増えるという点は 質問コードで既に用いられていますね.
fana

2021/09/07 08:21

とりあえず回答に書いたコードで,質問文に例示されている2例 ・X=1, Y=5 ・X=3, Y=7 については,結果が質問文記載の表と一致することを確認した. 実行時間に関しては実行環境依存となろうが, X=30, Y=7 とかでも「一瞬」で終わるので,Yのパターン分繰り返しても大した時間は要しないのではないかと思う. (結果があってるかどうかの検算まではしていないが.面倒すぎるので^^)
angeltime

2021/09/07 09:25

回答ありがとうございました。 考え方だけでなくコードまでつけて頂き、非常に分かりやすかったです。 自分のコードで求めたX=10までの結果とも等しかったです。 とても助かりました!
guest

0

サイコロ1回の確率分布はYによって最初に決まるので、あとはX回累積していけばよいでしょう。
そのさい、同じポイントならまとめます。
以下、Pythonで愚直に実装しても一瞬です。

Python

1# サイコロ一回の確率分布 2def get_p(y): 3 d = {} # キー=ポイント, 値=確率 4 d[2] = 0.1 5 d[-1] = 0.1 6 d[1] = 0.1*(y-1) 7 d[0] = 0.1*(9-y) 8 return d 9 10def calc(X,Y): 11 cs = {0:1} # 初期はポイント0で確率は1 12 ps = get_p(Y) 13 for x in range(X): 14 cn = {} # 累積確率 15 for kc,vc in cs.items(): 16 # 次のサイコロの結果 17 for kp,vp in ps.items(): 18 k = kc + kp 19 v = vc * vp 20 if k not in cn: 21 cn[k] = 0 22 cn[k] += v # 同じポイントなら重ね合わせ 23 cs = cn 24 25 print(f'X={X}, Y={Y}') 26 for k in sorted(cs.keys()): 27 print(f'{k:2d} = {cs[k]*100}%') 28 total = sum(cs.values()) 29 print(f'total={total*100}%') 30 31calc(1,5) 32""" 33X=1, Y=5 34-1 = 10.0% 35 0 = 40.0% 36 1 = 40.0% 37 2 = 10.0% 38total=100.0% 39""" 40 41calc(3,7) 42""" 43X=3, Y=7 44-3 = 0.10000000000000002% 45-2 = 0.6000000000000002% 46-1 = 3.0000000000000004% 47 0 = 8.300000000000002% 48 1 = 19.200000000000006% 49 2 = 26.400000000000006% 50 3 = 29.10000000000001% 51 4 = 11.400000000000004% 52 5 = 1.8000000000000005% 53 6 = 0.10000000000000002% 54total=100.00000000000003% 55""" 56 57calc(30,7) 58""" 59X=30, Y=7 60-30 = 1.0000000000000017e-28% 61-29 = 6.00000000000001e-27% 62-28 = 1.920000000000003e-25% 63-27 = 4.295000000000007e-24% 64-26 = 7.482000000000012e-23% 65-25 = 1.0752852000000018e-21% 66-24 = 1.3212907500000025e-20% 67-23 = 1.4221402800000024e-19% 68-22 = 1.3642783200000026e-18% 69-21 = 1.1818021246000021e-17% 70-20 = 9.338154924960017e-17% 71-19 = 6.784952085000013e-16% 72-18 = 4.562878534900509e-15% 73-17 = 2.855474942272205e-14% 74-16 = 1.6704082999252832e-13% 75-15 = 9.169100281793844e-13% 76-14 = 4.73808035180431e-12% 77-13 = 2.3113356502258042e-11% 78-12 = 1.0669826300269449e-10% 79-11 = 4.670853808253032e-10% 80-10 = 1.942555783975026e-09% 81-9 = 7.687447347558051e-09% 82-8 = 2.898865920221126e-08% 83-7 = 1.0428957093227026e-07% 84-6 = 3.583292565890607e-07% 85-5 = 1.1769385860713239e-06% 86-4 = 3.6983040975543256e-06% 87-3 = 1.1125676547788542e-05% 88-2 = 3.2061039402944635e-05% 89-1 = 8.854550263163403e-05% 90 0 = 0.00023445760643797724% 91 1 = 0.0005953950945956934% 92 2 = 0.0014504017071929436% 93 3 = 0.003389836242944168% 94 4 = 0.0076016699979969266% 95 5 = 0.01635609743971916% 96 6 = 0.03376445030939296% 97 7 = 0.0668631008684591% 98 8 = 0.12698902083101876% 99 9 = 0.23124522828867378% 10010 = 0.40359702466316716% 10111 = 0.6748387159590861% 10212 = 1.0804451869333935% 10313 = 1.6553669852579709% 10414 = 2.4253553557748746% 10515 = 3.395530033498209% 10616 = 4.5384882925340495% 10717 = 5.785796255731776% 10818 = 7.02739157052915% 10919 = 8.122438699582188% 11020 = 8.92217522980884% 11121 = 9.30087041063324% 11222 = 9.186806600477004% 11323 = 8.583292848029442% 11424 = 7.57165170052293% 11525 = 6.293775796550219% 11626 = 4.91915190657864% 11727 = 3.6069620346182942% 11828 = 2.475273254616503% 11929 = 1.5857826981064225% 12030 = 0.9459604951271544% 12131 = 0.5240296824383176% 12232 = 0.26886166661489375% 12333 = 0.1274204777116115% 12434 = 0.055637019724470695% 12535 = 0.022326780386830936% 12636 = 0.008214996935576495% 12737 = 0.0027653630129821235% 12838 = 0.0008498934857256249% 12939 = 0.0002380112317889737% 13040 = 6.062265577455562e-05% 13141 = 1.4017432498517185e-05% 13242 = 2.9367646696764194e-06% 13343 = 5.563607991254611e-07% 13444 = 9.509638554506276e-08% 13545 = 1.4628530592320676e-08% 13646 = 2.0193348162987405e-09% 13747 = 2.4929366639816986e-10% 13848 = 2.7412972803885247e-11% 13949 = 2.6720008404564667e-12% 14050 = 2.2950647489833977e-13% 14151 = 1.7246053638910043e-14% 14252 = 1.1235949518480028e-15% 14353 = 6.274837938000016e-17% 14454 = 2.9597725235000067e-18% 14555 = 1.1562150360000026e-19% 14656 = 3.6400800000000086e-21% 14757 = 8.874300000000019e-23% 14858 = 1.5720000000000033e-24% 14959 = 1.8000000000000032e-26% 15060 = 1.0000000000000017e-28% 151total=100.00000000000028% 152"""

投稿2021/09/07 08:50

can110

総合スコア38278

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

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

angeltime

2021/09/07 09:32

回答ありがとうございます。 今回は先に回答頂いた方をベストアンサーにしましたが、コードの内容としてはそちらとほぼ同じような考え方かと思いますので、こちらも非常に参考になる回答だと思います。 ありがとうございました。
guest

0

配列を数値じゃなくて確率にして、一個前の値ごとの確率を掛ければ結構短縮できそう、
例えばX=4,X=7のとき、2~-1となる確率はpoint_table={0.1, 0.6, 0.2, 0.1}で
例のX=3,Y=7の結果からー3~6となる確率がX3={0.001,0.006,0.03,0.083,0.192,0.264,0.291,0.114,0.018,0.001}だと判明しているのなら
X3の1番目からpoint_tableを順にかけた値をX4の配列に加えるのを最後まで繰り返せば早そう。
X4={0.0001,0.0006,0.0002,0.0001,0,0,0,0,0,0,0,0,0}
X4={0.0001,0.0006+0.0006,0.0002+0.0036,0.0001+0.0012,0+0.006,0,0,0,0,0,0,0,0}
-3のときの4つ目のダイスの結果とー2の時の4つ目のダイスの結果は別事象なので足し合わせられるはず。

投稿2021/09/07 08:12

NKJSM

総合スコア58

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

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

angeltime

2021/09/07 09:30

回答ありがとうございます。 今回は先に回答頂いた方をベストアンサーにしましたが、考え方のヒントを教えてくださり勉強になります。 ありがとうございました。
guest

0

openmpを使ってみてはどうでしょうか

投稿2021/09/07 07:26

Spi_muto

総合スコア75

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

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

angeltime

2021/09/07 09:28

回答ありがとうございます。 今回は解決できるコードを提示してくださった方が居たため、そちらの方法で行いたいと思います。 openmpについても今後のために調べておきたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.46%

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

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

質問する

関連した質問