セル範囲や複数セルなどRangeオブジェクトの各セル値を格納した、
1次元配列を生成する方法を解説します。
Rangeオブジェクトから1次元配列を生成する
ソースコード
Rangeオブジェクトを渡すと1次元配列(Array)を返してくれる関数と、
それを利用した実行例が以下の通りです。
Function Rangeから1次元配列を生成(対象Range As Range) As Variant Dim Arr生成配列() As Variant ReDim Arr生成配列(対象Range.Cells.Count - 1) As Variant Dim i As Long: i = 0 Dim cell As Range For Each cell In 対象Range.Cells Arr生成配列(i) = cell.Value i = i + 1 Next Rangeから1次元配列を生成 = Arr生成配列 End Function
実行例
Dim Arr商品リスト As Variant Arr商品リスト = Rangeから1次元配列を生成(Range("A2:A10")) MsgBox Join(Arr商品リスト, ",") ' ↑りんご,みかん,いちご などセル値をカンマ区切りにしたものが表示されます。
解説
すべてのセルを教科書通りのFor Each文でループし、
生成する配列へ値を格納していくコードです。
配列の要素数はRange.Cells.Countで先に調べておくことができるため、
動的配列の宣言は冒頭部分で終わらせています。
実行例で使用したJoin関数のように、1次元配列を受け取る関数へセル値を渡したいときなどに活用してください。
なお、教科書通りと言いつつ意外に知られていない罠があるのですが、
For Each cell In 対象Range.Cells
↑このコードを、
For Each cell In 対象Range
↑こう書いてしまうと、対象RangeにRows/Columnsプロパティを渡した場合に不具合が起きます。
とりあえず、
「For Each cell In Range.Cells」の.Cellsは必ずつける
と覚えておいてください。
詳細を知りたい方はこちらをどうぞ。
www.limecode.jp
空白セルは除外して1次元配列に出力する
ソースコード
Function Rangeから空セルを除外して1次元配列を生成(対象Range As Range) As Variant Dim Arr生成配列() As Variant Dim i As Long: i = 0 Dim cell As Range For Each cell In 対象Range.Cells If cell.Value <> "" Then ReDim Preserve Arr生成配列(i) As Variant Arr生成配列(i) = cell.Value i = i + 1 End If Next Rangeから空セルを除外して1次元配列を生成 = Arr生成配列 End Function
解説
出力するセルに条件が付く場合のコードです。
セル値が""出ないコードでのみ、Arrayに出力しています。
この場合は配列の要素数が不定であるため、
For Each文の中でReDim Preserveを実行し、
配列の要素数を動的に増やして処理しています。
関数のハイブリッド化
上記の2関数は、引数を使ってハイブリッド化することもできます。
Function Rangeから1次元配列を生成(対象Range As Range _ , Optional is空セルは除外する As Boolean = False) As Variant Dim Arr生成配列() As Variant Dim i As Long: i = 0 Dim cell As Range For Each cell In 対象Range.Cells If Not (is空セルは除外する And cell.Value = "") Then ReDim Preserve Arr生成配列(i) As Variant Arr生成配列(i) = cell.Value i = i + 1 End If Next Rangeから1次元配列を生成 = Arr生成配列 End Function
' 実行例 ' 空セルも格納(値が""の要素あり) Arr商品リスト = Rangeから1次元配列を生成(Range("A2:A10")) ' 空セルも格納(値が""の要素なし) Arr商品リスト = Rangeから1次元配列を生成(Range("A2:A10"), True)
このように、関数の挙動を少し変えたい場合は、
Boolean型の引数で分岐を制御する方法が使えます。
ちなみにこのコードは
「空白を無視しない場合でもRedim Preserveしていて遅くなりそう」
な気がしますが、
100,000セルから配列に変換してみたとして、
どちらも0.1~0.2秒程度で終わります。
もちろんRedim Preserveを使わない方が早いのですが、
この量のセルでこの差ですので、気にするレベルではありません。
ReDimステートメントは配列の再生成というイメージがあるかもしれませんが、
実際はちゃんと元の状態を再利用して拡張しており高速です。
もちろんReDim Preserveを使わない方が読みやすいため、
「読みやすさのためになるべく使わない」という選択は大事な場面がありますが、
速度面では気にする必要がないことは覚えておくとよいと思います。
元のセル範囲がすでに1次元配列のような1行/1列のセル範囲の場合
上記のコードはどんなRangeオブジェクトに対しても動きますが、
もし対象のRangeオブジェクトが「1行n列/n行1列のセル範囲」の場合は、
Transpose関数を使ったワンライナーで処理することもできます。
' n行1列の行方向データの場合 Arr商品リスト = WorksheetFunction.Transpose(Range("A2:A10")) ' n行1列の列方向データの場合 Arr見出しリスト = WorksheetFunction.Transpose( _ WorksheetFunction.Transpose(Range("A1:C1")))
Transpose関数は本来「行列を入れ替える」関数なのですが、
「結果が1行n列になったときは1次元配列にしてくれる」
という面白い特徴があります。
それを利用することで、セル範囲を1次元配列に1発で変換ができるということですね。
と、1発でといいましたが、この仕様は「結果が1×n」のとき限定で、
結果が「n×1」になった場合には動きません。
よって最初から「1×n」の列方向セル範囲だった場合は、
Transpose関数は2発必要になりますのでご注意ください。
なお、わざわざTranspose関数を使っている通り、
Arr商品リスト = Range("A2:A10").Value
↑このコードでは1次元配列を作ることができません。
セル範囲.Valueで取得できる配列は、たとえ1列/1行のデータでも2次元配列
になりますのでこちらもご注意ください。
プロシージャ分割の活用について
今回のコードはすべて、
「Rangeオブジェクトを渡すと1次元配列(Array)を返してくれる関数」
を作るコードを記述していました。
もちろん上記コードは関数化せずに本体マクロ内にも書けるのですが、
配列を扱うコードを書くなら、しっかり関数を切り分けることをお勧めします。
なぜかというと、配列は目に見えないメモリ上の値を扱うため、
処理を追う際、シート上の処理に比べて脳のリソースを消費するからです。
このため配列を扱うコードを書く際は、
「簡単な処理を関数内に追いやって本体マクロのコード量を減らすことで、
重要でないコードを読む時間を減らすこと」
が、より重要になってきます。
配列を処理する部分が複雑になればなるほど、ただ格納している部分が、
Dim Arr商品リスト As Variant Arr商品リスト = Rangeから1次元配列を生成(Range("A2:A10"))
こうなって読み飛ばせるようになることが大きいメリットになりますからね。
Function 慣れていない方は、このあたりで配列と一緒に勉強してみてください。
なお、Functionなどのプロシージャ間で配列を受け渡しする場合は、
返り値でも引数でもすべてVariant型で受け渡ししてしまってOKです。
VariantにしておけばあとはVBAさんがうまくやってくれますので、
このあたりは気にせずプロシージャ分割をしてみてください。