リスト
同じ種類のデータは,1 つの箱にまとめて入れる.繰り返しと組み合わせると,一気に力が出ます.
変数は「数を 1 つ覚えておく箱」でした.では,覚えておきたい数が 5 つあったら? 100 個あったら? 変数を 100 個作って,100 行の命令を書くのでしょうか.
今回覚えるリストは,同じ種類のデータをまとめて入れる箱です. リストと,すでに習った繰り返しを組み合わせると, データが 5 個だろうと 100 個だろうと,プログラムの長さは変わりません. ここがプログラミングの大きな山のひとつです.
[ ]でリストを作り,添字で 1 つずつ取り出せるlen()で個数を調べ,中身を書き換えられるappend()・pop()・remove()でリストを伸ばしたり縮めたりできるfor x in リストとfor i in range(len(リスト))を使い分けられる- 色・長さ・座標をリストに入れて,繰り返しで図を描ける
- リストにかめを入れて,何匹でも一斉に動かせる
sum・max・minを使い,条件に合うものを自分で数えられる- スライス・リストのリストを知っている
16.1 変数が増えてくると,こうなる
第 8 章で,5 匹のかめを動かしました.そのときのプログラムを思い出してください. かめ 1 匹につき変数を 1 つ作り,動かす命令も 1 匹ぶんずつ書きました.
from turtle import * t0 = Turtle() t1 = Turtle() t2 = Turtle() t3 = Turtle() t4 = Turtle() t0.goto(-200, 0) t1.goto(-100, 0) t2.goto(0, 0) t3.goto(100, 0) t4.goto(200, 0) t0.fd(80) t1.fd(80) t2.fd(80) t3.fd(80) t4.fd(80)
動きはします.けれども,このプログラムには 3 つの困りごとがあります.
- 増やすのが大変.かめを 20 匹にしたければ,変数を 15 個足し,命令も 15 行 × 2 か所ぶん書き足さなければなりません.
- 直すのが大変.
fd(80)をfd(120)に変えたければ,5 か所すべてを直します.1 か所忘れたら,そのかめだけ違う動きをします. - 「全部に」ができない.「いちばん進んだかめは?」「全部のかめの合計は?」といったことを調べるのに,毎回 5 つの変数を並べて書く必要があります.
for に任せました.
今回は「同じ種類のデータの並び」をコンピュータに任せます.
そのための道具がリストです.
16.2 リストに入れる
16.2.1 リストを作る — [ ]
リストは,角かっこ [ ] の中に,コンマで区切って並べて作ります.
数でも文字でも,何を入れてもかまいません.
scores = [80, 95, 72, 100, 64] colors = ["red", "green", "blue"] empty = [] # 空っぽのリストも作れる
score ではなく scores,
かめなら turtle ではなく turtles.
こうしておくと,for score in scores: のように
プログラムが英語の文のように読めます.
16.2.2 取り出す — 添字は 0 から始まる
リストの中の 1 つを取り出すには,何番目かを角かっこで指定します. この番号を添字(そえじ,index)といいます. ここで最初に必ずつまずくのですが, 添字は 1 ではなく 0 から始まります.
| 1 つめ | 2 つめ | 3 つめ | 4 つめ | 5 つめ | |
|---|---|---|---|---|---|
| 中身 | 80 | 95 | 72 | 100 | 64 |
| 添字 | 0 | 1 | 2 | 3 | 4 |
scores = [80, 95, 72, 100, 64] print(scores[0]) # 1 つめ → 80 print(scores[1]) # 2 つめ → 95 print(scores[4]) # 5 つめ → 64 print(scores[-1]) # 最後 → 64 print(scores[-2]) # 後ろから 2 つめ → 100
scores[5] と書くと,
IndexError: list index out of range(範囲外)というエラーになります.
最後の添字は「個数 − 1」です.
角かっこの中には,数をそのまま書くだけでなく,
変数でも計算式でも書けます.
ここが「変数を 5 個作る」やり方との大きな違いです.
score3 という変数名は計算で作れませんが,
scores[i] の i なら計算で決められます.
import random
prize = ["特賞", "1 等", "2 等", "3 等", "はずれ"]
i = random.randint(0, len(prize) - 1) # 0 から「個数 − 1」までのでたらめな数
print("添字", i, "→", prize[i])
# 同じことを 1 行で書く道具もある
print(random.choice(prize))
# 何度も引いてみる
for kai in range(5):
print(kai + 1, "回目:", random.choice(prize))
random.randint(0, 5) と書くと 0 から 5 まで,
つまり 6 通りの数が出ます.5 個のリストには添字 5 がないので,
ときどき IndexError になります.
こういうときこそ len(prize) - 1 と書いてください.
個数を数字で書いてしまうと,リストを増やしたときに必ず忘れます.
16.2.3 個数を数える — len()
len(リスト) で,入っている個数がわかります.
プログラムの中に個数を直接書かないために,とても大事な命令です.
scores = [80, 95, 72, 100, 64] print(len(scores)) # 5 print(scores[len(scores) - 1]) # 最後の要素 → 64 colors = ["red", "green", "blue"] print(len(colors)) # 3 print(len([])) # 0
len は length(長さ)の略です.
文字列にも使えて,len("こんにちは") は 5 になります.
16.2.4 書き換える
添字で場所を指定して,= で新しい値を入れれば書き換えられます.
変数への代入とまったく同じ書き方です.
scores = [80, 95, 72, 100, 64] print(scores) scores[2] = 88 # 3 つめを 88 に print(scores) scores[0] = scores[0] + 10 # 1 つめに 10 点おまけ print(scores)
座標のリストから 1 つずつ取り出して,3 か所に点を打ちます. ____ を埋めてください. 添字は 0 からでしたね.
from turtle import *
ht()
pu()
xs = [-180, 0, 180]
colors = ["crimson", "gold", "royalblue"]
goto(xs[0], 0)
dot(60, colors[____])
goto(xs[____], 0)
dot(60, colors[1])
goto(xs[2], 0)
dot(60, colors[____])
# 個数を表示する
goto(0, -120)
pencolor("dimgray")
write(f"点は {____(xs)} 個", align="center", font=("Arial", 22, "normal"))
done()
16.3 リストを伸ばす・縮める
リストは,あとから中身を増やしたり減らしたりできます. ここが「変数を 5 個作る」やり方との決定的な違いです. プログラムが動いている最中に,入れ物の大きさを変えられるのです.
| 命令 | 意味 |
|---|---|
リスト.append(値) | 最後に 1 つ付け足す |
リスト.insert(添字, 値) | 指定した場所に割り込ませる |
リスト.insert(0, 値) | 先頭に 1 つ割り込ませる |
a += b(a.extend(b)) | リスト b の中身をまとめて a の最後に付け足す |
b.extend(a) のあと a.clear() | a の中身を全部 b へ移す(移したほうは空になる) |
リスト.pop() | 最後の 1 つを取り出して,リストから消す |
リスト.pop(添字) | 指定した場所の 1 つを取り出して,リストから消す |
リスト.pop(0) | 先頭の 1 つを取り出して,リストから消す |
リスト.remove(値) | その値を探して,最初に見つかった 1 つを消す |
リスト.clear() | 全部消して空にする |
insert(0, 値)(先頭に入れる)と pop(0)(先頭から取る)は,
append() や pop() に比べて少し手間がかかります.
先頭に場所を空けたり,空いた場所を詰めたりするために,
後ろの要素を全部ひとつずつずらす必要があるからです.
要素が 10 個くらいなら気になりませんが,何万個もあるときは差が出ます.
とくに理由がなければ,後ろ(append・pop)を使ってください.
scores = [] print(scores) scores.append(80) scores.append(95) scores.append(72) print(scores) # [80, 95, 72] print(len(scores)) # 3 last = scores.pop() # 最後の 72 を取り出す print(last) # 72 print(scores) # [80, 95] scores.append(100) scores.append(64) print(scores) # [80, 95, 100, 64] scores.remove(95) # 値 95 を消す(添字ではない) print(scores) # [80, 100, 64]
pop は添字で,remove は値で消します.
scores.remove(2) は「3 つめを消す」ではなく
「2 という値を探して消す」です.
見つからなければ ValueError になります.
scores.append(80) の書き方は,これまでの fd(100) とは
少し形が違います.「誰に」対する命令かを
.(ドット)の前に書くのです.
第 8 章で t1.fd(100) と書いたのと同じ形です.
「scores さん,80 を付け足してください」と読んでください.
買い物かごに品物を入れたり出したりします. ____ を埋めてください. 付け足すのと,値を指定して消すのと, 個数を数えるのは,それぞれ何という命令でしたか.
kago = ["りんご", "たまご"]
kago.____("ぎゅうにゅう") # 付け足す
kago.append("パン")
print(kago)
kago.____("たまご") # 「たまご」をやめる
print(kago)
print(f"かごの中は {____(kago)} 個です")
print(f"最後に入れたのは {kago[____]} です")
16.4 for でリストを回す
ここからが本番です.リストの力は,繰り返しと組み合わせたときに出ます. 書き方は 2 つあります.
16.4.1 for x in リスト — 中身を順に取り出す
for 変数 in リスト: と書くと,
リストの中身が先頭から順に 1 つずつ変数に入り,
そのたびに中の命令が実行されます.
scores = [80, 95, 72]
for s in scores:
print(s)
1 回目は s に 80 が,2 回目は 95 が,3 回目は 72 が入ります.
リストが何個でも,プログラムの長さは変わりません.
これが「コンピュータに繰り返させる」ということです.
scores = [80, 95, 72, 100, 64]
for s in scores:
print(f"点数は {s} 点")
print("---")
colors = ["red", "green", "blue"]
for c in colors:
print(f"色は {c}")
for i in range(5): も,じつは同じ形です.
range(5) が 0, 1, 2, 3, 4 という並びを作り,
それを 1 つずつ i に入れていたのです.
for は「並んでいるものを順に取り出す」命令だと
覚えておいてください.
16.4.2 for i in range(len(リスト)) — 添字で回す
もうひとつの書き方は,添字のほうを回すやり方です.
range(len(リスト)) は 0 から
個数 − 1 までを順に作るので,
それを添字として使えば,やはり全部をなめられます.
scores = [80, 95, 72]
for i in range(len(scores)):
print(f"添字 {i} は {scores[i]} 点")
scores = [80, 95, 72, 100, 64]
for i in range(len(scores)):
print(f"{i + 1} 人目は {scores[i]} 点")
print("---")
# 添字が使えると「書き換え」ができる
for i in range(len(scores)):
scores[i] = scores[i] + 5 # 全員に 5 点おまけ
print(scores)
16.4.3 どちらを使うか
for x in リスト | for i in range(len(リスト)) | |
|---|---|---|
| 読みやすさ | とても読みやすい | やや読みにくい |
| 中身を使う | そのまま使える | リスト[i] と書く |
| 添字(何番目か)を知る | わからない | i でわかる |
| 中身を書き換える | できない | できる |
| 2 つのリストを同時に使う | できない | できる |
つまりまずは for x in リスト を使い,
「添字」が要るときや「書き換えたい」ときだけ添字で回す,
と決めておけば迷いません.
for s in scores: の中で s = s + 5 と書いても,
リストの中身は変わりません.
s は中身を写しとった別の変数だからです.
リストを書き換えたいときは,scores[i] = … と
添字を使って書いてください.
点数のリストから,合計・人数・平均を求めます. ____ を埋めてください. 合計を入れておく変数は,0 から始めるのを忘れずに.
scores = [80, 95, 72, 100, 64]
total = ____
for s in ____:
total = total + ____
print(f"合計は {total} 点")
print(f"人数は {____(scores)} 人")
print(f"平均は {total / len(scores)} 点")
16.5 リストの中身を使って図を描く
16.5.1 色のリスト
第 2 章では,辺ごとに色を変えるのに pencolor を何行も書きました.
色をリストに入れれば,3 行で済みます.
from turtle import *
ht()
pu()
colors = ["#e74c3c", "#e67e22", "#f1c40f",
"#2ecc71", "#3498db", "#9b59b6", "#34495e"]
x = -270
for c in colors:
goto(x, 0)
dot(70, c)
x = x + 90
done()
for で回した.色を足したいときはリストに 1 つ書き足すだけ.[ ] が閉じるまでが 1 つの命令として扱われるので,
長いリストは上の例のように途中で折り返すと読みやすくなります.
16.5.2 長さのリスト — 棒グラフ
数のリストがあれば,そのまま棒グラフになります. 添字で回せば,何本目かもわかるので,横の位置を決められます.
from turtle import *
ht()
scores = [80, 95, 72, 100, 64]
names = ["A", "B", "C", "D", "E"]
pensize(28)
pencolor("#3498db")
for i in range(len(scores)):
x = -220 + i * 110 # i 本目の横位置
pu()
goto(x, -120)
pd()
goto(x, -120 + scores[i]) # 点数ぶん上へ
# 名前と点数を書く
pu()
goto(x, -155)
pencolor("dimgray")
write(names[i], align="center", font=("Arial", 22, "bold"))
goto(x, -110 + scores[i])
write(scores[i], align="center", font=("Arial", 18, "normal"))
pencolor("#3498db")
done()
ここでは scores と names という
2 つのリストを同じ添字で使っています.
scores[2] と names[2] は同じ人のもの,という約束です.
こういうときは for x in リスト では書けないので,
添字で回す書き方の出番になります.
16.5.3 座標のリスト
座標も数の並びですから,リストに入れられます. x のリストと y のリストを作って,同じ添字で取り出せば, 好きな形の折れ線が描けます.
from turtle import *
ht()
pensize(4)
pencolor("#e74c3c")
xs = [-240, -150, -60, 30, 120, 210]
ys = [-80, 60, -30, 110, 10, 90]
pu()
goto(xs[0], ys[0])
pd()
for i in range(len(xs)):
goto(xs[i], ys[i])
dot(16, "#c0392b")
done()
色のリストを使って,辺ごとに色の変わる正六角形を描きます. ____ を埋めてください. 正六角形の曲がる角度は 360 ÷ 6, 繰り返す回数は色の個数と同じにします.
from turtle import *
pensize(10)
pu()
goto(-70, -100)
pd()
colors = ["crimson", "darkorange", "gold",
"seagreen", "royalblue", "purple"]
for c in ____:
pencolor(____)
fd(120)
rt(____)
ht()
done()
16.6 リストにかめを入れる
リストには数や文字だけでなく,かめそのものも入れられます. ここが今回いちばん強力なところです. 16.1 節の「変数を 5 個作る」問題が,これで解決します.
16.6.1 かめをまとめて作る
空のリストを用意し,for の中で Turtle() を作っては
append していきます.
5 を 20 に変えるだけで 20 匹になるところを味わってください.
from turtle import *
ht()
turtles = [] # 空のリストを用意
for i in range(5):
t = Turtle() # かめを 1 匹作って
t.shape("turtle")
t.shapesize(1.6)
t.pu()
t.goto(-220, -80 + i * 45) # 添字 i に合わせた場所へ
t.color("#3498db")
turtles.append(t) # リストに入れる
# 全員まとめて動かす
for t in turtles:
t.pd()
t.pensize(3)
t.fd(150)
done()
for で一斉に進ませた.range(5) を range(20) にすれば 20 匹になる.かめごとに違う色にしたいときは,色のリストを添字で合わせます.
from turtle import *
ht()
colors = ["#e74c3c", "#e67e22", "#f1c40f", "#2ecc71", "#3498db", "#9b59b6"]
lengths = [60, 110, 160, 210, 260, 310]
turtles = []
for i in range(len(colors)):
t = Turtle()
t.shape("turtle")
t.shapesize(1.4)
t.pu()
t.goto(-260, -120 + i * 48)
t.color(colors[i])
t.pensize(5)
turtles.append(t)
# 添字 i のかめは lengths[i] だけ進む
for i in range(len(turtles)):
turtles[i].pd()
turtles[i].fd(lengths[i])
done()
16.6.2 一斉に動かす
第 10 章の ontimer と組み合わせます.
1 コマの処理の中で for を回せば,
何匹いても 3 行で全員を動かせます.
from turtle import *
import random
ht()
bgcolor("#f6f7fb")
colors = ["#e74c3c", "#e67e22", "#f1c40f",
"#2ecc71", "#3498db", "#9b59b6"]
turtles = []
for i in range(len(colors)):
t = Turtle()
t.shape("turtle")
t.shapesize(1.4)
t.pu()
t.goto(-260, -130 + i * 52)
t.color(colors[i])
t.pensize(4)
t.pd()
turtles.append(t)
def step():
for t in turtles: # 全員に同じ命令
t.fd(random.randint(2, 12))
if t.xcor() > 260:
t.pu()
t.goto(-260, t.ycor())
t.pd()
ontimer(step, 80)
step()
done()
random.randint(2, 12) は 2 から 12 までのでたらめな整数を返します
(使うには import random が要ります).
進む距離が毎回違うので,かめレースになります.
かめの数を増やしたいときは colors に色を足すだけです.
step の中は 1 文字も変えなくてよいことを確かめてください.
4 匹のかめをリストに入れ,縦に並べて,一斉に同じ距離だけ進ませます. ____ を埋めてください.
from turtle import *
ht()
turtles = ____ # 空のリスト
for i in range(4):
t = Turtle()
t.shape("turtle")
t.shapesize(1.8)
t.pu()
t.goto(-200, -90 + i * 60)
t.color("seagreen")
turtles.____(t) # リストに入れる
for t in ____: # 全員に命令する
t.pd()
t.pensize(4)
t.fd(____)
done()
16.7 リスト全体を調べる
合計や最大値は for で書いてもよいのですが,
Python には最初から用意されています.
| 命令 | 意味 |
|---|---|
len(リスト) | 個数 |
sum(リスト) | 合計(中身が数のときだけ) |
max(リスト) | いちばん大きい値 |
min(リスト) | いちばん小さい値 |
値 in リスト | その値が入っていれば True |
リスト.index(値) | その値の添字(最初に見つかった場所) |
リスト.count(値) | その値がいくつ入っているか |
sorted(リスト) | 小さい順に並べ替えた新しいリスト |
scores = [80, 95, 72, 100, 64]
print(f"個数 : {len(scores)}")
print(f"合計 : {sum(scores)}")
print(f"平均 : {sum(scores) / len(scores)}")
print(f"最高点 : {max(scores)}")
print(f"最低点 : {min(scores)}")
print(f"100 点はいる? : {100 in scores}")
print(f"100 点の添字は? : {scores.index(100)}")
print(f"並べ替え : {sorted(scores)}")
print(f"もとのまま : {scores}")
sorted(scores) は新しいリストを作って返すだけで,
scores 自身は並び替わりません.
scores そのものを並べ替えたいときは scores.sort() です.
並べ替えのしくみは,第 17 章で自分の手で組み立てます.
調べた結果は,第 11 章で覚えた write で画面に出せます.
棒グラフの上に平均線を引く,といったことも簡単です.
from turtle import *
ht()
scores = [80, 95, 72, 100, 64]
best = max(scores)
pensize(26)
for i in range(len(scores)):
x = -220 + i * 110
if scores[i] == best:
pencolor("#e74c3c") # 最高点だけ赤
else:
pencolor("#bdc3c7")
pu()
goto(x, -120)
pd()
goto(x, -120 + scores[i])
pu()
goto(0, 120)
pencolor("#2c3e50")
write(f"最高 {best} 点 / 平均 {sum(scores) / len(scores)} 点",
align="center", font=("Arial", 24, "bold"))
done()
max で求めた最高点の棒だけ色を変えた.ただし,できあいの道具でできるのは「全部の合計」「全部の最大」までです.
「60 点以上は何人か」のように
条件に合うものだけを集計したいときは,自分で書きます.
形は虫食い 16-C の合計とまったく同じで,
数える箱を 0 から始めて,for の中で 1 ずつ増やすだけです.
scores = [80, 95, 72, 100, 64, 55, 88, 43]
kazu = 0 # 数える箱.0 から始める
wa = 0 # 合計の箱.これも 0 から始める
for s in scores:
if s >= 60:
kazu = kazu + 1
wa = wa + s
print("全部で", len(scores), "人")
print("60 点以上は", kazu, "人")
print("60 点未満は", len(scores) - kazu, "人")
print("60 点以上の人の平均は", wa / kazu, "点")
# 「ぴったりこの値」を数えるだけなら count() がある
print("100 点は", scores.count(100), "人")
for で回してやることは,たいていこの 4 つのどれかです.
どれも「覚えておく箱を先に用意して,繰り返しの中で更新する」という
同じ形をしています.
この形が手になじめば,リストを使ったプログラムはほとんど書けます.
探すことと,いちばんを見つけることは,次の第 17 章でくわしくやります.
0 にし忘れると,
NameError: name 'kazu' is not defined というエラーになります.
また,条件に合うものが 1 つも無いときに wa / kazu を計算すると,
ZeroDivisionError(0 で割った)で止まります.
平均を出す前に if kazu > 0: と確かめておくと安全です.
16.8 一部だけ取り出す — スライス
リスト[start:stop] と書くと,
start 番から stop 番の手前までを切り出した新しいリストができます.
これをスライスといいます.
「stop は含まない」のがポイントです.
scores = [80, 95, 72, 100, 64, 88, 91] print(scores[0:3]) # 0,1,2 番 → [80, 95, 72] print(scores[2:5]) # 2,3,4 番 → [72, 100, 64] print(scores[:3]) # 先頭から 3 つ print(scores[4:]) # 4 番から最後まで print(scores[-2:]) # 後ろから 2 つ print(scores[:]) # まるごと写し print(len(scores[1:4])) # 4 - 1 = 3 個
range(0, 3) が
0, 1, 2 になるのと同じ決まりです.
おかげで [0:3] の個数はいつも 3 − 0 = 3 個と,
引き算だけで求められます.
16.9 リストの中にリストを入れる
リストには何を入れてもよいので,リストを入れることもできます. これを二次元のリスト(リストのリスト)といい, 表やマス目を表すのに使います.
board = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(board[0]) # [1, 2, 3] ← 1 行目まるごと
print(board[0][2]) # 3 ← 1 行目の 3 つめ
print(board[2][0]) # 7 ← 3 行目の 1 つめ
board[行][列] の順で指定します.
取り出すときは,まず board[0] で「1 行ぶんのリスト」を取り出し,
さらにその添字 [2] の要素を取り出している,と読んでください.
from turtle import *
ht()
pu()
board = [[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0]]
for row in range(len(board)):
for col in range(len(board[row])):
x = -200 + col * 100
y = 130 - row * 100
goto(x, y)
if board[row][col] == 1:
dot(80, "#2c3e50")
else:
dot(80, "#ecf0f1")
done()
0 と 1 を並べた表から模様を作った.表の数字を書き換えれば模様が変わる.for を 2 段に重ねます
(第 7 章の「繰り返しの入れ子」です).
外側が行,内側が列と決めておくと混乱しません.
この形は,ゲームの地図(どこに壁があるか)や表を表すのによく使われます. 演習 16-6 では,この表を書き換えて自分のドット絵を作ります.
16.10 この章で出てきた命令
| 書き方 | 意味 |
|---|---|
xs = [10, 20, 30] | リストを作る |
xs = [] | 空のリストを作る |
xs[0] | 1 つめを取り出す(添字は 0 から) |
xs[-1] | いちばん後ろを取り出す |
xs[1] = 99 | 2 つめを書き換える |
len(xs) | 個数 |
xs.append(値) | 最後に付け足す |
xs.insert(添字, 値) | 途中に割り込ませる |
xs.pop() | 最後を取り出して消す |
xs.pop(添字) | その場所のものを取り出して消す |
xs.remove(値) | その値を探して消す |
for x in xs: | 中身を順に取り出して繰り返す |
for i in range(len(xs)): | 添字を順に作って繰り返す |
sum(xs) max(xs) min(xs) | 合計・最大・最小 |
値 in xs | 入っているかどうか(True/False) |
xs.index(値) | その値の添字(最初に見つかった場所) |
xs.count(値) | その値がいくつ入っているか |
random.choice(xs) | でたらめに 1 つ選ぶ(import random が要る) |
sorted(xs) | 小さい順に並べた新しいリスト |
xs[1:4] | スライス.1 番から 4 番の手前まで |
board[行][列] | 二次元リストの 1 マス |
追加実習:仕組みを確かめてから設計する
要素の値と,その位置を使い分ける
リストの中の値を読むだけなら,for value in valuesと書くと簡潔です.
一方,その場所の値を更新したい場合には,添字を使って代入する必要があります.ループ変数valueへ別の数値を代入しても,元のリストの要素が自動で置き換わるわけではありません.
変数への代入と,リストの要素への代入を区別してください.
例えば,各要素を10増やすつもりでvalue += 10と書くと,その回の変数valueは変わりますが,元の数値のリストはそのままです.
values[index] += 10なら,指定した場所を新しい値へ置き換えます.元を変更しない仕様なら,新しいリストへ計算結果をappendする方法もあります.
どちらを望むかを先に決めます.
位置と値の両方を使う場合には,for index in range(len(values))で添字を回し,values[index]で値を取り出します.
ただし,走査中に要素を削除すると,後の位置がずれることがあります.値の置き換えと,要素数を変える操作は別の問題です.
まず長さを保つ更新から練習します.
以下は,元の一覧を保ち,10増やした新しい一覧を作ります.
最初の値と結果を並べて確認してください.次の演習では,値が0以上の要素だけを2倍にした新しい一覧へ変えます.
条件によって更新する場合も,元の一覧を保つという約束は同じです.
values = [10,20,30]
changed = []
for value in values:
changed.append(value+10)
print(values,changed)
assert values == [10,20,30]
[-3,0,5]のうち0以上だけを2倍にし,[-3,0,10]を新しいリストで作ってください.元のリストは変更しません.ループ変数へ代入しただけでは元が変わらない理由も説明します.
必須(すべて満たして正しく動けば 8)
- 元のリストを変えずに,新しいリストへ append して [-3,0,10] を作っている
- 0 以上かどうかを if で分け,0 以上だけ 2 倍にしている
- ループ変数へ代入しても元が変わらない理由をコメントで説明している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 添字で回して元のリストを書き換える版も作り,違いを示している
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
追加と,まとまりごとの追加を区別する
appendは1個の要素を末尾へ追加します.
追加する値がリストなら,そのリスト自体が1個の要素として入ります.extendは,渡したものの各要素を順に追加します.
座標の一覧を作る場合と,点数の一覧を結合する場合では,どちらを使うべきかが違います.見た目が似ていても,結果の入れ子の深さを確認してください.
例えば座標[10,20]を1個の位置として持ちたいなら,その2つを入れたリストをappendします.
xとyを別々の要素としてextendすると,位置のまとまりが失われるかもしれません.逆に,[30,40]という追加の点数を既存の点数一覧へつなぎたいなら,extendが適しています.
文法名ではなく,1個の要素が何を意味するかから選びます.
入れ子のリストは,行と列を表す場合にも使います.
外側の長さが行数,内側の長さが列数という約束なら,appendするものは1行分のリストです.誤ってextendすると,行の区切りがなくなります.
lenだけでなく,各要素の型や内容も表示すると,構造の違いを観察できます.
以下の例はappendとextendを並べています.
次の演習では2行の表を作り,それぞれの行の合計を求めます.同じ数値が入っていても,平らな一覧と表では操作の仕方が違うことを確認します.
必要な構造を作る段階で間違えると,後の計算で不自然な条件分岐が増えるため,まず入れ物の形を確かめます.
a = [10,20] b = [10,20] a.append([30,40]) b.extend([30,40]) print(a,len(a)) print(b,len(b))
空のリストへ[10,20]と[30,40]を1行ずつ追加し,2行の表を作ってください.行ごとの合計が30と70になることを確認します.extendではなくappendを選ぶ理由を説明してください.
必須(すべて満たして正しく動けば 8)
- 空のリストに 2 行を append して,len(rows) が 2 の表を作っている
- for で行ごとの合計を求め,[30, 70] になることを確かめている
- extend ではなく append を選ぶ理由をコメントで説明している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 3 行目を足しても同じ処理で合計が求まることを確かめている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
空・1個・複数のデータで集計を考える
リストを扱う関数は,要素数が変わっても意味が保たれるように設計します.
合計なら空のとき0ですが,平均では個数0で割れません.「空ならどうするか」は,エラーが起きてから場当たり的に直すより,先に決めたほうが使いやすくなります.
ここでは平均を求められない場合にNoneを返す約束を使います.
平均0と平均が存在しない状態は違います.
[0,0]の平均は0ですが,空の一覧には平均がありません.if not resultだけで判定すると両方を混同します.
Noneかどうかをis Noneで調べると,0を有効な値として扱えます.データの意味と,Pythonで偽とみなされる値を分けて考えます.
入力を並べ替えたり変更したりする必要がない集計では,元の一覧を読むだけにします.
同じ一覧を別のグラフや計算にも使えるからです.最大値,最小値,合計,平均などを複数求める場合にも,どの関数が入力を変更するかをそろえておくと,呼び出し順による不具合を減らせます.
以下は平均を求める関数です.
0,負の数,1個だけの場合も試してください.次の演習では,しきい値以上の値が何個あるかを数えます.
集計の対象を先に絞るか,ループの中で条件を調べるかは選べますが,空の一覧が自然に0件になるようにします.
def average(values):
if not values:
return None
return sum(values)/len(values)
assert average([]) is None
assert average([0,0]) == 0
assert average([10]) == 10
print(average([10,20,30]))
count_at_least(values,limit)を作り,limit以上の値の個数を返してください.空なら0,境界と等しい値は数えます.[49,50,80]でlimitが50なら2になることを確認します.
必須(すべて満たして正しく動けば 8)
- count_at_least が数える箱を 0 から始め,for と if で limit 以上を数えて return している
- 空のリストで 0,境界と等しい値も数えることを assert で確かめている
- [49, 50, 80] と 50 で 2 になる
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- limit より小さい値の個数を返す関数も作り,合計が len と一致することを確かめている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
作品を確かめる観点
作成した関数は,空の一覧,1個だけの一覧,同じ値が複数ある一覧で確認します.
値を変更する操作では,元のリストを保つ仕様かどうかも説明してください.入力と結果を並べて表示すると,意図しない変更に気付きやすくなります.
演習
「▶ この演習を開く」で右側に読み込み,各演習の「評価の目安」の必須を満たしてから発展させてください. できあがったら「⤓ 保存」でファイルに保存して,手元に残しておきましょう.
棒グラフを描くプログラムを与えます. リストの中身を自分のデータに差し替えて, 意味のあるグラフにしてください. 1 週間の勉強時間,好きな食べ物の点数,科目ごとの得点 — 何でもかまいません.
- データは 6 個以上にすること(
valuesとlabelsの個数をそろえる) maxを使って,いちばん大きい棒だけ色を変えること(たとえば最大は"#e74c3c",ほかは"#3498db")sumとlenで平均を計算し,writeで表示すること- 平均以上の日(項目)がいくつあるかを,
forとifで数えて表示すること(16.7 節の「数える箱」) - これもやりましょう:平均の高さに,棒とは違う色の横線を引く.棒の根元が
y = -120なら,平均線の高さはy = -120 + 平均で,左端の棒より左から右端の棒より右まで引く.
たとえばデータが [45, 90, 30, 120, 75, 150, 20](月〜日)なら,
最大は 150(土)で,平均は 530 ÷ 7 = 75.7,
平均以上の日は 90・120・150 の 3 日です.
自分のデータでも,手で計算した値と表示が合うか確かめてください.
x = -220 + i * 110 の 110 が間隔です.
7 個なら x = -260 + i * 88 くらいにすると画面に収まります.
平均は割り切れないと長い小数になるので,round(avg, 1) で小数第 1 位までにすると読みやすくなります.
必須(すべて満たして正しく動けば 8)
- 自分のデータを 6 個以上入れ,個数を直接書かずに
lenで回している maxで求めた最大の棒だけ色が違うsumとlenで求めた平均をwriteで表示し,手で計算した値と合っている- 平均以上の個数を,数える箱を 0 から始めて
forとifで数え,表示している(count()は使わない) - 平均の高さ(根元の y + 平均)に,棒と違う色の横線を引いている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 棒の間隔と左端の位置を
len(values)から計算で決め,データを何個にしても画面の中央に収まる - 最大の値が 2 つ以上あるときの扱い(全部色を変える,など)を決め,同じ値を入れたデータで確かめている
- 最大の項目名を
values.index(max(values))で求め,labelsから取り出して表示している
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
色のリストを使って模様を描くプログラムを与えます. 次のことができるように直して,自分の模様にしてください.
- 色を 7 個以上にする(たとえば
"#ecf0f1"を足す).曲がる角度をrt(60)と数で書かず,rt(360 / len(colors))にする.色を足しても減らしても,図形が閉じたままになる. - 大きさのリスト
sizes = [40, 60, 80, 100]を作り,大きさごとにfor c in colors:を回して,大きさの違う図形を 4 つ重ねる. - これもやりましょう:太さのリスト
widths = [2, 4, 6, 8]を作り,大きさごとに太さを変える.for i in range(len(sizes)):で添字を回し,sizes[i]とwidths[i]を同じ添字で使う(小さい図形ほど細く,大きい図形ほど太くなる). - これもやりましょう:大きさを 1 つ描き終わるたびに,回す角度を数で書かずに
rt(360 / len(sizes))で回し,図形が均等にずれて重なるようにする(大きさ 4 つなら 90 度ずつ.sizesとwidthsに 1 つずつ足すと,ほかを直さなくても 5 つが 72 度ずつ回って重なる).
必須(すべて満たして正しく動けば 8)
- 色を 7 個以上にし,角度を
360 / len(colors)で決めて,図形が閉じている sizes = [40, 60, 80, 100]を作り,大きさごとにfor c in colors:を回して4 つの図形を重ねているwidthsを作り,sizes[i]とwidths[i]を同じ添字で使って,大きさごとに太さが変わっている- 大きさを 1 つ描くたびに
rt(360 / len(sizes))で回し,sizes・widthsに 1 つずつ足しても,ほかを直さずに均等に回って重なる
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
widthsを手で書かず,sizes[i]から式で決める(たとえばsizes[i] / 20)ので,sizesを変えるだけで太さもそろうsizesとwidthsの個数が違うと何が起きるかを確かめ,起きないように直している- 1 つの図形を描く部分を関数(引数は大きさと色のリスト)にまとめている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
append と pop を使って,
点を打った場所を覚えておくプログラムを作ります.
出発点では 3 か所に打つだけです.次のように発展させてください.
forを使って 10 か所以上に打つこと(場所は計算で決めてよい.たとえば 12 か所ならx = -260 + i * 48,y = 120 - (i % 4) * 70)- 打った座標をリストに
appendしていくこと - 最後に打った点を
popで 1 つ取り消すこと(xsとysの両方から) - 残った場所を線でつなぐこと(リストを
forで回してgoto) lenで残った点の個数をwriteすること(12 か所に打って 1 つ取り消したなら「残った点は 11 個」)- これもやりましょう:
popは取り出した値を返す.last_x = xs.pop()のように受け取り,その座標へ移動して,取り消した点の上に灰色の点(dot(24, "lightgray"))を重ねて,どの点を取り消したかが見えるようにする.
必須(すべて満たして正しく動けば 8)
forで 10 か所以上に点を打ち,その座標をxs・ysにappendしている- 最後の点を
xs.pop()・ys.pop()で取り消し,線は残った点だけをつないでいる - 残った点の個数を
lenで求めてwriteで表示し,個数が合っている popが返した座標を変数で受け取り,取り消した点に灰色の点を重ねている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
insertで途中に点を割り込ませ,線がその点も通るようにしている(xs・ysの両方に同じ添字で)- 隣どうしの点の距離を
math.sqrtで求めて合計し,折れ線の長さを表示している(第 14 章) - 点の数を変数 1 つにまとめ,その値を変えるだけで打つ数・残る数・表示がすべて合う
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
リストにかめを入れて一斉に動かすプログラムを与えます. 次のように発展させてください.
- 色のリスト
colorsを作り,かめの数をlen(colors)で決めること(10 匹以上.出発点のrange(5)をrange(len(colors))にする) colors[i]で,1 匹ずつ違う色にすること- 向きは
t.seth(i * (360 / len(colors)))にして,何匹でも均等に散らばるようにすること - 進む距離のリスト
lengthsを,colorsと同じ個数で作り,for i in range(len(turtles)):でturtles[i].fd(lengths[i])として,添字をそろえて 1 匹ずつ違う距離を進ませること(たとえば 80・115・150・185・220 のくり返し) - これもやりましょう:
lengthsを手で並べて書かず,空のリストlengths = []からforとappendで作る.i番目の長さは80 + (i % 5) * 35(80・115・150・185・220 のくり返し)にする.こうするとcolorsを増やしても減らしても,lengthsの個数が自動でそろう.
"red"・"gold"・"teal"・"orchid" など,
たくさん使えます(第 2 章).
lengths の個数が colors より少ないと,lengths[i] で
IndexError になります.2 つのリストの個数をそろえてください.
必須(すべて満たして正しく動けば 8)
- 色のリストを作り,かめの数を
len(colors)で決めて 10 匹以上にしている(rangeに匹数を直接書かない) colors[i]で 1 匹ずつ違う色になり,360 / len(colors)で均等に散らばっているcolorsと同じ個数のlengthsを作り,for i in range(len(turtles)):で添字をそろえて 1 匹ずつ違う距離を進ませているlengthsを空のリストからforとappendで作り,i番目を80 + (i % 5) * 35にしている(colorsを増減しても個数がそろう)
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- いちばん遠くまで進んだかめを
maxとindexで見つけ,その色の名前をwriteで表示している - 式の 80・35・5 を変数にし,その値を変えるだけで長さの並び方が変わる
- かめを作る部分を関数(引数は色)にし,作ったかめを戻り値で返している
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
リストのかめを ontimer で一斉に動かす「かめレース」です.
出発点は 4 匹がでたらめに進むだけです.次のように発展させてください.
- かめを 6 匹にすること(
colorsに色を 2 つ足すだけで増えるはずです).かめの名前のリストnames(たとえば「あか」「だいだい」…)も,同じ順で 6 個作る.6 匹が画面に収まるよう,出発位置はt.goto(-260, -140 + i * 56)にする. x = 230の位置に,縦のゴールの線を引くこと- 誰かがゴール(
xcor()が 230 より大きい)に着いたら,writeで勝者の名前を発表して止めること - これもやりましょう:1 位で止めずに 3 位まで決める.ゴールした順にかめの添字を入れていくリスト
order = []を作り,ゴールを越えたかめのうちまだorderに入っていないものをappendする.ゴールしたかめはそれ以上進ませない.len(order)が 3 になったら,「1 位 ○○」「2 位 ○○」「3 位 ○○」を3 行でwriteして止める.
ontimer を呼ばずに関数を終えるだけです.
「誰がゴールしたか」を知るには,
for i in range(len(turtles)): で添字も一緒に回し,
colors[i] や names[i] を使います.
「まだ入っていない」は,16.7 節の in と第 5 章の not を組み合わせて not (i in order) で調べられます.
order[0] が 1 位のかめの添字なので,名前は names[order[0]] です.
必須(すべて満たして正しく動けば 8)
colorsとnamesを 6 個ずつにしてかめを 6 匹にし,stepの中は匹数に関係なく動くx = 230にゴールの線を引き,ゴールを越えたかめをfor i in range(len(turtles)):で見つけている- ゴールした順に添字を
orderにappendし,同じかめを 2 回入れていない.ゴールしたかめはそこで止まっている - 3 匹ゴールしたら,
names[order[0]]などで 1 位〜3 位を表示し,ontimerを呼ばずに止めている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 全員がゴールするまで続け,6 位までの順位表を表示している
- かめごとに速さの範囲(リスト)を変え,ハンデをつけている
- 同じ回に 2 匹以上がゴールを越えたとき,
xcor()の大きいほうを上の順位にしている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
0 と 1 を並べた表から模様を作るプログラムを与えます.
表を書き換えて,自分のドット絵を作ってください.
ハート,顔,文字,キャラクター — 方眼紙に下書きしてから写すと楽です.
- 表は 縦 6 行 × 横 6 列以上にすること
01だけでなく2も使い,3 色にすること(elifを足します)- これもやりましょう:マスの間隔を変数
size(たとえば 52)にし,絵の真ん中が画面の中心 (0, 0) に来るようにする.基準点は左上のマスで,その中心をx = -(列数 - 1) / 2 * size,y = (行数 - 1) / 2 * sizeに置く(列数はlen(board[row]),行数はlen(board)で求め,数を直接書かない).点の直径はsize - 4にする. - これもやりましょう:表の中に
1がいくつあるかを,forを 2 段にして数え,絵の下に「1 のマスは ○ 個」とwriteで表示する.
dot(80, …) の 80 と,
col * 100 row * 100 の 100 で決まっています.
たとえば 7 列で size = 52 なら,左端の列の中心は x = −3 × 52 = −156,
右端は +156 になり,左右がそろいます.
必須(すべて満たして正しく動けば 8)
- 表(二次元リスト)を 6 行 × 6 列以上に書き換え,絵として何かが分かる
0・1・2の 3 種類をelifで 3 色に塗り分けている- マスの間隔を変数
sizeにし,len(board)・len(board[row])を使って絵の中心を (0, 0) にそろえている(行や列を増やしても中央からずれない) 1の個数をfor2 段で数え,正しい個数をwriteで表示している
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 色のリスト
colorsを作り,colors[board[row][col]]で 4 色以上にしている 0・1・2それぞれの個数を数えて表示している- 描く部分を関数(引数は表・中心の座標・
size)にまとめ,行数・列数の違う別の表でも同じ関数で中央にそろえて描けることを確かめている
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
リストを使った作品を自由に作ってください. 「データを差し替えると絵が変わる」ことが伝わる作品にしてください.
- リストを 2 つ以上使うこと
forでリストを回して描くことlenを使い,個数を直接書かないこと(リストの中身を増やしても,そのまま動くように)- 先頭にコメントで,題名・氏名・日付を書くこと
ontimer を足してください.
必須(すべて満たして正しく動けば 8)
- 出発点のデータを自分の題材のデータに差し替え,リストを 2 つ以上使っている
forでリストを回して描いている- 個数を直接書かずに
lenを使い,リストに 1 つ足すだけで絵も増える - 先頭のコメントに題名・氏名・日付を書いている
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 条件に合うものを
forとifで数えるか,いちばん大きい(小さい)ものを見つけ,結果をwriteで表示している - 位置や間隔を
lenから計算で決め,データの個数を変えても画面に収まる - 描く部分を関数にまとめ,別のデータのリストを渡しても同じ関数で描ける
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
まとめ
- 同じ種類のデータが増えたら,変数を増やすのではなくリストにまとめる.
- リストは
[ ]で作り,添字は 0 から.最後の添字は「個数 − 1」で,-1と書いても取れる. len()で個数.個数をプログラムに直接書かないのがコツ.appendで伸ばし,pop・removeで縮める.for x in リストが基本.添字が要るとき・書き換えたいとき・2 つのリストを合わせるときはfor i in range(len(リスト)).- 色・長さ・座標,そしてかめ自身もリストに入る.入れてしまえば,何個あっても命令は 1 つで済む.
sum・max・min・スライス・リストのリストも使える.- 合計する・数える・探す・いちばんを見つけるは,どれも「箱を先に用意して,繰り返しの中で更新する」同じ形.
次の回では,このリストを使ってアルゴリズムを組み立てます.
「探す」「並べ替える」を,sorted に頼らずに自分の手で書いてみます.
コンピュータが何をしているのかが,はっきり見えるようになります.
復習:虫食いで確かめる
コードの ____ を埋めてください.実行前に結果を予測し,実行後に問題文の条件と照合しましょう.assert は条件が成り立つかを調べ,成り立たないとエラーになります.
最後に青を追加します.表示が赤・緑・青の順になり,assert が通るようにしてください.
colors = ["red", "green"]
colors.____("blue")
print(colors)
assert colors == ["red", "green", "blue"]