Excel (VBA) |
![]() ![]() |
(Windows 10全般 : Excel 2019)
再帰呼出について
投稿日時: 23/06/08 16:07:20
投稿者: tamtam
|
---|---|
お世話になっております。
|
![]() |
投稿日時: 23/06/08 16:56:31
投稿者: QooApp
|
---|---|
引用: 関数からどのように関数へ飛んでいるかイメージすることが大切です。 ここでは 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
投稿者: たらのり
|
---|---|
こんにちは
|
![]() |
投稿日時: 23/06/08 17:18:26
投稿者: たらのり
|
---|---|
追伸
|
![]() |
投稿日時: 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
投稿者: たらのり
|
---|---|
こんばんは
|
![]() |
投稿日時: 23/06/08 22:44:37
投稿者: WinArrow
|
---|---|
再帰処理で答えよ
|
![]() |
投稿日時: 23/06/09 06:29:35
投稿者: tamtam
|
---|---|
QooApp様
|
![]() |
投稿日時: 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
|
---|---|
引用: この中の >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) (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
|
---|---|
少し先走り過ぎたようなので、少し戻ってみます。
引用: 既に頂いている回答と重複しますが、改めて最初の質問にお答えします。 (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
|
---|---|
質問者さんは、格闘しているのでしょうか?
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様
|
![]() |
投稿日時: 23/06/11 07:48:38
投稿者: simple
|
---|---|
・sample_subは一つだけではなく、名前は同じだが別のものがいくつも呼び出されている
分類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
|
---|---|
お尋ねにも回答をもらえなかったので、メモしておいたものを書いて、私の区切りとします。
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
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
投稿者: たらのり
|
---|---|
こんにちは
|
![]() |
投稿日時: 23/06/12 15:09:51
投稿者: tamtam
|
---|---|
ご回答いただいた順で失礼いたします。
|