2022/11/23

可変長配列(Dictionary等)の機能整理




連想配列と呼ばれるCollectionやDictionaryのような可変長配列について、下記のようなシリーズで説明していきます。
・可変長配列の機能整理   ←今回
可変長配列を使った重複除外リスト(単列)
可変長配列を使った重複除外リスト(複数列)

ここでは「Collection」「Dictionary」「ArrayList」「SortedList」の4オブジェクトを取り上げます。
この他にも、「HashList」というArrayListやSortedListの仲間のオブジェクトもあります。名前の通りKey値を数値に変換し、その数値が示すインデックス位置にデータを保管するというもののようです。しかし保管したデータをKey値以外で取り出す方法が不明(Index等で取り出す手段が分からない)なため、今回の説明からは省いています。
とは言っても、理論的にはデータの存在有無を確認するのが高速に思えるので、何か面白い使い方を思いついたら、また御紹介したいと思います。

1.各可変長配列の特徴と機能

各可変長配列の特徴を簡単に図1にまとめ、その後で機能ごとに比較をしていきます。
Object特徴
Collectionどんな値でも格納可能。ユニークなKeyをセットにして格納すると、そのKeyで取り出し可。
Dictionary値とKeyをセットで格納。格納時にKeyの重複をチェック可能で、Keyも値も単データ・全データの取り出し可。
ArrayList必要に応じてサイズを動的に拡大できる配列。並へ替えのメソッドも有り、値の単データ・全データの取り出し可。
SortedList値とKeyをセットで格納し、格納した時点でKeyにより自動的に並べ替えられる。Keyも値も単データの取り出し可。
図1


1ー1.宣言とオブジェクト生成

オブジェクトを使用するには、まずオブジェクトを生成する必要があります。各オブジェクトの宣言・生成方法は図2のようになります。 なお生成するオブジェクトの変数名は、ここでは各オブジェクトの頭文字(「C」「D」「A」「S」)としています。
項目CollectionDictionaryArrayListSortedList
事前
バイン
ディング
参照Microsoft Scripting Runtimemscorlib.dll
宣言Dim C As CollectionDim D As Dictionary
(Dim D As Scripting.Dictionary)
Dim A As ArrayListDim S As SortedList
生成Set C = New CollectionSet D = New Dictionary
(Set D = New Scripting.Dictionary)
Set A = New ArrayListSet S = New SortedList
実行時
バイン
ディング
宣言Dim D As ObjectDim A As ObjectDim S As Object
生成Set D = CreateObject
("Scripting.Dictionary")
Set A = CreateObject
("System.Collections.ArrayList")
Set S = CreateObject
("System.Collections.SortedList")
図2


「Collection」オブジェクトは、標準状態(参照設定へのライブラリファイルの追加不要)で宣言・生成することで使用できるようになります。

「Dictionary」は、事前バインディング(=アーリーバインディング)時には参照設定(VBEのツール→参照設定)で、「Microsoft Scripting Runtime」のライブラリファイルを追加します。
実行時バインディング(=レイトバインディング)時には、CreateObject関数に「Scripting.Dictionary」を指定して、オブジェクト生成を行います。
なお、単に「Dictionary」となっている場所を「Scripting.Dictionary」としても、宣言・生成が可能です。

「ArrayList」と「SortedList」を使用するには、まず「.NET Framework 3.5」がインストールされている必要があります。インストールされていな場合に、例えば実行時バインディングでCreateObjectを実行すると「オートメーションエラー」が発生してしまいます。
「.NET Framework 3.5」をインストールするには、図3のように「Windowsの機能」で「.NET Framework 3.5(.Net 2.0および 3.0を含む)」を有効化しインストールします。
.NET Framework 3.5の有効化
図3


Windows10と11では「Windowsの機能」に辿り着くルートが少し異なります。
Windows10の場合は図3の左側のように、「コントロールパネル」→「プログラムと機能①」→「Windowsの機能の有効化または無効化②」を選択するとダイアログが表示されます。ダイアログ一番上の「.NET Framework 3.5(.Net 2.0および 3.0を含む)」にレ点③をし、OKボタンをクリックすれば、ダウンロードが始まり機能が追加されます。
Windows11の場合は図3の右側のように、「設定」→「アプリ④」→「オプション機能⑤」→一番下の「Windowsのその他の機能⑥」を選択するとダイアログが表示されます。ダイアログ一番上の「.NET Framework 3.5(.Net 2.0および 3.0を含む)」にレ点⑦をした後はWindows10と同じです。

「ArrayList」「SortedList」で事前バインディングを行う場合は、参照設定で「mscorlib.dll」を選択します。もしリスト内に存在しない場合は「C:¥Windows¥Microsoft.NET¥Framework¥version¥mscorlib.tlb」を参照ボタンから選択して登録して下さい。versionの部分は、PCの環境によって異なるようです。
実行時バインディングを行う場合は「System.Collections.ArrayList」「System.Collections.SortedList」をCreateObject関数で指定します。

なお宣言と生成を一緒に実行する方法として、例えば「Dim C As New Collection」と1行で宣言+実行をする手法も他サイトでは紹介されています。しかし、この方法を使うと「オブジェクトが実行されるたびに存在がチェックされ、無ければ作成」する動作となってしまいます。(Microsoftの解説ページ
よって、オブジェクトをNothingで解除した後でもオブジェクトが使えてしまう(=使おうとした時にオブジェクトが再生成される)という不自然な動きになります。短くて良いのですが「使わない」方が良さそうです。

1ー2.データの追加

各可変長配列にデータを追加する時に必要なコードと要件は、図4の通りです。
項目CollectionDictionaryArrayListSortedList
データ
の追加
末尾へC.Add(Item,Key)D.Add(Key,Item)
D.Item(Key) = Item
A.Add(Value)S.Add(Key,Value)
S.Item(key) = Value
位置指定C.Add(Item,Key,Before,After)A.Insert(Index,Value)
入力要否Key任意必須必須
必須必須必須必須
重複可否Key不可不可不可
入力可能
データ型
KeyString型のみ配列以外OK(※1)文字列、数値、日付(※2)
何でもOK(※3)何でもOK(※3)何でもOK(※3)何でもOK(※3)
データ型
の混在
Key不可(String型のみ)不可
可(※4)
値の修正不可D.Item(Key) = ItemA.Item(Index) = ValueS.Item(key) = Value
存在
チェック
Key
(On Error Resume Next を使用)
D.Exists(Key)S.Contains(Key)
S.ContainsKey(Key)
A.Contains(Value)S.ContainsValue(Value)
先頭Index1000
※1:文字列、数値、日付、Object、Null値(配列は入力不可。ObjectはKey指定可だが存在チェックも値の取り出しも不可)
※2:配列・Object・Null値も初回は入力可だが、並べ替え時に比較が出来ない為に2回目以降は入力できず
※3:文字列、数値、日付、配列、セル範囲等のObject、Null値
※4:但しSortメソッド使用時は、比較が成立するように「配列、Object、Null値以外の同じデータ型」にする必要あり
図4


「Collection」は「Keyの指定は任意」ですが、設定するのであればString型のみです。Keyですので、指定するのであれば当然ながら「Keyの重複は不可」です。
またCollectionには、Keyの存在を調べるものがありませんが、「Keyの重複は不可」ですので「重複しているとエラー」が発生します。そのエラーを使い「On Error Resume Next」でAddメソッドの「値追加→エラー」をスルーさせることで、「エラー停止することなく、Keyも重複しないCollectionオブジェクト」を作成することが可能です。
またAddメソッドでパラメータを指定せずに引数を渡す場合は、第一引数「Item」・第二引数「Key」であり、他の可変長配列とは異なります。インデックスが「1」から始まるのも他の可変長配列と異なります。
Collectionには、位置を指定して格納するためのパラメータ「Before」「After」があります。どちらかを指定可能で、両方省略した場合は「末尾に追加」ということになります。「C.Count値以内」の整数で、挿入位置を指定します。

「Dictionary」は、Collectionとは異なり「Keyと値(Item)」は必須です。またAddメソッドでパラメータを指定しない場合は「Key」「Item」の順序になります。
Keyには配列以外は指定できますが、Objectを使ってしまうと「存在チェックのExistsメソッドで調べても必ずFalseが戻る」ことになるため役に立ちません。また図5で示す個別データ取得も出来ませんが、「全データ取得のD.Keysメソッドでは、Objectもまとめて取得可」となるようです。KeyにObjectを使用する際には注意が必要です。
値の修正時には「D.Item(Key) = Item」という式を使いますが、この式は新規データ追加時にも使用できます。つまりExistsメソッドで存在確認をせずに「重複したキーが出てきたら、どんどん値を上書きする」という手法も使えそうです。

「ArrayList」にはKeyはありません。ほぼ配列と同じ形です。またArrayListにはSortメソッドが備わっており、メソッド実行のみで並べ替えが可能です。但し値に「比較が出来ない配列・オブジェクト・Null値」などを入れてしまうと並べ替えが出来ず、Sortメソッド時にエラーが発生してしまいますので注意が必要です。
また「Insertメソッド」を使用すれば、挿入位置を指定してデータ追加することが可能です。

「SortedList」は、格納した直後に並べ替えが実行されます。ですので並べ替えの基準となるKey値には、「文字列・数値・日付」のみが可能です。しかも比較ができるようにするために「同じデータ型」で揃える必要があります。
このSortedListも、Dictionaryと同様に「S.Item(key) = Value」で値修正及びデータ新規追加が出来ます。

なお「値」のパラメータには、CollectionとDictionaryでは「Item」を使用しますが、ArrayListとSortedListでは「Value」を使用します。

1ー3.データの取り出し

格納したデータを取り出す時に必要なコードは図5の通りです。
項目CollectionDictionaryArrayListSortedList
要素数取得C.CountD.CountA.CountS.Count
個別
データ
取得
KeyD.Keys()(Index)S.GetKey(Index)
C.Item(Index)
C.Item(Key)
C(Index)
C(Key)
D.Items()(Index)
D.Item(Key)
D(Key)
A.Item(Index)
A(Index) 
S.GetByIndex(Index)
S.GetByIndex(S.IndexOfKey(Key))
S.Item(Key)
S(Key)
全データ
取得
KeyD.Keys
D.ItemsA.toArray
図5


「Collection」では、Keyは補助的なものであるためか、Key値を取得する手段がありません。また、全ての値を一気に取り出すものも存在しませんので、For~NextやFor Each~Nextを使用することになります。

「Dictionary」では、インデックスで値を取り出す際は「D.Items()(Index)」とします。VBAでは「オブジェクトの集合体であるコレクションには『s』をつける」ので、「D.Items(Index)」でも良いような気もしますが、受け付けてくれません。「Items」はプロパティでは無くメソッドで、「全ての値を戻す」という機能だからのようです。
ですので一旦「D_All = D.Items」のように変数で全ての値を受け取れば、「D_All(Index)」で受け取れるようになります。キーを受け取る「Keys」も同じくメソッドのため、同じように記述する必要があります。

「ArrayList」は「toArrayメソッド」で「全ての値」が取得できます。複数の値が入っている場合は、配列の形で戻されます。なおAddで格納する値を一次元配列とした場合には、toArrayで戻される値は二次元配列では無く「配列のネスト」が戻ります。戻された配列のインデックスはゼロスタートとなっています。

「SortedList」には全ての値・キーを取得する存在しないので、For~Next使用することになります。格納されているデータは、Keyの昇順で並んでいますので、降順で並べたい時にはFor~Nextを「Step -1」で逆に回して取り出します。

1ー4.データの削除

格納したデータを削除する時に使用するコードは図6の通りです。
項目CollectionDictionaryArrayListSortedList
個別
削除
Index指定C.Remove(Index)D.Remove(D.keys()(Index))A.RemoveAt(Index)S.RemoveAt(Index)
Key指定C.Remove(Key)D.Remove(key)S.Remove(key)
値指定A.Remove(Value)
全削除Set C = New CollectionD.RemoveAllA.ClearS.Clear
図6


インデックス番号、キー値で削除する項目を指定する方法は、どの可変長配列でもほぼ同じです。SortedListには「S.RemoveRange(Index,個数)」というメソッドもありますが、VBAでは使用できない様です。

なおArrayListでは、値を指定したRemoveメソッドが使用でき、「インデックスの先頭から探して、最初に見つかった値の要素のみを削除」するようです。もし、ある値を全て削除したい時には、For~Next等で要素数(A.Count)が減らなくなるまで繰り返し実行すると良さそうです。但し、値がセル範囲などのオブジェクトや配列などの場合は、見つけることが出来ない(?)ようで、削除されませんので注意が必要です。

また全てのデータを削除する際、Collectionでは対応するメソッドが無いので、オブジェクトを生成し直すしかないようです。

アプリ実例

CSVファイルでデータを読み書きする月間予定表
サンプリング周期が異なるデータの補間法
複数行1データの並び替え
データの重みを考慮したComboBox入力補助
先入先出の入出庫管理システム
DVD等の内容・保管場所等管理システム


コード・機能比較表(its-019.xlsx) ・・・このページの表をA4横にまとめたものです。