重複無し乱数
Posted feedbacks - Flatten
Nested 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) "
|
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();
}
}
|
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行問題
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]
|
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
|
巧みだなあと思ったんですが、ごくわずかの確率でrandが同じ値を返すケースがあるはずで、その場合の要素の並び順はsortの実装によって固定されてしまうから、微妙に偏りが出てくるんじゃないかな、という気もします。
(例えば簡略化のために要素数を2つ、randは0か1しか返さないとして、sortがstableだとすると、可能な4通りの組み合わせのうち(0 1)が3回、(1 0)が1回となる)
現実にはdoubleが重なる確率なんて極めて低いでしょうけど。
ランダムソートでいいのかな
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;
}
|
ライブラリを使ってシャッフルしているケースでも もしかするとそういう偏りがある可能性はありますね。
気になったので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]
% 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
}
}
|
rand() % 2 ではだめでしょう。
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]
|
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 | function r = bingo(n)
[m r] = sort(rand(1,n));
|
参考になりました、あと stdlib.h の qsort() を使い回したのも良くないですね。 皆さんのより良いシャッフル方法を参考にさせて頂こうと思います。
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));
}
};
|
(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
|
標準で必要な関数があるのにあえて別解を用いるメリットが最初の例では特に無かったので、少し書き換えて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);
|
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(Integer.parseInt(args[0])));
}
}
|
抽出して最後に追加が見あたらなかったので投稿~ Cだとswapすることになるのかな? めんどそうだからperlで(笑 [10] => 8,10,6,1,3,5,9,4,7,2 [3] => 3,1,2 [2] => 2,1 # rand * 1000 だとうまくいかなかったなぜ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | sub bingo($;)
{
my $x = shift ;
my @num = (1 .. $x);
my $r;
for( my $n = $x; $n>0; $n --)
{
$r = rand; $r *= 1000;
push(@num, splice(@num, $r % $n, 1) );
}
print "[$x] => " . join(",", @num) . "\n";
}
srand;
bingo(10);
bingo(3);
bingo(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';
|
1 2 3 4 | import java.util.*
function bingo(n) {Collections.shuffle(x = list(range(1,n))); x}
println(bingo(10))
|
Ruby 1.9にはArray#shuffleが存在します
1 2 3 | def bingo(n)
[*1..n].shuffle
end
|
どこかで見たことがある問題だなぁ… コードはあまり確かめずに書いた. 配列の参照範囲とか間違ってなければいいけど.
1 2 3 4 5 6 7 8 | def bingo( n )
ary = (1..n).to_a
n.downto(1) {| i |
e = ary.delete_at( rand( i ) )
ary.push( e )
}
ary
end
|
1 2 3 4 5 6 7 8 9 | import Random (randomRIO)
import Data.List ((\\))
bingo :: Int -> IO [Int]
bingo n = b [1..n] n []
where b [] n ret = return ret
b xs (n+1) ret = do
r <- randomRIO (0,n)
let m = xs !! r in b (xs \\ [m]) n (ret ++ [m])
|
このshuffle関数ではリストは完全にシャッフルされない。 具体的には [1..1000]というリストを与えた場合。 500 <-> 501, 10 <-> 15 などの近距離では問題ないが 1 <-> 1000 などの遠距離の交換が起こる可能性が極めて低い。 良い実装法だれか教えてください。
rand * 1000 は rand() * 1000 にすれば動きます。 かっこがないため、 rand( * 1000) と解釈されているのだと思います。
リハビリを兼ねて、軽めに。
1 2 3 4 5 6 7 8 9 | <?php
function bingo($n) {
$rtn = range(1,$n);
shuffle($rtn);
return $rtn;
}
print_r(bingo(25));
?>
|
>どこかで見たことがある問題だなぁ… orz… 見たことがない問題を投稿してください…
1000って何かと思ったら$nであまりを取るんですね。 なぜ$nを掛けなかったのか少し気になります。 # 0付近だけ確率が高くなってしまう…$nが1000を超えたら大変…
1-n のリストを作っといてランダムに取り出せばいいかなと思いましたが、 それだとあまりきれいにいかなくて考えた末こうなりました。 コメントアウトを外すとわかりやすいと思いますが リングをランダムに回しながら取り出すようなイメージで作ってます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | (defun bingo (n)
(let ((r '#1=(nil . #1#)))
(do ((i 1 (1+ i)))
((> i n))
(push i (cdr r)))
(let (buf)
(do ((i n (1- i))
(st (make-random-state t)))
((= i 0))
(push (pop (cdr (nthcdr (random i st) r)))
buf)
;; (write r :circle t) (terpri)
)
(write buf))))
|
なるほど、何となくそんな予感はしていましたけど、やっぱそれしか考えられない? (*1000)てなにか意味のある文法なのかな? *Nとかならわかるんだけど。。。
もしかすると知っているかもしれませんが perlのrand()は0~1の値を返してくるので *1000しているのです。 範囲が1000を超えちゃうとまずいのは確かですね。 かといって *$nしてしまうと微妙に計算がおかしくなりそうな予感。 それ以外には、同じ値の発生率を1/1000にしたつもりです。
もしかしたら配列の範囲超えちゃうかなと思って剰余を使用しましたが よく考えてみるとかけ算でも良かったかも。 最大で0.9掛けにしかならないし。 あれ?でも確率的には1.0もありうる?
1 2 3 4 5 6 7 8 9 10 11 12 | --- doukaku46.pl.org 2007-08-23 00:40:42.921875000 +0900
+++ doukaku46.pl 2007-08-23 00:41:49.203125000 +0900
@@ -5,8 +5,7 @@
my $r;
for( my $n = $x; $n>0; $n --)
{
- $r = rand; $r *= 1000;
- push(@num, splice(@num, $r % $n, 1) );
+ push(@num, splice(@num, rand() * $n, 1) );
}
print "[$x] => " . join(",", @num) . "\n";
}
|
100行以内にかけそうなものというとある程度決まってるので、 こういった基本形は仕方ない気がします。 みんながみんな400行くらいのものを書いても 平気ならそれなりに凝ったお題もあるのでしょうけど。。。 #解析系とか
型ブログということでいいみたいです。 *1000 はどんなパッケージ内であっても、 mainパッケージに含まれているようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/usr/local/bin/perl -w
use strict;
package hoge;
my $piyo = (*1000);
print $piyo, "\n";
my $fuga = (*N);
print $fuga, "\n";
# 出力結果 ( Perlのバージョンは5.8.8 )
# Name "hoge::N" used only once: possible typo at ./test.pl line 9.
# *main::1000
# *hoge::N
|
「perlのrand()は0~1の値を返す」ということですけど、 「0以上1未満」じゃないですか? Perlのことは詳しくないので間違っているかも知れませんが、 0以上1未満なのであればnを掛けて切り捨てることで 0以上n未満の整数が得られます…。
こうすると配列作成コスト1回で済む・・・? 繰り返し部分をxsを返す式にしようとinjectやら何やら色々試したけど いまひとつきれいにできないのであきらめました.
1 2 3 4 5 | def bingo(n)
xs = *1..n
n.times {|i| xs << xs.slice!(rand(n - i)) }
xs
end
|
マニュアル確認しました。 1未満ですね失礼しました。 しかも、rand()は引数に指定したN未満という形で返すらしいです。 結局、掛け算する必要性すらなくorz さらに、別のページでは僕の書いたコードは悪い例として載っていました(笑 下のように書くと良いらしいです。 #コードはそのページからのコピペです。 #ページ内検索:How do I shuffle an array randomly? perlならではの書き方でなるほど~という感じ
see: perlfaq4 - データ操作
1 2 3 4 5 6 7 8 9 10 11 | # fisher_yates_shuffle( \@array ) :
# generate a random permutation of @array in place
sub fisher_yates_shuffle {
my $array = shift;
my $i;
for ($i = @$array; --$i; ) {
my $j = int rand ($i+1);
@$array[$i,$j] = @$array[$j,$i];
}
}
fisher_yates_shuffle( \@array ); # @array そのものを入れ替える
|
;; random-permutation (random t) (bingo 10)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (defun iota (n &optional a step)
(setq step (or step 1))
(do ((n n (- n 1))
(ret '() (cons a ret))
(a (or a 0) (+ a step)))
((<= n 0) (nreverse ret))))
(defun bingo (a &optional b)
(let* ((ret (vconcat (mapcar '1+ (iota a b))))
(num (length ret))
val rnd)
(dotimes (x num ret)
(setq rnd (random num)
val (aref ret x))
(aset ret x (aref ret rnd))
(aset ret rnd val))))
|
Rubyのコードのパフォーマンス比較記事があったのでリンクしておきます。
Rubyのsort_byが安定ソートではないので偏りは生じないのでは、という話が続編でありました。
「安定ソートではないので偏りは生じないのでは」という話につっこみですが、 「安定ソートならば偏りが確実に生じる」というだけで、 「不安定ソートならば偏りが生じない」というわけではありません。 あえて言うなら「偏りが生じることが保証されていない」というソートです。 でも、shiroさんのつっこみも、間違いではなくて、 確かにシミュレーションなどで使うと偏りが問題になるのですが、 そもそもそういう目的にRubyを使うのがダメな気がするので あまり気にするほどのことでもないかと思います…
see: shiroさんのつっこみ
#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;
|
ただ、暗号とかストカスティックシミュレーションみたいな、膨大なデータから統計的に意味を見つけ出すような分野で、素材となる元のランダム列に偏りが入っていると結果に影響が出てくるわけで、「そういう用途には使えない」ということだけわかっていれば良いのでは。標準Cの rand() が「用途によっては使えない」のと同じような意味で。
今回のお題はbingoですから、その範囲では十分に題意に沿った回答であると思います。
PS C:\> bingo(3) 2 1 0 PS C:\> @(bingo(10000) | sort | get-unique).length 10000
1 2 3 4 5 6 7 | function bingo([int] $n)
{
$r = new-object system.random
$a = 0..($n-1)
0..($n-1) | %{ $i = $r.next($n-1); $a[$_],$a[$i] = $a[$i],$a[$_] }
$a
}
|
先の投稿の意図は(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番目に来る確率みたいなのをチェックする方がより直接的かな? (してません)
Schemeの解答がなかったので投稿
1 2 3 4 5 6 7 8 9 10 | (use srfi-1)
(use srfi-27)
(define (bingo n)
(define (%bingo lst)
(if (null? lst)
'()
(receive (head tail) (split-at lst (random-integer (length lst)))
(cons (car tail) (%bingo (append head (cdr tail)))))))
(%bingo (iota n 1)))
|
1 2 3 4 5 | shuffle([],[]).
shuffle(X,[R|Y]) :- length(X, XL), N is random(XL),
nth0(N, X, R), select(R, X, X1), shuffle(X1, Y).
bingo(N) :- findall(X, between(1, N, X), L), shuffle(L, S), write(S).
|
ビンゴ本体より、準備行のほうが多い気がする。
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 | implement Bingo;
include "sys.m";
sys: Sys;
include "draw.m";
include "keyring.m";
include "security.m";
random: Random;
argv0: string;
Bingo: module
{
init: fn(nil: ref Draw->Context, argv: list of string);
};
usage()
{
sys->fprint(sys->fildes(2), "usage: %s n\n", argv0);
raise "fail:usage";
}
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
random = load Random Random->PATH;
argv0 = hd argv;
argv = tl argv;
n := 10;
if(argv != nil)
n = int hd argv;
a := array[n] of int;
for(i := 0; i < n; i++)
a[i] = i+1;
while(n > 0){
p := rand() % n;
sys->print("%d\n", a[p]);
a[p] = a[--n];
}
}
rand(): int
{
r := random->randomint(Random->ReallyRandom);
if(r < 0)
r = -r;
return r;
}
|
Perlで書いてみた
1 2 3 4 5 6 7 8 9 10 11 12 | sub bingo()
{
my @array = ( 1 .. shift );
my @out = ();
while ( my $x = splice( @array, int(rand(scalar @array)) , 1 ) )
{
push @out , $x ;
}
return [ @out ];
}
print join "," , @{ &bingo(10) };
|
もし,sample 関数がないとしたら,他の関数を使ってどう書くかを つまり,n個の一様乱数を発生させ,一様乱数の大きさの順で1〜nを並び替える > foo(10) [1] 10 9 1 3 2 5 7 8 4 6 > foo(10) [1] 7 10 2 9 5 4 1 8 3 6 > foo(100) [1] 7 84 53 70 26 21 90 37 33 82 65 40 74 95 24 1 4 97 38 6 [21] 94 15 18 12 29 19 43 52 85 47 3 64 78 88 81 46 87 55 16 28 [41] 62 41 17 36 25 42 39 11 56 58 61 91 44 20 51 71 60 31 75 96 [61] 80 13 50 14 10 57 59 35 5 48 30 77 27 67 100 72 92 68 54 83 [81] 79 89 73 23 98 99 69 76 8 2 22 86 63 93 66 32 34 9 45 49
1 | foo <- function(n) (1:n)[order(runif(n))]
|
1〜nの要素を持つベクトルを作り,端から順に無作為に並べ替えてゆく。 ランダムさはそれで十分だろう。 スクリプト中のnへ値を渡すのは -v n=5 のように > awk -f p3402.awk -v n=10 4 10 9 1 2 6 5 8 3 7 > awk -f p3402.awk -v n=10 4 10 9 1 2 6 5 8 3 7 > awk -f p3402.awk -v n=10 3 2 5 4 7 9 10 1 6 8
1 2 3 4 5 6 7 8 9 10 11 12 | BEGIN {
srand()
for (i = 1; i <= n; i++) x[i] = i
for (i = 1; i <= n; i++) {
j = int(rand()*n)+1
t = x[i]
x[i] = x[j]
x[j] = t
}
for (i = 1; i <= n; i++) printf " %i", x[i]
print ""
}
|
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 | #include <stdio.h>
#include <stdlib.h>
#include <time.h>
void bingo( int n ){
int tmp;
int i,j;
int *deck = (int*)malloc(sizeof(int)*n);
for( i = 0; i < n; i++ )
deck[i] = i+1;
for( i = 0; i < n; i++ ){
j = rand() % (n-i);
tmp = deck[j];
deck[j] = deck[ n-i-1 ];
deck[ n-i-1 ] = tmp;
}
for( i = 0; i < n; i++)
printf("%d ", deck[i] );
free(deck);
}
int main ( void ){
srand(time(NULL));
bingo(10);
printf("\n");
bingo(15);
printf("\n");
return 0;
}
|
出力がランダムな場合、皆さんどんな風にテスト書いてるんでしょうね
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static string bingo(int num)
{
//ランダムな配列を作成
System.Collections.ArrayList al= new System.Collections.ArrayList(num);
Random r= new Random();
for(int i=1; i<= num; i++)
al.Insert(r.Next(i), i);
//文字列で出力
System.Text.StringBuilder sb= new System.Text.StringBuilder();
for(int i=0; i< al.Count; i++)
sb.Append(al[i].ToString()+",");
return sb.ToString().Trim(',');
}
|
もっと短く出来そうですが素直に。
1 2 3 4 5 6 7 | bingo(10)を" "で配列結合して表示
●bingo(n)
rとは配列
(n)回
rに回数を配列追加
rを配列シャッフル
rで戻る
|
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 | #import <Foundation/Foundation.h>
@interface NSArray (Bingo)
+ (NSArray*)bingo:(int)n;
@end
@implementation NSArray (Bingo)
+ (NSArray*)bingo:(int)n
{
NSMutableArray* array = [NSMutableArray array];
int i;
for (i=1; i<=n; i++) [array addObject:[NSNumber numberWithInteger:i]];
NSMutableArray* result = [NSMutableArray array];
for (i=1; i<=n; i++) {
int size = n - i + 1;
int index = rand() % size;
[result addObject:[array objectAtIndex:index]];
[array removeObjectAtIndex:index];
}
return result;
}
@end
int main(int argc, char** argv)
{
NSAutoreleasePool* pool = [NSAutoreleasePool new];
srand(time(0));
NSLog(@"%@", [NSArray bingo:3]);
NSLog(@"%@", [NSArray bingo:10]);
[pool release];
return 0;
}
|
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 | #module
#deffunc bingo int n
dim dest, n
repeat n
dest.cnt = cnt+1
loop
repeat n
r = rnd( n - cnt ) + cnt
tmp = dest.r
dest.r = dest.cnt
dest.cnt = tmp
loop
buf = ""
repeat n
if ( cnt > 0 ) {
buf += " "
}
buf += str( dest.cnt )
loop
mes buf
return
#global
randomize
bingo 10
bingo 3
bingo 3
bingo 10
|
乱数を発生させる関数がなかったのでそこから実装。
HSP ( #4289 ) と同じ結果になるように Visual C++ の rand と同じアルゴリズム・定数を使いました。 #4289 と乱数の種を同じにしたら同じ結果になるでしょう。 ( HSP の randomize の引数、秀丸マクロの srand サブルーチンの引数 )
see: 良い乱数・悪い乱数
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 | call srand tickcount;
call bingo 10;
call bingo 3;
call bingo 3;
call bingo 10;
endmacro;
bingo:
##i = 0;
while( ##i < ##1 ) {
##dest[##i] = ##i + 1;
##i = ##i + 1;
}
##i = 0;
while( ##i < ##1 ) {
call rand;
##r = ##return % ( ##1 - ##i ) + ##i;
##tmp = ##dest[##r];
##dest[##r] = ##dest[##i];
##dest[##i] = ##tmp;
##i = ##i + 1;
}
##i = 0;
while( ##i < ##1 ) {
if ( ##i > 0 ) {
insert " ";
}
insert str( ##dest[##i] );
##i = ##i + 1;
}
insert "\n";
return;
rand:
#rand_x = #rand_x * 214013 + 2531011;
if ( #rand_x < 0 ) {
return ( ( #rand_x + 1 ) / 65536 - 1 ) & 32767;
}
return #rand_x / 65536 & 32767;
srand:
#rand_x = ##1;
return;
|
うーん,なかなか短くならない.
1 2 3 4 5 | def bingo(n)
pool, ret = (1..n).to_a, []
ret << (pool.delete_at(rand(pool.size))) until pool.empty?
ret
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 | import std.random;
int[] bingo(int x)
{
int[] xs;
for (int i = 1; i <= x; ++i) {
xs ~= i;
}
for (int i = 0; i < x; ++i) {
uint r = rand() % x;
int tmp = xs[r];
xs[r] = xs[i];
xs[i] = tmp;
}
return xs;
}
/*
import std.stdio;
import std.string;
void main(char[][] args)
{
for (int i = 1; i <= 10; ++i) {
int[] xs = bingo(i);
string s = "[";
foreach (j; xs) {
s ~= toString(j) ~ ",";
}
s = s[0 .. $ - 1] ~ "]";
writef("%s\n", s);
}
}
*/
|
1> c(bingo).
{ok,bingo}
2> bingo:bingo(10).
[3,1,9,2,5,10,8,4,6,7]
3> bingo:bingo(3).
[2,1,3]
4> bingo:bingo(3).
[1,3,2]
5> bingo:bingo(3).
[1,2,3]
6> bingo:bingo(10).
[10,3,5,1,4,6,2,7,8,9]
1 2 3 4 5 6 7 8 9 10 | -module(bingo).
-export([bingo/1]).
bingo(Num) -> bingo_sub(lists:seq(1, Num), Num).
bingo_sub(List, 0) -> List;
bingo_sub(List, Num) ->
R = lists:nth(random:uniform(length(List)), List),
L = [R | [X || X <- List, X =/= R]],
bingo_sub(L, Num - 1).
|
「n人中m人が当選するくじ」の n=mの場合と解釈しました。 bingo 10 1 2 9 3 7 8 10 5 6 4 bingo 3 3 2 1 bingo 3 1 3 2 bingo 3 2 1 3 bingo 10 5 1 3 7 10 2 4 9 8 6
1 | bingo=.3 :'>:y?y'
|
気付くと嬉しいアルゴリズム。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module Array = struct
include Array
let bury arr n len =
Array.blit arr (n+1) arr n (len-(n+1))
let shuffle arr =
let len = Array.length arr in
for i = len downto 1 do
let v = Random.int i in
let value = arr.(v) in
bury arr v i;
Array.set arr (i-1) value;
done
end;;
let bingo n =
let res = Array.init n (fun i -> i+1) in
Array.shuffle res;
res;;
|
randメソッドを利用して自作してみた.
1 2 3 4 | def bingo(n)
ary = Array.new(n){|i| i+1}
Array.new(n){|i| ary.delete_at(rand(n-i).modulo(n-i))}
end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import std.stdio;
import std.random;
void bingo(uint n){
uint[] a;
a.length = n;
foreach(i, ref e; a){
e = i + 1;
}
randomShuffle(a, Random(unpredictableSeed()));
writefln(a);
}
void main(){
bingo(10);
}
|
割とCommon Lispみたいになってしまいました。
1 2 3 4 5 6 7 8 9 10 11 12 | (def bingo (n)
(let lst (iota n 1)
(for i 0 (- n 1)
(swap (lst i) (lst (rand n))))
lst))
(def iota (n (o start 0) (o step 1))
(let res ()
(repeat n
(push start res)
(++ start step))
(rev res)))
|
バッチで書いてみました。27行目の行末に半角空白が 1つあるので注意してください。
環境変数%RANDOM%は 0~32767の範囲で乱数を返します。乱数値の一桁目( 0~9)だけを採用
し、要素数 nで除算すると剰余は 0~n-1のいずれかの値になります。その値を擬似配列の
インデックスとして利用するため、値に 1を加えて補正しています。シャッフルに偏りが
生じるとは思いますが、これが考えつく限界でした。
e.g.
C:\>bingo 10
[ 10, 4, 9, 3, 1, 8, 7, 6, 2, 5 ]
C:\>bingo 3
[ 2, 1, 3 ]
C:\>bingo 3
[ 2, 3, 1 ]
C:\>bingo 3
[ 2, 1, 3 ]
C:\>bingo 10
[ 1, 7, 10, 5, 6, 4, 8, 2, 9, 3 ]
遅延環境変数展開と環境変数%RANDOM%を利用しているので、Windows NTでは動作しません。
Windows XPで動作を確認。
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 | :: bingo.bat
@echo off
setlocal enabledelayedexpansion
set i=0
set j=0
set t=
set v=0
echo %1|findstr /r "[^0-9]" >NUL 2>&1
if %ERRORLEVEL% equ 0 (echo %0 [NUMBER] & goto :EOF)
:: 擬似配列を生成
for /l %%i in (1,1,%1) do set v_%%i=%%i
:: 値をシャッフル
set i=1
:loop
set /a j=%RANDOM:~-1%%%%1+1
set v=!v_%i%!
set v_%i%=!v_%j%!
set v_%j%=%v%
set /a i+=1
if %i% lss %1 goto loop
for /l %%i in (1,1,%1) do (
set t=!t!!v_%%i!
if %%i lss %1 set t=!t!,
)
endlocal & echo [ %t% ]
goto :EOF
|
引数( n)の桁数に応じて、乱数値から切り出す桁数を変えました。 nの値が大きい場合の
偏りは軽減されたものの、ソースの見通しは悪くなったかもしれません。
e.g.
5a6
> set l=0
15a17,18
> call :length %1 l
> echo set j=%%RANDOM:~-%l%%%>_.bat
17,18c20,23
< :loop
< set /a j=%RANDOM:~-1%%%%1+1
---
> :shuffle
> :: 8進数として解釈されないよう 0を加算
> call _.bat & set /a j+=0
> set /a j=%j%%%%1+1
23c28,29
< if %i% lss %1 goto loop
---
> if %i% lss %1 goto shuffle
> del _.bat
31a38,50
> :length
> setlocal
> set i=0
> set t=%1
> set t=%t:"=%
>
> :loop
> set t=%t:~1%
> set /a i+=1
> if not "%t%" == "" goto loop
> endlocal & set %2=%i%
> goto :EOF
# 小数が扱えないのは痛いなぁ...
ランダムシャッフルならリストより配列を使った方がいいですね。理由は簡単で要素にアクセスする時間からです。
1 2 3 4 5 6 7 8 9 10 11 | (defun bingo(max)
(let ((vec (make-array 0 :fill-pointer t)))
(loop for i from 1 to max do
(vector-push-extend i vec))
(loop for i from max downto 1 collect
(if (equal i 1)
(vector-pop vec)
(let ((loc (random i)))
(rotatef (aref vec (1- i))
(aref vec loc))
(vector-pop vec))))))
|
arefはsvrefより遅いのでsvref版を用意しました。
1 2 3 4 5 6 7 8 9 10 11 | (defun bingo-v2(max)
(let ((vec (make-array max)))
(loop for i from 1 to max do
(setf (svref vec (1- i)) i))
(loop for i from max downto 1 collect
(if (equal i 1)
(svref vec (1- i))
(let ((loc (random i))
(ii (1- i)))
(rotatef (svref vec ii) (svref vec loc))
(svref vec ii))))))
|
svref と arefの差は次のようなものです。 consingで2/3 実時間で1/2.5程度ですかね。 -- CL-USER> (time (and (bingo 100000) nil)) Evaluation took: 0.074 seconds of real time 0.056004 seconds of user run time 0.004001 seconds of system run time 0 calls to %EVAL 0 page faults and 3,698,800 bytes consed. NIL CL-USER> (time (and (bingo-v2 100000) nil)) Evaluation took: 0.027 seconds of real time 0.016001 seconds of user run time 0.0 seconds of system run time 0 calls to %EVAL 0 page faults and 2,397,456 bytes consed. NIL
最後に最適化オプションをつけて。。。bingo_v2より1/2の時間 になりましたね。 CL-USER> (time (and (bingo-v3 100000) nil)) Evaluation took: 0.013 seconds of real time 0.016001 seconds of user run time 0.0 seconds of system run time 0 calls to %EVAL 0 page faults and 2,397,536 bytes consed. NIL
1 2 3 4 5 6 7 8 9 10 11 12 13 | (defun bingo-v3(max)
(declare (optimize (speed 3) (debug 0) (safety 0)))
(declare (fixnum max))
(let ((vec (make-array max)))
(declare (simple-vector vec))
(loop for i from 1 to max do
(setf (svref vec (1- i)) i))
(loop for i from max downto 1 collect
(if (equal i 1)
(svref vec (1- i))
(let ((loc (random i)) (ii (1- i)))
(rotatef (svref vec ii) (svref vec loc))
(svref vec ii))))))
|
あまりへんかしてないや。^^;
1 2 3 4 5 6 7 8 9 10 | def bingo(n) {
def list = (1..n).toList()
Collections.shuffle(list)
println list
}
bingo(10)
bingo(3)
bingo(3)
bingo(10)
|
$ bingo 10 2 10 3 5 6 4 1 8 7 9
see: [ruby-list:40892]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function bingo() {
local n=$1
local -a array
local -i i j tmp
for ((i = 0; i < n; i++)) {
array[i]=$((i+1))
}
for ((i = 0; i < n; i++)) {
((j = RANDOM % (i + 1),
tmp = array[i], array[i] = array[j], array[j] = tmp))
}
echo ${array[@]}
}
|
1 2 3 4 5 | int[] bingo(int n)
{
Random r = new Random();
return Enumerable.Range(1, n).OrderBy(a => r.Next()).ToArray();
}
|
Haskellは参照透明な言語なので、同じ引数に関数を束縛したら、同じ値がかえってこなければならないので、お題を満たせません。
そこで乱数生成器も引数に取ることにしました。
1 2 3 4 5 6 7 8 9 10 11 | import Random
import List
randomN :: Int->StdGen -> (StdGen,[Int])
randomN n stdGen = mapAccumL (\r lim->swapTuple (randomR (0,lim) r)) stdGen [n-1,n-2..0]
where swapTuple (a,b) = (b,a)
bingo ::Int->StdGen->[Int]
bingo n stdGen = snd $ mapAccumL f [1..n] $ snd (randomN n stdGen)
where f xs x = (deleteAt x xs,xs !! x)
deleteAt i xs = take i xs ++ tail (drop i xs)
|
GNU coreutilsのshufで。 $ bingo 6 2 1 6 4 5 3
1 2 3 4 | #!/bin/sh
bingo() {
seq $1 | shuf
}
|
GroovyでCollections#shuffleは既出(#6677)のようなので、 rubikitchさん(Ruby:#2256)のGroovy版で。 実質メソッド内はワンライナー。 --出力結果-- [1, 9, 3, 6, 7, 10, 2, 8, 4, 5] [2, 3, 1] [3, 2, 1] [2, 1, 4, 3, 5, 7, 10, 8, 9, 6]
1 2 3 4 5 6 7 | def bingo(n) {
(1..n).toList().sort{ Math.random() }
}
println bingo(10)
println bingo(3)
println bingo(3)
println bingo(10)
|






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