【Excel】二重ループ(二重のFor文)の徹底理解(速読VBA単語Program3-6-2補講)
今回は、「速読VBA単語」というタイトルにもかかわらず新しく覚える単語が1つもありませんので、最後まで無料で読むことのできる無料公開のページといたします。
Program3-6とProgram3-7の中間に位置する補講です。
前回Program3-6では配列の練習をしました。次回Program3-7では「二重ル―プ」「1次元・2次元配列」「If文」を組み合わせてコードを記述する練習をしますが、いきなりこれらを組み合わせるのは難易度が高いです。そこで、今回は、少しずつ組み合わせる練習をします。
まず、今回新たに登場する二重ル―プだけを練習します。次に、「二重ループとIf文」「二重ループと1次元配列」の2つの組み合わせをそれぞれ練習します。最後に、二重ループと1次元配列とIf文の3つを組み合わせる練習をします。
なお、このプログラムは速読VBA単語Program3-6までの知識が必要となります。すでに解説したことについてはその説明を省略しています。
練習問題(無料)
速読VBA単語Program3-6-2までを出題範囲とする練習問題を無料で公開しています。
注意事項
「速読VBA単語」は、文法を完全に無視して、難易度順に単語練習をするだけでExcelVBAを習得しようという「邪道」な企画です。本講座は、極めて強い副作用があり安全上注意が必要です。この説明書は本講座を正しく受講するための説明文書です。受講する前に必ずお読みください。
無料ゾーンご案内
速読VBA単語Program1-1, Program1-2, Program2-1は無料で公開しております。ぜひご覧ください。
また、WordやExcel(マクロ以外)の記事はすべて無料で公開しております。
1.For文の復習
まずは、For文がサクッと書けることが前提です。速読VBA単語Program3-2に戻ってしっかり練習をしましょう。次の図のようにセルA1:A12に1月から12月までの文字列をFor文で表示します。
For i = 1 To 12
Cells(i, 1) = i & "月"
Next
これを、速読VBA単語Program3-6で学習した出力行数のカウントを使うと次のようになります。Cells(i,1)をCells( [別の変数] ,1)とすることによって、For文の「i」とは関係なく行数をカウントすることができます。ただし、その[別の変数]は勝手に増えることはありませんので、インクリメントをする必要があります。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
rowcnt = rowcnt + 1
Next
2.外側のループの中にループを入れる
さらに、1月から12月まですべて31日まであると仮定して、B列に日付を表示します。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
Cells(rowcnt, 2) = "1日"
rowcnt = rowcnt + 1
Cells(rowcnt, 2) = "2日"
rowcnt = rowcnt + 1
Cells(rowcnt, 2) = "3日"
rowcnt = rowcnt + 1
[ 途中省略 ]
Cells(rowcnt, 2) = "31日"
rowcnt = rowcnt + 1
Next
1行目に「1月」と「1日」を出力します。そして、カウンターの変数を1つずつ足しながら2日~31日まで記述します。「1月31日」の段階で行数は31で、最後の「rowcnt = rowcnt + 1」で32行目となってFor文の先頭に戻り、32行目に「2月1日」が入ります。これを12月31日まで繰り返しています。このように、For文で同じ処理を何回も繰り返すことを「ループ(Loop)」またはループ構造といいます。
このループの中で、31回繰り返している部分があります。その部分だけFor文で記述すると次のようになります。
For i = 1 To 31
cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
これをループの中に入れたいのですが、「i」は既に1~12の整数を表す変数として使っていますので、その次の「j」を使います。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
For j = 1 To 31
Cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
Next
「i」は1~12の整数を表す変数で、「j」は1~31を表す変数です。
このようにループの中にループを入れることを「二重(にじゅう)ループ」といい、それぞれのループを「外側のループ」「内側のループ」といいます。
3.ループを繰り返すために外側にループを付ける
ところで、さきほどの例でB列(日付)のほうを先に考えたとします。つまり、セルB1~B31に1日から31日までの文字列をFor文で表示します。
For i = 1 To 31
Cells(i, 2) = i & "日"
Next
これを、行番号をカウントする方法に変えると次のようになります。
rowcnt = 1
For i = 1 To 31
Cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
「月」を表示しながら12回繰り返します。
rowcnt = 1
Cells(rowcnt, 1) = "1月"
For i = 1 To 31
Cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
Cells(rowcnt, 1) = "2月"
For i = 1 To 31
Cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
Cells(rowcnt, 1) = "3月"
For i = 1 To 31
Cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
[ 途中省略 ]
Cells(rowcnt, 1) = "12月"
For i = 1 To 31
Cells(rowcnt, 2) = i & "日"
rowcnt = rowcnt + 1
Next
1行目から順に1月1日~31日まで出力します。続けて2月1日~31日まで出力します。rowcntは1ずつ増えていきます。これを12月31日まで繰り返しています。
1月~12月まで同じ手順を12回繰り返していて、For文は全く同じ形です。
rowcnt = 1
Cells(rowcnt, 1) = "1月"
[ B列に1日~31日を表示するFor文 ]
Cells(rowcnt, 1) = "2月"
[ B列に1日~31日を表示するFor文 ]
Cells(rowcnt, 1) = "3月"
[ B列に1日~31日を表示するFor文 ]
[ 途中省略 ]
Cells(rowcnt, 1) = "12月"
[ B列に1日~31日を表示するFor文 ]
12回の繰り返しをFor文で記述すると次のようになります。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
[ B列に1日~31日を表示するFor文 ]
Next
「i」は1~12を表す変数、「j」は1~31を表す変数として二重ループにします。さきほどと同じコードになります。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
For j = 1 To 31
Cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
Next
このように二重ループには2つのパターンがあります。
・ループの中に繰り返しの部分があるから、内側のループにするパターン
・同じ形のループが繰り返されているから、外側のループで囲むパターン
■練習問題
2020年の場合について1月1日~12月31日まで表示しなさい。なお、各月の日数は次の数式で求めることができます。
Day(DateSerial(2020, 月+1, 0))
内側のFor文の直前で、数式で計算した日数を変数に入れて「For j = 1 To 日数」とします。ちなみに2020年はうるう年なので、2月は29日まであり366行出力されます。
Sub Tokyo()
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
eom = Day(DateSerial(2020, i + 1, 0))
For j = 1 To eom
Cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
Next
End Sub
4.二重ループの読み方
今度は、二重ループのコードを読む練習をしましょう。
次のコードを読んでみましょう。A~Eはそれぞれ何回表示されるでしょうか?
Sub Tokyo()
Debug.Print "A"
For i = 1 To 3
Debug.Print "B"
For j = 1 To 4
Debug.Print "C"
Next
Debug.Print "D"
Next
Debug.Print "E"
End Sub
(日本語訳)
"A" を表示
[3回繰り返し i=1~3]
"B" を表示
[4回繰り返し j=1~4]
"C" を表示
[繰り返し終わり]
"D" を表示
[繰り返し終わり]
"E" を表示
二重ループがあって、「i」は1~3を表す変数、「j」は1~4を表す変数です。ところで、受験数学では、2つのアルファベットに範囲があって、2つとも動くと訳が分からなくなるので、1つの文字を固定したほうが解きやすいという「1文字固定法」と呼ばれる解法の鉄則があります。
二重ループを読むときは外側のループを固定します。繰り返しをせず「 i=1 」として考えます。
Debug.Print "A"
[ループ1回目]
Debug.Print "B"
For j = 1 To 4
Debug.Print "C"
Next
Debug.Print "D"
[1回目終わり]
Debug.Print "E"
A、Bのあと、Cを4回繰り返して、D、Eとなります。ABCCCCDEとなり、そのうち「BCCCCD」が固定した部分です。その固定した部分を3回繰り返すので、A BCCCCD BCCCCD BCCCCD Eとなります。
Debug.Print "A"
[1回目]
Debug.Print "B"
For j=1 to 4
Debug.Print "C"
next
Debug.Print "D"
[2回目]
Debug.Print "B"
For j=1 to 4
Debug.Print "C"
next
Debug.Print "D"
[3回目]
Debug.Print "B"
For j=1 to 4
Debug.Print "C"
next
Debug.Print "D"
Debug.Print "E"
■練習問題
さきほどの日付のコードを、外側のループを「i=1」で固定する方法で読みなさい。
rowcnt = 1
For i = 1 To 12
Cells(rowcnt, 1) = i & "月"
For j = 1 To 31
Cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
Next
外側のループを「i=1」で固定すると次のようになります。1行目に1月1日と表示して、その後、行数のカウントをしながら31日まで表示します。
rowcnt = 1
[1回目]
Cells(rowcnt, 1) = "1月"
For j = 1 To 31
Cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
[1回目おわり]
その固定した部分を12回目まで繰り返します。
[2回目]
Cells(rowcnt, 1) = "2月"
For j = 1 To 31
cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
[2回目おわり]
[ 途中省略 ]
[12回目]
Cells(rowcnt, 1) = "12月"
For j = 1 To 31
cells(rowcnt, 2) = j & "日"
rowcnt = rowcnt + 1
Next
[12回目おわり]
5.二重ループとIf文を使った問題
次の表で、2行目から13行目までの縦方向の合計を、プラスの数とマイナスの数に分けて求めてみましょう。
ちなみに、金額を入力するのが面倒なら次の数式を使ってください。数式を使った場合は値の貼り付けをして数式を消しておいてください。
=INT(RAND()*100)*2000-100000
この場合、縦に12個の合計を求めて、それを横方向に4回繰り返すことが分かっています。繰り返す順序が決まっている場合は、先に繰り返すほうのループ(12個の合計を求める)を先に作ります。プラスだけの合計とマイナスだけの合計を格納する変数を用意します。
plus = 0
minus = 0
For i = 2 To 13
Next
B列の合計だけを求めます(条件付き合計について詳しくは速読VBA単語Program3-4参照)。このとき、列番号を「2」に固定していることを意識しながらコードを記述します。B列の合計が正しく計算できていることを確認しておきます。
plus = 0
minus = 0
For i = 2 To 13
kingaku = Cells(i, 2)
If kingaku > 0 Then
plus = plus + kingaku
Else
minus = minus + kingaku
End If
Next
Cells(14, 2) = plus
Cells(15, 2) = minus
これを4回繰り返しますから外側にループを付けます。列番号は2~5までとなります。
Sub tx()
For i = 2 To 5
plus = 0
minus = 0
For j = 2 To 13
kingaku = Cells(j, i)
If kingaku > 0 Then
plus = plus + kingaku
Else
minus = minus + kingaku
End If
Next
Cells(14, i) = plus
Cells(15, i) = minus
Next
End Sub
■練習問題
横方向の合計を、プラスの数とマイナスの数に分けて求めなさい。
2行目だけの合計を求めます。このときも行番号を「2」に固定していることを意識しながら記述します。
Sub tx()
plus = 0
minus = 0
For i = 2 To 5
kingaku = Cells(2, i)
If kingaku > 0 Then
plus = plus + kingaku
Else
minus = minus + kingaku
End If
Next
Cells(2, 6) = plus
Cells(2, 7) = minus
End Sub
行番号を2~13にします。
Sub tx()
For i = 2 To 13
plus = 0
minus = 0
For j = 2 To 5
kingaku = Cells(i, j)
If kingaku > 0 Then
plus = plus + kingaku
Else
minus = minus + kingaku
End If
Next
Cells(i, 6) = plus
Cells(i, 7) = minus
Next
End Sub
6.二重ループと配列を使った問題
(1)ループの中に繰り返しの部分があるから、内側のループにするパターン
ここで、次のシートを作ります。
・セルA2:A49は、適当な文字列を入力しておきます。連番である必要はありません。
・セルB2:B49は、A、B、Kの3種類の文字列をランダムに入力しておきます。ランダムに入力するには「=CHOOSE(INT(RAND()*3)+1,"A","B","K")」という数式を使えば良いですが、数式を使った場合は値の貼り付けをして数式を消しておいてください。
次の図のようにセルA2:A49に入力されている文字列を、D, F, H, J, L列に転記してみましょう。
まずは、配列を用意します(速読VBA単語Program3-6参照)。
Sub akiba()
Dim akb(48)
End Sub
次に、セルA2:A49の値を配列に代入します。
For i = 1 To 48
akb(i) = Cells(i + 1, 1)
Next
これをD, F, H, J, L列に代入します。
For i = 1 To 48
Cells(i, 4) = akb(i)
Cells(i, 6) = akb(i)
Cells(i, 8) = akb(i)
Cells(i, 10) = akb(i)
Cells(i, 12) = akb(i)
Next
ところで、列番号の4~12の連番はFor文を使って次のように記述することができます。
For j = 4 To 12 Step 2
Cells(i, j) = akb(i)
Next
全てまとめると次のようになります。
Sub akiba()
Dim akb(48)
For i = 1 To 48
akb(i) = Cells(i + 1, 1)
Next
For i = 1 To 48
For j = 4 To 12 Step 2
Cells(i, j) = akb(i)
Next
Next
End Sub
配列を使わずに、次のように記述することもできます。
Sub akiba()
For i = 1 To 48
akb = Cells(i + 1, 1)
For j = 4 To 12 Step 2
Cells(i, j) = akb
Next
Next
End Sub
(2)同じ形のループが繰り返されているから、外側のループで囲むパターン
次の図のようにセルA2~A6に入力されている文字列を配列に入れて、それをセルD1~D150に転記してみましょう。
まずは、配列を用意します。
Sub akiba()
Dim akb(5)
End Sub
次に、セルA1~A5の値を配列に代入します。
For i = 1 To 5
akb(i) = Cells(i + 1, 1)
Next
セルD1~D5に代入します。
For i = 1 To 5
Cells(i, 4) = akb(i)
Next
ここで、行数のカウンタで転記する方法にします。
rowcnt = 1
For i = 1 To 5
Cells(rowcnt, 4) = akb(i)
rowcnt = rowcnt + 1
Next
「rowcnt = rowcnt + 1」で1行飛ばしながら同じコードを25回繰り返すだけで完成です。
rowcnt = 1
For i = 1 To 5
Cells(rowcnt, 4) = akb(i)
rowcnt = rowcnt + 1
Next
rowcnt = rowcnt + 1
For i = 1 To 5
Cells(rowcnt, 4) = akb(i)
rowcnt = rowcnt + 1
Next
rowcnt = rowcnt + 1
For i = 1 To 5
Cells(rowcnt, 4) = akb(i)
rowcnt = rowcnt + 1
Next
rowcnt = rowcnt + 1
[ 途中省略 ]
For i = 1 To 5
Cells(rowcnt, 4) = akb(i)
rowcnt = rowcnt + 1
Next
全く同じことを25回繰り返すのですから、For文を使えば良いです。
Sub akiba()
Dim akb(5)
For i = 1 To 5
akb(i) = Cells(i + 1, 1)
Next
rowcnt = 1
For i = 1 To 25
For j = 1 To 5
Cells(rowcnt, 4) = akb(j)
rowcnt = rowcnt + 1
Next
rowcnt = rowcnt + 1
Next
End Sub
7.二重ループと配列とIf文を使った問題
さきほどのExcelで、Aグループだけ抽出してD~E列に表示します。
まずは、Subプロシージャと配列を2つ用意します。
Sub akiba()
Dim no(48)
Dim group(48)
End Sub
そして、それぞれA列とB列のデータの2行目以降の48人分を配列に入れます。
For i = 1 To 48
no(i) = Cells(i + 1, 1)
group(i) = Cells(i + 1, 2)
Next
次に、D~E列に転記をしますが、そのときに、転記先の行番号を変数にします。 2行目からデータを表示するのでスタートを「2」にします。
rowcnt = 2
For i = 1 To 48
Cells(rowcnt, 5) = no(i)
Cells(rowcnt, 4) = group(i)
rowcnt = rowcnt + 1
Next
B列のデータは配列group()に代入していますので、「If group(i) = "A" Then」とします。これでAグループの場合だけ転記をすることができます。
rowcnt = 2
For i = 1 To 48
If group(i) = "A" Then
Cells(rowcnt, 5) = no(i)
Cells(rowcnt, 4) = group(i)
rowcnt = rowcnt + 1
End If
Next
全てまとめると次のようになります。
Sub akiba()
Dim no(48)
Dim group(48)
For i = 1 To 48
no(i) = Cells(i + 1, 1)
group(i) = Cells(i + 1, 2)
Next
rowcnt = 2
For i = 1 To 48
If group(i) = "A" Then
Cells(rowcnt, 5) = no(i)
Cells(rowcnt, 4) = group(i)
rowcnt = rowcnt + 1
End If
Next
End Sub
さらに、その下にKグループ、Bグループを表示します。
For i = 1 To 48
If group(i) = "K" Then
Cells(rowcnt, 5) = no(i)
Cells(rowcnt, 4) = group(i)
rowcnt = rowcnt + 1
End If
Next
For i = 1 To 48
If group(i) = "B" Then
Cells(rowcnt, 5) = no(i)
Cells(rowcnt, 4) = group(i)
rowcnt = rowcnt + 1
End If
Next
「"A"」「"K"」「"B"」の部分が違うだけでコードの形は同じです。このような場合は、「"A"」「"K"」「"B"」を配列にすればFor文が使えます。
team = Array("A", "K", "B")
「For i = 0 To 2」として3回繰り返します。
Sub akiba()
Dim no(48)
Dim group(48)
team = Array("A", "K", "B")
For i = 1 To 48
no(i) = Cells(i + 1, 1)
group(i) = Cells(i + 1, 2)
Next
rowcnt = 2
For i = 0 To 2
For j = 1 To 48
If group(j) = team(i) Then
Cells(rowcnt, 5) = no(j)
Cells(rowcnt, 4) = group(j)
rowcnt = rowcnt + 1
End If
Next
Next
End Sub
8.初めに二重ループの枠を用意する方法
次の図のように、セル範囲A1:J20にCells()の文字列を出力してみましょう。
このように繰り返す方向が縦横2方向で、しかも、縦横どちらを先に繰り返してもよい場合は、次のように二重ループの枠を作ってしまう方法があります。縦 i=1~20、横 j=1~10とします。通常は縦が i で、横が j です。
For i = 1 To 20
For j = 1 To 10
Next
Next
そして、「i=3, j=5」などと適当に値を決めて考えます。
Cells(3, 5) = "Cells(3,5)"
「3→i、5→j」に変えて完成です。
Sub tsukuba()
For i = 1 To 20
For j = 1 To 10
Cells(i, j) = "Cells(" & i & "," & j & ")"
Next
Next
End Sub
■練習問題
次の図で、A列と1行目の数値をかけた値を、セル範囲B2:K10に求めなさい。
1行目とA列の値をそれぞれ配列に入れます。
Dim a(9)
Dim b(10)
For i = 1 To 9
a(i) = Cells(i + 1, 1)
Next
For i = 1 To 10
b(i) = Cells(1, i + 1)
Next
縦 i=1~9、横 j=1~10の二重ループを作って、かけ算すれば完成です。
Sub tsukuba()
Dim a(9)
Dim b(10)
For i = 1 To 9
a(i) = Cells(i + 1, 1)
Next
For i = 1 To 10
b(i) = Cells(1, i + 1)
Next
For i = 1 To 9
For j = 1 To 10
Cells(i + 1, j + 1) = a(i) * b(j)
Next
Next
End Sub
9.内側のループのExit For
さきほどの「Cells(i,j)」の問題で、i=1~100、j=1~100にすると、100行100列表示されます。
For i = 1 To 100
For j = 1 To 100
Cells(i, j) = "Cells(" & i & "," & j & ")"
Next
Next
縦を20行で止めたい場合は外側のループの最後にExit Forを入れます。
For i = 1 To 100
For j = 1 To 100
Cells(i, j) = "Cells(" & i & "," & j & ")"
Next
If i = 20 Then Exit For
Next
外側のループを20回繰り返して、21回目に行かずにそのままFor文が終わります。これで20行100列となります。このように繰り返しが終わることを「ループを抜ける」といいます。
横を20列で止めたい場合は内側のループの最後にExit Forを入れます。
For i = 1 To 100
For j = 1 To 100
Cells(i, j) = "Cells(" & i & "," & j & ")"
If j = 20 Then Exit For
Next
Next
内側の繰り返しは20回になりますが、外側は100回のままです。したがって、100行20列となります。このように、内側のループにExit Forを入れると、内側のループは抜けますが、外側のループは抜けません。20行20列にしたい場合は両方に入れる必要があります。
For i = 1 To 100
For j = 1 To 100
Cells(i, j) = "Cells(" & i & "," & j & ")"
If j = 20 Then Exit For
Next
If i = 20 Then Exit For
Next
10.お知らせ
Program3-6-2は以上となります。二重ループの練習問題を無料で公開しています。
次回はProgram3-7「2次元配列に入れて2次元配列を出力する」です。
引き続きよろしくお願い申し上げます。
この記事が気に入ったらサポートをしてみませんか?