challenge 重複無し乱数

整数nを渡すと1 ~ n までの整数を重複しないようランダムに出力する関数「bingo」を作ってください。

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

>>> bingo(10)
[10, 7, 8, 4, 5, 2, 3, 1, 6, 9]
>>> bingo(3)
[2, 3, 1]
>>> bingo(3)
[2, 3, 1]
>>> bingo(3)
[3, 1, 2]
>>> bingo(10)
[7, 3, 8, 6, 4, 10, 9, 2, 1, 5]

Posted feedbacks - Flatten

Nested Hidden
Squeak 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 ではだめでしょう。

Cleanでやってみた。
擬似乱数を生成するライブラリをつかってます。
本当の乱数じゃないので、同じ数を与えると同じ乱数表になってしまう…
1
2
3
4
5
module RandList
import StdEnv, MersenneTwister

Start = bingo 10
bingo n  = map snd  (sort  (zip ((genRandInt n),[1..n])))

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]);
	}
}

あ、freeしてねぇ orz...
mainの最後の行に free(result) を追加してください。

投稿してから気づきましたが、#2254やpythonのshuffleアルゴリズムと同じですね。


	
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の要素取得が
できるようになってます.
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

std::random_shuffle を使います。乱数生成関数オブジェクトを指定しないと、何度実行しても同じ乱数列を発生させてしまうので、srand()を使う関数オブジェクトを定義します。

普通は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);
}

標準のrandpermという関数が問のbingoと全く同じ動作をします。投稿のコードはsortがソート前の順序を返すことを使った別解です。
1
2
function r = bingo(n)
[m r] = sort(rand(1,n));

参考になりました、あと stdlib.h の qsort() を使い回したのも良くないですね。
皆さんのより良いシャッフル方法を参考にさせて頂こうと思います。

#2269 によると rand() % n はまずいということなので、修正しました。std::min や std::max は [0, n) の範囲にあることを保証するため、念のため。
 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へのリンクを晒しておきます。
# 今も拙いって?

配列にするかリストにするか迷ったのですが、とりあえずリストにしておきました。
 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ならではの書き方でなるほど~という感じ
 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のコードのパフォーマンス比較記事があったのでリンクしておきます。

trotrの日記 どう書く?(重複なし関数)

Rubyのsort_byが安定ソートではないので偏りは生じないのでは、という話が続編でありました。

trotrの日記 どう書く?(重複なし関数)2


「安定ソートではないので偏りは生じないのでは」という話につっこみですが、
「安定ソートならば偏りが確実に生じる」というだけで、
「不安定ソートならば偏りが生じない」というわけではありません。
あえて言うなら「偏りが生じることが保証されていない」というソートです。

でも、shiroさんのつっこみも、間違いではなくて、
確かにシミュレーションなどで使うと偏りが問題になるのですが、
そもそもそういう目的にRubyを使うのがダメな気がするので
あまり気にするほどのことでもないかと思います…

っと、向こうにも同じ趣旨のことをshiroさんが書いていましたね(^^;

trotrの日記

# どう書くorgの書き込みからトラックバック打てたらいいのかもなぁ


#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;

ご指摘ごもっともと思ったのでMATLABでこのアルゴリズムで生成する数列の偏りをチェックしてみました(#2615)。このチェック方法がいいのかどうかは実はよく分からないのですが、これでおおざっぱに見る限り普通の精度の乱数を使えば偏りはものすごく小さそうです。ちなみにMATLABの標準のrandpermという同じ目的の関数も#2256と同様のアルゴリズムになっています。


もちろん、IEEE doubleを使った場合2^53通りの値がrandで出現するので (Rubyはメルセンヌツイスタでしたよね? )、「値が衝突する可能性」自体がものすごく少ないです (サンプル数10^5くらいじゃその計算をdoubleでやったら計算誤差よりはるかに小さいくらいの影響)。

ただ、暗号とかストカスティックシミュレーションみたいな、膨大なデータから統計的に意味を見つけ出すような分野で、素材となる元のランダム列に偏りが入っていると結果に影響が出てくるわけで、「そういう用途には使えない」ということだけわかっていれば良いのでは。標準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の精度に対してどれくらい小さいかを直感的に見たというだけのもので、最初の投稿について題意云々を批判したつもりはまったくないのですが、そのように読めたのでしたらもうしわけなかったです。失礼しました。


ああ、私もshgさんの投稿を批判と取ったわけではありません。2番目と3番目のパラグラフはshgさんの投稿に対してではなく、このやりとりを読んだ第3者の人が必要以上に問題を大げさにとらえたらまずいなという予防線みたいなつもりでした。まぎらわしくってすみません。

で、実際の偏りの評価なんですが、「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 ""
}

破壊的シャッフル。#3645, #2254 に似たものが C でなかったので書いてみました。

 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 サブルーチンの引数 )

 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);
        }
}
*/

古い話へのフォローですが、ちゃんと衝突確率を計算してるページを見つけました。(タイトルだけ見るとsort_by{rand}のせいでBigDecimalに問題が生じたのかと勘違いしそうですが、単に衝突確率計算で多倍長整数演算を使いまくったというだけの話のようです。)

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

D 2.008 で std.random に追加された機能を使っていますが、dmd 2.009 ではコンパイルできませんでした。
 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);
}

Arcです。
割と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))))))

あまりへんかしてないや。^^;


Rubyをリスペクトして書きました。 ---------------- 実行結果 [5, 1, 10, 8, 2, 7, 9, 3, 4, 6] [1, 2, 3] [2, 1, 3] [9, 4, 10, 5, 1, 7, 6, 2, 3, 8]
 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
 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)

Index

Feed

Other

Link

Pathtraq

loading...