VBAのプログラミングで繰り返しのFor~Next文をネストするコツを解説します。人に教える中で普通のFor文が書けてもFor文のネストの話になると苦労した人がいrるためです。For~Next文の書き方の解説は世の中にあふれるほどありますが、ネストのコツを解説したものは一度しか見たことがないので、この記事が参考になればと思います。
目次
- For~Next文の構造
- For~Next文における2通りのネストの考え方
- サンプル題材:4桁の数字のパスワードを破る
- 簡単な文法を組み合わせて複雑な処理をすると言うこと
- For~Next文をネストするコツまとめ
For~Next文の構造
まずは普通のFor~Next文の構造を確認します。文法は以下の通りです。
For文は、変数をある数から一定の値ずつ変化させ、決めた数値に至るまでForとNextの間に書かれた作業を繰り返し、ある数に達するとループを抜けます。Step 数値の部分を省略すると1ずつカウントアップします。Nextの次に書いてあるiは省略可能ですが、複雑なプログラムになってくるとどのNextがどのForに対応しているのかわかりづらづらくなるので、省略せずに書くのがオススメです。For~Next文の簡単なサンプルコードを3つ挙げます。
なお、Debug.PrintはVBEの表示⇒イミディエイトウィンドウで表示させられるイミディエイトウィンドウにその内容を表示させるというメソッドです。なぜ親しみのあるMsgBoxを使わないかと言うと、いちいち【OK】を押すのが面倒だからです。
Sub For_Next文サンプル1() Dim i As Long For i = 3 To 5 Debug.Print "iの値は" & i; "です。" Next i End Sub
このサンプルではStepの記述を省略しているので1ずつカウントアップします。iを行番号に使う事によって表の操作などに使う事ができ、よく見かける使い方です。
<実行結果>
Sub For_Next文サンプル2() Dim i As Long For i = 10 To 1 Step -2 Debug.Print "iの値は" & i; "です。" Next i End Sub
次はStepに-2を設定した例です。マイナスの数を入れることによってカウントダウンが可能です。列を右から左に順に操作したい場合などに使います。
<実行結果>
Sub For_Next文サンプル3() Dim f As Double For f = 2 To 2.5 Step 0.1 Debug.Print "fの値は" & f; "です。" Next f End Sub
カウントアップのStepに小数を使う事もできるというサンプルです。実行結果としてなぜ2.5が出力されていないのかは分かりません。もし分かる方がいらっしゃったらコメント欄でご教示頂ければ大変ありがたいです。
<実行結果>
For~Next文を書くコツ
ここでFor~Next文を書くコツを一つ紹介します。それはForの行とNextの行を先に記述してから、その間に繰り返したい作業を記述する、と言う事です。上から順番に書いていくことももちろん可能です。しかし、For~Nextの間にさらに複数のFor~Nextを記述したり、If~End Ifを記述する必要がある場面があります。上から順番に書いていく方法だと、どのNextがどのForなのか分からなくなってしまい、書き忘れたNextを書こうとしてもどこにかけばいいのかわからない、と言うようなことが発生する可能性があります。
先にForとNextの行をセットで記述してしまう。その中にまたForとNextの行をセットで記述する。このようにすることで、安心してFor~Next文の中にFor~Next文を記述する事ができます。このように、For~Next文の中にFor~Next文を記述する、と言った様に同じ構造を持った一塊の文法を中に入れることをネストするや入れ子にすると言います。
以下、ネストを書くコツを解説します。ひとつはすでに解説した先にFor~Nextをセットで記述してしまうと言う事です。
For~Next文における2通りのネストの考え方
For~Next文をネストする時につまづいた方は処理の流れがよく分からない感じでした。F8でステップイン*1実行すると流れをなんとか追い掛けることができるのですが、自分では書けない、という感じです。
記述するときに役立つ考え方は2通りあります。
- 繰り返す事(A)を繰り返す(B)と言う捉え方
- 内側から外側に処理が移るイメージ
いずれも同じ事を言っています。今回は繰り返す事(A)を先に作成してしまうイメージを解説します
繰り返すこと(A)を繰り返す(B)と言う捉え方
ストレートに解釈した捉え方です。サンプルコードを使って解説します。アクティブなシートは下図のようになっています。
まず、繰り返すこと(A)は左から右にあいさつを表示させる事です。列方向にカウントアップの為の変数を活用する事で実現可能です。
Sub A列からD列の値を順に表示させる() Dim i As Long For i = 1 To 4 Debug.Print Cells(1, i) Next i End Sub
<実行結果>
これはカウントアップする変数を列番号に利用する事によって、A列からD列までの値を取り出したと言う事です。繰り返しのFor~Next文を使って値を取り出しているのですね。次はこのA列からD列への繰り返しを行方向に繰り返します。繰り返す事を繰り返すとはこういうことです。
Sub A列からD列の値を順に表示させることを3行目まで繰り返す() Dim i As Long '列方向のカウントアップ Dim j As Long '行方向のカウントアップ For j = 1 To 3 '内側のFor~Nextは前のプログラムの行番号をjに書き換えただけ For i = 1 To 4 Debug.Print Cells(j, i) Next i Next j End Sub
<実行結果>
慣れてくるとこのようなことをせずに外側のFor~Nextを書いてから内側のFor~Nextを書くようになります。その時は先に外側の枠を用意して、「よし、これから繰り返したい中身を書くぞ」という感じです。一方、この慣れてくると、の方法は上のような1つのネストの時には有効ですが、2回以上のネストをする場合は、やはり繰り返す事を繰り返す、という繰り返される中身の方を先に作る方が書きやすい場合があります。
次の項で例を示します。
サンプル題材:4桁の数字のパスワードを破る
簡単な文法で複雑なことを実現する。この第一歩の練習として適切なサンプルを紹介します。どのようなプログラムを書けば問題が解決できるか考えてみて下さい。
題材の内容
今回は数字4桁のパスワードを破るプログラムを考えます。
状況は以下のようにA1セルに数字4桁が入力されています。
A1セルはセルの書式設定が文字列に変更されており、0123と入力されれば0123として文字列が保持されように変更されています。セルの書式設定が標準のままだと、0123と入力すると0が落ちて123となってしまうため、書式の設定を変更しています。
今回やりたい事は、0000、0001、0002と順番にA1セルの値と比較して、A1セルの値と一致したときに「パスワードは5926です」などと表示してプログラムを終了することです。ひたすら数字を4つ組み合わせた文字列を照合してパスワードを破るというプログラムが今回のサンプルです。
サンプルコードに含まれる文法の補足解説
このプログラムを作成するにあたり、必要な文法を予め解説します。解説する文法は以下です。
・文字の結合
・Endステートメント
<文字の結合>
文字列を結合するには&を使います。「アンド」と言う事が多いですが、「アンパサンド」とも言います。どちらも正解です。文字列の結合とは文字つなげるということです。以下にサンプルを示します。
Sub アンパサンドサンプル() Debug.Print "こんにちは、" & "お元気ですか""" Debug.Print TypeName(0) Debug.Print TypeName(3) Debug.Print 0 & 3 Debug.Print TypeName(0 & 3) End Sub
実行結果は以下の通りです。
一つ目は文字列と文字列の結合なので予想通り、「こんにちは、お元気ですか」と表示されました。TypeName関数は引数の型名を返します。0も3も数字なのでIntegerと表示されました。これは整数という意味で、Longと同じだと思って良いです。
注目すべきは次の行で、「0 & 3」が「03」と表示され、その方はString、つまり文字列となっているところです。&を使うと、数値型など文字列型以外の方の値を結合しても文字列型となる事が分かりました。
このことは何を意味しているかというと、&で結合した数値同士は文字列なので0、1、2、3の4つの数値を結合したときに0が勝手に落ちることがないことを意味しています。
<Endステートメント>
Endステートメントはその場で全てのVBAプログラムを終了するコードです。中断ではありません。中断はStopステートメントで実装します。For~Next文などの繰り返しはExitステートメントを使う事でループを抜けることができますが、現在実行されている部分のFor~Nextを抜けるだけなので、ネストにした場合は外側のFor~Nextに移る事となります。
今回はパスワードの照合作業が完了した段階でプログラムを終えることが目的なのでEndステートメントを利用しました。他にもGoToステートメントを利用して一気にネストしたFor~Nextを抜けることもできますが、無理矢理プログラムの流れを制御している感じがするので、今回はEndを使う事にしましょう。
サンプルコードと内容の解説
それではサンプルコードを作成していきます。以下の手順を見る前に少し自分で考えてみましょう。どのような手順で記述していけば良いでしょうか。少し複雑なプログラムになる場合はいきなり書き始めるよりも構造を考えてから記述した方が良い場合があります。
私は以下の方針でプログラムを記述することにしました。
- 左から順に1桁目をn1、2桁目をn2、3桁目をn3、4桁目をn4とするループを入れ子にする
- 一番内側のFor~Nextの中でn4を生成したのちにn1 & n2 & n3 & n4を結合し、A1セルの値と照合する
繰り返す事を繰り返すと言う方針を説明したので、入れ子の内側から作る手順で紹介します。
Sub 一番内側のn4の部分() Dim try_password As String '照合用 Dim ans_password As String '答えのパスワードを代入しておく ans_password = Cells(1, 1) For i = 0 To 9 n4 = i try_password = n4 If try_password = ans_password Then MsgBox "パスワードは【" & try_password & "】です。" End End If Next i End Sub
まず、For~Next以外の部分では結合した値を入れておく変数とA1のあたいを入れておく変数を用意しました。こういった変数を用意する方が、プログラムの中身が分かりやすくなるためです。For~Nextの間の部分で一番右の桁にiを代入して作成。それを照合用の変数try_passwordに代入しています。ここはn1、n2、n3と結合していくように書き換える予定です。称号下結果、一致していればメッセージを表示してプログラムが終わるようにしています。
繰り返す事を繰り返すので、右から2番目の桁のn3生成の分だけさらに繰り返す様にします。n3が0,1,2・・・とカウントアップしていくそれぞれの過程でn4が0,1,2、・・・とカウントアップしていくようにしていきます。こうして入れ子にすると以下のようになります。
Sub 左から3桁目の部分を追記() Dim try_password As String '照合用 Dim ans_password As String '答えのパスワードを代入しておく ans_password = Cells(1, 1) For j = 0 To 9 'このループを追記 n3 = j '左から3桁目を生成する部分を追記 For i = 0 To 9 n4 = i try_password = n3 & n4 ' 左から3桁目を結合するように変更 If try_password = ans_password Then MsgBox "パスワードは【" & try_password & "】です。" End End If Next i Next j End Sub
これで二桁の称号ができました。コツが分かったので一番左から2番目と一番左の部分も外側に付け足していってプログラムを完成させるようにすると以下の様になります。
Sub BruteForce() '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> '【考え方】 'このプロシジャは4桁の数字のみからなるパスワードを破る '数字を結合させて仮パスワードを生成する '生成した4桁のパスワードを片っ端から照合していく '【プログラムの流れ】 '左から順に1桁目~4桁目を生成するFor文を入れ子にする '一番内側のFor~Nextの中で生成と照合を実施する '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Dim try_password As String '照合用 Dim ans_password As String '答えのパスワードを代入しておく Dim i As Long, j As Long, k As Long, l As Long Dim n1 As String, n2 As String, n3 As String, n4 As String ans_password = Cells(1, 1) For l = 0 To 9 n1 = l For k = 0 To 9 n2 = k For j = 0 To 9 n3 = j For i = 0 To 9 n4 = i try_password = n1 & n2 & n3 & n4 If try_password = ans_password Then MsgBox "パスワードは【" & try_password & "】です。" End End If Next i Next j Next k Next l MsgBox "パスワードの解除に失敗しました。" End Sub
総当たりですが、万が一称号ができなかったときに備えて最後にパスワードの解除に失敗した旨のメッセージを追加しました。
入れ子を理解するには、繰り返す事を繰り返すと言う事を意識するのがよい事について解説し、実際にその手順に従ってプログラムを書いてきました。しかし、この手順は実は、理解のための考え方です。
For~Next文がバッチリ理解できて、ネストしたプログラムを書くのにも慣れてくると、内側から書くのではなく外側から書くのが早いし、記述ミスも少なくなくなります。その方法は、外側から順にFor Nextを書いてしまうと言うやり方です。
Sub 慣れてきたらこの手順で記述する() Dim try_password As String '照合用 Dim ans_password As String '答えのパスワードを代入しておく Dim i As Long, j As Long, k As Long, l As Long Dim n1 As String, n2 As String, n3 As String, n4 As String ans_password = Cells(1, 1) For i = 0 To 9 For j = 0 To 9 For k = 0 To 9 For l = 0 To 9 Next l Next k Next j Next i End Sub
先にここまで書いてしまってから、それぞれのFor~Nextの中身を追記していくと言うのがお住めての順です。このように書く事によって、カウントアップ用の変数のi,j,k,lが内側から使われているという違和感もなくなります。
簡単な文法を組み合わせて複雑な処理をすると言うこと
この解説記事を書いていて改めて感じたことがあります。それはVBAにおけるプログラミング力はFor~Next文やIf~End If文のような簡単な文法を使ってどれだけ複雑な処理がこなせるかが鍵になると言う事です。この、簡単な文法で複雑な処理をこなすと言うところが肝心です。よく知恵袋などの掲示板でこのようなことは可能でしょうかというような質問をお見かけします。ニュアンス的にはこのような事が可能な文法があれば教えて下さい、と言うように見えるのですが、工夫すれば簡単な文法を組み合わせればなんとかなるものが多いのです。
このブログではAPIやOLE、といった高度な文法の解説もしていますが、それらは必要な時にピンポイントで使えれば良いと思います。やはり大事なのはForやIf。これらを使いこなすことだな、と感じます。
For~Next文をネストするコツまとめ
この記事で解説した内容をまとめます。
- For~Next文はForとNextの行を先に書いてしまい、後から繰り返す中身を書く
- For~Nextの入れ子の理解が難しいなら、【繰り返す事を繰り返す】と言う事を意識する
- 慣れるまでは【繰り返す事を繰り返す】を実装するために、先に内側のFor~Nextを記述し、それを繰り返すFor~Nextを後に書く
- 慣れてきたら外側のFor~Nextから内側へと順に書くが、すべてのFor~Nextを先に書いてしまう
- プログラミング力はFor~Nextのような簡単な文法を組み合わせて複雑なことが実現できる能力である
今回解説したネスト構造の理解の仕方はDo~Loopや条件分岐でも役に立ちます。しっかり理解して、プログラミング力を向上していきましょう。
<ゆんの電子書籍>ゆんの電子書籍はすべてkindle unlimitedで読み放題です!
*1:1行ずつプログラムを実行すること