配列の任意の位置へのデータの追加や削除を行うには
VBAの配列はRedim Preserve構文で終端より先の領域を拡張することはできますが、配列の途中の部分にデータを挿入したり削除したりする仕組みはありません。
一般的に一連のデータの途中にデータの追加や削除を行いたい場合は連結リストという仕組みを利用しますが、VBAには連結リストが実装されていません。
連結リストがどうしても必要であればクラスを自作するとかの話になります。ただ、そこまではちょっと面倒です。
ここでは、連結リストクラスを自作するのが面倒なので、配列を使って任意の位置にデータを追加したり、任意の位置を削除したりする方法を紹介します。
紹介しているデータ追加関数とデータ削除関数はどちらも処理速度は最小O(1)から最大O(n)(n=配列要素数+1)の範囲になります。
配列の任意の位置にデータを追加する関数
以下の関数は配列の任意の位置にデータを追加します。追加された箇所以降は1つ後ろのインデックスにシフト(移動)します。
引数が3つあり、「配列」「追加データ」「追加する位置(配列のインデックス値)」を指定します。追加データのデータ型はなんでもOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Sub InsertToArray(a_Ary, a_Data, a_iPosition) Dim i '// ループカウンタ Dim iCount '// 配列要素数 '// 引数配列要素数を取得(拡張分として1を加算) iCount = UBound(a_Ary) + 1 '// 配列の領域を拡張 ReDim Preserve a_Ary(iCount) '// 設定位置が配列要素より大きい場合あh配列最終要素位置に設定する If a_iPosition > iCount Then a_iPosition = iCount End If '// 配列終端から先頭に向かって追加位置までループ For i = iCount To a_iPosition + 1 Step -1 '// 1つ前の値を現在の値としてセット(1つ後ろにシフト) Call SetValue(a_Ary(i - 1), a_Ary(i)) Next '// 追加データをセット Call SetValue(a_Data, a_Ary(a_iPosition)) End Sub |
配列の任意の位置にデータを削除する関数
以下の関数は配列の任意の位置のデータを削除します。削除された箇所以降は1つ前のインデックスにシフト(移動)します。
引数が2つあり、「配列」「削除する位置(配列のインデックス値)」を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Sub EraseToArray(a_Ary, a_iPosition) Dim i '// ループカウンタ Dim iCount '// 配列要素数 '// 引数配列要素数を取得 iCount = UBound(a_Ary) '// 指定位置から終端直前までループ For i = a_iPosition To iCount - 1 Call SetValue(a_Ary(i + 1), a_Ary(i)) Next '// 領域を1つ削除(終端を削除) ReDim Preserve a_Ary(iCount - 1) End Sub |
データコピー関数
上記の2つ関数はどちらも配列のデータ型はなんでもOKにしています。
そのため、Integer型やString型などのプリミティブ型でも、Range型やDictionary型などの各種オブジェクト型でも動作します。
ただ、オブジェクト型と非オブジェクト型ではデータの代入にSetステートメントを使うかどうかというコーディングの仕方が異なるため、この関数でデータ型を判定した上でそれに応じた代入を行うようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Sub SetValue(a_From, a_To) Dim bType As Boolean '// 引数配列の種類 '// 引数データ型判定 bType = IsObject(a_From) '// オブジェクト型の場合 If bType = True Then Set a_To = a_From '// 非オブジェクト型の場合 Else a_To = a_From End If End Sub |
使い方
String型の配列とRange型の配列を用意して、配列の任意の要素位置にデータの追加を行い、その後任意の要素位置のデータを削除するテストコードです。
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 48 49 50 51 52 53 54 55 56 57 |
Sub InsertEraseToArrayTest() Dim arString() As String '// 文字列配列 Dim arRange() As Range '// セル配列 Dim i As Long '// ループカウンタ '// テスト用の初期値を設定 ReDim arString(7) ReDim arRange(7) arString(0) = "a" arString(1) = "b" arString(2) = "c" arString(3) = "d" arString(4) = "e" arString(5) = "f" arString(6) = "g" arString(7) = "h" Set arRange(0) = Range("A1") Set arRange(1) = Range("A2") Set arRange(2) = Range("A3") Set arRange(3) = Range("A4") Set arRange(4) = Range("A5") Set arRange(5) = Range("A6") Set arRange(6) = Range("A7") Set arRange(7) = Range("A8") '// 配列に新規データを追加 Call InsertToArray(arString, "abc", 1) Call InsertToArray(arRange, Range("G100"), 0) '// 文字列配列への追加結果を出力 For i = 0 To UBound(arString) Debug.Print CStr(i) & " - " & arString(i) Next '// セル配列の追加結果を出力 For i = 0 To UBound(arRange) Debug.Print CStr(i) & " - " & arRange(i).Address(False, False) Next Debug.Print "" '// 配列からデータを削除 Call EraseToArray(arString, 1) Call EraseToArray(arRange, 0) '// 文字列配列の削除結果を出力 For i = 0 To UBound(arString) Debug.Print CStr(i) & " - " & arString(i) Next '// セル配列の削除結果を出力 For i = 0 To UBound(arRange) Debug.Print CStr(i) & " - " & arRange(i).Address(False, False) Next End Sub |
実行結果
0 – a
1 – abc(ここが追加部分)
2 – b
3 – c
4 – d
5 – e
6 – f
7 – g
8 – h
0 – G100(ここが追加部分)
1 – A1
2 – A2
3 – A3
4 – A4
5 – A5
6 – A6
7 – A7
8 – A8
0 – a
1 – b(ここが削除部分。2のbがこっちにシフトした。)
2 – c
3 – d
4 – e
5 – f
6 – g
7 – h
0 – A1(ここが削除部分。1のA1がこっちにシフトした。)
1 – A2
2 – A3
3 – A4
4 – A5
5 – A6
6 – A7
7 – A8