🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
アルゴリズム

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

Q&A

解決済

2回答

1836閲覧

数値を無作為に分配するアルゴリズム

mamyonya768

総合スコア4

アルゴリズム

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

0グッド

0クリップ

投稿2021/01/06 08:40

編集2021/01/06 14:10

数値を無作為に分配するアルゴリズムを考えています。
かなりの回数実行する予定なので、ある程度速いアルゴリズムを求めています。

数値を無作為に分配というのは、10であれば
3,3,4や9,0,1や2,7,1のように分配することを指しています。

正規分布に従う手法であれば(タイトルから少しそれますが)以下リンク先の手法で実装できそうですが、処理負荷が高そう(平方根,sin,cos...)なので他を探しています。
Box-Muller法を用いて正規分布に従う分配を行う - Qiita

おそらくかなり偏った値になりますが、現状考えているのは、
1番目を「010のランダムな値」
n番目を「0
10-(1~n番目にでた合計値)のランダムな値」とする手法です。

他にいい感じのアルゴリズムや、このような要件で一般的に使用されているアルゴリズムがありましたら、教えていただけると幸いです。

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

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

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

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

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

swordone

2021/01/06 08:52

リンク先の話は「正規分布に従って」、つまり「平均値に近い値ほど高い確率で分配される」分配法です。 タイトルの「無作為に」には適しません。
quickquip

2021/01/06 10:52 編集

結果に0が入っていると現実的には無理じゃありませんか? 9, 0, 0, 0, 0, (100億個の0が並んでいる), 1 が《0でない確率で》出現しなければなりませんよね? (数学的には確率0ですが) まあ、分割は n-1 までとして、3を処理したときに (3),(1, 2),(2, 1),(3, 0),(0, 3),(1, 1, 1),(1, 2, 0),(2, 1, 0),(0, 1, 2),(0, 2, 0),(2, 0, 1),(1, 0, 2),(3, 0, 0),(0, 3, 0),(0, 0, 3) が「全部同じ確率で出てくる」ことを期待しているのかどうかは気になりますね。
guest

回答2

0

ベストアンサー

0以上の整数で分配するという前提で回答します。
重複組み合わせの考え方を利用すると、
「分配の仕方」は「仕切りの入れ方」ととらえることができます。
そこで、「仕切りを入れる場所」として、0以上(分配する数+分配する個数-1)未満の乱数を
重複しないように(分配する個数-1)個取れば、どの分け方も等確率で得られます。
例えば、例の場合、つまり10を3つに分配する場合、10+3-1=12なので、
0~11の12個から乱数を2つ取得します。
乱数で得られた位置に仕切りを入れることで、分配の仕方が以下のように対応します。
(位置を示す番号は16進表記)

plain

1 0 1 2 3 4 5 6 7 8 9 A B 23,7 -> o o o | o o o | o o o o -> 3,3,4 39,10 -> o o o o o o o o o | | o -> 9,0,1

重複のないようにするには、0~(分配する数+1)の配列などを作って、
シャッフルするかフィッシャー・イェーツの方法を利用するのが確実になると思いますが、
分配する数より分配する個数が十分少なければ、逐次乱数を取り出して重複チェックするのでも
十分実用に足りると思います。

Javaしかできないので、Javaで実装してみました。
仕切りが〇より多くなる場合は、逆に〇を入れる位置を考える、という考え方をしています。

java

1import java.util.*; 2 3public class Main { 4 public static void main(String[] args) { 5 for (int i = 0; i < 10; i++) { 6 System.out.println(Arrays.toString(randomSplit(10,3))); 7 } 8 } 9 10 private static int[] randomSplit(int n, int p) { 11 int r = p - 1; 12 int total = n + r; 13 boolean wall = total > r * 2; 14 if (!wall) { 15 r = n; 16 } 17 BitSet set = new BitSet(total); 18 while (set.cardinality() < r) { 19 set.set((int)(Math.random() * total)); 20 } 21 if (!wall) { 22 set.flip(0, total); 23 } 24 25 int[] result = new int[p]; 26 int prev = 0; 27 for (int i = 0; i < r; i++) { 28 int next = set.nextSetBit(prev); 29 result[i] = next - prev; 30 prev = next + 1; 31 } 32 result[r] = total - prev; 33 return result; 34 } 35}

実行結果(10回ループしています)

[6, 0, 4] [2, 0, 8] [1, 8, 1] [1, 3, 6] [7, 1, 2] [5, 4, 1] [0, 1, 9] [5, 0, 5] [2, 1, 7] [5, 5, 0]

投稿2021/01/06 09:13

編集2021/01/06 10:07
swordone

総合スコア20669

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

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

mamyonya768

2021/01/06 14:05

ご回答ありがとうございます! ソースコードまでありがとうございました。
episteme

2021/01/06 22:19

正規分布うんぬんは関係なかったってことですか?
guest

0

逆に考えよう。平方根や三角関数が速ければよいのだと。

というわけでこちら。
高速三角対数指数関数
fast sqrt
どこまで偏りが出るかは未知数ですが、用途次第だと思います。少なくとも下手にアルゴリズム考えるよりはよいかと。あとメルセンヌツイスター自体が遅いというのもあるので X or shift 使って高速化しちゃいましょう。
Xorshift - Wikipedia

三角関数・平方根に関してはメモリが許せばテーブル化という方法も考えられます。ある値に対しての sin はいくつっていうのを予め配列にしておくのです。そして値が飛んできたら適当に一次近似して値を出すと。メモリが許せば許すほど精度が高くなる方法です。その割にスピードも出るので検討の余地はありかと。

いずれにせよ既存手法の高速化がベターかなと思います。

投稿2021/01/06 08:53

A_kirisaki

総合スコア2853

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

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

mamyonya768

2021/01/06 14:07

既存手法の高速化は考えていませんでした。 ご回答ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問