ゆんの業務改善ブログ

①生産性向上 ②業務改善 ③自動化 について情報発信しています。VBAプログラムは本当の初心者から他のアプリケーションを呼び出して使う上級者的な使い方まで幅広いレベルで解説していきます。

VBA 繰り返しのFor~Next文をネストするコツ|4桁のパスワードを破る

VBAプログラミングで繰り返しのFor~Next文をネストするコツを解説します。人に教える中で普通のFor文が書けてもFor文のネストの話になると苦労した人がいrるためです。For~Next文の書き方の解説は世の中にあふれるほどありますが、ネストのコツを解説したものは一度しか見たことがないので、この記事が参考になればと思います。

目次

For~Next文の構造

まずは普通のFor~Next文の構造を確認します。文法は以下の通りです。

繰り返したい内容をForとNextで囲むのがFor文の構造
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を行番号に使う事によって表の操作などに使う事ができ、よく見かける使い方です。
<実行結果>

Stepの記述を省略して1ずつカウントアップ
サンプル1実行結果

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を設定した例です。マイナスの数を入れることによってカウントダウンが可能です。列を右から左に順に操作したい場合などに使います。
<実行結果>

Stepにマイナスの値を入れるとカウントダウンも可能
実行結果

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が出力されていないのかは分かりません。もし分かる方がいらっしゃったらコメント欄でご教示頂ければ大変ありがたいです。

<実行結果>

Stepに小数の値を指定することもできる
実行結果

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)と言う捉え方

ストレートに解釈した捉え方です。サンプルコードを使って解説します。アクティブなシートは下図のようになっています。

1~3行目のA~D列に値が入力されている

まず、繰り返すこと(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

<実行結果>

繰り返す事が繰り返されて3x4の範囲の値全てが取得できた
実行結果

慣れてくるとこのようなことをせずに外側のFor~Nextを書いてから内側のFor~Nextを書くようになります。その時は先に外側の枠を用意して、「よし、これから繰り返したい中身を書くぞ」という感じです。一方、この慣れてくると、の方法は上のような1つのネストの時には有効ですが、2回以上のネストをする場合は、やはり繰り返す事を繰り返す、という繰り返される中身の方を先に作る方が書きやすい場合があります。

次の項で例を示します。

サンプル題材:4桁の数字のパスワードを破る

簡単な文法で複雑なことを実現する。この第一歩の練習として適切なサンプルを紹介します。どのようなプログラムを書けば問題が解決できるか考えてみて下さい。

題材の内容

今回は数字4桁のパスワードを破るプログラムを考えます。

状況は以下のようにA1セルに数字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や条件分岐でも役に立ちます。しっかり理解して、プログラミング力を向上していきましょう。

*1:1行ずつプログラムを実行すること