重複無し乱数
Posted feedbacks - Nested
Flatten HiddenSqueak Smalltalk で。
1 2 3 | | bingo |
bingo := [:n | (1 to: n) asArray shuffled].
bingo value: 10 "=> #(6 9 3 4 2 1 10 5 8 7) "
|
Pythonでも1~nのリストを作ってシャッフルする方法が一番てっとりばやいですね。 >>> bingo(10) [10, 8, 2, 7, 4, 5, 3, 6, 1, 9]
1 2 3 4 5 | def bingo(n):
from random import shuffle
result = range(1, n + 1)
shuffle(result)
return result
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using System;
using System.Text;
class Program
{
static void Main()
{
Console.WriteLine(bingo(3));
}
static string bingo(int n)
{
Random r = new Random();
int[] a = new int[n];
for (int i = 0; i < n; ++i) a[i] = i + 1;
for (int i = n; i > 1; --i)
{
int k = r.Next(i);
int tmp = a[i - 1];
a[i - 1] = a[k];
a[k] = tmp;
}
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < n; ++i)
{
if (i > 0) sb.Append(", ");
sb.Append(a[i]);
}
sb.Append(")");
return sb.ToString();
}
}
|
久々の1行問題
1 2 3 4 5 6 7 8 | def bingo(n)
(1..n).to_a.sort_by{rand}
end
bingo 10 # => [4, 2, 1, 10, 8, 5, 3, 7, 6, 9]
bingo 3 # => [3, 1, 2]
bingo 3 # => [1, 3, 2]
bingo 3 # => [2, 3, 1]
bingo 10 # => [3, 2, 6, 5, 7, 4, 1, 8, 10, 9]
|
巧みだなあと思ったんですが、ごくわずかの確率でrandが同じ値を返すケースがあるはずで、その場合の要素の並び順はsortの実装によって固定されてしまうから、微妙に偏りが出てくるんじゃないかな、という気もします。
(例えば簡略化のために要素数を2つ、randは0か1しか返さないとして、sortがstableだとすると、可能な4通りの組み合わせのうち(0 1)が3回、(1 0)が1回となる)
現実にはdoubleが重なる確率なんて極めて低いでしょうけど。
ライブラリを使ってシャッフルしているケースでも もしかするとそういう偏りがある可能性はありますね。
気になったのでPythonのrandomライブラリを見てみたところ#2254と同じアルゴリズムでした。
for i in reversed(xrange(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = int(random() * (i+1))
x[i], x[j] = x[j], x[i]
ただ、暗号とかストカスティックシミュレーションみたいな、膨大なデータから統計的に意味を見つけ出すような分野で、素材となる元のランダム列に偏りが入っていると結果に影響が出てくるわけで、「そういう用途には使えない」ということだけわかっていれば良いのでは。標準Cの rand() が「用途によっては使えない」のと同じような意味で。
今回のお題はbingoですから、その範囲では十分に題意に沿った回答であると思います。
先の投稿の意図は(rand自体の品質の話ではなく)このアルゴリズム自体に起因する偏りがrandの精度に対してどれくらい小さいかを直感的に見たというだけのもので、最初の投稿について題意云々を批判したつもりはまったくないのですが、そのように読めたのでしたらもうしわけなかったです。失礼しました。
で、実際の偏りの評価なんですが、「randの精度に対して急速に小さくなる」というのはまあ予想できることで、「ものすごーく小さいはずなんだけれど実際のところ53bit精度では具体的にどのくらいの偏りなのかな」という点に触れられていなかったのでああいう書き方になってしまった、ようです。今読み返すとちゃんとしたコメントになっていませんね。失礼しました。
実は#2264時点で衝突する確率の計算も試みてみたのですが、doubleで計算したらすぐに誤差に埋もれてしまうし、無限多倍長数で計算したら計算時間が爆発したので諦めていたのでした。ちゃんと近似式を導いて評価するのが良いのでしょうが…
乱数が衝突する確率を直接計算すると、pを乱数の精度(最小単位の意味で)として、
1-(1-p)(1-2p)(1-3p)...(1-(n-1)p)
みたいな感じでしょうか。p=2^(-53)でn=25だとおよそ2.1e-14。p=2^(-31)でn=100だとおよそ2.3e-6。n個の中で衝突する確率なのでこの値は当然nに大きく依存しますが、実際は衝突は全体にまんべんなく確率的に起きるわけでnが大きいと偏りの問題が大きくなるというわけではないですよね。
bingoの評価としては、1〜nのそれぞれが1番目、2番目、.. n番目に来る確率みたいなのをチェックする方がより直接的かな? (してません)
1 2 | use List::Util qw/shuffle/;
sub bingo {shuffle(1..$_[0]);}
|
1 2 3 4 5 6 | Number bingo := method(
Range
1 to(self) asList shuffle
}
10 bingo println
|
普通にやってみた
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def bingo(num)
raise ArgumentError.new("fixnum only") unless num.kind_of? Fixnum
raise ArgumentError.new("1 ijyou de onegai simasu") unless num >= 1
results = []
ary=(1..num).to_a
ary.size.times{
index = (rand * ary.size).to_i
results << ary.delete_at(index)
}
results
end
|
ランダムソートでいいのかな
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <stdio.h>
#include <stdlib.h>
static int cmp(const void *p1, const void *p2)
{
return (rand() % 2)?1:-1;
}
void bingo(int num)
{
int i;
int *nums;
if(num <= 0) return;
nums = (int*)malloc(sizeof(int) * num);
for(i=0; i<num; i++){
nums[i] = i + 1;
}
qsort(nums, num, sizeof(int), (int (*)(const void*, const void*))cmp);
for(i=0; i<num; i++){
printf("%d\n", nums[i]);
}
free(nums);
}
int main(int argc, char *argv[])
{
if(argc < 2){
return EXIT_FAILURE;
}
srand(time(NULL));
bingo(atoi(argv[1]));
return EXIT_SUCCESS;
}
|
rand() % 2 ではだめでしょう。
参考になりました、あと stdlib.h の qsort() を使い回したのも良くないですね。 皆さんのより良いシャッフル方法を参考にさせて頂こうと思います。
% awk -f bingo.awk 5 [3, 5, 1, 4, 2] 24 [22, 11, 5, 2, 4, 3, 9, 24, 6, 7, 17, 16, 21, 15, 19, 18, 12, 14, 13, 23, 10, 20, 8, 1] 10 [7, 4, 9, 3, 5, 10, 2, 1, 8, 6] 10 [10, 8, 3, 2, 6, 7, 1, 9, 5, 4] 10 [5, 7, 1, 8, 10, 3, 6, 2, 4, 9] 10 [6, 2, 3, 5, 1, 9, 8, 10, 7, 4] 10 [6, 9, 8, 5, 4, 2, 1, 3, 10, 7]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | BEGIN {
srand
}
{
n = $1
bingo(n,ar)
show(n,ar)
}
function show(n,ar, i)
{
printf "["
for (i=1; i<n; i++) printf("%d, ", ar[i])
printf("%d]\n", ar[n])
}
function rand_between_1_and_n(n, x)
{
x = 1 + int(rand * n)
return (x <= n)? x : rand_between_1_and_n(n)
}
function bingo(n,ar, i,x,y,t)
{
delete ar
for (i=1; i<=n; i++) ar[i] = i
for (i=n*2; i>0; i--) {
x = rand_between_1_and_n(n)
y = rand_between_1_and_n(n)
if (x == y) continue
t = ar[x] ; ar[x] = ar[y] ; ar[y] = t
}
}
|
System.Random を使う。シャッフルをナイーブに実装したもの
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | module Main (main) where
import System.Environment
import System.Random
shuffle :: [a] -> StdGen -> Int -> [a] -> [a]
shuffle acc _ 0 _ = acc
shuffle acc g n xs = case randomR (0,n-1) g of
(i,g') -> case splitAt i xs of
(ys,z:zs) -> shuffle (z:acc) g' (n-1) (ys++zs)
main :: IO ()
main = do { x:_ <- getArgs
; g0 <- getStdGen
; let n = read x
; putStrLn $ show $ shuffle [] g0 n [1..n]
}
{-
% ./bingo 10
[4,10,3,2,9,5,7,8,6,1]
% ./bingo 10
[2,7,6,8,4,1,9,5,3,10]
% ./bingo 10
[1,7,3,10,6,4,8,2,5,9]
% ./bingo 10
[6,8,9,3,10,4,1,5,2,7]
-}
|
1 2 | import scala.util.Sorting.stableSort
def bingo(n:int) = stableSort[int,double](1 to n,{x=>Math.random})
|
1 2 3 4 5 6 7 8 9 | Array.prototype.$huffle = function(){
for(var $, r, i = this.length; i;)
$ = this[r = Math.random() * i-- | 0], this[r] = this[i], this[i] = $;
return this;
}
function bingo(n){
for(var a = []; n > 0;) a[a.length] = n--;
return a.$huffle();
}
|
C言語と再帰で解いて見た。 ソート関数は利用していません。多分線形時間。 **intを与えているのがちょっとかっこ悪いですが。 あと↓の部分をまともになるように書き換えればOKだけど、アルゴリズムの本質ではないので放置。 swap_point = rand() % (num+1); アルゴリズムは以下のような感じ [1~n-1までがシャッフルされた配列] [n] ↑のような配列があったときに、 末尾要素と、ランダムで選ばれた末尾より前の要素とスワップすることで、 全体が1~nまでがシャッフルされた配列になります。 あとはそいつを再帰している感じ。 ループで書いてもよかったかな。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <stdio.h>
#include <stdlib.h>
void _bingo(int num, int *result, int num_max){
int temp;
int swap_point;
result[num] = num + 1;
swap_point = rand() % (num+1);
temp = result[swap_point];
result[swap_point] = result[num];
result[num] = temp;
if (num + 1 == num_max){
return;
}
_bingo(num+1, result, num_max);
return;
}
void bingo(int num, int** result)
{
*result = (int*)malloc(num * sizeof(int));
_bingo(0, *result,num);
}
int main(void)
{
int *result;
int num;
int i;
num = 100;
bingo(num, &result);
for(i = 0; i < num; i++){
printf("%d,", result[i]);
}
}
|
mainの最後の行に free(result) を追加してください。
1 2 3 4 5 6 | #light
let shuffle ls =
let rnd = new System.Random() in
List.sort (fun x y-> rnd.Next(-1,2)) ls;;
let bingo n = shuffle [1..n]
|
このshuffle関数ではリストは完全にシャッフルされない。 具体的には [1..1000]というリストを与えた場合。 500 <-> 501, 10 <-> 15 などの近距離では問題ないが 1 <-> 1000 などの遠距離の交換が起こる可能性が極めて低い。 良い実装法だれか教えてください。
1~nまでのリストをRangeで生成して,
RandomSampleでシャッフルしました.
実行例:
In[2]:= bingo[10]
Out[2]= {6, 3, 10, 8, 1, 2, 9, 5, 4, 7}
RandomSampleで再利用無しの要素取得,
RandomChoiceで再利用OKの要素取得が
できるようになってます.
see: Mathematica - RandomSample
1 | bingo[n_] := RandomSample[Range[n]];
|
AddしてRemoveAt。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Public Sub bingo(ByVal n As Integer)
Dim list As New List(Of Integer)
For i As Integer = 1 To n
list.Add(i)
Next
Dim r As New Random
For i As Integer = 1 To n
Dim index As Integer = r.Next(0, n)
list.Add(list(index))
list.RemoveAt(index)
Next
For Each i As Integer In list
Console.Write(i.ToString & ", ")
Next
End Sub
|
普通はtime()で得られる値を種にするらしいのですが、これは秒単位なので、同一秒内で再度実行すると、やはり同じ乱数列が得られてしまい、うまくありません。
そこで、Windowsでは、起動からのミリ秒を種として渡すことにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <cstdlib>
#ifdef _WIN32
# include <windows.h>
# define seed() ::GetCurrentTime()
#else
# include <ctime>
# define seed() std::time(NULL)
#endif
struct RandomGenerator
{
RandomGenerator()
{
std::srand(static_cast<unsigned>(seed()));
}
int operator()(int n) const
{
return std::rand() % n;
}
};
void bingo(size_t n)
{
std::vector<size_t> v;
for (size_t i = 0; i < n; ++i)
{
v.push_back(i);
}
static RandomGenerator gen;
std::random_shuffle(v.begin(), v.end(), gen);
std::copy(v.begin(), v.end(), std::ostream_iterator<size_t>(std::cout, " "));
std::cout << std::endl;
}
int main()
{
bingo(10);
bingo(3);
bingo(3);
bingo(3);
bingo(10);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 | --- main.orig Sat Aug 18 00:35:58 2007
+++ main.cpp Sat Aug 18 00:41:24 2007
@@ -21,7 +21,9 @@ struct RandomGenerator
int operator()(int n) const
{
- return std::rand() % n;
+ const int i = static_cast<int>(static_cast<double>(std::rand()) * n / RAND_MAX);
+
+ return std::max(0, std::min(i, n - 1));
}
};
|
1 2 | function r = bingo(n)
[m r] = sort(rand(1,n));
|
標準で必要な関数があるのにあえて別解を用いるメリットが最初の例では特に無かったので、少し書き換えてk個の列を一度に返せるようにした。標準のrandpermでk個生成するにはループでk回呼ばないといけない。余談だがMATLAB7.4ではデフォルトの乱数生成アルゴリズムとして#2269さんのリンク先でも紹介されているメルセンヌ・ツイスタが採用されている。
1 2 3 4 5 6 7 8 9 | function r = bingo(n,k)
% r = bingo(n) retruns a random permutation of 1:n.
% r = bingo(n,k) returns a k-by-n array, each row of which is a random
% permutation of 1:n.
% (ja.doukaku.org Q46)
if nargin<2
k = 1;
end
[m r] = sort(rand([k n]),2);
|
最初の投稿でえらそうに別解です、なんて書いたけどMATLABのrandpermは実は.mファイル(つまりMATLABで書かれたスクリプト)でその中身は投稿したコードと全く同じだということに気付いた。情けないのであらためて別解。前と同様第二引数で何個生成するか指定する。sortを使う方法よりずいぶん遅い。次のようにすると計算時間の測定と順序が均等かどうかのおおまかな確認ができる。
tic;r=bingo(100,10000);bar(sum(r,1));toc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function r = bingo(n,k)
% r = bingo(n) retruns a random permutation of 1:n.
% r = bingo(n,k) returns a k-by-n array, each row of which is a random
% permutation of 1:n.
% (ja.doukaku.org Q46)
if nargin<2
k = 1;
end
[s r] = meshgrid(1:k,1:n);
% q = zeros(k,2);
for p = n:-1:2
q(:,1) = ceil(rand(k,1)*p);
q(:,2) = p;
q = q+s(1:2,:)'*n-n;
r(q) = r(q(:,[2 1]));
end
r = r';
|
#2264の指摘をみてなるほどと思ったので簡単なテスト。順位相関を使ってみた。
#2285のコード(MATLAB標準のrandpermも同じ方法)は最初に乱数を要求された数列の個数分だけ作り、その順序を返す。MATLABのsortは値が同じ場合は順序を保存するので、たとえば1番目と2番目の乱数が同じ数字だと必ず1が2の前になってしまう。
投稿のコードは乱数の有効桁数(最初の行の最後の引数)を制限し、生成した数列とその位置の順位相関の分布と平均を表示する。有効桁数を1にした場合はかなり偏りが生じるが、3桁くらいでほとんど偏りが無い。順位相関を見る限りでは、普通の精度の乱数を使えばこのアルゴリズムでの偏りを気にする必要は無さそう。
1 2 3 4 5 6 7 8 9 10 11 12 | x = bingo(25,1000,1);
rho = corr((1:size(x,2))',x(1:size(x,1),:)','type','Kendall');
figure(1); hist(rho,-1:0.1:1); axis([-1 1 0 300])
fprintf('Mean = %f, skewness = %f\n', mean(rho), skewness(rho));
function r = bingo(n,k,p)
% r = bingo(n,k) returns a k-by-n array, each row of which is a random
% permutation of 1:n, using random numbers with p significant digits .
[m r] = sort(randr([k n],p),2);
function r = randr(M,p)
r = floor(rand(M).*10^p)./10^p;
|
(print (bingo 10)) => (2 4 8 10 6 7 5 9 3 1)
1 2 3 4 5 6 7 8 9 | (defun bingo (n)
(loop repeat n
for rand = (loop
(let* ((*random-state* (make-random-state t))
(rand (1+ (random n))))
(if (not (member rand lst))
(return rand))))
collect rand into lst
finally (return lst)))
|
Rもそうですが、MathematicaやMATLABのような数値解析系はこういうの簡単に書けますね。 元のお題は「n * nのビンゴを出力せよ」みたいな感じだったのかな。 > sample(10) [1] 1 9 4 10 3 5 7 2 8 6
1 | bingo <- sample
|
4年前に/.J日記界隈で話題になったネタです。 当時のblogへのリンクを晒しておきます。 # 今も拙いって?
see: カードまぜまぜほげふが。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import java.util.*;
public class Sample {
public static List<Integer> bingo(int num) {
Integer[] deck = new Integer[num];
for (int i = 0; i < num; i++) {
deck[i] = i + 1;
}
List<Integer> nums = (List<Integer>) Arrays.asList(deck);
Collections.shuffle(nums);
return nums;
}
public static void main(String[] args) {
System.out.println(bingo( |





raynstard
#3402()
Rating0/0=0.00
このお題はraynstardさんの投稿を元にしています。ご投稿ありがとうございました。 投稿の内容には表示のしかたも含まれていたのですが、 このお題では「重複しない1~nまでの乱数をどうやって作るか」という点に集中することにして、 結果の整形は続編としてこの後のお題で出すことにします。 サンプル入出力は下のようになります。
[ reply ]