Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(指定なし : 指定なし)
ダブらない乱数
投稿日時: 23/04/28 20:07:38
投稿者: がんばりやさん

配列の勉強を始めました。
二重にする配列を教えて下さい。
 
やりたいこと
A列にダブらない1から100までの数値を出力したい
 
やったこと
VBAでA1からA100に0から1までの乱数を入れました。
B列にRank関数を入れました。
A列に入れた数値は格納させることに成功しました。
B列に入れたものを格納させる事は出来ませんでした。
二重目の配列変数でエラーになります。
配列の次元が一致していません というエラーです。
 
いろいろやっても動いてくれません
これは二次元配列というのでしょうか
下に書いた内容を修正お願いします。
 
Sub 重複しない乱数()
 
'Dim ransuu(0 To 1) As Double
Dim ranran(1 To 100, 1 To 2) As Integer
'Dim ranran(100, 2) As Integer
Dim ransuu(1 To 100) As Double
'Dim ranran(1 To 100) As Integer
Dim i As Integer
 
    Randomize '乱数が毎回同じ数字から始まるのを防止
 
For i = 1 To 100
 ransuu(i) = Rnd '小数点以下の数値をransuu(i)に格納
Next i
                    Stop
For i = 1 To 100 'ransuu(i) が実際に格納されていたのか確認
 Cells(i, 1) = ransuu(i)
Next i
                    Stop
    'Rank関数を使用すれば乱数の重複表示を防げるのでは
     
For ii = 1 To 100 ' Rank関数が動いてくれるのか確認
 Cells(ii, 2) = WorksheetFunction.Rank(Cells(ii, 1), Range(Cells(1, 1), Cells(100, 1)))
Next ii
                    Stop
       '上のRank関数でB列に出力した数値も格納できないだろうか
For i = 1 To 100
ranran(i) = WorksheetFunction.Rank(ransuu(i), Range(ransuu(1, 100)))
Next i
  '変数名(行のインデクス番号, 列のインデクス番号) = 格納する値
   '***配列の次元が一致していません*** というエラーが出る
 
End Sub
 
よろしくお願い致します。

回答
投稿日時: 23/04/28 21:04:59
投稿者: WinArrow

>Rank関数を使用すれば乱数の重複表示
 
重複チェックは、Dictionaryを使いましょう。

投稿日時: 23/04/28 21:35:57
投稿者: がんばりやさん

回答ありがとうございます。
 
すみません
説明が不足しておりました。
 
最初の乱数というのは小数点以下で出しました。
これを格納することが出来ましたが
これにランクを付けた数値をダブらない乱数ということにしました。
このランクを付けた数値を格納しようとしても、やりかたがわからずうまく出来ません。
この方法を教えて戴けませんでしょうか。

回答
投稿日時: 23/04/28 21:42:23
投稿者: simple

    For i = 1 To 100
        ranran(i) = WorksheetFunction.Rank(ransuu(i), Range(ransuu(1, 100)))
    Next i
この部分についてメモしておきます。参考にしてください。
(1)
Range(ransuu(1, 100)) というRangeオブジェクトの指定方法は認められません。
・ransuuは一次元配列なのに、ransuu(1, 100)という二次元配列的な指定は不可
・さらに、Rangeオブジェクトの指定方法としても間違いです。
(2)
ワークシート関数 Rankのヘルプによれば、
第二引数は、
「数値の範囲の配列またはその範囲への参照を指定します。」とありますが、
前段の"数値の範囲の配列"というのが意味不明です。
 
実際に、配列で確認しましたが、「オブジェクトが必要です」というエラーになります。
二次元でも一次元でもエラーとなります。
したがって、セル範囲の参照(つまり、A1:A100といったもの)しか使えないと思います。
あなたが最初にされた方法です。仕様ではないでしょうか。
 
【余談】(以下、スキップ下さい)
VBAの乱数ジェネレータRnd関数の周期は、16,777,216だったと思います。
(16,777,216回取り出して初めて同じものが出てきて、あとはそれを周期的に繰り返します。)
100個程度の一様乱数の数値を取り出して、
同じものが出てくることはあり得ません。
 
なお、ワークシートで使っている乱数RAND()は、これとは異なりさらにもっと長周期です。
作成方法は長らく開示されてきませんでしたが、最近、メルセンヌツイスタという
日本人が開発した乱数ジェネレータに変更されたはずです。
(その他のプログラム言語でもこれが標準として使われることが多かったのですが、
  最近は、長周期である性質よりも、速度に重点が置かれ、別の乱数ジェネレータに
  変わりつつあります。)

回答
投稿日時: 23/04/29 00:34:49
投稿者: hatena
投稿者のウェブサイトに移動

ご希望のことが、

引用:
A列にダブらない1から100までの数値を出力したい

であるなら、
Rank関数でできてますね。
提示の関数ではB列に出力してますが。
 
100個程度ならダブることはないというのはsimpleさんから回答がありますね。
 
シート上に出力したいなら下記のような方法もよく使われます。
 
A列に1から100までの連続数を出力。
B列に乱数を出力。
B列でソートする。
 
シートは使わずに配列で結果が欲しいということなら、1から100までの連続数を配列に格納して、それをシャッフル(ランダムな並べ替え)すればいいでしょう。
シャッフルのアルゴリズムは下記が有名です。
 
フィッシャー–イェーツのシャッフル - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%83%E3%82%B7%E3%83%A3%E3%83%BC%E2%80%93%E3%82%A4%E3%82%A7%E3%83%BC%E3%83%84%E3%81%AE%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB
 
コード例
 
'配列をシャッフルする関数
Public Sub AryShuffle(ByRef MyAry)
    Dim i As Long, buf, LB As Long, P As Long    
    If Not IsArray(MyAry) Then Exit Sub
     
    Randomize
    LB = LBound(MyAry)
    For i = UBound(MyAry) To LB + 1 Step -1
        P = Int((i + 1) * Rnd) + LB
        buf = MyAry(P)
        MyAry(P) = MyAry(i)
        MyAry(i) = buf
    Next
End Sub

Sub Test()
    Dim ranran(1 To 100) As Long
    Dim i As Integer
    
    For i = 1 To 100 '1から100までの連続数を配列に格納
        ranran(i) = i
    Next
    
    AryShuffle ranran '配列をシャッフル
    
    For i = 1 To 100 '結果をA列に出力
        Cells(i, 1) = ranran(i)
    Next
End Sub

投稿日時: 23/04/29 08:40:16
投稿者: がんばりやさん

 
 simple さん
 
お礼が遅くなりすみません。
昨日に回答いただいたのに、高齢ですのでもう寝てしまいました。
私がやりたかったことをよく理解して頂きありがとうございました。
 
Rank関数を使用しなくとも重複は、まずありえません。という丁寧な説明をありがとうございます。
こんなに深いところまでの知識を持っておられるのには驚きです。
 
乱数の重複を体験したのは Randomize を知る前でした。
それも VBA ではなくセルに直接 =INT(RAND()*100+1)と書いたためでした。
B列を作業列としてRank関数を使用したら出来ましたので、私の知識が少ないのに
今度は作業列を使わずにやってみよう
としてうまくいかずエラーの連発でした。
作業列を使わずに、この方法でやることは出来ない
という事を理解させていただきありがとうございました。
 
配列を勉強し始めたばかりで
これは二次元になるのかなとウロウロしていました。
小数点以下の数値を格納するのと、次に整数の数値を格納するという
2種類の数値の配列変数を格納させることが出来ないものかとやっていました。
私の脳のレベルでは1種類の変数を格納させるのが精一杯だと分かりました。
どうもありがとうございました。
 

投稿日時: 23/04/29 09:50:28
投稿者: がんばりやさん

hatena さん
 
参考サイトは難しいですが、これを私に分かり易いように
コードにして戴き、ありがとうございました。
書いていただいたコードをひとつづつ送っていくと
理解出来そうな気がします。
理解するまでにかなり時間がかかりそうですが
せっかく書いていただきましたので
そのお礼にこたえるためにも頑張って理解しようと思います。
作業列を使わない方法がやりたかったので嬉しく思います。
どうもありがとうございました。
 
 
この歳になって百人一首を始めました。
何とか覚えかけたところですが
かなり、あやふやです。
順番が変わっても覚えられるようにしたかったので
 
A列は  百人一首につけられている番号
B列は  頭の5文字
C列は  次の7文字
D列は  後の5文字
E列は  下の句
F列は  詠み人
 
各セルには Vlookup Left Mid Right
などを入れています。
 
A列の番号が動いたら以降の各列が自動的に動きます。
 
ですので、A列しか さわることが出来ません。
 
このたびはどうもありがとうございました。
大変感謝いたします。

回答
投稿日時: 23/04/29 11:27:03
投稿者: simple

補足です。
(1)
ワークシートのRANK関数の第二引数に配列が使えない(ヘルプでは使えるような記述あり)と
いうのはExcel2003頃からの伝統のようです。私の手元の2019でも同様の状況です。
ただし、Excel365では未確認です。(GoogleSpreadSheetでは第二引数に配列定数が使えたので、
ひょっとして使えるかも。ただし、間違ったヘルプ記述というお家芸が続いているような気もします。)
(2)
ワークシート上の作業領域を使わずに、1〜100をシャッフルしたい、とのことですが、
やはり私は、作業領域を使用する案を推奨します。
簡単なことは簡単にすます、が私のポリシーです。
既に指摘があり、またご本人も認識されているようですが、
・A列に1〜100を入れ
・B列に =RAND() を入れ、
・A、B列を対象として、B列をキーにソートする
というのが簡便でしょう。
別シートなどで作業して、結果だけを使用箇所にコピーすれば邪魔にはなりません。
(3)
どうしても、作業領域はヤダということなら、hatenaさんの案に加えて、
.NetのSortedListを使う方法もあります。
・SortedListは、keyとvalueのペアを保持して、
 しかも、keyでその都度ソートした状態で保持してくれる、というものです。
・乱数をkey、連番をvalueにしてセットして、
・これをgetbyindexを使って順番に取り出せば、
 結果的にシャッフルした状態の連番が得られます。

Sub test()
    Dim sl As Object
    Dim k As Long
    Dim j As Long
    Dim ary(1 To 100) As Long

    Set sl = CreateObject("System.Collections.SortedList")

    For k = 1 To 100
        sl.Add Rnd(), k
    Next
    
    For j = 1 To 100
        ary(j) = sl.getbyindex(j - 1) 'sortedListは0から始まる
    Next
    
    Range("A1").Resize(100, 1) = Application.Transpose(ary)
End Sub
(4)
ちなみに、ワークシートのRAND関数にMersenne twisterを導入したのは
Excel2010からでした。最近じゃなかったですな。
周期は、2の19937乗-1 (≒4.315×10の6001乗)というむやみに長い周期です。

投稿日時: 23/04/29 20:34:32
投稿者: がんばりやさん

 
 
hatena さん
 
難しいプログラムを作って頂きありがとうございます。
 
Sub Test() の方は理解出来ますが
Public Sub の方はかなり厳しいです。
 
Sub Test() の中間位置に AryShuffle がありますので
Public Sub の中でシャッフル処理して
参照渡しをするように作られている(ByRef MyAry)
 
If Not IsArray(MyAry) Then Exit Sub
もし(MyAry)が配列でない場合は ここから脱出しなさい
 
Randomize ランダム処理の初期化
 
 LB = LBound(MyAry) 最小値を取得
 
For i = UBound(MyAry) To LB + 1 Step -1
大きい値から小さい値へと逆のループを作っている
 
P = Int((i + 1) * Rnd) + LB
ランダムな整数を取得しているが
これは難しすぎます。
 
buf = MyAry(P)
MyAry(P) = MyAry(i)
MyAry(i) = buf
 
MyAry(P) と MyAry(i)を入れ替えているのかな
 
 
自分の想像ですがこんな理解でよいのか書いてみましたが
間違っているところを教えて戴けませんでしょうか。
 

投稿日時: 23/04/29 20:52:39
投稿者: がんばりやさん

 
 
simple さん
 
丁寧な補足説明をありがとうございます。
 
バージョンは今まで気にせずに使っていましたが
Excel2013 でした。もうこの歳ですので買い換える予定はありません。
 
私のポリシーも同じです。ただ、頭が固すぎて・・・
書いてくれたら。。なあんだ でした。
 
+を押して新しいシートに作って、どこの列でもええから乱数を作成して
これをコピーして、百人一首のA列に貼り付ければええことじゃんか。
A列以外は触ったらアカンという気持ちが強すぎて
こんな簡単な事に気が付かなかったとは  ザンネンです。
新しい Sheet なら少々間違ったプログラムを書いても怖くないですね。
 
NetのSortedListを使う方法もあります。ですが
 
せっかくプログラムを書いていただいたのですが
今の私の頭脳ではとてもじゃないですが壊れてしまいそうです。
 
99.99%重複しません。という説明ありがとうございます。
 

回答
投稿日時: 23/04/30 10:08:36
投稿者: hatena
投稿者のウェブサイトに移動

引用:
自分の想像ですがこんな理解でよいのか書いてみましたが
間違っているところを教えて戴けませんでしょうか。

 
おおまかには、そんな感じの理解でいいと思います。
 
いちおうAryShuffleに処理の説明のコメントを追加しました。
 
'配列をシャッフルする関数
Public Sub AryShuffle(ByRef MyAry)
    Dim i As Long, buf, LB As Long, P As Long    
    If Not IsArray(MyAry) Then Exit Sub
     
    Randomize
    LB = LBound(MyAry) '配列の先頭のインデックスを取得
    '配列の最後から先頭の一つ前までループする
    For i = UBound(MyAry) To LB + 1 Step -1
        '先頭インデックスからiまでの範囲でランダムな整数を取得
        P = Int((i + 1) * Rnd) + LB
        '以下pとiの位置の要素を入れ替える
        buf = MyAry(P)
        MyAry(P) = MyAry(i)
        MyAry(i) = buf
    Next
End Sub

 
全体としてやっていることの意味は、前回紹介したリンク先の「現在のアルゴリズム」に表をつかった解説があるのでそこをみてください。
https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%83%E3%82%B7%E3%83%A3%E3%83%BC%E2%80%93%E3%82%A4%E3%82%A7%E3%83%BC%E3%83%84%E3%81%AE%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB#%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
 
 
私自身も上記のようなコードを何もみずに一から自分で作ることはできませんが、このようなよくあるものはアルゴリズムとして昔から研究されて公開されているので、概要が理解できればあとはブラックボックスとして積極的に利用していけばいいとと思ってます。
 
「簡単なことは簡単にすます」というのは私も同じポリシーです。
ただ、何が簡単かというのはその人のスキルや好みによります。
私の場合、要素をランダムに並べ替える(シャッフルする)ということはよくあるので、関数をひとつ作成しておけば、あとは
AryShuffle 配列
と1行記述するだけですむので、私にとっては簡単かなということです。

投稿日時: 23/04/30 12:26:10
投稿者: がんばりやさん

 
hatena さん
 
お忙しいところを丁寧に詳しい説明ありがとうございました。
 
これは何かの時に使えそうです。
 
大切にしまっておきます。
 
この度は、どうもありがとうございました。