2次元配列での要素数の調整(ReDim)について
前回までの記事では、2次元配列の要素数を事前に宣言していましたが、繰り返し計算の結果でしか、要素数が決まらない場合があります。
その場合は、動的配列で宣言し、"Redim"により後から要素数を調整する方法がありますが、注意しなければならない点が有ります。
今回の記事では、その注意点を含めた2次元配列の決まったパターンの取り扱い方について紹介したいと思います。
サンプルのグラフ
下のグラフのように、線と線の間隔が1度づつ増加する線分を1周(360度)内に描くデータを作成してみます。
グラフ
コード
Sub test()
Const pi = 3.14159265358979 '円周率
Const incremental = 1 '角度の分割(度)
Const nRepeat = 35 'データの点数
Const startR = 50 '線分の始点径
Const endR = 70 '線分の終点径
Dim i As Long 'カウンター
Dim div As Double '線分と線分の間隔の角度
Dim theta As Double '角度(度)
Dim rtheta As Double '角度(rad)
Dim circularLine(360, 1) As Variant
'繰り返し計算の初期値設定
div = 0
theta = 0
i = -1
Do
i = i + 1
rtheta = theta * pi / 180
circularLine(3 * i, 0) = startR * Cos(rtheta) '線分の始点X座標
circularLine(3 * i, 1) = startR * Sin(rtheta) '線分の始点Y座標
circularLine(3 * i + 1, 0) = endR * Cos(rtheta) '線分の終点X座標
circularLine(3 * i + 1, 1) = endR * Sin(rtheta) '線分の終点Y座標
div = div + incremental
theta = theta + div
Loop While theta < 360
'シートへの書き込み
With Sheet3
.Range(.Cells(2, 2), .Cells(3 * i + 3, 3)) = circularLine
End With
End Sub
今回のプログラムは繰り返し計算の部分で "Do~Loop While " を使用しており線分の角度(theta)が360度以下の間行われ、配列の要素数はDo~Loopの繰り返し計算を抜けるまで分かりません(今回の事例では計算することは可能ですが。)
そのため、配列の宣言時に、以下の赤字部のように余分な配列の要素数を宣言しています。
Dim circularLine(360, 1) As Variant
この、余分な要素数を削減することを考えます。
不要な要素の削減方法(Redim)とエラー
何故、不要な要素を削減するのかについては、後でお話しするとして、ここでは不要な要素を削減する方法について説明します。
要素数を変更(削減)するためには、"動的配列" というものを使用します。
(最初に要素数を決めて宣言するのは ”静的配列” と呼ばれます)
不要な要素の削減方法(動的配列)
動的配列のコードの書き方は以下のようになります。
Dim 配列名() As データ型 '動的配列として宣言 ReDim 配列名(要素数) '余裕のある配列数に設定しておく '繰り返し計算などで必要な配列数が決まる '不要な要素数を除去 ReDim Preserve 配列名(必要な要素数)
今回のサンプルのコードを動的配列に書き換えるとつぎのようになります。
Dim circularLine() As Variant '動的配列として宣言 ReDim circularLine(100, 1) '余裕のある配列数に設定しておく (途中省略) Do (途中省略) Loop While theta < 360 '不要な要素数を除去 ReDim Preserve circularLine(3 * i + 1, 1)
エラー
しかし、これを実行すると、、、以下のようにエラーとなってしまうのです。
これはなぜかというと、VBAは言語の仕様によりRedimは、2次元以上の配列では最後の次元の要素数しか変更できないからです。
ReDim Preserve 配列名(要素数,要素数) '赤字側の要素数しか変更できない。
2次元配列での動的配列の使い方
この仕様を分かっていれば、以下のように増えていく方の要素を最後の次元になるよう入れ替えると、Redimにより要素数を調整することが出来ます。
circularLine(360,1) ⇒ circularLine(1,360)
ただし、ここで以前の記事を思い出してください。
大量のデータを高速でセルに書き込むためには、以下のコードのようにセルの範囲に配列全体を書き込む必要がありますが、配列の次元を入れ替えると、行方向にデータが増えていくのではなく、列方向にデータが増えていくことになります。
Range(Cells(※,※), Cells(※,※)) = 配列
よって、配列を Redim で要素数を調整した後で、行と列を入れ替えることを考える必要があります。
行と列の入れ替えは、”For~Next" 等のループ処理によって、1つづつの要素を入れ替えることも出来ますが、エクセルのワークシート関数である ”Transpose()” を使用して、以下のように簡単な記述で行うことが出来ます。
Dim 入れ替え後の配列名() As Variant '動的配列として宣言 入れ替え後の配列名 = WorksheetFunction.Transpose(入れ替え前の配列名)
Sub test() Const pi = 3.14159265358979 '円周率 Const incremental = 1 '角度の増分(度) Const nRepeat = 35 'データの点数 Const startR = 50 '線分の始点径 Const endR = 70 '線分の終点径 Dim i As Long 'カウンター Dim div As Double '線分と線分の間隔の角度 Dim theta As Double '角度(度) Dim rtheta As Double '角度(rad) Dim tmpArr() As Variant '動的配列として宣言 ReDim tmpArr(1, 360) '余裕のある配列数に設定しておく '繰り返し計算の初期値設定 div = 0 theta = 0 i = -1 Do i = i + 1 rtheta = theta * pi / 180 tmpArr(0, 3 * i) = startR * Cos(rtheta) '線分の始点X座標 tmpArr(1, 3 * i) = startR * Sin(rtheta) '線分の始点Y座標 tmpArr(0, 3 * i + 1) = endR * Cos(rtheta) '線分の終点X座標 tmpArr(1, 3 * i + 1) = endR * Sin(rtheta) '線分の終点Y座標 div = div + incremental theta = theta + div Loop While theta < 360 '不要な要素数を除去 ReDim Preserve tmpArr(1, 3 * i + 1) '行と列の入れ替え Dim circularLine() As Variant circularLine = WorksheetFunction.Transpose(tmpArr) 'シートへの書き込み With Sheet3 .Range(.Cells(2, 2), .Cells(UBound(circularLine) + 1, 3)) = circularLine End With End Sub
Transpose()関数の注意点
Transpose()関数により、行と列が入れ替えられた配列は、変換前の配列のインデックスが "0" から始まっていても、"1" からインデックスが始まっているので注意が必要です。
なぜ、配列の不要な要素を削減するのか?
ここで、なぜ、配列の不要な要素を削減するのか?という問題に戻ります。
それは、上のサンプルプログラムでは、さらっと使用してしまったのですが、
UBound(配列名)
により、正確な配列の要素数を得たいからです。
正確な要素数を得たい理由は、筆者の使用状況では以下の点が挙げられます。
- データをシートに書き込む際に、Variant型の配列では問題ないが、Double型の配列の場合は余分な配列の部分に "0" の値が張り付いてしまう。
- XY座標の配列のデータの場合、不要な要素を含まないデータでないと、移動・反転・回転・拡大・縮小等の加工を行う時に扱いづらい。
今回の事例では、1つのサブプロシージャ内で配列のデータを作成して、シートに貼り付けているので、正確なデータ数を得ることは用意です。しかし、次の記事でお話しする予定のクラスモジュール等、別のモジュールやプロシージャから配列データを受け取る場合に配列の不要なデータを削減していないと、もう一度データ内の必要な要素数を確認する処理を追加したり、要素数の情報も別の変数で受け渡ししたりする必要が出てきます。
次回の記事では、VBAでのクラスモジュールの使い方についてお話ししたいと思います。