LoginSignup
4
4

VBA Collection を自作して処理速度を向上してみた

Last updated at Posted at 2023-06-03

はじめに

どの現場でもExcelを触る機会って、結構あると思う。
色々な情報をExcelで管理してるもんだから、意図せずExcel VBA に詳しくなっていってしまう...
今回は、Excel VBA をある程度触れてきた人に向けての記事を書こうと思う。

経緯

開発してるVBAが思ってるより処理速度が遅くて悩んでいたのだが、
ログで見てみても、特段遅い処理は見つからない。
色々調べてみると、Collectionは値の取得が遅い なんて記事を見つけたもんだから、
じゃあCollection自作しちゃおうかっていう思考に至って、今回の記事となった。
とはいえ、疑似的なCollection作成なんてそんなに難しくもないよな~とか思って調べてみたけど、
意外と記事なんかは処理速度が遅そうな実装が多くて驚いた。
VBAなんてそもそも処理速度遅いんだから、本気で速度向上しないと不便なのにな~

開発方針

Collectionの実態は動的配列とする

処理速度を上げたければ、配列を使用するのは定石。
ほかの記事でもやはり動的配列を実態としていた。

・使用感はほとんどCollectionクラスと同じとする

同じような仕様のほうがわかりやすいので。

Removeメソッドは実装しないこととする

動的配列は性質上、削除は苦手なんだよね~。
できなくはないけど、処理速度半端なく遅い場合があるから、今回は実装なしにしちゃう。
削除する場合はCollectionを使うほうがいいかな。
VBSの場合はScripting.Dictionaryで対応ね。

・配列変換が容易であること

せっかく実態が動的配列なら、その配列取得できないとメリットないよね?
ってことでこれは絶対外したくない。
実際、蓄積データをJoin関数でくっつけたいとき多々あるし、あったら便利だよね。

2023/11/05 追記

コメントで、上部に記述すればするほど、若干処理速度が速くなるといただいたので、
使用頻度の高い順にメソッドを並び替えてます!
また、要素の追加において、単純にif文で実装したほうが、
エラーハンドリング張るより体感早かったので、
実装変えています!

ソースコード

ではでは早速ソースコードどーん!!!

ReaderCollection.cls

Option Explicit

Private Const OBJECT_NAME As String = "ReaderCollection"

Private collectionArray() As String
Private rangeSize As Long
Private Count_ As Long


'要素の追加'
Public Sub Add(addItem As String)

    Count_ = Count_ + 1
    If Count_ > UBound(collectionArray) Then
        ReDim Preserve collectionArray(1 To Count_ + rangeSize)
    End If
    collectionArray(Count_) = addItem

End Sub


'要素の取得'
Public Function Item(index As Long) As String

    Item = collectionArray(index)

End Function


'要素の値更新 (Collectionにはないが、あったら便利かと思って実装)'
Public Sub Update(index As Long, updateItem As String)

    collectionArray(index) = updateItem

End Sub


'要素数を返却'
Public Property Get Count() As Long

    Count = Count_

End Property


'コンストラクタ'
Private Sub Class_Initialize()

    Call Clear

End Sub


'指定要素数で初期化'
Public Sub Clear(Optional rangeSize_ As Long = 1024)

    rangeSize = rangeSize_
    ReDim collectionArray(1 To rangeSize)
    Count_ = 0

End Sub


'配列の返却'
Public Function ToArray() As String()

    ReDim Preserve collectionArray(1 To Count_)
    ToArray = collectionArray

End Function


'コレクション内を文字列として取得 (ログ出力用)'
Public Function ToString() As String

    ToString = "(" & OBJECT_NAME & ")[""" & Join(Me.ToArray(), """,""") & """]"

End Function


解説

メンバ変数

OBJECT_NAME: ログ出力用のクラス名。別になくても問題ない。 

collectionArray: 実態の動的配列。今回はString型にしてみた。

rangeSize: 動的配列の一回の拡張幅。Clearメソッドで幅変更可能。

Count_: 動的配列に追加した要素の数。
※動的配列の拡張回数を減らすため、要素を入れられなくなったタイミングで、
 rangeSizeで指定の幅を追加していく。
 そのため、Count_は動的配列の実際の要素数とは異なる。

コンストラクタ

Clearメソッドを呼び出して初期化を行う。

プロパティ

Count: メンバ変数Count_Getter
※補足すると...
 Getterとは、Private属性のメンバ変数にアクセスできるようにしたメソッド(VBAだとプロパティ)。
 値の取得はできるが、書き換えができないようにしてる。

メソッド

Clear: 動的配列の初期化を行う。引数を指定することで、配列拡張幅を指定できる。
    最初から要素幅が確定してる場合は、引数指定してClearメソッドを呼ぶことで、
    不要な自動拡張を防げる。

Item: 動的配列から要素を取得する。
    Count_以上の値を指定した場合に、エラーにならずに空文字が取得できてしまう
    可能性があるので、使用時は注意すること。
    今回は処理速度の向上を求めているので、不要なif文は加えていません。
    通常、値の取得にはFor文でCountまで繰り返すため、if文は不要と考えました。

Add: 引数に指定した値を要素に追加。動的配列の要素幅が足りない場合は自動拡張する。
    自動拡張のためにif文を入れていますが、配列サイズを気にしなくていいのが
    Collectionクラスの強みなので、ここではif文を入れざる負えませんでした。

Update: 引数に指定されたインデックスの要素の値を、引数に指定した値で更新する。
    Count_以上の値を指定した場合に、エラーにならずに値更新できてしまう
    可能性があるので、使用時は注意すること。

ToArray: 動的配列のサイズをCount_までに縮小し、返却する。
    ToArray使用後の値追加では必ず自動拡張されるので注意すること。

ToString: 動的配列の内容を出力する。ログ取得したいときなんかにどうぞ。
    ToStringメソッドはToArrayメソッドを実行するため、
    次回値追加時に必ず自動拡張されるので注意すること。

処理速度

よく見かけるソースコードでは、配列拡張を値追加のたびに行っていたので、
多分めっちゃ遅いと思います。(違ってたら教えてください。)
今回は処理速度をめっちゃ考慮してるので、かなり早くなったはず。
てことで、既存のCollectionクラスと処理速度比較してみよか~。

比較データとして、3万データがある場合を想定しています。
以下が処理速度結果です。
image.png
自作のほうが値の蓄積が若干遅いように見えるけど、データ見るとCollectionも同じ時間かかってる時もあるし、この辺はほぼ差異なしとみていいと思う。
値の取得は段違いで早くなってますね。いや~よかったよかった。
配列の生成はほぼ無敵な速度ですね。
関数かませるときに配列変換したいときあると思うので、配列生成が早いのはうれしい誤算。
値を蓄積して、後で参照する。みたいなときは自作のほうがいいですね。
逆に追加と削除を繰り返す場合は、通常のCollectionのほうがいいかも

さいごに

もっと早い方法があるよ~とか、まちがってるよ~っていうことがあれば、ジャンジャン教えてください。

4
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4