和風スパゲティのレシピ

日本語でコーディングするExcelVBA

ジャグ配列(配列の配列)を二次元配列に変換する

配列の中に配列が入っている、いわゆるジャグ配列を、
ただの二次元配列に変換するコードを紹介します。

ソースコード

' 二次元配列←ジャグ配列
Function GetArr二次元配列←ジャグ配列(Arrジャグ配列 As Variant) As Variant
    
    Dim i As Long, j As Long ' ジャグ配列のインデックス
    Dim R As Long, C As Long ' 二次元配列のインデックス

    Dim Arr出力配列()

    Dim 最大要素数 As Long: 最大要素数 = 0

    ' 配列内の配列の中で最も大きい要素数を取得
    For i = LBound(Arrジャグ配列) To UBound(Arrジャグ配列)
        
        Dim 現要素数 As Long: 現要素数 = Count配列の要素数(Arrジャグ配列(i))
        If 現要素数 >= 最大要素数 Then 最大要素数 = 現要素数

    Next
    
    ' 出力二次元配列を準備
    ReDim Arr出力配列(1 To Count配列の要素数(Arrジャグ配列), 1 To 最大要素数)
    
    ' 全要素を代入
    R = 1
    For i = LBound(Arrジャグ配列) To UBound(Arrジャグ配列)
        C = 1
        For j = LBound(Arrジャグ配列(i)) To UBound(Arrジャグ配列(i))
            
            Arr出力配列(R, C) = Arrジャグ配列(i)(j)
            
            C = C + 1
        Next
        R = R + 1
    Next
    
    GetArr二次元配列←ジャグ配列 = Arr出力配列

End Function

' 配列の要素数取得
Function Count配列の要素数(Arr, Optional 次元 = 1) As Long
    Count配列の要素数 = UBound(Arr, 次元) - LBound(Arr, 次元) + 1
End Function

解説

ジャグ配列中のすべての配列要素をFor文2つで二重ループし、

Arr出力配列(R, C) = Arrジャグ配列(i)(j)

このコードで二次元配列へ代入しているコードです。


代入を始める前に箱となる二次元配列の大きさを取得する必要があるため、
一旦ジャグ配列内の配列をループして最大要素数を取得しています。


中身の配列の要素番号のズレをそろえているので、
たとえArr(1 To 5)、Arr(2 To 6)、Arr(3 To 7)…だったとしても、
すべて「1 To 5」にそろえた状態で二次元配列に出力します。


私はこの処理を「セルへの一括出力用」に使用することが多いので、
生成する二次元配列のインデックスは1始まりとしました。

0始まりがお好みの方は、
「ReDimの指定」と「RCの初期値」を0に変えてご使用ください。

使用例:Itemに配列が入ったDictionaryをセルに出力

ジャグ配列を自分で作る機会はめったにないと思いますが、
自然界?に存在するジャグ配列としては配列 In Dictionary が挙げられます。


DictionaryのItemに配列を入れたとき、
全Itemを配列で取得するItemsメソッドはまさにジャグ配列
を返します。


これをセルに出力する際、全keyを1行ずつ出力しては遅くなりますので、
一旦二次元配列にして一括出力する際に本コードは利用できます。

' 各keyごとに1行ずつ出力するコード
Dim key
R = 2
For Each key In Dic.Keys
    Cells(R, 1).Resize(1, Count配列の要素数(Dic(key))) = Dic(key)
    R = R + 1
Next

' ↓ セル出力を二次元配列一回にすることで高速化
Dim Arr
Arr = GetArr二次元配列←ジャグ配列(Dic.Items)
Cells(2, 1).Resize(Ubound(Arr, 1), Ubound(Arr, 2)) = Arr
    ' ※ Ubound - Lbound + 1 をしなくていいように開始番号を1にしていました。

これでセルへのアクセス回数が1回になり高速化されます。


さらには「二次元配列→セル出力」も関数にしてしまうと、
標準モジュールを以下のように1行にすることもできます。

' 標準モジュールでの実行
Call Itemが一次元配列のDictionaryをセルに出力する(Range("A2"), Dic)

' Dictionary(Item:配列) → セル
Sub Itemが一次元配列のDictionaryをセルに出力する(出力始点セル As Range, Dic As Dictionary)
    
    Dim Arr: Arr = GetArr二次元配列←ジャグ配列(Dic.Items)
    Call 二次元配列をセルに出力する(出力始点セル, Arr)
    
End Sub

' 二次元配列 → セル
Sub 二次元配列をセルに出力する(出力始点セル As Range, Arr出力配列 As Variant)

    出力始点セル.Resize(Count配列の要素数(Arr出力配列, 1) _
                              , Count配列の要素数(Arr出力配列, 2)).Value = Arr出力配列

End Sub

' 配列の要素数取得
Function Count配列の要素数(Arr, Optional 次元 = 1) As Long
    Count配列の要素数 = UBound(Arr, 次元) - LBound(Arr, 次元) + 1
End Function

Callの親子関係がひ孫まで行くネストが深いコードですが、
書きやすく読みやすいコードになっているんじゃないかなと思います。


「配列の中に配列」のような親子関係が複雑なコードで、
さらに要素数取得のUbound-Lbound+1まで生で書くと大変なことになります。

こまめに汎用関数にしておくと書くのも簡単で頭も整理できるので、
配列まわりの処理はしっかり整理整頓しておきましょう。