乱数を格納した配列を作るFunction
文字をランダムに並べ替える
乱数を作るのはめんどくさい
ランダムに並べ替えるという作業をするときには、乱数を発生させて使えば良いのだが、毎度毎度乱数を発生させる処理を書くのは正直メンドクサイ。
最大数を与えたら、1~最大数をランダムに並べて配列にぶち込んでくれるような関数でもあれば、その配列を0~最大数マイナス1の順で呼び出してコレクションのインデックスにすることによって、コレクションをランダムに並べ替えて出力することが可能になると考えた。
ランダムに並べ替えて配列にぶち込むFunction
リスト1 標準モジュール
Public Function createRandomArray( _ ByVal maxNum As Integer, _ ByVal allowDuplicate As Boolean) _ As Variant '……(1)' Dim flg() As Boolean ReDim flg(maxNum - 1) '……(2)' Dim i As Integer Dim retArray() As Integer '……(3)' ReDim retArray(maxNum - 1) Randomize Dim tmp As Integer For i = 0 To maxNum - 1 Do tmp = Int(maxNum * Rnd + 1) '……(4)' '///乱数:Int((最大値 - 最小値 + 1) * Rnd + 最小値)' Loop Until flg(tmp - 1) = False '……(5)' retArray(i) = tmp '……(6)' If Not allowDuplicate Then flg(tmp - 1) = True '……(7)' Next createRandomArray = retArray '……(8)' End Function
ちょっとめんどくさいけれど、自分の備忘のためにも説明を書いておく。
(1)の
Public Function createRandomArray( _ ByVal maxNum As Integer, _ ByVal allowDuplicate As Boolean) _ As Variant
引数maxNumは最大数。たとえば、こいつを10にしたら、1~10までをランダムに取り出して要素数10の配列にぶち込んでいくということ。
引数allowDuplicateは、番号の重複を許可するかどうか。Trueにすると重複無しで配列を作成。Falseにすると重複ありで配列を作成することになる。Falseを指定する場面があるのかどうかは不明。
返り値の型はVariantにした。最初Integerにしていたんだけれど、「型が一致しません」エラーが出て対応策が分からなかったので。
(2)の
ReDim flg(maxNum - 1)
では、引数で渡されたmaxNumを用いて配列変数flgをRedimしている。はじめから
Dim flg(maxNum - 1) As Boolean
でうまく行きそうなもんだが、こうすると「定数式が必要です」エラーが出る。
(3)の
Dim retArray() As Integer
は、返り値用の配列変数。createRandomArrayを配列みたいにして直接値をぶち込んで行くことができないので、一旦配列を作っておいて、完成後この配列を返す、という形を取る。
(4)の
tmp = Int(maxNum * Rnd + 1)
で、一旦1~maxNumの範囲の整数をランダムに生み出して変数tmpに格納する。
(5)の
Loop Until flg(tmp - 1) = False
でDo~Loopの終了条件を指定している。
たとえば、tmpに10が入っているとすると、flg(10-1)、すなわち配列変数flg()の10番目の要素がfalseだったらループを抜けるということ。
ループを抜けると、(6)の
retArray(i) = tmp
で配列変数retArrayにtmpの値をぶち込み、(7)の
If Not allowDuplicate Then flg(tmp - 1) = True
で、重複を許可しない場合に限ってflg(tmp - 1)、すなわちtmpが10の場合は配列変数flg()の10番目をTrueに変える。
こうすることで、この後仮に(4)で10がtmpに代入されたとしても、(5)のループ終了条件を満たさなくなる。すなわち、この後10が配列変数retArrayの要素になることはないということ。
こうして、Forループが終了すると配列変数retArrayには1~maxNumまでの整数がランダムにぶち込まれていることになるので、後は(8)の
createRandomArray = retArray
で配列retArrayを返しておしまい。
実行
標準モジュールに下記のコードを書いて実行してみる。
リスト2 標準モジュール
Public Sub test() Dim a As Variant a = createRandomArray(10, False) Dim i As Integer For i = 0 To 9 Debug.Print a(i) Next End Sub
この通り、無事に1~10が重複無しのランダムに並んでいる。
選択範囲の単語をランダムに並べ替える
自作Function「createRandomArray」を利用して、選択範囲の単語をランダムに並べ替えるマクロを作ってみる。
リスト3 標準モジュール
Public Sub randomSortByWord() Dim num As Long Dim wordsArray() As String With Selection num = .Words.Count '……(1)' ReDim wordsArray(num - 1) '……(2)' Dim i As Integer For i = 0 To num - 1 '……(3)' wordsArray(i) = .Words(i + 1) Next End With Dim wordsOrder As Variant wordsOrder = createRandomArray(num, False) '……(4)' Dim str As String For i = 0 To num - 1 '……(5)' str = str & wordsArray(wordsOrder(i) - 1) Next Selection.TypeText Text:=str '……(6)' End Sub
(1)の
num = .Words.Count
では、変数numにSelectionオブジェクト(この場合は選択範囲)のWordsコレクションのCountプロパティを参照することで選択範囲の「単語数」を取得し、変数numにぶち込んでいる。
(2)では、(1)で得られた単語数をもとに配列変数wordsArray()をRedim。
(3)からの3行
For i = 0 To num - 1 '……(3)' wordsArray(i) = .Words(i + 1) Next
で、一旦選択範囲の各単語を配列に格納。配列のインデックスは0から始まるけれど、Wordsコレクションのインデックスは1から始まるので、Wordsコレクションのインデックスのところは「i + 1」になる。
(4)の
wordsOrder = createRandomArray(num, False)
では、単語を取り出す順番を格納する配列変数wordsOrderにcreateRandomArray関数の返り値を格納。
これで配列変数wordsOrderには1~単語数のそれぞれの数字がランダムな順番で格納されることになる。
(5)からの3行
For i = 0 To num - 1 '……(5)' str = str & wordsArray(wordsOrder(i) - 1) Next
では、変数strに配列変数wordsArrayに格納されている単語を1つづつ配列変数wordsOrderの要素で指定して取り出し、連結していく。
Forループが終了した時点で、strには、単語をランダムに並べ替えた文字列が完成していることになる。
んで最後に(6)の
Selection.TypeText Text:=str
で、SelectionオブジェクトのTypeTextメソッドを用いてstrに格納された文字列を書き込んでおしまい。
SelectionオブジェクトのTypeTextメソッドは、文字列が選択された状態で実行すると、選択範囲を引数Textで指定された文字列で上書きする。
実行結果
こんなふうに文字列を選択状態にして実行すると、
このようになる。
悲しいかな、「単語」と言っても、「Wordが認識する単語」に過ぎず、結果はめちゃくちゃである。
おわりに
今回はFunctionでやってみたが、Functionの返り値がVariant型になってしまったり、その結果、呼び出す側でも変数をVariantにしないといけないというのは何ともブサイクなので、イマイチだなあと思ってしまう。
クラスにした方がキレイに書けるかも知れない。
相変わらず使い道があるのかどうかはよく分からないw
追記
例によってid:imihito さんからアドヴァイスをいただいた。
返り値の型を
`As Integer()`
のように後ろに丸括弧を付けた形にすることで指定した型の配列を返すことができます。
とのこと。
早速、上掲のリスト1とリスト2を、次のように書き換えてみた。
リスト1改 標準モジュール
Public Function createRandomArray( _ ByVal maxNum As Integer, _ ByVal allowDuplicate As Boolean) _ As Integer() '……(*)' Dim flg() As Boolean ReDim flg(maxNum - 1) Dim i As Integer Dim retArray() As Integer ReDim retArray(maxNum - 1) Randomize Dim tmp As Integer For i = 0 To maxNum - 1 Do tmp = Int(maxNum * Rnd + 1) '///乱数:Int((最大値 - 最小値 + 1) * Rnd + 最小値)' Loop Until flg(tmp - 1) = False retArray(i) = tmp If Not allowDuplicate Then flg(tmp - 1) = True Next createRandomArray = retArray End Function
(*)のところで、返り値の型指定を
As Integer()
にしました。
リスト2改 標準モジュール
Public Sub randomSortByWord() Dim num As Long Dim wordsArray() As String With Selection num = .Words.Count ReDim wordsArray(num - 1) Dim i As Integer For i = 0 To num - 1 wordsArray(i) = .Words(i + 1) Next End With Dim wordsOrder() As Integer '……(*)' wordsOrder = createRandomArray(num, False) Dim str As String For i = 0 To num - 1 str = str & wordsArray(wordsOrder(i) - 1) Next Selection.TypeText Text:=str End Sub
こちらも(*)の部分、createRandomArrayの返り値を受け取る変数wordsOrderの宣言を
Dim wordsOrder() As Integer
このようにIntegerの配列型にしました。
結果
無事、実行できました。
毎度毎度のことながら、id:imihito さん、
ありがとうございました!!!!!!!!(*´∀`)