• ベストアンサー

For-Next の結果が正しくない?

Excel2003についてです。 セルC1からC800に、1から4までのいずれかの数値を入れていこうとしています。1の場合は25個、2を150個、3を300個、残りを4とします。 各数値をばらばらに散らしたいのでセルC1からC800を乱数で指定して、もし指定したセルが空白なら数値を入れ、空白でなければセルをもう一度指定しなおす、というマクロを作成しました。 下記のように記述(『残りを4』については省略、行頭の空白は全角です)したのですが、これを実際に走らせて見ると、各数値の指定した(はずの)個数と実際に入った数値の個数が一致しません。 セルをクリアしてもう一度実行してみると前回と個数が違う場合もあります。 VBAについては仕事でたまに触る程度で、まだまだ青二才です。 どうぞアドバイスをよろしくお願いします。 Dim A, B, C, D, n   For A = 1 To 3     Select Case A       Case 1         B = 25       Case 2         B = 150       Case 3         B = 300     End Select     For n = 1 To B       Do         C = Int(Rnd() * 1000) + 1         Cells(1, 1) = C         If C <= 800 Then           D = Cells(C, 3)           If D = "" Then             Cells(C, 3) = A           End If         End If       Loop While C > 800     Next n Next A

質問者が選んだベストアンサー

  • ベストアンサー
  • KenKen_SP
  • ベストアンサー率62% (785/1258)
回答No.1

こんにちは。 この手の問題で、よく使われる方法を2つ紹介しておきます。  # コードの修正はしませんでした。すみません。 Sub SampleProc()      ' // データセット   Range("C1:C25").Value = 1   Range("C26:C175").Value = 2   Range("C176:C475").Value = 3   Range("C476:C800").Value = 4   ' // チェック数式(参考用)   Range("E1:E4").Formula = "=ROW()"   Range("F1:F4").Formula = "=COUNTIF($C$1:$C$800,$E1)"   MsgBox "データセット終了"   ' // 作業列(D列)にソート用乱数を設定し値化   With Range("D1:D800")     .Formula = "=Rand()"     .Value = .Value   End With   MsgBox "ソートキーセット終了"   ' // 作業列をキーにしてソート   Range("C1:D800").Sort Key1:=Range("D1"), _              Order1:=xlAscending, _              Header:=xlGuess   MsgBox "ソート終了"   ' // 作業列   Range("D1:D800").ClearContents   MsgBox "作業列消去" End Sub 作業列を使わないのであれば、重複のない乱数を求める手法が 応用できます。考え方は難しいものではありませんが、中級者 向けの内容かな...? こんな感じ。 Sub SampleProc2()      Dim Src As Variant, i As Long        ' // 配列に要素をセットします   ReDim Src(1 To 800)   For i = 1 To UBound(Src)     Select Case i       Case Is <= 25: Src(i) = 1       Case Is <= 175: Src(i) = 2       Case Is <= 475: Src(i) = 3       Case Else:   Src(i) = 4     End Select   Next   ' // 配列をシャッフルします(例)50000回   Call ShuffleArray(Src, 50000)   ' // 結果をセルに書き出します   Range("C1:C800").Value = Application.Transpose(Src) End Sub Private Sub ShuffleArray(ByRef Src As Variant, ByVal Count As Long)      Dim tmp  As Variant   Dim lLower As Long, lUpper As Long   Dim n1   As Long, n2   As Long   Dim i   As Long      If Not IsArray(Src) Then Exit Sub   lLower = LBound(Src)   lUpper = UBound(Src)   Randomize Now()   For i = 1 To Count     ' // 入れ替える配列の添え字 n1,n2 を乱数で求める     n1 = Int((lUpper - lLower + 1) * Rnd() + lLower)     n2 = Int((lUpper - lLower + 1) * Rnd() + lLower)     ' // 値を入れ替える     tmp = Src(n1)     Src(n1) = Src(n2)     Src(n2) = tmp   Next End Sub

square-one
質問者

お礼

まったく違ったアプローチを示していただき、ありがとうございました。目からうろこが落ちる、というのを実感できました。 特に、1つ目の例を見たときには、自分がえらく複雑な手順を考えていたのがわかりました。 2つ目については、配列、Private Subプロシージャの使用、引数の渡し方など、私にとってこれまでなじみの薄い(というより避けて通ってきたかも)もので、勉強になりました。なんとか、各部分でそれぞれ何をしているのか理解できました。 本当に助かりました。ありがとうございました。

その他の回答 (1)

  • tos_net
  • ベストアンサー率48% (66/137)
回答No.2

セルに数値を代入できたかどうかがLoop Whileを抜ける条件になるので変数xを追加してみました。   Dim A, B, C, D, n, x   For A = 1 To 3     Select Case A       Case 1         B = 25       Case 2         B = 150       Case 3         B = 300     End Select     For n = 1 To B       x = 0  '初期化       C = Int(Rnd() * 800) + 1       Do         If Cells(C, 3) = "" Then           Cells(C, 3) = A           x = 1  '数値代入完了         Else           C = Int(Rnd() * 800) + 1  '代入できなかったのでランダム値取り直し         End If       Loop While x = 0     Next n   Next A

square-one
質問者

補足

解決策を示していただき、ありがとうございました。 2つ目のFor文で、変数Bまでまわすので、その中の結果がどうであれ Aの値がB個代入されていくはず、と考えていたのですが、 その中のif文で条件に当てはまらない場合の処置をしていないために 代入せずにそのまま Next n へ進んで次の n+1 のループに入ってしまった、という理解でいいでしょうか?

関連するQ&A