Quantcast
Channel: 連想配列タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 129

ExcelVBA連想配列+二次元配列で、特定の単語ごとに、最速で条件分岐する

$
0
0

連想配列

 連想配列(Dictionary オブジェクト)使ってますか?御存知のとおり、文字列をキーにした配列です。
https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/dictionary-object
(リンク microsoft公式)

 えっ重複チェックにしか使ってない?itemにいらない情報しか入れたことがないって?奇遇ですね。半年前までの私と完全に同じです。
 ただ、それはあまりにもったいない!Excelならではの連想配列の使い途があるんです。

連想配列の利点

 ハッシュ検索を行えることです。特定の単語と一致するものを検索するとき、先頭から順に確認していく線形探索法を使っていては,平均探索回数が(n+1)/2となってしまうところ,連想配列に登録されたkeyにあるかをexistsでチェックすれば,平均探索回数1回で検索できるのです。
https://www.atmarkit.co.jp/ait/articles/0603/31/news116.html
(リンク @IT atmarkit)
 なので,重複チェックに使ったりするわけです。
 ただ,重複チェックはkeyの情報だけでできるので,itemにはいらない情報を入れることになります。カウンタとか。それだけではあまりにもったいない!

文字列データの修整に,連想配列を使ってみる

 スクレイピングしたデータや、社内システムから引いてきたデータの表記変更をするとします。
 私は、こういうときExcel上に表を作って表記のユーザー編集を可能にしています。SelectCase文の条件分岐を表にするイメージです。
 たとえば,氏名から略称を引くための表を作成したとします。
2020-08-11 (2).png

もし氏名に該当したら略称をメッセージで出すというマクロを書きたいとき,単純に上から順に繰り返し処理で確認するとこのようになります。

繰り返し処理
Submsg_short_name()DimiAsIntegeri=1DoWhileCells(i,1)<>""IfCells(i,1)=Cells(1,4)ThenMsgBoxCells(i,2)EndIfi=i+1LoopEndSub

ただ、これを繰り返すとどうなるでしょうか。
2020-08-11 (3).png
1回探すなら(n+1)/2
4回探したら(n+1)/2*4
かなり時間がかかってしまいます。

 そこで、連想配列の出番です。

連想配列
Submsg_short_name()DimnameArrayAsObjectSetnameArray=CreateObject("Scripting.Dictionary")DimiAsIntegeri=1DoWhileCells(i,1)<>""IfNotnameArray.Exists(Cells(i,1))ThennameArray.AddCells(i,1).Value,Cells(i,2).ValueEndIfi=i+1LoopIfnameArray.Exists(Cells(1,4).Value)ThenMsgBoxnameArray.Item(Cells(1,4).Value)EndIfEndSub

※cellの値をkeyやitemとして代入したり,existsで確認したりするときは,.valueをつけてセルの値であることを明示する必要があります。

 いったん全てのkey(氏名),item(略称)を連想配列に格納したら,その後はnameArray.Exists(文字列)で存在確認でき,nameArray.Item(文字列)でitemを取り出すことができます。
1回探すなら1
4回探したら4
と,表の行数にかかわらない処理速度で要素を取り出せるのです。

もっと複雑な条件分岐に対応する

 連想配列自体は二次元配列にすることができません。
 ただ,連想配列のKeyから、もっと色々な情報を引きたくなったら、セルの行を外部キーにして、可変二次元配列と関係づけ、データベースのようにつなげることができます。
2020-08-11 (4).png
たとえば,氏名から略称・受賞作の双方を引く必要があったとすると,下の図のように配列をつくります。
2020-08-11 (5).png

連想配列+可変二次元配列
Submsg_short_name()DimnameArrayAsObjectSetnameArray=CreateObject("Scripting.Dictionary")DimshortNameArray()AsStringDimiAsIntegeri=1DoWhileCells(i,1)<>""IfNotnameArray.Exists(Cells(i,1))ThennameArray.AddCells(i,1).Value,iReDimPreserveshortNameArray(1,i)shortNameArray(0,i)=Cells(i,2).ValueshortNameArray(1,i)=Cells(i,3).ValueEndIfi=i+1LoopIfnameArray.Exists(Cells(1,4).Value)ThenDimforeignKeyAsIntegerforeignKey=nameArray.Item(Cells(1,4).Value)DimmsgAsStringmsg="セル(1,4)の外部キー:"&foreignKey&vbLfmsg=msg&"略称:"&shortNameArray(0,foreignKey)&vbLfmsg=msg&"代表作:"&shortNameArray(1,foreignKey)MsgBoxmsgEndIfEndSub

※可変二次元配列は、最後の要素数しか可変にできません。

2020-08-11 (7).png
氏名の文字列から、略称・受賞作の両方を引けるようになりました。

連想配列の使いどころ

 ずばり、文字列についてそこそこ分岐の多いSelectCase文を使うときです。特定の単語ごとに関連要素を引くとき、と言い換えることもできます。
 分岐を増やせば増やすほど遅くなるというときの特効薬になります。セルの読み込みは一回のみですし、分岐のための検索は、平均一回のみで済みますから。
 ただ、含む(Instr)という分岐などには使えないので、やはりスクレイピングなどのローデータ処理が向くと思います。

 あまりに膨大な連想配列(や可変二次元配列)を作ると、メモリを圧迫してしまう可能性はありますが、文字列処理レベルではまず大丈夫かと思います。


Viewing all articles
Browse latest Browse all 129

Trending Articles