t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA ParamArrayで再帰する?

※今回の記事はAriawaseというモジュールを使う前提のコードですので、一般的な環境で実行してもコードは動きません。

Ariawaseについてはこちらをご参照ください。
github.com

とあるブログ(既に閉鎖)で、ParamArrayを使った関数で再帰したいという変なお題が出されたので回答。

ParamArrayで再帰なんて、何に使うんだろうか。

いちおう、出来た。
多分以下で答えになっているはず。

Option Explicit

Sub Sample20150630()
    Dim arr: arr = level(0, "a", "b", "c")
End Sub

Private Function level(ByRef i As Variant, ParamArray arg() As Variant) As Variant
    
    Debug.Print "Level " & i
    IncrPst i
    
    Dim v
    For Each v In arg
        Debug.Print Dump(v) & vbNewLine
    Next v
    
    If i = 10 Then
        End
    Else
        'もしParamArrayの1つ目が配列なら、その配列の要素1を引数に渡す。
        If IsArray(arg(0)) Then
            level i, arg(0)
        Else
            'そうでなければ、そのまま渡す。
            level i, arg
        End If
    End If
    
End Function


再帰で躓いたときは、主な処理を一旦消してしまって再帰構造に集中した方が分かりやすい。
さらに再帰も外してシンプルに考えると、ただの関数呼び出しに展開できる。

Sub Sample20150630()
    Dim arr: arr = level_A("a")
End Sub

Private Function level_A(ParamArray arg() As Variant) As Variant
    level_B arg
End Function

Private Function level_B(ParamArray arg_b() As Variant) As Variant
    level_C arg_b
End Function

Private Function level_C(ParamArray arg_c() As Variant) As Variant
    Debug.Print Dump(arg_c)
End Function

ParamArrayは複数の引数を配列として受け取るので、たとえば配列Aを渡せば配列Aを第一要素とする配列ができあがる訳である。
こうして配列がネストされていくことになるので、それを回避したい場合、第一引数が配列だった場合はその第一要素だけ渡してやれば良い。


最初に挙げた例ではvのDump結果はArray("a","b","c")と配列化されてしまっているが、配列化を避けたければ以下のようにすればできる。

Private Function level(ByRef i As Variant, ParamArray arg() As Variant) As Variant
    
    Debug.Print "Level " & i
    IncrPst i
    
    Dim v
    
    Dim arg2: arg2 = arg
    If IsArray(arg(0)) Then arg2 = arg(0)
    
    For Each v In arg2
        Debug.Print Dump(v) & vbNewLine
    Next v
    
    If i = 10 Then
        End
    Else
        level i, arg2
    End If
    
End Function
7/1 追記

mmYYmmddさんから以下のコメントをいただいたので追記。

確認してませんけど、配列を複数渡した場合でも大丈夫でしょうか?

以下、複数配列に対応したもの

Sub Sample20150701()
    Dim arr: arr = level(0, Array("a", "b", "c"), Array("d", "e"), "f", "g")
End Sub

Private Function level(ByRef i As Variant, ParamArray arg() As Variant) As Variant
    
    Debug.Print "Level " & i
    i = i + 1
    
    Dim arg2: arg2 = arg
    If UBound(arg) < 1 Then arg2 = arg(0)
    
    Dim v
    For Each v In arg2
        Debug.Print Dump(v) & vbNewLine
    Next v
    
    If i = 10 Then
        End
    Else
        level i, arg2
    End If
    
End Function

まぁ、Level関数が何も返してないので、メインルーチンのarrに意味のあるデータは入らない。
なぜFunctionで再帰させたのかは分からないが、ネストは避けられているのでこれで元記事の要望とは合ってるんじゃないかと思われる。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。