アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

ExcelVBA入門 #11:配列を使ってみる

シリーズもくじはこちら
ExcelVBA入門 もくじ - アナログCPU:5108843109

今回は「配列」について。

そもそも「配列」って何?

まず、変数ってあるじゃないですか。データを入れる箱。
ExcelVBA入門 #3:プログラミングには必須!変数ってなんぞ? - アナログCPU:5108843109
配列とは、このデータを入れる箱をたくさん並べてひとかたまりにしたもの、です。

いやー、ExcelVBAからプログラミングを始めた方は幸運ですね。
他のどの言語よりも配列が理解しやすいはずです。
だってそこのワークシート、
「データを入れる箱をたくさん並べてひとかたまりにしたもの」じゃないですか。
配列はそういうものをイメージすればとりあえずOKです。

もう一つ、ExcelVBAで幸運なことがあります。
それは「理解しなくてもOK」ということ。
大体はワークシートで代用できますからね。

ということで、配列の理解が難しければ一旦諦めて、代わりにワークシートを使いこなしてみましょう。
必要になったらもう一度チャレンジしよう、くらいのつもりで後回しにしてしまっても良いのです。
(ただし、オブジェクト類をまとめて扱いたければワークシートではつらいでしょうし、
 膨大なデータを処理するときは配列で扱った方が圧倒的に速くなります)

配列を使うとどんないいことがあるの?

複数のデータをひとつにまとめて扱える利点があります。
プログラム上で扱う「同じ種類のたくさんのデータ」や「いくつ存在するかもわからないデータ」を
ひとつの名前で扱えるようになるのです。
例えばExcelで住所録を作ろう、というとき、
1ファイルに1人…なんてやらずに、1行に1人くらいにして、複数人をまとめますよね?
そういったイメージで捉えればOKです。

ただ、まとめて扱うということは、どこに何が入っているかを意識して使う必要があります。
なんでもかんでもまとめてしまうとバグの原因になりますので、
できるだけ「わかりやすくするために使う」という意識を忘れないようにしましょう。

配列の超基本

まずは以下にサンプルプログラムを用意しました。
これは、コード中のコメントにもあるとおり、
決まったサイズの箱を用意して、値を入れて、表示するだけのプログラムです。

Public Sub ArrayTest()
    '0番~2番までの番号がついた箱を集めた「配列」を定義
    Dim lList(0 To 2) As Long

    'それぞれの箱に値を入れる
    lList(0) = 0
    lList(1) = 10
    lList(2) = 20

    'それぞれの箱を表示する
    Debug.Print lList(0)
    Debug.Print lList(1)
    Debug.Print lList(2)
End Sub

いつもの変数と、使い方はあまり変わりませんね。
定義のときは変数名の後に「何番から何番の箱を用意する」という情報を付けて、
使うときは変数名の後に「何番の箱を使う」という情報を付けてあげるだけです。

これなら値の数を増やしたいときも簡単です。

Public Sub ArrayTest()
    Dim lList(0 To 3) As Long

    lList(0) = 0
    lList(1) = 10
    lList(2) = 20
    lList(3) = 30

    Debug.Print lList(0)
    Debug.Print lList(1)
    Debug.Print lList(2)
    Debug.Print lList(3)
End Sub

え、定義部分が短くなっただけ?変数に数字を付けるだけでもいい?
これだと確かにそうですね。

ではこれならどうでしょう。

Public Sub ArrayTest()
    Dim lList(0 To 3) As Long
    Dim i As Long

    For i = 0 To 3
        lList(i) = i * 10
    Next

    For i = 0 To 3
        Debug.Print lList(i)
    Next
End Sub

繰り返し文を使うことで、簡単にまとめて処理することができるようになります。

たとえ要素が100個になっても変わりません。

Public Sub ArrayTest()
    Dim lList(0 To 99) As Long
    Dim i As Long

    For i = 0 To 99
        lList(i) = i * 10
    Next

    For i = 0 To 99
        Debug.Print lList(i)
    Next
End Sub

ワークシートっぽいと言うからには…

先述の例だと「データを一列に並べただけ」ですが、
ワークシートのように「行×列のデータ」を作ることもできます。

Public Sub TwoDimentionArrayTest()
    Dim vProfile(1 To 2, 1 To 4) As Variant '数値も文字列も扱うのでここではVariant

    '1人目のプロフィール
    vProfile(1, 1) = "鈴木太郎"
    vProfile(1, 2) = 20
    vProfile(1, 3) = "03-1111-1111"
    vProfile(1, 4) = "東京都新宿区"

    '2人目のプロフィール
    vProfile(2, 1) = "山田花子"
    vProfile(2, 2) = 25
    vProfile(2, 3) = "06-1111-1111"
    vProfile(2, 4) = "大阪府大阪市"

    Dim i As Long
    For i = 1 To 2
        Debug.Print vProfile(i, 1) & "(" & vProfile(i, 2) & ") " & vProfile(i, 3) & " " & vProfile(i, 4)
    Next
End Sub

ちなみに上記の結果はこんな表示になります。

鈴木太郎(20) 03-1111-1111 東京都新宿区
山田花子(25) 06-1111-1111 大阪府大阪市

これを二次元配列と呼びますが、三次元以上も可能です。
とはいえ、あまり多次元なものを使う機会はそうそうないと思いますし
わかりにくいコードにもなりがちなので、参考程度に留めておいてください。
(あまり増やしすぎるとメモリオーバーとなってしまいます)

'四次元配列の例
Public Sub FourDimentionArrayTest()
    Dim lList(0 To 1, 0 To 1, 0 To 1, 0 To 1)

    lList(0, 0, 0, 0) = 1
    lList(0, 0, 0, 1) = 2
End Sub

定義の書き方、長くない?

サイズ指定するときに、箱の最大番号だけを書く方法もあります。

Dim lList(1) As Long

「0 To」が省略されているとみなされるので、この場合は「0 to 1」、つまり2つの要素を持つ配列になります。
配列のサイズ指定(「2個の箱を作りたいから2!」とか)ではないことに注意してください。

そもそも、最初にサイズ指定しなきゃいけないの? 途中でサイズも変えたいし…

はい、ここまでが基本でしたが、実際にコードを書く時には
「最初はサイズがわからない」「途中でサイズを変えたい」というケースが大半だと思います。
当然これらに対応した書き方があります。

まず、最初の定義ではサイズを指定しません。

Dim lList() As Long

ただし、そのまま使おうとするとエラーになってしまいます。
箱がいくつあるかも分からないのに値を入れようとしたり使おうとしたりすることはできないのです。

そこでReDimの出番です。

Dim lList() As Long
ReDim lList(0 To 1)

これで、「Dim lList(0 To 1) As Long」したのと同じように、2個の箱が入った配列になります。
あとの使い方は同じ。

同じ要領で、途中でサイズを変更することもできます。
ただし、既に配列にデータが入っていてそれを維持したい場合は「ReDim Preserve」としましょう。
「ReDim」だけだと中身がクリアされてしまいます。

Dim lList() As Long
ReDim lList(2)
lList(0) = 1
ReDim lList(3) '「lList(0)」にセットした値が消える
lList(0) = 1
ReDim Preserve lList(4) '「lList(0)」にセットした値が消えない

でも、途中でサイズを変更したいときって、
往々にして「3つに増やしたい!」じゃなくて
「今いくつか知らないけど1つ増やしたい!」というパターンなんですよね。

そういうときは、今の最大値を調べるUBound関数を併用します。
まずはUBound関数単体で動かしてみましょう。ついでに似た関数であるLBoundも。

Public Sub BoundTest()
    Dim lNum(2 To 5) As Long
    Debug.Print UBound(lNum) '5
    Debug.Print LBound(lNum) '2

2番の箱~5番の箱の4つがある配列に対して使ってみると、
UBoundは「5」、最小値を調べるLBoundは「2」を返してくれます。
(この2つを組み合わせると、箱の数もわかりますね)

これを利用して、「配列をひとつ大きくする」が簡単にできます。

Public Sub ChangeArraySizeTest()
    Dim lNum() As Long
    Dim i As Long

    '配列のサイズを定義(0 To 0)
    ReDim lNum(0)
    '配列のサイズを1つずつ増やす
    For i = 1 To 4
        '「UBound(lNum) + 1」で現在の最大値+1を表すので、それを用いて再定義
        ReDim Preserve lNum(UBound(lNum) + 1)
    Next
End Sub

ReDimについてはもっと細かい話もいろいろありますが、
詳細を書くとめちゃくちゃ長くなってしまったので別の記事に起こしました。
もっと詳しく知りたい方はどうぞ。

配列のサイズが既に決まっているかどうかを知りたいんだけど…

先ほどの「配列をひとつ大きくする」サンプルプログラムでは、
ループの前に一度ReDimで定義しています。
どうしてこういうことをしているかというと、この行がないとエラーになってしまうのです。

でも本来なら先にReDimしたくない、というパターンは多くあります。
「定義されていなければ要素を1つで定義して、定義されているなら+1したい」と思うのですが、
定義されているかどうかを調べる方法は、実は確立されていません。

ググってみると、「判定はできないので、きちんとしたエラー処理を入れる」や
「Sgn関数で代用する」「Not Not 配列名」などの方法が主に出てきます。
(個人的にSgn関数で代用するのはおすすめしません。本来の目的と違いすぎる…)

一番きれいなのは、きちんとしたエラー処理を用いたチェック関数を自作してしまうことかなと思います。
(todo:そのうち作って記事にしておきます…)

参考:
VBAの動的配列で要素を宣言する前の状態をチェックする方法 | アイビースター
動的配列がReDimされたか判定(Filter関数で上限要素を-1に初期化する) - ×××Diary
VBAで配列のNull判定にSgn関数を使ってはいけない

おしまい

正直なところ、簡単なプログラムを作っているうちは配列が必要になるケースは少ないと思います。
せっかくExcelを使っているのですし、無理せずワークシートを利用した方がスマートに済む場合も多いです。
(他言語で配列を使いこなせている人は逆にワークシート利用のほうが難しいかもしれませんが…)
こういうものもある、と頭の隅に留めておいて、いざというときに見直してみるくらいでいかがでしょうか。

参考文献:
VBAの配列まとめ(静的配列、動的配列)|VBA技術解説
ReDim ステートメント
【VBA入門】配列の初期化(ReDim、Preserve、Array、Erase) | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト
VBAで定義可能な配列の最大次元数 | Excel作業をVBAで効率化