ランダムに生成するプログラム

http://ap.atmarkit.co.jp/bbs/core/fjava/21486
http://d.hatena.ne.jp/issei_y/20100601/1275379705
http://chocobo.yasuda-u.ac.jp/~nisimura/mymove/index.cgi?no=1789

「ある会社から面接前にC#の宿題を出されました。一番効率良いのベストなコードを書いた方のみ面接するということでした。」...ということでその問題が以下の通りです...

「1から10,000の番号をランダムに生成するプログラムを作りなさい。プログラムを実行するごとに番号をランダムに生成すること。番号は重複使用しないこと。」

この問題は調べてみると意外と根が深いことが判明すますた...

最上リンクのページで様々な提案がでたのですが、中でも注目(?)を集めたのが以下のコードです。

   var random = new Random();
   source.OrderBy(_ => random.Next());

このコードの意図は「ソートを行うときに意図的に順序を決める部分(Comparator)にデタラメな答えを出す(ランダム)様にする...よって、結果はランダムに成る」...なのですが、結論から言うと「これでは駄目」です。

http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html

上記の技法はマイクロソフトも”ランダム”な字列を作成するために用いたのですが結果としてはあまり”ランダム”に成らなかったのですね...

In this case Microsoft gave the following comparison function:

function RandomSort (a,b)
{
return (0.5 - Math.random());
}

Since Math.random() should return a random number chosen uniformly between 0 and 1, the RandomSort() function will return a random value between -0.5 and 0.5. If you know anything about sorting, you can see the problem here. Sorting requires a self-consistent definition of ordering. The following assertions must be true if sorting is to make any sense at all:

1. If a<b then b>a
2. If a>b then b<a
3. If a=b then b=a
4. if a<b and b<c then a<c
5. If a>b and b>c then a>c
6. If a=b and b=c then a=c

All of these statements are violated by the Microsoft comparison function. Since the comparison function returns random results, a sort routine that depends on any of these logical implications would receive inconsistent information regarding the progress of the sort. Given that, the fact that the results were non-random is hardly surprising. Depending on the exact search algorithm used, it may just do a few exchanges operations and then prematurely stop. Or, it could be worse. It could lead to an infinite loop.

ざ~っと訳すと、

マイクロソフトはソート用の比較に以下の関数を用いました。

function RandomSort (a,b)
{
return (0.5 - Math.random());
}

Math.random() は0から1までの乱数を返すので RandomSort() はー0.5から+0.5までの乱数を生成します。ソートについてそれなりに詳しい人ならば問題に気付くでしょう。ソートには以下の矛盾しない順番付けに関する公理を満たさなければなりません。

1. If a<b then b>a
2. If a>b then b<a
3. If a=b then b=a
4. if a<b and b<c then a<c
5. If a>b and b>c then a>c
6. If a=b and b=c then a=c

マイクロソフト作成の関数(RandomSort())はこの点において全てに違反しています。よってこの関数は(同じパラメターで関数を呼び出しても)矛盾した情報を返すことができるためソートがどの程度進んでいるかを計ることができません。従ってソートに使われたアルゴリズム次第では数回数字を交換して終わる...なんてこともありえますし、最悪の場合には無限ループに陥ります。

...と言う訳で「これでは駄目」です。詳細はリンクから読むべし。

では模範回答(?)はどうあるべきか???

まず問題を振り返りましょう。

「1から10,000の番号をランダムに生成するプログラムを作りなさい。プログラムを実行するごとに番号をランダムに生成すること。番号は重複使用しないこと。」

...色々書いてありますが、この問題の意図は「番号をランダムに生成する」のではなく、「1から10,000の番号をランダムにシャッフルせよ」...ということに成ります。トランプをシャッフルして一枚一枚捲っていくのと同じですね。

((初級))

1.1から10,000の番号を行列に書き込む
2.ランダムに二つ選んで交換する。
3.(2)を適当な回数繰り返す

...お手軽に実装できますが、「ランダム度」は交換の回数によります。一応問題の条件を満たしますが、試験官はそれほど関心しないでしょう。

((中級))

1.1から10,000の番号を行列に書き込む
2.ランダムに一つ選ぶ...そしてその数字を記録する。
3.(2)で選んだ数字を行列から消す...つまり、行列の「間」を詰める
4.数字が無くなるまで(2) と(3)を繰り返す

...一応問題の条件を満たしますし、「ランダム度」も高くなります。ですが『行列の「間」を詰める』のは意外と時間の浪費になります。

((上級)) (だと思う)

1.1から10,000の番号を行列に書き込む
2.ランダムに一つ選ぶ...そしてその数字を記録する。
3.(2)で選んだ数字を行列から消す... 行列の最後の数字で上書きします。そして行列のサイズを一つ縮める。(正確には行列の最後の位置を覚えておく...ですね。)
4.数字が無くなるまで(2) と(3)を繰り返す

...一応問題の条件を満たしますし、「ランダム度」も高くなります。 上記に上げた欠点も直しています。 O(n) です。

投稿者: 紫外線 投稿日時: 木, 06/03/2010 - 10:56 categories [ ]

コメントの表示オプション

お好みの表示方法を選択し、「設定の保存」をクリックすると、表示方法を変更することができます。

信用

>(つまり信用していない)

業務時は一から全部自前で作るわけにはいかないのでAPIに頼る(正確・正直であるか?)のが残念ながら実情ですね。

>その時の時刻をキーにして、あれこれいじって乱数にする

これは乱数生成の「種」数値を決めるときの常套手段ですね。

大学時のクラス(確か題名は Computational Mathematics だったと思う。大学も我の場合こちらです。)で最初の課題が「乱数生成プログラム」でした。乱数はいわば「自然の行動・摂理」...これを全人工のソフトで真似るわけですが、中々奥の深いトピックでした。

「API」とは?

http://ja.wikipedia.org/wiki/Application_Programming_Interface

API = Application Programming Interface

『API(アプリケーション・プログラミング・インタフェース、Application Programming Interface)とは、アプリケーションから利用できる、オペレーティングシステムやプログラミング言語で用意されたライブラリなどの機能の入り口となるものである。』

...早い話がコード書き用の「道具の詰まった箱」です。

時刻

私なら、他人の作った関数を検証するよりも
(つまり信用していない)

その時の時刻をキーにして、あれこれいじって
乱数にする

これ、経験的には
けっこうばらつきます。

補足みたいなもの

http://d.hatena.ne.jp/issei_y/20100603/1275551084

我の指摘が「間違っている」とのご意見なので少々書き足しましょう...

「間違っている」理由は「OrderBy()で呼ばれた関数は比較のたびに呼ばれたりしません」であるからとのご指摘ですが...

http://msdn.microsoft.com/en-us/library/bb534966%28v=VS.100%29.aspx

我はしばらくc#を使用していない(もちろんVisualStudioは使いません)ので「OrderBy()」の部分を理解するため.NETのAPIを熟読しました。

最初の結論に達した部分を引用します。

”To order a sequence by the values of the elements themselves, specify the identity function (x => x in Visual C# or Function(x) x in Visual Basic) for keySelector.

...

This method compares keys by using the default comparer Default.”

これによると、”_ => random.Next()”が個別番号を返す関数となりますね。そして”Default”の比較オブジェクトを使用すると明記しています。

ここで我が指摘したいことは「OrderBy()が何回関数を呼び出すかはAPIに明記されていない」...と言う事です。「バッファリングはされてて無駄にrandom.Next()が呼ばれまくるということは無かった気がしますが」...とありますがこれは「現場の実際・実験の結果」であり公式仕様ではない...と言う事です。つまりこの部分は.NETの開発者が「(悪質な副作用でも出ない限り)なにやっても構わない・許される部分」なので環境・バージョン次第で変更されても文句は言えません。

つまり「OrderBy()が関数を呼ぶ回数」は知っても無益な情報であり、ましてやそれを当てしてにコードを書くべきではありません。(多分、オリジナルのコードを書いた人も知らなかったと推測します。)

今回以下の記述が問題なく作動したのは...

   var random = new Random();
   source.OrderBy(_ => random.Next());

OrderBy()の行動が今回の問題に都合の良い実装であったためであり、(当然ありえる)バッファリング無しでのソートでは前記の問題が起きることに異議は無いでしょう。

公式仕様外の「特徴」にたよったコードはその結果が正解を出せたとしても、その恒常性に問題があり(「.NETのバージョンが変わってもコードの行動・結果をAPIによって保障できますか?」)、我にいわせれば「駄目」ですョ...と言う事です。

コメントの表示オプション

お好みの表示方法を選択し、「設定の保存」をクリックすると、表示方法を変更することができます。