繰り返しの入れ子
繰り返しを,さらに繰り返す.模様はここから生まれます.
前の章,正方形を描く繰り返しを覚えました.では その正方形を 10 個並べたいときはどうするでしょうか. 繰り返しのプログラムを 10 回書く — それでは元の木阿弥です.
答えは「繰り返しを,繰り返す」です. 正方形を描く繰り返しを,まるごと別の繰り返しの中に入れてしまいます. これを入れ子(ネスト)といいます. たった数行増えるだけで,絵は一気に模様らしくなります.
- 繰り返しの中に繰り返しを書き,同じ図形を何個も並べられる
- 外側と内側のループ変数が,それぞれ何回変わるかを説明できる
- 外側のループ変数を使って,1 個ずつ大きさや角度が変わる模様を描ける
- 剰余演算子
%で周期を作り,色や形を交互に変えられる - 二重の繰り返しを行と列と考えて,格子状・階段状に並べられる
printのendを使って,文字や数を表の形に並べられる- 繰り返しの回数から描く線の本数を見積もり,描きすぎを避けられる
7.1 二重の繰り返し
7.1.1 窓を 4 つ並べる
正方形を 4 つ,向きを変えながら描いて「窓」を作ります. 正方形を描く繰り返しを, それを 4 回くり返す繰り返しの中に入れています.
from turtle import *
pensize(2)
pencolor("navy")
for j in range(4): # 窓を 4 つ(外側)
for i in range(4): # 正方形を 1 つ(内側)
fd(50)
rt(90)
rt(90) # 次の正方形の向きへ
ht()
done()
内側の for が正方形 1 つを描きます.
外側の for は,その「正方形を描く仕事」をまるごと 4 回くり返します.
間にある rt(90) は内側の外・外側の中にあるので,
正方形 1 つを描き終わるたびに 1 回だけ実行されます.
7.1.2 外側と内側のループ変数
入れ子では,ループ変数が 2 つ登場します.
外側を j,内側を i とするのが昔からの習慣です
(どちらも好きな名前で構いません).
外側が 1 回進むあいだに,内側は最後まで全部回ります.
print で確かめましょう.
for j in range(3):
print("外側", j, "回目")
for i in range(4):
print(" 内側", i)
print("ぜんぶ終わり")
コンソールをよく見てください.外側 0 回目 のあとに
内側 0・1・2・3 が並び,そのあとで 外側 1 回目 に進みます.
つまり内側の中身は 3 × 4 = 12 回実行されます.
| 変わり方 | 中身が実行される回数 | |
|---|---|---|
外側 j | 0, 1, 2 とゆっくり進む | 3 回 |
内側 i | 0, 1, 2, 3 を外側 1 回ごとに繰り返す | 3 × 4 = 12 回 |
7.1.3 字下げが構造を表す
入れ子になると,字下げは 2 段・3 段と深くなります. どの行がどの繰り返しに属するかは,字下げの深さだけで決まります.
for j in range(4): # 深さ 0(どの繰り返しの外でもない)
for i in range(4): # 深さ 1(外側の中)
fd(50) # 深さ 2(内側の中)← 16 回実行
rt(90) # 深さ 2 ← 16 回実行
rt(90) # 深さ 1(内側の外,外側の中)← 4 回実行
ht() # 深さ 0(全部終わってから) ← 1 回実行
- 字下げは半角スペース 4 つ.1 段深くなるごとに 4 つ増やす.
Tabキーを押せば 4 つ分入ります.手で数える必要はありません.- 思ったとおりの絵にならないときは,行の左端を縦に見てそろっているか確かめること.
7.1.4 入れ子と,ただ並べることのちがい
円弧を 2 つつなぐと,花びらが 1 枚できます. 花を描くには,その花びらを 6 回くり返す必要があります. つまり「円弧を描く繰り返し」を「花びらを描く繰り返し」の中に入れる,二重の入れ子です.
from turtle import *
pensize(2)
pencolor("hotpink")
for j in range(6): # 花びらを 6 枚(外側)
for i in range(120): # 1 つめの円弧(内側)
rt(1)
fd(1)
rt(60) # 花びらの先で折り返す
for i in range(120): # 2 つめの円弧(内側)
rt(1)
fd(1)
ht()
done()
次は,よくある間違いです.2 つめの円弧の字下げを忘れて, 外側の繰り返しの外に出してしまいました. プログラムの見た目はほとんど同じなのに,結果はまるで違います.
右は,1 つめの円弧だけを 6 回くり返したあとで, 2 つめの円弧を最後に 1 回だけ描いています. 「繰り返しを繰り返す」ためには, 繰り返したいものを,まるごと外側の中に入れる必要があります.
正方形を 4 つ並べて窓を作ります. 外側は何回まわせばよいでしょうか.また,1 つ描き終わるたびに 何度回れば次の正方形が別の向きになるでしょうか. ____ を 2 つ埋めてください.
from turtle import *
pensize(2)
pencolor("navy")
for j in range(____):
for i in range(4):
fd(50)
rt(90)
rt(____)
ht()
done()
7.2 外側のループ変数で,1 個ずつ変える
同じ図形をただ並べるだけなら,入れ子の値打ちは半分です.
外側のループ変数 j を内側で使うと,
「1 個ずつ大きくなる」「1 個ずつ細かくなる」模様が作れます.
7.2.1 だんだん大きくなる正方形(貝殻)
from turtle import *
pensize(2)
pencolor("saddlebrown")
rt(20)
for j in range(1, 11): # j は 1, 2, 3, … 10
for i in range(4):
fd(10 * j) # ← j 番目の正方形は 1 辺 10×j
rt(90)
rt(20) # 少し回してから次の正方形へ
ht()
done()
ここで range(1, 11) と書いたのには理由があります.
range(10) だと j が 0 から始まり,
1 個目の正方形の 1 辺が 10 × 0 = 0 になって消えてしまうからです.
1 から数えたいときは range(1, 11) と書きます.
rt(20) の 20 を 15 や 30 に変えると,巻き方が変わります.
10 * j の 10 を 5 にすれば細く小さな貝殻になります.
数字を 1 つ変えるだけで別の模様になるのが,入れ子の面白さです.
7.2.2 だんだん細かくなる円(目玉)
前の章,円は fd(1) と rt(1) を 360 回くり返せば描けました.
この 曲がる角度そのものを j にすると,
大きさの違う円がいくつも描けます.
曲がる角度が大きいほど早く 1 周してしまうので,円は小さくなります.
from turtle import *
pensize(1)
pencolor("navy")
for j in range(2, 11): # 曲がる角度を 2 度から 10 度まで
for i in range(0, 360, j): # 合計 360 度になる回数だけ
fd(1)
rt(j)
ht()
done()
内側の range(0, 360, j) は「0 から 360 手前まで j 飛ばし」,
つまり 360 ÷ j 回という意味です.
1 回に j 度ずつ曲がるので,合計はいつでもちょうど 360 度になります.
外側の変数が,内側の回数と角度の両方を決めている点に注目してください.
j 番目の正方形の 1 辺を,j の 10 倍に
してください.____ に入るのは数ではなく,
j を使った式です.
from turtle import *
pensize(2)
pencolor("saddlebrown")
rt(20)
for j in range(1, 11):
for i in range(4):
fd(____)
rt(90)
rt(20)
ht()
done()
7.3 あまり % で周期を作る
7.3.1 % は割り算のあまり
% は剰余演算子といい,割り算のあまりを求めます.
7 % 3 は 1,8 % 2 は 0 です.
これを i に対して使うと,
一定のリズム(周期)を作ることができます.
print(7 % 3)
print(8 % 2)
for i in range(6):
print(i, i % 2, i % 3)
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
i % 2 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
i % 3 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 |
i % 2 は 0 と 1 が交互に,
i % 3 は 0・1・2 が順番に現れます.
i % 2 == 0 なら i は偶数,
i % 2 == 1 なら奇数です.
7.3.2 i % 2 で交互に色を変える
繰り返しの中に if を書く形は,第 6 章でも使いました.
繰り返しの中の分岐も,りっぱな入れ子です.
今回は,その条件に % を使います.
from turtle import *
pensize(8)
for i in range(12):
if i % 2 == 0: # 偶数回目なら
pencolor("crimson")
else: # 奇数回目なら
pencolor("royalblue")
fd(60)
bk(60)
rt(30)
ht()
done()
7.3.3 i % 3 で 3 色を回す
3 つ以上で回したいときは,elif を足します.
i % 3 は 0・1・2 しか取らないので,
if・elif・else の 3 つで足ります.
from turtle import *
pensize(8)
for i in range(18):
if i % 3 == 0:
pencolor("crimson")
elif i % 3 == 1:
pencolor("gold")
else:
pencolor("seagreen")
fd(70)
bk(70)
rt(20)
ht()
done()
% 3 が周期を作っている.i % 4 にして elif を 1 つ増やします.
「何個ごとに繰り返すか」が % の右の数,
「そのうちの何番目か」が比べる数です.
7.3.4 形そのものを交互に変える
if の中身を,色ではなく繰り返しそのものにすることもできます.
偶数回目は正方形,奇数回目は正三角形を描いてみましょう.
for の中に if があり,その中にまた for がある,
三重の入れ子です.
from turtle import *
pensize(2)
pencolor("darkslateblue")
pu()
goto(-170, -20)
pd()
for i in range(8):
if i % 2 == 0:
for j in range(4): # 正方形
fd(30)
lt(90)
else:
for j in range(3): # 正三角形
fd(30)
lt(120)
pu() # 次の図形の位置へ
fd(45)
pd()
ht()
done()
if の中に for が入っている.18 本の線を,3 色が順ぐりに現れるように塗り分けます. ____ を 2 つ埋めてください. 「何個ごとに繰り返すか」と「そのうちの何番目か」を考えます.
from turtle import *
pensize(8)
for i in range(18):
if i % ____ == 0:
pencolor("crimson")
elif i % 3 == ____:
pencolor("gold")
else:
pencolor("seagreen")
fd(70)
bk(70)
rt(20)
ht()
done()
7.4 二重の繰り返しで「表」を作る
7.4.1 行と列という考え方
入れ子でいちばんよく使うのが,格子状に並べる使い方です. 外側の繰り返しを行(よこの並び), 内側の繰り返しを列(たての並び)と考えます. 表計算ソフトのマス目や,カレンダーと同じ考え方です.
for row in range(行の数):
for col in range(列の数):
(row 行 col 列に何かを描く)
row(行)と col(列)は,
i・j の代わりによく使われる名前です.
意味のある名前を付けると,あとで読み返したときに分かりやすくなります.
位置は goto で計算します.
from turtle import *
pensize(1)
pencolor("teal")
for row in range(5): # 行:上から下へ 5 段
for col in range(8): # 列:左から右へ 8 個
pu()
goto(-160 + col * 40, 120 - row * 40) # ← 位置を計算する
pd()
for k in range(4): # 正方形を 1 つ
fd(30)
rt(90)
ht()
done()
| 式 | 意味 |
|---|---|
col * 40 | 列が 1 つ進むごとに,右へ 40 ずらす |
-160 + … | 左端の位置.画面の真ん中に来るように調整する |
- row * 40 | 行が 1 つ進むごとに,下へ 40 ずらす |
120 - … | 上端の位置.y は上が大きいので,行が進むと引く |
4 行 6 列の格子に正方形を並べます. 列の数と,行が 1 つ進むときに下へずらす量(40)を ____ に入れてください.
from turtle import *
pensize(1)
pencolor("teal")
for row in range(4):
for col in range(____):
pu()
goto(-120 + col * 40, 90 - row * ____)
pd()
for k in range(4):
fd(30)
rt(90)
ht()
done()
7.4.2 特定のマスだけ変える
row と col が分かっているので,
if で「この場所だけ」を選び出せます.
row == col はななめの対角線のマスです.
from turtle import *
pensize(2)
for row in range(6):
for col in range(6):
pu()
goto(-96 + col * 32, 96 - row * 32)
pd()
if row == col: # ななめの位置なら
pencolor("crimson")
else:
pencolor("silver")
for k in range(4):
fd(26)
rt(90)
ht()
done()
row == col のマスだけ赤くした.条件を (row + col) % 2 == 0 に変えると
市松模様(チェック柄)になります.
% と行・列を組み合わせると,模様の幅が一気に広がります.
7.4.3 階段状に並べる
内側の繰り返しの回数そのものを,外側の変数で決めることもできます.
for col in range(row): と書けば,
1 行目は 1 個,2 行目は 2 個…と増えていきます.
from turtle import *
pensize(2)
pencolor("seagreen")
for row in range(1, 7): # 1 段目から 6 段目まで
for col in range(row): # ← row 個だけ並べる
pu()
goto(-150 + col * 34, 110 - row * 34)
pd()
for k in range(4):
fd(28)
rt(90)
ht()
done()
print(row, col) を入れて,順番を目で確かめてみてください.
1 段目に 1 個,2 段目に 2 個…と並べます. 内側の繰り返しは何回まわせばよいでしょうか. ____ には数ではなく, 外側のループ変数の名前が入ります.
from turtle import *
pensize(2)
pencolor("seagreen")
for row in range(1, 7):
for col in range(____):
pu()
goto(-150 + col * 34, 110 - row * 34)
pd()
for k in range(4):
fd(28)
rt(90)
ht()
done()
7.4.4 文字と数を並べる — 九九の表
入れ子の繰り返しは,絵を描くためだけのものではありません. 行と列という考え方は,文字や数を並べて表を作るときにも そのまま使えます.掛け算の九九の表を作ってみましょう.
そのために,print の書き方をひとつだけ覚えます.
print はふつう,表示したあとに改行します.
end=" " を付けると,改行する代わりに半角スペースを打ちます.
こうすると,次の print が同じ行の続きに表示されます.
| 書き方 | 表示したあとにすること |
|---|---|
print(x) | 改行する(次は下の行の頭から) |
print(x, end=" ") | 半角スペースを打つ(次は同じ行の続きから) |
print(x, end="") | 何もしない(すきまなく横に続ける) |
print() | 何も表示せず,改行だけする |
for row in range(1, 10): # 1 の段から 9 の段まで(9 行)
for col in range(1, 10): # 1 倍から 9 倍まで(9 列)
print(row * col, end=" ")
print() # ← 1 行ぶん並べ終わったので改行する
print("おしまい")
row が段(行),col が何倍か(列)です.
内側の繰り返しが 1 行ぶんの数を横に並べ,
それが終わったところで改行する — この繰り返しが 9 回で表になります.
print() をどこに書くか
print() の字下げが,このプログラムの急所です.
- 字下げ 4 つ(内側の外・外側の中) … 正解.1 行終わるごとに 1 回改行する.
- 字下げ 8 つ(内側の中) … 数を 1 つ表示するたびに改行してしまい,81 行の縦 1 列になる.
- 字下げなし(外側の外) … 全部並べ終わってから 1 回だけ改行するので,81 個が横 1 行に並ぶ.
* を使って,次のような 10 行の直角三角形を表示します.
____ を埋めてください.
1 行目は 1 個,2 行目は 2 個…と増えていきます.
7.4.3 の階段と同じ考え方です.
* ** *** **** (…10 行目まで)
for row in range(1, 11):
for col in range(____):
print("*", end="")
print()
7.5 描きすぎに注意する
入れ子の繰り返しは,回数が掛け算で増えます. 外側 10 回・内側 10 回・その中で 4 本の線なら, 線は 10 × 10 × 4 = 400 本です. ここまでは一瞬ですが,うっかりすると何十万本にもなります.
| プログラム | 計算 | 引かれる線の本数 |
|---|---|---|
| 正方形 1 つ | 4 | 4 本 |
| 正方形を 10 個(7.1 の窓の形) | 10 × 4 | 40 本 |
| 5 行 8 列の格子(図 7.10) | 5 × 8 × 4 | 160 本 |
| 10 行 10 列の格子 | 10 × 10 × 4 | 400 本 |
| 円(360 本)を 100 個 | 100 × 360 | 36000 本 |
| 円(360 本)を 500 個 | 500 × 360 | 180000 本 → 止まります |
- 試すときは,まず回数を小さくして(
range(3)など)形を確かめ,うまくいってから増やすこと. ht()を入れ,速さを「とても速い」にすると表示が軽くなります.while True:は絶対に書かないこと.ブラウザが固まり,実行を止められなくなります.ずっと動かし続けたいときは,第 10 章で習うontimerを使います.
7.6 この章で出てきた書き方
| 書き方 | 意味 |
|---|---|
for j in …: の中の for i in …: | 入れ子.外側 1 回につき,内側が最後まで回る |
| 外側 n 回 × 内側 m 回 | 中身は n × m 回実行される |
fd(10 * j) | 外側のループ変数を使って,1 個ずつ変える |
i % 2 | 2 で割ったあまり.0 と 1 が交互 = 偶数・奇数 |
i % 3 | 3 で割ったあまり.0・1・2 が順ぐりに現れる |
if を for の中に書く | 繰り返しの何回目かによって,色や形を変える |
for row in …: / for col in …: | 行と列.格子状に並べるときの型 |
goto(x0 + col * d, y0 - row * d) | 行と列の番号から位置を計算する.y は行が進むと引く |
for col in range(row): | 内側の回数を外側の変数で決める(階段状) |
print(x, end=" ") | 改行せずに横へ並べる.表を作るときに使う |
print() | 改行だけする.内側の外・外側の中に置く |
演習
「▶ この演習を開く」で右側に読み込み,それを発展させて作品にしてください. できあがったら「⤓ 保存」でファイルに保存して,手元に残しておきましょう. どの演習も入れ子の繰り返しを必ず使うことが条件です.
正方形を横に 10 個並べるプログラムを与えます.これをもとに,
並べ方・大きさ・色を変えて模様を作ってください.
j を使って大きさを変える,j % 2 で色を交互にする,
ななめに並べる — どれでもかまいません.
10 行 10 列の格子に正方形を 100 個並べるプログラムを与えます.
行や列によって大きさ・色を変えるなどして,模様にしてください.
row と col の両方が使えることを思い出してください.
格子の中で,四角形と三角形を交互に描くプログラムを与えます.
3 種類以上の図形を回す,
色も一緒に変えるなどして発展させてください.
% 3 や % 4 を使えば,周期を変えられます.
だんだん大きくなる正方形で貝殻を描くプログラムを与えます.
正方形を別の図形に変える,
回す角度や大きさの増え方を変えるなどして発展させてください.
j を使えば,色をだんだん濃くすることもできます.
四角形を少しずつ回しながら 45 個描いて,タイヤのような図を作ります.
ずらす角度は 8 度(rt(98) は 90 + 8 の意味),
1 辺は 10 歩です.ホイールの模様を足す・中心に円を描く・
本数や角度を変えるなどして発展させてください.
入れ子の繰り返しだけで,ドラえもんの顔を描くプログラムを与えます. 使っているのは今回習ったことばかりです.
- 顔・目のふち・鼻・鈴は,どれも
fdとltを繰り返した円 - 黒目は,7.2.2 の目玉とまったく同じ入れ子(曲がる角度を変えながら円を重ねる)
- 口は,円を描く途中で
pu()とpd()を切り替えて下半分だけ残したもの - ひげは,外側のループ変数で開始位置と向きを変えた直線 3 本
このプログラムをもとに,独自のアレンジを加えてください. 色を変える,ほっぺを足す,首輪と鈴を付ける, 顔の形を自分の好きなキャラクターに作り変える — どれでもかまいません.
3 行 10 列のキーを並べ,特定のキーだけ横長にするプログラムを与えます.
キーの数・配置・大きさを変えて,
本物のキーボードらしくしてください.
if の条件を増やせば,「このキーだけ色を変える」こともできます.
入れ子の繰り返しを使って,好きな絵や模様を描いてください. 演習 7-6 のドラえもんは,円を何重にも重ねて目を作り, 繰り返しでひげを引いているだけでした. 同じように,身近なものを繰り返しだけで表現してみましょう.
- 二重以上の入れ子を必ず使うこと
- 外側のループ変数を内側で1 回は使うこと(大きさ・角度・色のどれかを変える)
%を使った周期か,forの中のifを1 か所以上使うこと- 先頭にコメントで,題名・氏名・日付を書くこと
- 描く線が多くなりすぎないよう,回数を見積もってから実行すること
% で選ぶ,
階段(7.4.3)をピラミッドにする,あたりから試してみてください.
ここまでの演習は,すべて絵でした. 最後に,入れ子の繰り返しを計算に使ってみます. 素数とは,1 と自分自身でしか割り切れない 2 以上の整数のことです. 2, 3, 5, 7, 11, 13, 17, 19 … と続きます.
第 6 章の 6.9.4 で,1 つの数が素数かどうかを フラグを使って調べました.出発点のプログラムはその形です. 今度はそれを繰り返しの中に入れて, 2 から 50 までの素数をぜんぶ見つけてください.
- 外側の繰り返しで,調べたい数
numを 2 から 50 まで動かす. - 内側の繰り返しで,
numを 2 からnum - 1まで順に割ってみる. - 1 回でも割り切れたら,フラグを
Falseにする. - 内側が終わったところでフラグを調べ,
Trueのままならnumを表示する.
True に戻す場所が急所です.
is_prime = True を外側の繰り返しの外に書くと,
一度 False になったきり戻らず,4 以降が 1 つも表示されません.
調べる数が変わるたびに旗を立て直す必要があります.
50 を 200 にしてみてください.
表示が横に長くなるので,print(num, end=" ") を使うと見やすくなります.発展 2:内側の繰り返しは,
num - 1 まで調べなくても答えは変わりません.
どこまで調べれば十分か考え,range を書き換えてください.
速くなったかどうかは,表示される個数が同じままかで確かめます.
まとめ
- 繰り返しの中に繰り返しを書けるのが入れ子.外側が 1 回進むあいだに,内側は最後まで回る.
- 中身が実行される回数は外側 × 内側.回数は掛け算で増える.
- どの行がどの繰り返しに属するかは,字下げの深さだけで決まる.1 段ずれると,エラーも出ないまま別の絵になる.
- 外側のループ変数を内側で使うと,1 個ずつ大きさや角度が変わる模様ができる(貝殻・目玉).
%は割り算のあまり.i % 2で交互,i % 3で 3 つ周期.ifと組み合わせて色や形を変える.- 外側を行,内側を列と考えると,格子状に並べられる.内側の回数を外側の変数にすれば階段状になる.
- 行と列の考え方は文字の表にも使える.
print(x, end=" ")で横に並べ,1 行ぶん終わったところでprint()して改行する. - 入れ子は絵だけのものではない.外側で調べる数を,内側でその数を調べる手順を回せば,素数を見つけるようなアルゴリズムが書ける(演習 7-9).
- 入れ子は描きすぎに注意.まず少ない回数で試し,形が決まってから増やす.
while True:は使わない(動かし続けるのは第 10 章のontimer).
次の回では,かめを増やします.
Turtle() と書くと,かめが何匹でも生まれます.
1 匹ずつ名前をつけて,別々の場所・別々の色で動かせるようになります.
今回の入れ子とはちがう,登場人物が何人もいるプログラムの書き方です.