速度か、美しさか。
まだまだ人格[1]で生活中のZerOxです。テスト前なのに授業よりプログラムが気になる・・・。
今日は確率リストなるものを書き直ししていました。
オブジェクトをその確率とのペアにしてリストに登録し、Getな感じのメソッドでその確率にしたがってランダムなオブジェクトを取得するためのリストです。ゲームに便利。
書き直しというのは、実は以前から実装はされていたんですけど、なんとコードを読むと超絶な手抜きコード。確率は当然0以上なので型はuintを使用していたのに、Getメソッド内での乱数を取得する処理はintの範囲しか処理してない!!!
そりゃあ範囲がuintまで行ってしまうような壮大なゲームは作れるかどうかといえば疑問ですけど!疑問ですけど!(涙目
とりあえずそこをメインに書き直そうと思ったわけです。とりあえずこの確率リストの大体の処理の流れを書くと・・・。
- リスト内の全項目の確率の和を求める(total)
- 0からtotalの範囲の乱数を取得(random)
- とりあえずtotalはもう要らないので0を代入
- totalに各項目の確率を加算し、randomを上回ったときの項目を返す
というものです。(total使いまわしは・・・まぁ小さいし、命名困るから良いよね。
ちなみに手抜きコードでは最初の時点でtotalの型がintなので、uintの範囲にはみ出したらオーバーフローしてるところでした。させるような壮大なゲームを作れればですけどね。クソッ!
ひとまずはtotalやrandomの型をuintに。当然こうすると乱数取得部分を改良する必要が出てきます。負の値は使わないので無視できますが、問題はuintの範囲に及んだ場合です。(壮大なうんたらかんたら。
ここからが本題。つまりはintを返す乱数関数を使ってuintの範囲を取得するコードを書くことが目標となるわけです。
普通に考えて乱数を2種類取得して足してやればどうにかなりそうです。(ただしintの正の部分の2倍はuintの最大値より1小さいので、その辺も考慮すべき。
そういうわけで書いてみたのですが、ifがいっぱいで気持ち悪い。しかもすることは乱数取得のみなので、同じような文がたくさんのifの中に散らばって背中がかゆい!わあああああ。
別の書き方を考えることにしました。(ちなみにこの書き方だと10行くらい。
今度は別のアプローチで、最初にuintの範囲の乱数を取得しておいてから、リスト内の確率の和(total)との剰余を取得するというもの。こっちは簡潔なのでコードを載せておきますね。
random = (uint)(Random.Get(int.MaxValue) * 2 + Random.Get(2)); random %= total;
Random.Getというのは自作(と言ってもSystem.Randomを使ってるだけ)の静的クラスです。乱数取得部分だけなら2行で済んでしまいました。
しかし、このコードにも心配なところが1つあって、2行目の剰余の計算がどうも重そうということです。割り算って重いって言いますよね!
ひとまず先入観とかでは不十分なので実際に計測してみました。両者のコードを10000000*100回ループさせてみました。最初の10回の表を載せておきます。(単位はミリ秒です。
前者 | 後者 | |
---|---|---|
531 | 766 | |
547 | 766 | |
531 | 781 | |
282 | 750 | |
297 | 765 | |
312 | 766 | |
281 | 766 | |
562 | 766 | |
578 | 766 | |
281 | 750 | |
100回の平均 | 435.19 | 765.6 |
100回の最大 | 594 | 782 |
やっぱりね☆
後者は同じ処理しかしていないので、安定して遅いです。前者は分岐しているのでバラつきがありますが、後者より遅くなることはありませんでした。
まぁ、もっと深刻なプログラムなら前者をもっと改良して採用するところかもしれませんけど、正直10000000回もループすることは考えられないし、最近はパコソンが高性能で気にならない気もしますので、美しさをとって後者を採用することにしました。
いやぁ、明日も他のコードが頭にこびり付いて授業どころじゃないんだろうなぁ・・・。(にこにこ