- ベストアンサー
Rnd関数について
今Rnd関数を使って問題を作ろうとしているのですが, 40問中から重複なしに20問ランダムに出題するというものを作ろうとしています。 しかし,重複を無くすためのプログラムの仕方がわかりません。 ついこの間,VBを始めたばかり(プログラムはVBが始めて)なので, できれば詳しい解説を書いてほしいです。 今現在考えているのはこんなのです↓ Private Function RandomCnt() As Long '********************************* '* ランダムで問題の番号を取得 * '********************************* '一時的にランダムで取得した問題番号を格納 Dim RandomNo As Long RandomNo = Int((MaxRec * Rnd) + 1) 'ランダムで番号を取得 Do While volQuizNo(RandomNo) = True 'まだ出していない問題が見つかる間 RandomNo = Int((MaxRec * Rnd) + 1) 'ランダムで番号を取得 Loop volQuizNo(RandomNo) = True '出題問題のチェック RandomCnt = RandomNo End Function
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
先ほどの方法でコードを書いてみました。 1.配列を作ってそこにシリアル番号(順番)を入れます。この配列サイズは問題数です。(TanakaShinyaさんの例にもあります) 2.次にこのレコードの中身をランダムに並べ替えます。簡単な方法として、インデックス(添字)の数値と「インデックス+1」を入れ替えることを何回か行うことによって並び替えを行います。 3.後はその配列から順番にデータを取得する。 これをコードに直すと、 Option Explicit Const DATA_MAX As Integer = 100 ' データ数 Const LOOP_MAX As Integer = 10000 ' 並べ替え用ループ数 Private s_nDataRecord(DATA_MAX) As Integer ' レコードインデックス(ランダム値) Private s_nDataIndex As Integer ' レコードインデックスを取得するインデックス Public Sub SetData() Dim nCnt As Integer ' 汎用カウンタ Dim nRnd As Integer ' ランダムな数値用 Dim nTemp As Integer ' 並べ替え時の退避用 ' シリアル番号を設定 For nCnt = 1 To DATA_MAX s_nDataRecord(nCnt) = nCnt Next nCnt ' ランダムに並べ替え Randomize For nCnt = 1 To LOOP_MAX nRnd = Int(DATA_MAX * Rnd) nTemp = s_nDataRecord(nRnd) s_nDataRecord(nRnd) = s_nDataRecord(nRnd + 1) s_nDataRecord(nRnd + 1) = nTemp Next nCnt ' 取得するインデックスの初期化 s_nDataIndex = 1 End Sub Public Function GetData() As Integer Dim nReturn As Integer ' 戻り値 ' すべてのデータを取得し終わっていたらもう一度並べ替え If s_nDataIndex > DATA_MAX Then Call SetData End If ' データ取得 nReturn = s_nDataRecord(s_nDataIndex) ' インデックスを次へ s_nDataIndex = s_nDataIndex + 1 ' 戻り値 GetData = nReturn End Function という感じになります。 わかりやすくするために、インデックスデータをモジュール変数にしています。(また、このサイトの制限でしょうが、インデントが全て無くなっています。(^_^;) これを標準モジュールに組み込み、最初にCall SetDataして、あとは、GetDataを呼び出すとレコード番号が返されます。 注意としては、SetDataを呼び出さずにGetDataを呼んではいけないということです。 なお、ここでは問題数を Integerの範囲内としていますが、適当に Long等に変更してください。変更する場合は、すべての Dim宣言を変更しましょう。 このままのコードを使わずに、理解して自分のものにして使用しましょうね。 ここに書かれてあるキーワードはヘルプで検索できると思うので、詳しく見てみましょうね。 思ったより簡単でしょ?
その他の回答 (3)
- KojiS
- ベストアンサー率46% (145/312)
ちょっと一部間違っていました。(^_^; 並べ替えの時に配列のインデックスを1からにしているのに0が混じっちゃ駄目ですよね。 誤: nRnd = Int(DATA_MAX * Rnd) 正: nRnd = Int((DATA_MAX - 1) * Rnd) + 1
お礼
おっと,訂正ですか。 実はこの間違いはわかりました。 実は数値をひとつずつに入れていって,計算してみてこのようにすれば うまく0を出さなくなるということがわかりました。 ところでコンピューターって人の頭脳の何倍の処理能力があるんでしょうか? 左脳の働きで比較してみると?この数値代入法?で確認しているときに そんなことを考えながら自分の脳って遅いなーと思いました。
- TanakaShinya
- ベストアンサー率63% (7/11)
私のよく使うアルゴリズムは次のようなものです。 まず、問題番号のIndexを入れたテーブルを作成します。 Const c_MaxQuizNum = 40 ' 全問題数 Const c_QuizNum = 20 ' 出題数 Dim QuizIndex(c_MaxQuizNum) As Integer ' 問題の Index 格納用 Dim QuizLastNum as Integer ' 問題の残り数 Dim TmpRndm As Integer ' 選択された値 Dim RandomNo As Integer ' 選択された問題番号 Dim i As Integer For i = 1 To c_MaxQuizNum QuizIndex(i) = i '初期値を格納 Next これを利用し、重複しない問題番号を取り出すことが可能です。 QuizLastNum = c_MaxQuizNum ' 問題の残り数初期化 For i = 1 To c_QuizNum TmpRndm = Int((QuizLastNum * Rnd) + 1) ' 1~QuizLastNum の乱数 RandomNo = QuizIndex(TmpRndm) ' 問題番号を取得 QuizIndex(TmpRndm) = QuizIndex(QuizLastNum) ' 問題の選択肢をつめる QuizLastNum = QuizLastNum - 1 ' 問題の残り数を減らす ' 以降、ユーザ処理 Next つまり、簡単な例として c_MaxQuizNum = 5 としたとき、 QuizIndex には、 1 , 2 , 3 , 4 , 5 が順に入っています。 QuizLastNum には c_MaxQuizNum = 5 が入っていますね。 ここで、出題用の For ループ処理を始めると TmpRndm は 1~5 のいずれかの整数が入ります。 よって、RandomNo には 1 , 2 , 3 , 4 , 5 のいずれかが選ばれるわけです。 そして、ここからがミソなのですが、今仮に TmpRndm に 3 が選ばれるとします。 QuizIndex(TmpRndm) = QuizIndex(QuizLastNum) の処理により、QuizIndex には、 1 , 2 , 5 , 4 , 5 が入るわけです。 そして、QuizLastNum の値を 1 減らします。 すると 2 回目の For ループは どうなるでしょうか? TmpRndm は QuizLastNum を減らしたため 1~4 のいずれかの整数が入ります。 よって、RandomNo には 1 , 2 , 5 , 4 のいずれかが選ばれ、 先ほど出題した 3 は選択されません。 重複を無くし、かつ効率の良い方法でしょう? このアルゴリズムおわかりいただけましたでしょうか?
お礼
どうもありがとうございました。 いろいろなエラーもKojiSさんのアルゴリズムと見比べてみて 両方のアルゴリズムともにうまくいくようになりました。 話は違うんですが,なんかアルゴリズムって響きがいいですよね? TanakaShinyaさんに聞いてなんか気に入ってよく使っています。
補足
プログラムの意味はよくわかりました。 でも,「以降ユーザー処理」をどのようにしたらいいのかわかりません。 それと,RandomNoをどのように使ったらいいのかもわかりません。 まだ,配列もわからない状態で,やれ「インデックス~」とか「定数式がひつようです」とかエラーが大量に出てくる始末です。 何とか,自分で工夫してRandomNoのところをQuizrecord(20)とかいう配列にして QuizIndex(TmpRndm) から来るランダムな数値を格納して20までループして Quizrecord(20)を問題の順序に使用かとしたのですが,同じ乱数が同じ順序で出てきます。(8,9,7,5,2とか,これが何度も出る。)そのあたりもちょっとわかりにくい説明ですが,アドバイスいただけないでしょうか。お願いします。
- KojiS
- ベストアンサー率46% (145/312)
シリアル番号の配列を作って、それをランダムに並べ替えして、順番にその番号を取得するのが確実かつ簡単だと思います。
お礼
お返事ありがとうございます。 この方法は「TanakaShinya」さんと同じ方法のようですね。 自分から,ランダムに番号を作るんじゃなくて並べ替えるという方法が とてもわかりやすいです。 一日かけて重複なしのランダム関数を作ろうとしていたのですが, ついにわからずじまいでした。もっと勉強しなくちゃ。 こういう風な直接な回答じゃなくてもどんな風に考えるかという回答でも とても助かります。
お礼
どうも,お返事ありがとうございます。 最初見たときLOOP_MAX=10000ってなんだ?と思ったんですが, 読んでから,わかりました。どうやら,TanakaShinyaさんとは 少し違うようですね。今教えてもらえたのは,数値を選んでそれを前後 並べ替えという方法ですよね。確かに,これだと絶対に重複はなくなります。 こんなアルゴリズム自分ではまったく思いつきません。 ところで,最近プログラムが楽しくて仕方がありません。 もう一日中でもプログラムのことが頭から離れません。 こんな風なアルゴリズムのことを考えるだけで楽しくて仕方ありません。 そんな私ってちょっとおかしい? なにはともあれ,ご教授ありがとうございました。 また,何か会ったらお願いします。