Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(Windows 10全般 : Excel 2019)
再帰呼出について
投稿日時: 23/06/08 16:07:20
投稿者: tamtam

お世話になっております。
VBAを他サイト様の練習問題で勉強中なのですが、何日考えても分からないため
知恵をお貸しいただけると幸いです。
 
問題内容ですが、
 
分類A 分類B  分類C 分類D
項目A1 項目B1 項目C1 項目D1
項目A2 項目B2 項目C2 項目D2
項目A3 項目B3 項目C3 項目D3
項目A4     項目C4 項目D4
項目A5     項目C5 項目D5
項目A6     項目C6 
項目A7
項目A8
 
以上のような表の全組み合わせを別シートに書き出すというものです。
書き出し方は「項目A1,項目B1,項目C1,項目D1」「項目A1,項目B1,項目C1,項目D2」...
のように、カンマ区切りで1行ずつ書き出します。
 
 
以下解答のコードです。
Sub sample()
  Dim ary() As String
  Dim ws1 As Worksheet
  Dim ws2 As Worksheet
  Application.ScreenUpdating = False
  Set ws1 = ActiveSheet
  Set ws2 = Worksheets.Add
  ws2.Range("A1") = "組み合わせ文字"
  With ws1
    ReDim ary(1 To .Cells(1, .Columns.Count).End(xlToLeft).Column)
    Call sample_sub(ws1, ws2, ary, 1)
  End With
  Application.ScreenUpdating = True
End Sub
 
Private Sub sample_sub(ByVal ws1 As Worksheet, ByVal ws2 As Worksheet, _
            ByRef ary() As String, ByVal n As Integer)
  Dim i As Integer
  For i = 2 To ws1.Cells(ws1.Rows.Count, n).End(xlUp).Row
    ary(n) = ws1.Cells(i, n)
    If n >= UBound(ary) Then
      '横の繰り返しが終わったので出力
      With ws2
        .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row + 1, 1) = Join(ary, ",")
      End With
    Else
      'ここで自分自身を呼び出しています。
      Call sample_sub(ws1, ws2, ary, n + 1)
    End If
  Next
End Sub
 
ステップインで確認していくと、項目A1,項目B1,項目C1,項目D5まで出力された時点で
iは7、nは4となり、そこまでは理解できるのですが、その後iが2、nが3と変化する理由が分かりません。
また、その際にEnd Sub→End Ifと移動する理由も分かっておりません。
お手数ですがどなたかご教示ください。どうぞよろしくお願いいたします。
 
 

回答
投稿日時: 23/06/08 16:56:31
投稿者: QooApp

引用:
その後iが2、nが3と変化する理由が分かりません。
また、その際にEnd Sub→End Ifと移動する理由も分かっておりません。

 
関数からどのように関数へ飛んでいるかイメージすることが大切です。
ここでは
 
sample > @sample_sub > Asample_sub > Bsample_sub > Csample_sub(D列5行処理)
 
というようにsample_sub関数自体がsample_sub関数へアクセスしていることです。
 
F8キーで1行ずつ処理状況を確認される場合、「sample_sub」の関数に入った回数を数えてみてください。
 
Private Sub sample_sub(ByVal ws1 As Worksheet, ByVal ws2 As Worksheet, ByRef ary() As String, ByVal n As Integer)
 
にカーソル位置が来た回数を数えるということです。
i=7(5行書き出し)が済んだあと、Bのsample_sub関数の中間地点に帰ってくるので、Bsample_sub自体はまだ処理が完了していません。

回答
投稿日時: 23/06/08 17:09:45
投稿者: たらのり

こんにちは
 
# 語彙力が貧しく、ちょっと説明が難しですが
 
> その際にEnd Sub→End Ifと移動する理由も
 
呼び出したプロシジャの処理が終わると、(通常は)プロシジャの
次の命令に制御が移ります。
 
End Sub→End If の前者(End Sub)は呼び出されたプロシジャの
末尾に達した時点で、後者(End If)は呼び出し元が呼び出した
プロシジャの次のステップ(命令・行)になります。
 
 
> その後iが2、nが3と変化する理由が分かりません
 
i の値は、呼び出されたプロシジャごとに個別に保持されます。
 
皿を重ねるイメージに置き換えると、
 
sample_sub は 1枚の皿で、i は皿ごとに別の値として載っています。
 
最初の皿(sample_sub)の i が 2 の状態で 2枚目の皿(sample_sub)を
重ね、2枚目の皿の上の i は自由に変化(2、3、4……)させることが
できますが、この間、1枚目の皿の i の値は変化しません。
 
# 名前は同じ i ですが、呼び出しレベル固有の値になります。
 
2枚目の皿に用がなくなって取り除くと、1枚目の皿が残りますが、
1枚目の皿に載っている i は 2枚目の皿を載せる前の状態(2)のままです。
 
これを 3枚、4枚…… と。
 
再帰呼び出しでローカル変数は、それぞれ別の皿(※)に載っていて、
皿を重ねたときに操作されるのは一番上の皿のローカル変数のみで、
皿を取り除けば(呼び出し元のプロシジャに戻れば)、残った一番上の
皿の、呼び出し前のローカル変数の値(状態)に戻ります。
 
※ スタックといったりします

回答
投稿日時: 23/06/08 17:18:26
投稿者: たらのり

追伸
 
プロシジャの引数(n など)もローカル変数と考えてよいです。
 

回答
投稿日時: 23/06/08 18:51:08
投稿者: WinArrow

再帰呼出しの場合、組み方に寄りますが、
変数の値を検証する前に、挙動を検証した方がよいと思います。
 
再帰呼出しを使わない場合のコードを紹介します。
結果は、同じになります。
 

Sub test()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim A As Long, B As Long, C As Long, D As Long
Dim Ary
    
    Set ws1 = Sheets("Sheet1")
    Set ws2 = Sheets.Add(after:=Sheets(1))
    With ws1
        ReDim Ary(1 To 4)
        For A = 2 To .Cells(.Rows.Count, "A").End(xlUp).Row
            Ary(1) = .Cells(A, "A").Value
            For B = 2 To .Cells(.Rows.Count, "B").End(xlUp).Row
                Ary(2) = .Cells(B, "B").Value
                For C = 2 To .Cells(.Rows.Count, "C").End(xlUp).Row
                    Ary(3) = .Cells(C, "C").Value
                    For D = 2 To .Cells(.Rows.Count, "D").End(xlUp).Row
                        Ary(4) = .Cells(D, "D").Value
                        ws2.Cells(ws2.Rows.Count, "A").End(xlUp).Offset(1) = Join(Ary, ",")
                    Next
                Next
            Next
        Next
    End With
End Sub

回答
投稿日時: 23/06/08 21:50:20
投稿者: たらのり

こんばんは
 
ん〜〜〜,やっぱり説明が難しいですね……
 
プロシジャ(sample_sub)を呼び出すたびに皿を 1枚ずつ重ねていき,
プロシジャからもどるときに皿を 1枚ずつ取り除いていくイメージです。
 
ムズい,,

回答
投稿日時: 23/06/08 22:44:37
投稿者: WinArrow

再帰処理で答えよ
という問題なんですか?
 
再帰処理は、フォルダ階層を解析して、各フォルダ内のファイルパスを文字列で作成するのに
使われますが、
列A〜列Cをフォルダ階層、列Dをファイル名と考えると、
解答のコードは、少し違うように思います。
 
1回目:列Aを配列に入れる
2回目:列Bを配列に入れる。しかし、列Aの最初からループしている。・・列Aのループは必要ですか?
3回目:列Cを配列に入れる。ここでも列Aの最初からループしている。・・列Aのループは必要ですか?
 
Sample_subの組み立て方に、疑問があります。

投稿日時: 23/06/09 06:29:35
投稿者: tamtam

QooApp様
たらのり様
WinArrow様
  
皆様ご回答ありがとうございます。理解が追い付かず、少しずつ拝読しております。
練習問題の解答には再帰呼び出し以外の「再帰呼び出しを使わなくてもこんな方法もあるよ」
といった解答も載っておりますが、メインは再帰呼び出しでの解答のようです。
皆様のご回答何度も読ませていただきます。
 
また、QooApp様の仰っている
「i=7(5行書き出し)が済んだあと、Bのsample_sub関数の中間地点に帰ってくるので、Bsample_sub自体はまだ処理が完了していません。」
についてですが、中間地点に帰ってくるというのは何故なのでしょうか。
勉強不足で申し訳ありません。

回答
投稿日時: 23/06/09 06:34:26
投稿者: simple

もっと簡単な下記の例を取り上げます。

分類A 分類B 分類C
A1    B1    C1
A2    B2    C2

以下のような形で処理がされると思います。
sample_subは、線で囲まれたブロックに対応すると考えて下さい。
このような「入れ子」になっているわけです。
じっくり見ていると分かるような気がしませんか?
┌-----------------------┐
│  A1    ┌-----------┐│
│        │B1  ┌---┐││
│        │    │C1 │││
│        │    │C2 │││
│        │    └---┘││
│        │B2  ┌---┐││
│        │    │C1 │││
│        │    │C2 │││
│        │    └---┘││
│        └-----------┘│
│  A2    ┌-----------┐│
│        │B1  ┌---┐││
│        │    │C1 │││
│        │    │C2 │││
│        │    └---┘││
│        │B2  ┌---┐││
│        │    │C1 │││
│        │    │C2 │││
│        │    └---┘││
│        └-----------┘│
└-----------------------┘

回答
投稿日時: 23/06/09 09:31:32
投稿者: WinArrow

 
最初の質問時の説明に戻りますが、
 

引用:

項目A1,項目B1,項目C1,項目D5まで出力された時点で
iは7、nは4となり、

この中の
>iは7、
は、確認時点が違っていると思います。
>項目D5まで出力された時点
では、「i=6」のはず。(iは、2から始まっているので、5件おいうことです)
For〜Nextのループでは、
Nextが抜けると、実際に処理した時点の次を指しています。
 
>その後iが2、nが3と変化する理由
確認する時点を変えてみればわかると思います。
確認時点の「推奨」は
ws2のセルに格納したところで、Debug.priint 後「Stop」

回答
投稿日時: 23/06/09 12:44:01
投稿者: simple

それでもまだデータが大きいので、

分類A 分類B 分類C
A1    B1    C1
A2    B2    C2
くらいの小さいもので、最初から最後までステップ実行させてみると
どのような順序で処理が実行されるかがわかるように思います。
それが理解を進めるうえで、一番効果的かと思います。
 
== 言いたいことは以上です。
== 以下は、補足説明ですが、場合によっては混迷を増すかもしれません。
== 複数のプロシージャが同時に実行される有様を理解する足しになるかもしれません。
    
コード実行を途中で止めた段階で、
[表示] -「呼び出し履歴」
というのをクリックしてみると、
現時点でプロシージャがどのように呼ばれているかがわかります。
 
当初の例で、
> 項目A1,項目B1,項目C1,項目D5まで出力された時点で
これをもう少し進めて、Next(ここでiが7になる)を終えて、sample_subが終了したあとで
[表示] -「呼び出し履歴」を使うと、
VBAProject1.Module1.sample_sub
VBAProject1.Module1.sample_sub
VBAProject1.Module1.sample_sub
VBAProject1.Module1.Sample
と表示されます。
これが現在呼び出されているプロシージャたちです。
・一番下のものが、最初に実行したメインのプロシージャです。
・その上にあるのが、A列を処理するためのsample_subプロシージャの呼び出しです。
・その上にあるのが、B列を処理するためのsample_subプロシージャの呼び出しです。
・その上にあるのが、C列を処理するためのsample_subプロシージャの呼び出しです。
   (名前だけでは区別できませんが)
 
・新たに呼ばれたプロシージャは、一番上に積まれる。
・最初に終了するのは、一番に積まれているものです。(古いものが先に終了することはない)
ですので、こうしたものは「関数スタック」と呼ばれているようです。

回答
投稿日時: 23/06/09 23:48:14
投稿者: たらのり

こんばんは
 
# 酒気帯びです
 
(プロシジャ内の)ローカル変数の雰囲気は理解されているでしょうか。
 
呼び出されたプロシジャのローカル変数は,呼び出し元のプロシジャの
管理外で自由に存在しています。
 
再帰呼び出し(直接的・間接的に自分自身を呼び出す)では,印刷したコードの
字面を追っていると,ローカル変数(プロシジャ内で宣言された変数のこと)が
次々に上書きされてしまうようで……
 
 
普通のプロシジャの呼び出しの推移は,次のようだと思います。
caller は a) から始まって d) で終わるのですが,途中で callee を
呼び出しています。
ここでは, 1) の位置で callee を呼び出していて(Call callee のように),
callee の処理は b) から c) へ進み,処理が終わると呼び出し元へ戻ります。
戻る位置は,2),「Call callee」の次の行です。
 

  (caller)     (callee)
     │           │ 
   ┌┴┐   +-→┌┴┐   
   │a)│   |   │b)│   
   │  │   |   │  │   
   │1)│---+   │  │   
   │2)│←-+   │  │   
   │  │   |   │  │   
   │  │   |   │  │   
   │d)│   |   │c)│    
   └─┘   +---└─┘   

再帰呼び出しの場合は先ほども述べたように,紙の上で眺めていると同じ場所を
なぞり書きして,同じ変数を上書きしているように見えますが,あらたに別の
プロシジャを呼び出しているのと同じなのです(ホントかな):
 
                階層1        階層2        階層3
  (sample)   (sample_sub) (sample_sub) (sample_sub)
     │           │           │           │ 
   ┌┴┐   +-→┌┴┐   +-→┌┴┐   +-→┌┴┐   
   │a)│   |   │b)│   |   │c)│   |   │d)│   
   │  │   |   │  │   |   │  │   |   │  │   
   │1)│---+   │2)│---+   │3)│---+   │  │    
   │6)│←-+   │5)│←-+   │4)│←-+   │  │   
   │  │   |   │  │   |   │  │   |   │  │     
   │  │   |   │  │   |   │  │   |   │  │  
   │h)│   |   │g)│   |   │f)│   |   │e)│    
   └┬┘   +---└─┘   +---└─┘   +---└─┘   
     │
    (end)

問題の i や n は,呼び出し階層ごとに独立して存在しています。
繰り返しになりますが,階層1 の i や n は,階層2 より深い階層の処理を
している間は,その階層で i や n をいくら操作しても,階層1 の
それらは変化しません。別の階層(皿)にある別の変数なので。
 
 
「自分自身」を呼び出すのが再帰ですが,ローカル変数(メモリ)は共有して
おらず,まったく新しい空間へプロシジャや変数を展開しているように
イメージしていただけると(もう限界です
サイ○リヤのピザは,100円増しで Wチーズにするのがオススメです。
 
 

回答
投稿日時: 23/06/10 06:53:10
投稿者: simple

少し先走り過ぎたようなので、少し戻ってみます。

引用:
ステップインで確認していくと、項目A1,項目B1,項目C1,項目D5まで出力された時点で
iは7、nは4となり、そこまでは理解できるのですが、その後iが2、nが3と変化する理由が分かりません。
また、その際にEnd Sub→End Ifと移動する理由も分かっておりません。

既に頂いている回答と重複しますが、改めて最初の質問にお答えします。
 
(1)
End Subで呼ばれたサブプロシージャーの処理が終了したのですから、
これを呼び出した Call sample_sub(ws1, ws2, ary, n + 1)
の次の行にある、End Ifに移動するのは自然のことです。
(これは、普通のプロシージャーと全く同じです。
  呼ばれた側の処理が終了すれば、呼び出し元の呼び出し位置の次の行に制御が移動するのです。
  もし、普通のプロシージャーに慣れていなければ、その理解が先決です。)
ここはいいでしょうか?
 
(2)
また、「iは7、nは4となり」とありますが、この i は呼ばれた側のプロシージャーの変数です。
呼ばれた側のプロシージャーで使用する i は、そのプロシージャー内のローカル変数ですから、
プロシージャーが終了すれば、それは消えます。
 
それを呼び出したのは、n= 3 の処理のためのsample_subです。
そこに戻ったときの、 i や n は呼び出した側(親)のプロシージャの変数ですから、
同じiでも、先ほどのものとは全く別のもの(変数)です。
ですから、戻った当初は 、呼び出す前と同じ i=2 , n=3 で良いのです。
 
この二点についてはよろしいですか?

回答
投稿日時: 23/06/10 22:56:54
投稿者: WinArrow

質問者さんは、格闘しているのでしょうか?
 
sample_subを通常のサブルーチンに分解したコードを紹介します。
Sub1〜Sub4 が、分解したものと考えてください。
「i」と「n」を別々に考えるのではなく、
「i」が「n」に付随していると考えると分かりやすいと思います。
 
変数:iを各々で定義しています。

Option Explicit

Dim Ary
Dim ws1 As Worksheet, ws2 As Worksheet

Sub sub0()
Dim i As Long
Dim n As Long
    
    Set ws1 = Worksheets("Sheet1")
    Set ws2 = Worksheets.Add(after:=Sheets(1))
    With ws1
        n = 1
        ReDim Ary(1 To 4)
        Call sub1(n:=n)
    End With
End Sub

Sub sub1(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        Call Sub2(n:=n + 1)
    Next
End With
End Sub

Sub Sub2(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        Call sub3(n:=n + 1)
    Next
End With
End Sub

Sub sub3(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        Call sub4(n:=n + 1)
    Next
End With
End Sub

Sub sub4(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        ws2.Cells(ws2.Rows.Count, "A").End(xlUp).Offset(1) = Join(Ary, ",")
    Next
End With
End Sub

 
 
 

投稿日時: 23/06/11 07:08:40
投稿者: tamtam

simple様
WinArrow様
たらのり様
 
何度も丁寧にご教示いただきまして本当にありがとうございます。
お返事が追い付かず申し訳ありません。
皆様のご回答を少しずつ読み込ませていただいております。
分かった気がする、いや全然分かってないを繰り返しているので
何度も読み返しながらもう暫く格闘させていただきます。本当にありがとうございます。

回答
投稿日時: 23/06/11 07:48:38
投稿者: simple

・sample_subは一つだけではなく、名前は同じだが別のものがいくつも呼び出されている
  ことの理解が、スタートです。
 
・また、同じ 変数i といっても、複数のsample_subで使われるでは、全く別のものである
  ことも重要です。
 
・では、4つの列の文字列はどうやってつなげているか、という話になります。
 
・これは、変数aryをByRef(参照渡し)という方法で、引数を受け渡している所に秘密が
  あります。
  つまり、一度作成したary変数がメモリー上にある位置(アドレス)を受け渡しているので、
  4つのsample_subで、共通の変数に書き込みや読み込むをしているのと同じことになります。
 (大雑把に言っています)
・このことで、aryという変数を、複数のプロシージャで操作することが可能になって
  いるのです。
  つまり、1列目の要素の更新、2列目の要素の更新、等々が、別々のサブプロシージャで
  更新されているのです。
 (値渡し(ByVal)と参照渡し(ByRef)の違いといった話は、ここで全て説明しきれませんので、
   別途学習してみてください。)
 
 23/06/09 06:34:26投稿者: simpleの発言をもう少し丁寧に書くとこうなります。
 >もっと簡単な下記の例を取り上げます。

 分類A 分類B 分類C
 A1    B1    C1
 A2    B2    C2

 この場合の処理の流れは以下のようになります。参考にしてください。
▼sample_sub(ary, 1)
|   n=1, i= 2(ary (1)="A1")
|            ▼sample_sub(ary, 2)
|            |      n=2, i= 2(ary (2)="B1")
|            |                ▼sample_sub(ary, 3)
|            |                 |   n=3, i=2(ary(3)="C1")     => A1,B1,C1 を出力
|            |                 |   n=3, i=3(ary(3)="C2")     => A1,B1,C2
|            |                ▲End Sub
|            |      n=2, i= 3(ary(2)="B2")
|            |                ▼sample_sub(ary, 3)
|            |                 |   n=3, i=2(ary(3)="C1")     => A1,B2,C1
|            |                 |   n=3, i=3(ary(3)="C2")     => A1,B2,C2
|            |                ▲End Sub
|            ▲End Sub
|   n=1 i= 3(ary(1)="A2")
|            ▼sample_sub(ary, 2)
|            |      n=2, i= 2(ary(2)="B1")
|            |                ▼sample_sub(ary, 3)
|            |                 |   n=3, i=2(ary(3)="C1")     => A2,B1,C1
|            |                 |   n=3, i=3(ary(3)="C2")     => A2,B1,C2
|            |                ▲End Sub
|            |      n=2, i= 3(ary(2)="B2")
|            |                ▼sample_sub(ary, 3)
|            |                 |   n=3, i=2(ary(3)="C1")     => A2,B2,C1
|            |                 |   n=3, i=3(ary(3)="C2")     => A2,B2,C2
|            |                ▲End Sub
|            ▲End Sub
▲End Sub

回答
投稿日時: 23/06/11 08:06:19
投稿者: simple

お尋ねにも回答をもらえなかったので、メモしておいたものを書いて、私の区切りとします。
 
回答はこちらでしょうか?
https://excel-ubara.com/excel-answer/EXCELVBA624A.html
 
項目種類数(列数)が可変なので再帰を使っているようです。
# 再帰の学習のためには、再帰を使った階乗の計算とか、再帰を使ったフィボナッチ数の計算とか
# ステップを踏んで学習するのが普通です。いきなりこれを理解するのはバーが高いです。
# ネットの記事は、編集者が別途いる訳でも無いので、その辺の配慮が多少欠けることもあります。
# ですから、私は書籍による学習を推奨しています。
 
実務上の話であれば、この例では4重のループを書けばよいだけだと思います。
それが6個になったら6に増やせばいいだけでしょう。
 
どうしても可変だというのであれば、列数が少し多めのコードを用意しておきます。
余る部分については、候補が一つだけのデータにして、
出来上がったものに関して、余分な文字列を一括して""に置換するだけです。
それほど頻繁に出てくるような話でもないように思います。
 
項目種類が可変の場合、どうしても再帰を使用しないとできないというわけでもなく
再帰を使わなくても書くことはできます。
 
例えば、
位取り記数法のようなロジックで書けます。つまり、
  1 →(1,1,1,1)
  2 →(1,1,1,2)
  ・・・
  5 →(1,1,1,5)
  6 →(1,1,2,1)
  ・・・
720 →(8,3,6,5)
のように、1から720までの整数を、
各桁の基数を8,3,6,5としたときの表示にそれぞれ変換します。
変換したら、それを各列の文字列に変換して、それを","でJoinするだけです。
 
【参考コード】

Sub main()
    Dim ws      As Worksheet
    Dim wsOut   As Worksheet
    Dim colNum  As Long
    Dim myCount As Long
    Dim myIndex As Variant
    Dim s       As String
    Dim j       As Long
    Dim k       As Long

    Set ws = Worksheets("Sheet1")
    Set wsOut = Worksheets("Sheet2")

    wsOut.Range("A1").CurrentRegion.ClearContents
    
    colNum = ws.Cells(1, Columns.Count).End(xlToLeft).Column
    '各列のデータ数(rowNums配列)
    ReDim rowNums(1 To colNum) As Long
    For j = 1 To colNum
        rowNums(j) = ws.Cells(Rows.Count, j).End(xlUp).Row - 1
    Next

    '作成すべきデータ個数
    myCount = Application.Product(rowNums)

    '組み合わせの作成
    ReDim temp(1 To colNum)
    For k = 1 To myCount
        myIndex = mySplit(k, rowNums) '位取り記数に変換
        For j = 1 To colNum
            temp(j) = ws.Cells(1 + myIndex(j), j)
        Next
        wsOut.Cells(k + 1, "A") = Join(temp, ",")
    Next
End Sub

Function mySplit(k As Long, ar) As Variant
    Dim jmax    As Long
    Dim t       As Long
    Dim j       As Long

    jmax = UBound(ar)
    ReDim ans(1 To jmax)
    t = k - 1
    For j = jmax To 1 Step -1
        ans(j) = t Mod ar(j) + 1
        t = t \ ar(j)
    Next
    mySplit = ans
End Function

回答
投稿日時: 23/06/11 08:17:03
投稿者: WinArrow

>i = 7 & n = 4
>i = 2 & n = 3
 
のターミングの説明
 
私のレス(回帰処理を分解しt例)で説明
 
>i = 7 & n = 4
は、

Sub sub4(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        ws2.Cells(ws2.Rows.Count, "A").End(xlUp).Offset(1) = Join(Ary, ",")
    Next
    Debug.Print "SUB4-STOP i=" & i & " n=" & n
    Stop
End With
End Sub

のSUB4 のEnd Sub (終了時)の直前
この時の「i」は、列D用なので、7行目を指しています。
 
>i = 2 & n = 3
は、
Sub sub3(n)
Dim i As Long
With ws1
    For i = 2 To .Cells(.Rows.Count, n).End(xlUp).Row
        Ary(n) = .Cells(i, n).Value
        Call sub4(n:=n + 1)
        Debug.Print "SUB3-STOP i=" & i & " n=" & n
        Stop
    Next
End With
End Sub


SUB4 から戻ったところです。
このタイミングでは、3列目の「i」はカウントアップされていないので、
2行目のままです。
次のステップ(NEXT)でカウントアップされて、3行目が処理されます。
 
 

回答
投稿日時: 23/06/11 14:01:43
投稿者: たらのり

こんにちは
 
ある本に次のようなことが書かれていました:
 

  再帰関数というのは自転車に乗るようなもので,
  初めて乗るときにはとても乗りこなせるようには
  思えないが,練習をすればいつか自然な行為になる。

 
僕にとっても最初はとても理解不能だったし,いまでも
自転車に乗るほどに簡単には行かないことが多いです。
 
simple さんもおっしゃていますが,まずはもう少し
簡単な例で再帰の感覚を掴んでみるとよいと思います。
 
自転車の例えのとおり慣れです。練習あるのみです。
 
 
# 位ごとに基数がことなる表現には,思わず感嘆の声が
# 漏れてしまいました……
 

投稿日時: 23/06/12 15:09:51
投稿者: tamtam

ご回答いただいた順で失礼いたします。
 
 
simple様
私の理解度の低さをくみ取って様々な角度からご回答いただき本当にありがとうございます。また、6/10にご回答いただいた件についてすぐにお答えする事が出来ずに申し訳ありませんでした。
(1)についてですが、Call sample_sub(ws1, ws2, ary, n + 1)が呼び出したから処理が終了して次の行へ、という当然の事を、全体の複雑さに頭が混乱して見落としておりました。また(2)に関して、後日ご回答いただいた「名前は同じだが別のものがいくつも呼び出されている」という文言で理解できました。
記載していただいた処理の流れを何度も繰り返し拝見し、データを小さくしてステップ実行しようやく流れが頭に入ってきました。
練習問題は載せていただいたリンクのものです。仰る通り、ステップを踏んで学習出来ていないと痛感しましたので書籍を用意します。多岐にわたるアドバイス、本当にありがとうごさいます。
 
WinArrow様
再帰呼び出しを使わないコードのご紹介、ありがとうございます。組み合わせ文字がなぜこの順で変化していくのかも上手く理解していなかったので、記載いただいたコードをステップ実行することでよくよく理解できました。また、分解したコードについてもご記載いただきありがとうございます。iがnに付随する、という状況を中々頭に入れる事が出来なかったのですが、少しずつ理解できつつあります。本当にありがとうございます。
 
たらのり様
図解による分かりやすいご説明をありがとうございます。ローカル変数については理解できているつもりでいましたが、階層という概念をまったく持っていなかったので、皿を重ねていくという表現が理解するのに非常に助けになりました。ご紹介いただいた本の文言のように、乗りこなせる自信がまだまだ持てませんが、勉強と練習を重ねていきます。データを減らした表で繰り返しステップ実行している内に、流れが掴めてきたように思います。お優しい言葉をありがとうございます。学習を重ねていきます。
 
 
皆様お忙しい中、丁寧にコードを書いていただき、また図解していただき、根気よくアドバイスしてくださって本当にありがとうございます。完全に理解できたとはまだまだ言えませんが、理解度はぐっと深まりました。
皆様のご回答を繰り返し拝見しながら学習を続けます。感謝の気持ちでいっぱいです。本当にありがとうございました。