繰り返しの入れ子
繰り返しを,さらに繰り返す.模様はここから生まれます.
前の章,正方形を描く繰り返しを覚えました.では その正方形を 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 回だけ描いています. 「繰り返しを繰り返す」ためには, 繰り返したいものを,まるごと外側の中に入れる必要があります.
図 7.1 の窓は正方形 4 つでした.今度は正方形を 6 つ, 同じ角を中心にぐるりと 1 周するように描きます. 外側は何回まわせばよいでしょうか.また,1 つ描き終わるたびに 何度回れば,6 つでちょうど 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 の 8 倍に 20 を足した長さに
してください(1 個目が 28,2 個目が 36 …).____ に入るのは数ではなく,
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 が入っている.24 本の線を,4 色が順ぐりに現れるように塗り分けます. ____ を 2 つ埋めてください. 「何個ごとに繰り返すか」と「そのうちの何番目か」を考えます.
from turtle import *
pensize(8)
for i in range(24):
if i % ____ == 0:
pencolor("crimson")
elif i % 4 == ____:
pencolor("gold")
elif i % 4 == 2:
pencolor("seagreen")
else:
pencolor("royalblue")
fd(70)
bk(70)
rt(15)
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 は上が大きいので,行が進むと引く |
格子の虫食いを埋める前に,最初の二行の座標を紙に書いてみましょう.内側のループでは列番号が0から5まで変わり,行番号は同じです.次の行へ進むと列番号は再び0から始まります.実行後は図形が24個あることと,一行目より二行目が40だけ下にあることを確認します.個数が合っていても,行の更新でx座標を変えると斜めに並んでしまいます.個数と配置を別々に確かめると,回数の誤りと座標の誤りを見分けられます.
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) を入れて,順番を目で確かめてみてください.
図 7.12 とは逆に,1 段目に 6 個,2 段目に 5 個 … 6 段目に 1 個と並べます.
内側の繰り返しは何回まわせばよいでしょうか.
____ には数ではなく,
外側のループ変数 row を使った式が入ります.
row が 1 のとき 6,6 のとき 1 になる式を考えてください.
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 個並べるプログラムを与えます.これをもとに, 並べ方・大きさ・色を変えて模様を作ってください.
- 正方形は 10 個,横一列.いちばん左の正方形は
goto(-150, 0)から描き始める. - 外側のループを
for j in range(1, 11):にし,1 辺の長さを4 * j(4, 8, …, 40)にする. - 次の正方形へは(1 辺の長さ + 8)だけ進み,どの正方形の間にも 8 のすき間をあける.
- これもやりましょう:
j % 3で 3 色を回す.あまりが 0 なら"crimson",1 なら"royalblue",2 なら"gold". - これもやりましょう:出発点では正方形の上の辺がそろっている.これを下の辺がそろう並び方(大きい正方形ほど上に高くなる)に変える.
必須(すべて満たして正しく動けば 8)
- 外側のループ変数
jを内側のfdの引数の式に使い,正方形の大きさを 4, 8, …, 40 と 1 個ずつ変えている - 次の正方形へ移る量(
pu()のあとのfd)にもjを使い,いちばん左の (-150, 0) から,すき間 8 で重ならずに並んでいる - これもやりましょう:
j % 3で crimson・royalblue・gold の 3 色を回している - これもやりましょう:10 個の正方形の下の辺がそろっている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 正方形の個数を変数
nにし,nを変えても列全体が画面の左右中央にくるよう,描き始めの x 座標をnを使った式で計算する - 大きさの増え方(4)とすき間(8)も変数にし,どちらを変えても重ならずに並ぶことを確かめる
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
10 行 10 列の格子に正方形を 100 個並べるプログラムを与えます.
行や列によって大きさ・色を変えるなどして,模様にしてください.
row と col の両方が使えることを思い出してください.
- 並べ方は出発点のまま(10 行 10 列,左上のマスは
(-135, 135),マスの間隔 30). - 市松模様にする:
(row + col) % 2が 0 のマスは"crimson",1 のマスは"navy". - 正方形の 1 辺を
20 + rowにし,下の行ほど 1 ずつ大きくする(20〜29). - 描かれる線の本数を見積もり,先頭のコメントに「10 × 10 × 4 = 400 本」のように書く.
- これもやりましょう:左上から右下への対角線上のマス(
rowとcolが等しいマス,10 個)だけは,市松模様の色ではなく"gold"にする.
必須(すべて満たして正しく動けば 8)
(row + col) % 2を使った条件で,crimson と navy の市松模様にしている- 正方形の 1 辺を
20 + rowにし,下の行ほど大きくしている - 描かれる線の本数を見積もり,「10 × 10 × 4 = 400 本」のように先頭のコメントに書いている
- これもやりましょう:
row == colの 10 マスだけ gold になっている(if/elifの順番が正しい)
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 行と列の数を変数
Nにし,Nを変えても市松模様と両方の対角線(row == colとrow + col == N - 1)の色分けが正しく出るようにする Nが奇数のとき 2 本の対角線が交わるマスの色をどうするか決め,ifの順番で表す
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
格子の中で,四角形と三角形を交互に描くプログラムを与えます. これを,3 種類の図形が順ぐりに現れ, 色も図形と一緒に変わる模様に発展させてください.
- 並べ方は出発点のまま(5 行 6 列,左下の角が
(-140 + col * 40, 90 - row * 40),東向きからltで描く). (row + col) % 3の値で図形を決める.0 → 正三角形("crimson"),1 → 正方形("gold"),2 → 正五角形("seagreen").- どの図形も 1 辺は 20.(曲がる角度)×(回数)= 360 でちょうど閉じる.
- これもやりましょう:
if/elifで 3 つの描き方を書き分けるのではなく,角の数をn = (row + col) % 3 + 3で計算し,range(n)とlt(360 / n)の1 つのforで 3 種類とも描く(ifは色を決めるのにだけ使う).
必須(すべて満たして正しく動けば 8)
(row + col) % 3を使い,正三角形・正方形・正五角形を順ぐりに並べている- 図形ごとに crimson・gold・seagreen と色も変えている
- どの図形も 1 辺 20 で,(曲がる角度)×(回数)= 360 で閉じている
- これもやりましょう:角の数を
(row + col) % 3 + 3で計算し,1 つのfor(range(n)とlt(360 / n))で 3 種類とも描いている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 図形の種類の数を変数
kにし,(row + col) % k + 3で正三角形から正(k + 2)角形までを回す - 角の数が増えても間隔 40 に収まるよう,1 辺の長さを角の数
nから計算する
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
だんだん大きくなる正方形で貝殻を描くプログラムを与えます. 正方形を別の図形に変え, 回す角度や大きさの増え方を変えて,別の貝殻に作り変えてください.
- 正方形を正三角形(
rt(120)を 3 回)に変える.描き始めは原点. - 外側の
jを 1 から数え,三角形の 1 辺を6 * j(6, 12, 18, …)にする. - 色は
j % 3で回す.あまりが 0 なら"crimson",1 なら"darkorange",2 なら"gold". - これもやりましょう:三角形の個数を変数
n = 24にし,三角形を 1 つ描くごとに回す角度を360 / nで計算して,ちょうど 1 周(24 個で 360 度)する貝殻にする.nを 12 に変えても 1 周になることを確かめる.
必須(すべて満たして正しく動けば 8)
- 正方形を正三角形に変え,原点から描き始めている
- 外側の
jを使った6 * jで,三角形を 1 個ずつ大きくしている j % 3で crimson・darkorange・gold の 3 色を回している- これもやりましょう:個数を
n = 24にし,回す角度を360 / nで計算して,ちょうど 1 周する貝殻になっている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 図形の角の数を変数
mにし,1 つのforで正m角形の貝殻にする(mとnのどちらを変えても閉じて 1 周する) nを変えても最後の図形の 1 辺が同じ長さ(144)になるよう,1 辺をjとnの式で決める
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
四角形を少しずつ回しながら 45 個描いて,タイヤのような図を作ります.
ずらす角度は 8 度(rt(98) は 90 + 8 の意味),
1 辺は 10 歩です.45 × 8 = 360 なので,ちょうど 1 周で閉じます.
このタイヤにスポークを足し,
スポークの本数と溝の数を変数にして,数を変えても形が崩れないようにしてください.
出発点のタイヤは,中心がおよそ (5, -71),
内側のふちまでの半径がおよそ 70 です.
- スポーク:中心
(5, -71)から長さ 60 の線をforで描く.本数は変数n = 8にし,回る角度は360 / nで計算する(nを 12 に変えても等間隔に 1 周する). - これもやりましょう:
i % 2を使い,スポークの色を"navy"と"orange"の交互にする. - これもやりましょう:タイヤの溝の数(外側の回数)を変数
g = 45にし,range(g)で回す.1 回にずらす角度は360 / gで計算し,rt(98)をrt(90 + 360 / g)に書き換える.gが 45 のときは出発点と同じ図になり,gを 30 に変えてもちょうど 1 周で閉じる(タイヤは小さくなる)ことを確かめる.
必須(すべて満たして正しく動けば 8)
- 中心 (5, -71) からスポークを
forで描き,本数を変数n = 8・回る角度を360 / nで計算している(nを 12 にしても等間隔) - 長さ 60 のスポークがタイヤの内側に収まっている
- これもやりましょう:
i % 2でスポークの色が navy と orange の交互になっている - これもやりましょう:溝の数を変数
g = 45にし,ずらす角度を360 / gで計算している(rt(90 + 360 / g).gを 30 にしても 1 周で閉じる)
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 描かれる線の本数を
gとnの式(溝 1 つに 3 本,スポーク 1 本に 1 本)で表し,g = 45・n = 8のときの本数をコメントに書く nを奇数(7 など)にするとi % 2の色分けがどこで崩れるかを確かめ,理由をコメントに書く
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
繰り返しと入れ子で,ドラえもんの顔を描くプログラムを与えます. 使っているのは第 6 章と今回習ったことばかりです.
- 顔・目のふち・鼻は,どれも
fdとltを繰り返した円 - 目のふちは,外側の
mで左右の位置を変え,内側で円を描く入れ子 - 黒目は,7.2.2 の目玉と同じ入れ子(曲がる角度を変えながら円を重ねる)を,さらに
mで左右 2 回繰り返したもの - 口は,円を描く途中で
pu()とpd()を切り替えて下側だけ残したもの - ひげは,ループ変数
iで開始位置と向きを変えた直線 3 本
このプログラムに,左右対称の部品を入れ子で足したり,入れ子で描き直したりしてください.
- ほっぺ:目のふちと同じように,外側の
for m in range(2):で左右の位置を変え,内側のforで円を描く.描き始めは左が(-62, -75),右が(62, -75)(mを使った式で書く),東向き(seth(0))から. - ほっぺの円は
fd(2)とlt(10)を 36 回(10 × 36 = 360 で閉じる),色は"hotpink". - これもやりましょう:出発点では左右 2 つの
forで描いているひげを,外側for m in range(2):(0 が左,1 が右)と内側for i in range(3):の入れ子 1 つで描き直す.描き始めの x は-25 + m * 50,y は出発点と同じ.向きは,左が162 + i * 18,右は左を左右反転した180 - (162 + i * 18)(ifでmを見て選ぶ).できる図は出発点と同じ. - これもやりましょう:出発点では目のふちと黒目を,別々の 2 つの
for m in range(2):で描いている.これを1 つのfor mにまとめ,その中で目のふち(内側のfor)と黒目(for jとfor kの二重)を続けて描く.黒目の部分はfor m・for j・for kの三重の入れ子になる.位置の式(-27 + m * 54と-15 + m * 30)はそのまま使い,できる図は出発点と同じ.
必須(すべて満たして正しく動けば 8)
- ほっぺを,外側の
for mで左右の位置を変え,内側のforで描く入れ子で足している - ほっぺの円が (-62, -75) と (62, -75) から描かれ,10 × 36 = 360 でちょうど閉じている(色は hotpink)
- これもやりましょう:ひげを外側
m・内側iの入れ子 1 つで描き,x を-25 + m * 50,右の向きを180 - (162 + i * 18)で決めている(図は出発点と同じ) - これもやりましょう:目のふちと黒目を 1 つの
for mの中で描き,黒目がfor m・for j・for kの三重の入れ子になっている(for mは 1 つだけ.図は出発点と同じ)
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 目のふち・黒目・ほっぺ・ひげの左右の位置を,中心からのずれを表す変数 1 つで書き,それを変えると全部が左右対称のまま動くようにする
ifを使わず,mの式だけで右と左のひげの向きを決める
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
3 行 10 列のキーを並べ,特定のキーだけ横長にするプログラムを与えます.
キーの数・配置・大きさを変えて,
本物のキーボードらしくしてください.
キーは,goto した点を左上の角にして,右へ・下へ描かれます.
- キーを 4 段 × 10 列にする.キーの高さは 22,ふつうのキーの幅は 22,キーの間隔(左上の角どうし)は横 30・縦 30.
- いちばん上の段の左のキーは
(-170, 70).段が 1 つ下がるごとに,その段の左端を右へ 10 ずらす(本物のキーボードのように). if/elifで,各段の右端(col == 9)のキーだけ幅を変える.1 段目は幅 40(Backspace)・3 段目は幅 50(Enter)・4 段目は幅 32(Shift).2 段目の右端はふつうの幅のまま.- 色は,Backspace と Shift が
"navy",Enter が"crimson",ほかのキーは"dimgray". - これもやりましょう:1 段の列の数を変数
cols = 10にし,内側をrange(cols),右端の判定をcol == 9ではなくcol == cols - 1で書く.colsを 12 に変えても,各段の右端のキーだけが横長・色付きになることを確かめる.
必須(すべて満たして正しく動けば 8)
- キーが 4 段 × 10 列で,間隔 30 で並んでいる
rowを使った式で,段ごとにキーの左端を 10 ずつ右へずらしているif/elifで,右端のキーの幅を 40・50・32 にし,色も指定どおりに変えている- これもやりましょう:列の数を変数
colsにし,右端をcol == cols - 1で判定している(colsを 12 にしても右端だけが横長)
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 左端のキーも横長にしたとき,段ごとの右端がそろうよう,各キーの x 座標を前のキーの幅から計算する(右へ進む量を変数に足していく)
- 4 段目の下に幅 150 のキーを置くとき,左上の角の座標を段の数と間隔から式で求め,段の数を変えても位置が追従するようにする
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
入れ子の繰り返しを使って,好きな絵や模様を描いてください. 演習 7-6 のドラえもんは,円を何重にも重ねて目を作り, 繰り返しでひげを引いているだけでした. 同じように,身近なものを繰り返しだけで表現してみましょう.
- 二重以上の入れ子を必ず使うこと
- 外側のループ変数を内側で1 回は使うこと(大きさ・角度・色のどれかを変える)
%を使った周期か,forの中のifを1 か所以上使うこと- 先頭にコメントで,題名・氏名・日付を書くこと
- 描く線が多くなりすぎないよう,回数を見積もってから実行すること
% で選ぶ,
階段(7.4.3)をピラミッドにする,あたりから試してみてください.
必須(すべて満たして正しく動けば 8)
- 二重以上の入れ子の繰り返しを使っている
- 外側のループ変数を内側で使い,大きさ・角度・色のどれかを変えている
%を使った周期か,forの中のifを 1 か所以上使っている- 先頭のコメントに題名・氏名・日付がある
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 個数や回数を変数 1 つにし,角度や位置をその変数の式で決めて,値を変えても形が崩れないようにする
- 内側の回数を外側の変数で決める(階段・三角形状)など,外側と内側の関係を工夫している
- 線の本数を式で見積もってコメントに書いている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
ここまでの演習は,すべて絵でした. 最後に,入れ子の繰り返しを計算に使ってみます. 素数とは,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を表示する. - これもやりましょう:
print(num, end=" ")を使って,素数を1 行に横に並べて表示する. - これもやりましょう:見つかった素数の個数を
countで数え,最後に改行してから「素数は 15 個」と表示する.
正しくできると,次のように表示されます.
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 素数は 15 個
True に戻す場所が急所です.
is_prime = True を外側の繰り返しの外に書くと,
一度 False になったきり戻らず,4 以降が 1 つも表示されません.
調べる数が変わるたびに旗を立て直す必要があります.
50 を 200 にしてみてください(素数は 46 個になります).発展 2:内側の繰り返しは,
num - 1 まで調べなくても答えは変わりません.
どこまで調べれば十分か考え,range を書き換えてください.
調べる範囲を減らしても答えが変わらないことは,表示される素数が同じかどうかで確かめます.
必須(すべて満たして正しく動けば 8)
- 外側の繰り返しで,調べる数
numを 2 から 50 まで動かしている - フラグ
is_prime = Trueを外側の中・内側の前で毎回立て直している - 内側が終わってからフラグを調べ,素数だけ(2 から 47 までの 15 個)を表示している
- これもやりましょう:
print(num, end=" ")で素数を 1 行に並べている - これもやりましょう:素数の個数を
countで数え,「素数は 15 個」と表示している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 上限を変数
N = 50にし,N = 200に変えるだけで 46 個になることを確かめる - 内側で調べる範囲を減らし,答えが変わらないことを確かめる
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
追加実習:仕組みを確かめてから設計する
二重ループの回数を,描く前に確かめる
二重ループでは,内側の処理が何回実行されるかを先に予測すると,図形が多過ぎる・少な過ぎる原因を調べやすくなります.
外側が3回,内側が4回なら,内側の本体は12回です.ただし,内側の回数が外側の値で変わる場合には,単純な掛け算では求められません.
各行で何回になるかを表にしてから合計します.
例えば,1行目に1個,2行目に2個,3行目に3個描くなら,合計は1+2+3です.
range(row+1)という式が,外側の各回でどの値になるかを追います.rowは0から始まるので,表示上の行番号と1ずれることにも注意してください.
変数の意味を「何行目か」「その行の何個目か」と分けると,式を読みやすくなります.
描画を使わず,まずprintでrowとcolを表示してみましょう.
予測した表と一致してから,その場所へ図形を描く命令を入れます.この順なら,繰り返しの間違いと,タートルの向きや位置の間違いを切り分けられます.
絵が複雑になったときほど,数値だけの小さい実験が役立ちます.
次の例では3行×4列の組を数えます.
外側の開始前にtotalを0へ戻す理由も確認してください.内側で0へ戻すと,最後の行の個数しか残りません.
集計値をどの範囲で保持したいかに応じて,初期化する場所を選びます.字下げは繰り返す範囲だけでなく,値を初期化する頻度にも関係します.
total = 0
for row in range(3):
for col in range(4):
total += 1
print(row,col)
print("合計",total)
print("12 になれば予測どおり")
各行の個数を1,2,3,4と増やす二重ループへ変更してください.合計10になることを確認し,内側のrangeが各回で何を表すか説明します.
必須(すべて満たして正しく動けば 8)
- 内側の
rangeに外側の変数を使った式(row + 1など)を書き,各行の個数を 1, 2, 3, 4 にしている - 数えた合計が 10 になることを
printで確かめている print(row, col)の表示で,各行の個数が 1, 2, 3, 4 になっていることを確かめている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 行の数を 5 や 6 に変え,合計が 15・21 になることを確かめる
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
模様の間隔と,図形の大きさを別にする
格子状の模様では,図形そのものの大きさと,図形同士の配置間隔を区別します.
四角形の辺をsize,隣の開始位置までの距離をgapとすると,sizeを変えても配置の規則は同じにできます.この2つを同じ数値で書いていると,辺を変えるつもりで隙間まで変わることがあります.
変更したい意味ごとに変数を分けます.
列colのx座標は開始位置にcol×gapを加え,行rowのy座標は開始位置からrow×gapを引いて求めます(本文7.4.1と同じく,行が進むと下へ並びます).
この式なら,各図形を描いた後にタートルがどこへ残ったかに依存せず,次の開始位置を決められます.移動中に線を出さないため,penupしてからgotoし,描く前にpendownします.
配置と描画を別の段階として考える練習です.
一方,forwardだけで隣へ進む方法もあります.
その場合は,1個の図形を描いた後の位置と向きを毎回そろえる必要があります.どちらの方法でも作れますが,座標で指定する方法は,行や列の数を変更したときに結果を予測しやすい利点があります.
用途に応じて使い分けましょう.
以下の例で,sizeだけを変えた場合とgapだけを変えた場合を比べてください.
予測には「大きくなる」だけでなく,図形の左下の座標が変わるか,隙間が変わるかを書きます.次の演習では行と列で間隔を変えます.
1個のgapで両方を管理するより,横と縦を別々にする必要があることが分かります.
from turtle import *
ht()
speed(0)
size = 20
gap = 50
for row in range(3):
for col in range(4):
pu()
goto(-100+col*gap,60-row*gap)
seth(0)
pd()
for side in range(4):
fd(size)
lt(90)
done()
3行4列は保ち,横の間隔60,縦の間隔35,辺15へ変更してください.図形の配置に使う値と,図形を描く値を区別した変数名を使います.
必須(すべて満たして正しく動けば 8)
- 横の間隔と縦の間隔を別の変数(
gap_x・gap_y)にしている - 辺 15・横の間隔 60・縦の間隔 35 で,3 行 4 列の正方形を描いている
- 配置に使う値と図形の大きさに使う値を,別の変数名で区別している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 行数・列数も変数にし,1 か所変えるだけで格子の大きさが変わるようにする
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
色の規則を,行と列から作る
市松模様では,隣へ進むと色が変わり,次の行でも開始色が変わります.
列だけの偶奇で色を決めると,縦方向には同じ色が続く縞模様になります.行と列の両方を使って,どの位置でどの色になるかを考えます.
row+colの偶奇を使うと,横にも縦にも1つ進むたびに色を切り替えられます.
規則が分からない場合は,3行3列の小さい表を紙へ書き,各マスのrow,col,row+colを埋めます.
求めたい色と計算結果を対応させると,剰余の式を選ぶ理由を説明できます.式を丸暗記するより,別の模様へ応用しやすくなります.
3色を周期的に使いたい場合には,2ではなく3で割った余りを考えます.
条件分岐は,色を設定する場所へ置きます.
四角形の各辺で条件を判定すると,意図によっては辺ごとの色分けになります.図形1個につき色を決めるなら,辺を描くループの前で設定します.
二重ループの外と内のどこへ文を置くかが,見た目の規則へ直結します.
以下は図形を使わず,色の名前を並べて規則を確かめる例です.
まずrowとcolから得る値だけを確認し,正しいことが分かってから前の格子へ組み込みます.作品を仕上げるときは,行数・列数が奇数の場合も試してください.
偶数の例だけでは,行の切り替えで間違っていることを見落とす場合があります.
for row in range(3):
for col in range(3):
if (row+col)%2 == 0:
print(row,col,"青")
else:
print(row,col,"赤")
色を3色へ増やし,row+colを3で割った余りに応じて青,赤,緑を表示してください.4行4列で確認し,横へも下へも順に色が変わる理由を説明します.
必須(すべて満たして正しく動けば 8)
(row + col) % 3の値で色を選んでいるif/elif/elseで青・赤・緑の 3 色を決めている- 4 行 4 列のすべてのマスについて,
row・col・色を表示している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 格子の正方形に組み込み,3 色の模様として描く
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
作品を確かめる観点
模様を提出する前に,図形の個数と線分の本数を区別して数えてください.
3行4列に四角形を置くなら図形は12個,四角形を描くforwardは48回です.配置のための移動は,ペンを上げていれば線分には数えません.
このように数える対象をそろえると,プログラムの実行回数と画面の線の本数が一致しない理由を説明できます.次に,1行1列,1行4列,3行1列へ変更し,行末の処理が余分に働いていないか確認します.
最後に通常の3行4列へ戻し,色の規則と配置が両方保たれるかを調べます.行数や列数を変えるたびにコードを多数書き換える必要があるなら,回数を表す変数と位置の計算が分離できているか見直してください.
作品の説明には,変更できる値とその効果を対応させて書きます.『数字を変えると模様が変わる』だけではなく,『gap_xを変えると各図形の辺はそのままで列間隔が変わる』と書けば,設計した意図が伝わります.
まとめ
- 繰り返しの中に繰り返しを書けるのが入れ子.外側が 1 回進むあいだに,内側は最後まで回る.
- 中身が実行される回数は外側 × 内側.回数は掛け算で増える.
- どの行がどの繰り返しに属するかは,字下げの深さだけで決まる.1 段ずれると,エラーも出ないまま別の絵になる.
- 外側のループ変数を内側で使うと,1 個ずつ大きさや角度が変わる模様ができる(貝殻・目玉).
%は割り算のあまり.i % 2で交互,i % 3で 3 つ周期.ifと組み合わせて色や形を変える.- 外側を行,内側を列と考えると,格子状に並べられる.内側の回数を外側の変数にすれば階段状になる.
- 行と列の考え方は文字の表にも使える.
print(x, end=" ")で横に並べ,1 行ぶん終わったところでprint()して改行する. - 入れ子は絵だけのものではない.外側で調べる数を,内側でその数を調べる手順を回せば,素数を見つけるようなアルゴリズムが書ける(演習 7-9).
- 入れ子は描きすぎに注意.まず少ない回数で試し,形が決まってから増やす.
while True:は使わない(動かし続けるのは第 10 章のontimer).
次の回では,かめを増やします.
Turtle() と書くと,かめが何匹でも生まれます.
1 匹ずつ名前をつけて,別々の場所・別々の色で動かせるようになります.
今回の入れ子とはちがう,登場人物が何人もいるプログラムの書き方です.