配列の検索は遅い
ソートされていない配列から指定文字列を検索するには先頭もしくは最後から検索する必要があります。
この処理は線形探索のため、配列の要素数に比例して計算量が増えていきます。
そのため、配列サイズが大きければ大きいほど、処理が遅くなる欠点があります。
検索回数が2回以上なら連想配列に変換する
配列の先頭から指定文字列を検索する方法を別ページ「VBAで配列に指定文字列が存在する位置を調べる」で書いていますが、上記の理由で処理速度が遅いという弱点があります。もし同じ配列を何度も検索するのであれば、ハッシュを利用した方が検索は劇的に速くなります。
ハッシュの利用にはVBA標準のCollectionクラスと、Microsoft Scripting RuntimeのDictionaryクラスの2つが有名ですが、ここではDictionaryクラスを利用します。Dictionaryクラスを利用する理由は、DictionaryクラスのExistsメソッドに該当する機能がCollectionクラスに無いなど、使い勝手の差があるためです。
このページでは配列からDictionaryへの変換に特化して書いていますので、Dictionaryクラスの詳細については「VBAのDictionaryの使い方(全メソッドとプロパティ網羅)」をご参照ください。
配列をDictionaryに変換するソースコード
以下のソースコードは参照設定でMicrosoft Scripting Runtimeにチェックを付けておく必要があります。
コード説明は後述しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Sub convArrayToMap(ary(), map As Dictionary) Dim iLen '// 配列要素数 Dim i '// ループカウンタ '// 配列でない場合は処理を抜ける If IsArray(ary) = False Then Exit Sub End If '// 配列要素数を取得 iLen = UBound(ary) '// 配列全ループ For i = 0 To iLen '// keyにループカウンタ文字列、valueに配列値を設定 Call map.Add(ary(i), CStr(i)) Next End Sub |
ソースコードの説明
引数の条件
引数aryは呼び出し元で配列として初期化されている必要があります。
引数mapは呼び出し元でNewされている必要があります。
処理説明
処理の概要は、一次元配列の内容を配列の先頭から引数のDictionaryに格納します。格納の際に、keyにループカウンタを設定し、valueに配列値を設定しています。keyに配列値を入れていない理由は、Dictionaryクラスはkeyは重複を許可しないため、配列値が重複している場合を考慮しています。
もちろん、配列値の重複がないことが確定している場合はkey=配列値としても問題ありません。このあたりは要件によって変わってくるため適宜検討が必要になる部分です。
使い方
利用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
Sub convArrayToMapTest() Dim ary() '// 配列 Dim map As New Dictionary '// Dictionaryクラスオブジェクト Dim i, k '// ループカウンタ Dim tmStart As Double '// 計測開始時間 Dim tmEnd As Double '// 計測終了時間 Dim tmDiff As Double '// 計測経過時間 Dim s '// Dictionaryのvalue '// 動的配列を作成。先頭から"10000"から"0"までを設定。 ReDim ary(10000) For i = 0 To 10000 ary(i) = CStr(10000 - i) Next '// 配列をDictionaryに変換 tmStart = Timer Call convArrayToMap(ary, map) tmEnd = Timer tmDiff = tmEnd - tmStart Debug.Print "Dictionary変換に掛かる時間:" & tmDiff & "秒" '// 配列で指定文字列"8"を検索した場合を計測(配列の先頭から9992番目を検索) tmStart = Timer For i = 0 To 10000 For k = 0 To 10000 If ("8" = ary(k)) Then '// この時点の変数kの値が検索文字列がある配列の位置になる Exit For End If Next Next tmEnd = Timer tmDiff = tmEnd - tmStart Debug.Print "配列での検索経過時間:" & tmDiff & "秒" '// Dictionaryから指定文字列"8"を検索 tmStart = Timer For i = 0 To 10000 If (map.Exists("8") = True) Then s = map.Item("8") End If Next tmEnd = Timer tmDiff = tmEnd - tmStart Debug.Print "Dictionaryでの検索経過時間:" & tmDiff & "秒" End Sub |
利用例の説明
11行目から14行目で動的配列を用意し、18行目でそれをDictionaryに変換しています。
28行目では検索一致としてループを抜けています。この時点での変数kの値が指定文字列が格納されている配列のインデックスになります。
40行目で検索文字列の存在チェックを行い、存在する場合は検索文字列をキーとして元配列のインデックスを取得しています。
他は全て処理計測用のコードです。Dictionary変換、配列検索、Dictionary検索、のそれぞれで処理時間を計測しています。私の環境では以下のように経過時間が出力されました。
Dictionary変換に掛かる時間:0.029296875秒
配列での検索経過時間:6.720703125秒
Dictionaryでの検索経過時間:0.01953125秒
検索1万回を配列で行った場合は6.72秒、Dictionaryは0.019秒です。
約340倍もの圧倒的な差があります。検索を1回しか行わないのが確実であれば、配列で線形探索を行ってもいいのですが、そうでないのであれば速度が安定して高速なDictionaryに変換した方がいいです。