辞書
名前で引ける入れ物.データに意味を持たせる.
リストは便利な入れ物でした.
しかし,リストからデータを取り出すには番号が要ります.
a[0],a[3].
「0 番目には誰の点数が入っているんだっけ」と,
いちいち思い出さなければなりません.
今回習う辞書(dictionary)は,
番号ではなく名前で引ける入れ物です.
ten["さくら"] と書けば,さくらさんの点数が出てきます.
紙の辞書で「りんご」を引くと意味が書いてあるのと同じ仕組みです.
番号が名前に変わるだけ,と思うかもしれません. しかしこの変化は大きいのです. データに意味が付くからです. 今回の最後には,入力された言葉に応じて絵を描き分けるプログラムを作ります.
- リストでは足りない場面を説明できる
{ }で辞書を作り,キーと値の関係を説明できるd["key"]で取り出す・書き換える・付け加えることができるinとget()で,安全にキーを引けるforとitems()で辞書の中身を全部たどれる- 辞書とリストを組み合わせて,少し複雑なデータを表せる
- 辞書を使って,入力された言葉に応じた絵を描くプログラムが書ける
- 知らない命令を公式ドキュメントや
help()で調べられる
18.1 リストでは足りない場面
5 人の点数を持っておきたいとします.リストならこう書けます.
ten = [78, 92, 65, 88, 71] print(ten[2]) # → 65
65 は誰の点数でしょうか.分かりません.
「2 番目はさくらさん」という約束を,プログラムの外側で
覚えておかなければならないのです.これは危険です.
1 人減らしただけで,全部の番号がずれてしまいます.
18.1.1 2 本のリストで代用してみる
よくある工夫が,名前のリストと点数のリストを 2 本並べるやり方です. 同じ番号どうしが対応している,という約束にします.
# 名前のリストと点数のリストを並べて使う
namae = ["あかり", "けんた", "さくら", "たろう", "なな"]
ten = [78, 92, 65, 88, 71]
shirabetai = "さくら"
# 名前のリストから場所を探して(=第 17 章の線形探索)
k = -1
for i in range(len(namae)):
if namae[i] == shirabetai:
k = i
break
# その場所の点数を取り出す
if k >= 0:
print(shirabetai, "さんの点数は", ten[k], "点です")
else:
print(shirabetai, "さんは名簿にいません")
動きます.しかし,うれしくありません.
- 点数を 1 つ知るだけで,毎回リストを端から探すことになる.人数が増えるほど遅くなる.
- 2 本のリストの長さと順番がずれたら終わり.1 人だけ消したりすると,静かに壊れる.
- そもそも,書くのが面倒.毎回 8 行も書きたくない.
「名前から値を引く」というのは, プログラムを書いているとあまりにもよく出てくる要求です. だから,はじめから道具が用意されています.それが辞書です.
18.2 辞書を作る
辞書は波かっこ { } で作ります.
キーと値を : でつなぎ,
組を , で区切って並べます.
ten = {"あかり": 78, "けんた": 92, "さくら": 65, "たろう": 88, "なな": 71}
# ↑キー ↑値
| 言葉 | 意味 | 例 |
|---|---|---|
| キー(key) | 引くときの手がかり.紙の辞書の「見出し語」 | "さくら" |
| 値(value) | 引いて出てくる中身.紙の辞書の「意味」 | 65 |
| 組 | キーと値のひとそろい.辞書は組の集まり | "さくら": 65 |
長い辞書は,行を分けて書くと読みやすくなります.
最後の組のうしろにも , を付けてよいのが便利なところです.
ten = {
"あかり": 78,
"けんた": 92,
"さくら": 65,
"たろう": 88,
"なな": 71,
}
18.2.1 取り出す・書き換える・付け加える
取り出すときは,リストと同じ角かっこを使います. 中に書くのが番号ではなくキーになるだけです.
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
# 取り出す
print(ten["さくら"]) # → 65
# 書き換える(すでにあるキー)
ten["さくら"] = 70
print(ten["さくら"]) # → 70
# 付け加える(まだ無いキー)
ten["たろう"] = 88
print(ten)
# 何組あるか
print("人数は", len(ten), "人")
ten["さくら"] = 70 は,
「さくら」がすでにあれば書き換え,無ければ新規追加です.
リストの a[5] = 3 が「5 番目が無ければエラー」だったのと,ここが違います.
便利な反面,キーを打ち間違えると,静かに別の組が増えます.
「さくら」と「さくら 」(末尾に空白)は別のキーです.
空の辞書は {} で作れます.
そこへ 1 組ずつ入れていくのが,よく使う書き方です.
d = {} # 空の辞書
d["りんご"] = "red"
d["みかん"] = "orange"
print(d) # → {'りんご': 'red', 'みかん': 'orange'}
18.2.2 無いキーを引くとエラー
辞書に入っていないキーを引くと,プログラムはその場で止まります.
ten = {"あかり": 78, "けんた": 92}
print(ten["ゆうき"])
# → KeyError: 'ゆうき'
KeyError は「そんなキーはありません」という意味です.
入力された言葉を辞書で引くようなプログラムでは,
ユーザが何を入力するか分からないので,必ず起きます.
次の節で,その備え方を習います.
色の日本語名から,タートルで使える色の名前を引く辞書を作り, その色で点を打つプログラムです. ____ を埋めてください.
from turtle import *
ht()
iro = {"あか": "red", "あお": ____, "きいろ": "gold", "みどり": "seagreen"}
namae = ["あか", "きいろ", "みどり", "あお"]
x = -150
for n in namae:
pu()
goto(x, 0)
pencolor(iro[____]) # 日本語名から色の名前を引く
dot(70)
pu()
goto(x - 22, -70)
pencolor("black")
write(____, font=("Arial", 14, "normal"))
x = x + 100
print("辞書には", ____(iro), "色入っています")
done()
18.3 キーがあるか調べる
18.3.1 in で確かめる
リストのときと同じ in が使えます.
ただし辞書の in が調べるのは,キーのほうです.
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
print("さくら" in ten) # → True(キーにある)
print("ゆうき" in ten) # → False
print(65 in ten) # → False(値は調べない!)
# 値のほうを調べたいときは values() を付ける
print(65 in ten.values()) # → True
# 安全に引く定石
namae = "ゆうき"
if namae in ten:
print(namae, "さんは", ten[namae], "点")
else:
print(namae, "さんは名簿にいません")
in は中で線形探索をしている,と話しました.
辞書の in は違います.
辞書は,中身がどれだけ増えても,ほぼ一瞬でキーを見つけられる
仕掛け(ハッシュ表といいます)で作られています.
1000 人分でも 100 万人分でも,引く速さはほとんど変わりません.
18.1.1 節の「2 本のリスト」方式との,いちばん大きな違いがこれです.
18.3.2 get() — 無くてもエラーにしない
「無ければこの値ということにしてほしい」という場面はよくあります.
get() を使うと 1 行で書けます.
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
print(ten.get("さくら")) # → 65(ふつうに取り出せる)
print(ten.get("ゆうき")) # → None(エラーにならない)
print(ten.get("ゆうき", 0)) # → 0(無いときの値を指定できる)
# 出席回数を数える,というよくある使い方
kiroku = ["あかり", "けんた", "あかり", "さくら", "あかり", "けんた"]
kaisu = {}
for n in kiroku:
kaisu[n] = kaisu.get(n, 0) + 1 # 無ければ 0 から数え始める
print(kaisu)
| 書き方 | キーがあるとき | キーが無いとき |
|---|---|---|
d[k] | 値を返す | エラー(KeyError) |
d.get(k) | 値を返す | None を返す |
d.get(k, x) | 値を返す | x を返す |
k in d | True | False |
kaisu[n] = kaisu.get(n, 0) + 1 は,ぜひ覚えてほしい 1 行です.
「数える」「集計する」という処理は,ほとんどこの形で書けます.
文字列の中に,どの文字が何回出てくるかを数えて, 多い順に棒グラフで描くプログラムです. ____ を埋めてください.
for c in s: と書くと,文字列 s の文字を
1 つずつ取り出せます.文字列も「並んだもの」なので,
リストと同じように for で回せるのです.
sorted(kaisu, key=kazu, reverse=True) は,第 17 章の
sorted(w, key=len) と同じ形です.辞書を sorted に渡すと
キーが並べ替えられ,比べるときには kazu に通した値(回数)が使われます.
並べ替えの基準の決め方は,第 19 章でくわしく扱います.
from turtle import *
ht()
speed(0)
s = "abracadabra"
# 数える
kaisu = {}
for c in s:
kaisu[c] = kaisu.____(c, 0) + 1
print(kaisu)
# 多い順に並べ替える(回数を返す関数を key= に渡す)
def kazu(c):
return kaisu[c]
moji = sorted(kaisu, key=kazu, reverse=True)
# 棒グラフにする
x = -120
for c in moji:
fillcolor("mediumpurple")
pu()
goto(x, -80)
seth(0)
pd()
begin_fill()
fd(40)
lt(90)
fd(kaisu[____] * 30)
lt(90)
fd(40)
lt(90)
fd(kaisu[c] * 30)
lt(90)
end_fill()
pu()
goto(x + 12, -104)
write(____, font=("Arial", 16, "normal"))
x = x + 55
done()
18.4 中身を全部たどる
18.4.1 for で回す
for k in d: と書くと,キーが 1 つずつ取り出されます.
値ではありません.ここは最初にとまどうところです.
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
print("--- キーだけ ---")
for k in ten:
print(k)
print("--- キーと値(キーで引く) ---")
for k in ten:
print(k, "さんは", ten[k], "点")
print("--- キーと値(items) ---")
for k, v in ten.items():
print(k, "さんは", v, "点")
print("--- 値だけ ---")
for v in ten.values():
print(v)
print("合計", sum(ten.values()), "点/平均", sum(ten.values()) / len(ten), "点")
| 書き方 | 取り出されるもの |
|---|---|
for k in d: | キー(d.keys() と同じ) |
for v in d.values(): | 値 |
for k, v in d.items(): | キーと値の両方 |
len(d) | 組の数(要素数) |
for k, v in d.items(): の k, v は,
第 17 章に出てきた a, b = b, a と同じ仕掛けです.
items() は「キーと値がひと組になったもの」を返すので,
それを 2 つの変数に配っているのです.
辞書の中身は,書いた順(入れた順)に取り出されます. 昔の Python では順番がばらばらでしたが,いまは入れた順と決まっています.
18.4.2 辞書からグラフを描く
キーを名前に,値を棒の高さにすれば,そのままグラフになります. 辞書はグラフと相性がよいのです.
# 辞書から棒グラフを描く
from turtle import *
ten = {"あかり": 78, "けんた": 92, "さくら": 65, "たろう": 88, "なな": 71}
ht()
speed(0)
pensize(1)
pencolor("dimgray")
x = -175
for namae, ten_su in ten.items():
fillcolor("steelblue")
pu()
goto(x, -120)
seth(0)
pd()
begin_fill()
fd(48)
lt(90)
fd(ten_su * 2)
lt(90)
fd(48)
lt(90)
fd(ten_su * 2)
lt(90)
end_fill()
pu()
goto(x + 4, -145)
pencolor("black")
write(namae, font=("Arial", 14, "normal"))
goto(x + 10, -112 + ten_su * 2)
write(ten_su, font=("Arial", 13, "normal"))
pencolor("dimgray")
x = x + 72
print("平均", sum(ten.values()) / len(ten), "点")
done()
ten をそのまま棒グラフにしたもの.18.4.3 組を消す — del
要らなくなった組は del で消せます.
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
print(ten, len(ten))
del ten["けんた"]
print(ten, len(ten))
# 無いキーを del するとエラーになるので,先に確かめる
if "ゆうき" in ten:
del ten["ゆうき"]
else:
print("ゆうき は元からいません")
# pop なら,無いときの値を決めておける
x = ten.pop("ゆうき", -1)
print("消した値:", x)
ten.clear() # 全部消す
print(ten, len(ten))
for で辞書を回している最中に
del で組を消してはいけません.
RuntimeError: dictionary changed size during iteration というエラーになります.
どうしても消したいときは,消すキーを先にリストに集めておき,
for を抜けてから消します.
18.5 キーに使えるもの
キーには,文字列のほかに数も使えます. 「学生番号 → 点数」「年 → 人口」のように.
# 学生番号(数)をキーにする
ten = {2301: 78, 2302: 92, 2315: 65}
print(ten[2302])
# 数のキーは「番号」ではないので,とびとびでもよい
print(2315 in ten) # → True
print(0 in ten) # → False(リストなら 0 番目は必ずある)
# 位置(x, y)をキーにすることもできる(タプルを使う)
masu = {(0, 0): "白", (1, 0): "黒", (2, 3): "白"}
print(masu[(1, 0)])
# リストはキーにできない
# d = {[1, 2]: "だめ"} # → TypeError: unhashable type: 'list'
| キーに使えるか | 例 | 理由 |
|---|---|---|
| ○ 文字列 | "さくら" | 作ったあとで中身が変わらない |
| ○ 数 | 2302, 3.14 | 同上 |
| ○ タプル | (1, 0) | 同上(第 19 章) |
| × リスト | [1, 2] | あとから中身を変えられてしまうため |
| × 辞書 | {"a": 1} | 同上 |
なお,値のほうには何でも入れられます. 数,文字列,リスト,辞書,そして関数まで.次の節で見ていきます.
18.6 辞書とリストを組み合わせる
18.6.1 値がリストの辞書
「1 人につき複数の値」を持ちたいときは,値をリストにします.
# 1 人につき 3 回分のテストの点
ten = {
"あかり": [78, 82, 91],
"けんた": [92, 88, 85],
"さくら": [65, 70, 74],
}
print(ten["あかり"]) # → [78, 82, 91]
print(ten["あかり"][2]) # → 91(3 回目の点)
for namae, ten_list in ten.items():
heikin = sum(ten_list) / len(ten_list)
print(namae, "さん:", ten_list, "平均", heikin, "点")
# 1 回分を付け足す
ten["さくら"].append(80)
print(ten["さくら"])
ten["あかり"][2] は「辞書を引いて,出てきたリストの 2 番目」という意味です.
左から順に読むと分かりやすくなります.
18.6.2 辞書が並んだリスト
逆に,リストの中に辞書を並べることもあります. 「1 件=1 つの辞書」と考えると,表(エクセルの 1 行)をそのまま表せます.
# 1 件が 1 つの辞書.よく使う形
hoshi = [
{"namae": "シリウス", "akarusa": 25, "iro": "white"},
{"namae": "ベテルギウス", "akarusa": 18, "iro": "orangered"},
{"namae": "リゲル", "akarusa": 20, "iro": "lightskyblue"},
]
for h in hoshi:
print(h["namae"], "/明るさ", h["akarusa"], "/色", h["iro"])
# 明るい順に並べ替える(明るさを返す関数を key= に渡す)
def akarusa(h):
return h["akarusa"]
hoshi.sort(key=akarusa, reverse=True)
print("いちばん明るいのは", hoshi[0]["namae"])
key=akarusa は「関数 akarusa に通した値で比べて並べる」
という意味です.第 17 章の sorted(w, key=len) の len の代わりに,
自分で作った関数を渡しています.かっこを付けずに関数の名前を書くのは,
18.8 節で辞書に関数を入れるときと同じです.
reverse=True を付けると大きい順(明るい順)になります.
並べ替えの基準の決め方は,第 19 章でくわしく扱います.
上の hoshi を使って,星空を描きます.
辞書から位置・大きさ・色を取り出して点を打ってください.
____ を埋めてください.
from turtle import *
ht()
bgcolor("midnightblue")
hoshi = [
{"namae": "シリウス", "x": -150, "y": 60, "size": 26, "iro": "white"},
{"namae": "ベテルギウス", "x": 40, "y": 110, "size": 20, "iro": "orangered"},
{"namae": "リゲル", "x": 120, "y": -40, "size": 22, "iro": "lightskyblue"},
{"namae": "プロキオン", "x": -60, "y": -90, "size": 16, "iro": "lightyellow"},
]
for h in ____:
pu()
goto(h["x"], h[____])
pencolor(h["iro"])
dot(h[____])
pu()
goto(h["x"] - 30, h["y"] - 38)
pencolor("lightgray")
write(h["namae"], font=("Arial", 12, "normal"))
done()
18.7 辞書で図形を選ぶ
辞書の値には数や文字列だけでなく,描き方を決める情報も入れられます. 「言葉 → 図形」の対応表を作ってみましょう.
# 言葉 → 角の数 の辞書で,図形を描き分ける
from turtle import *
kado = {"さんかく": 3, "しかく": 4, "ごかく": 5, "ろっかく": 6, "はちかく": 8}
iro = {"さんかく": "tomato", "しかく": "royalblue", "ごかく": "seagreen",
"ろっかく": "goldenrod", "はちかく": "mediumpurple"}
ht()
speed(0)
pensize(3)
kotoba = "ごかく"
n = kado[kotoba] # 角の数を引く
pencolor(iro[kotoba]) # 色を引く
pu()
goto(-60, -70)
seth(0)
pd()
for i in range(n):
fd(120)
lt(360 / n) # 第 2 章でやった 360 ÷ n
print(kotoba, "は", n, "角形です")
done()
kotoba = "ごかく" のときの結果.言葉を変えれば図形が変わる.if を 1 つも書いていないのがポイントです.
もし辞書を使わなければ,こう書くことになります.
if kotoba == "さんかく":
n = 3
pencolor("tomato")
elif kotoba == "しかく":
n = 4
pencolor("royalblue")
elif kotoba == "ごかく":
n = 5
pencolor("seagreen")
elif ...
図形を 1 つ増やすたびに elif が 3 行ずつ伸びていきます.
辞書なら,表に 1 行足すだけです.
長い elif の列を見たら,辞書にできないか考える.
これはプログラミングの定石の 1 つです.
曜日から色を引く辞書と,曜日から半径を引く辞書を使って, 円を 7 つ並べて描きます.____ を埋めてください.
for k, v in d.items(): を使うと,
キーと値を同時に取り出せます(18.4.1 節).
from turtle import *
ht()
speed(0)
pensize(3)
iro = {
"月": "silver", "火": "tomato", "水": "royalblue",
"木": "seagreen", "金": "gold", "土": "steelblue", "日": "crimson",
}
ookisa = {
"月": 20, "火": 24, "水": 22, "木": 26, "金": 30, "土": 34, "日": 34,
}
x = -180
for youbi, col in iro.____():
pu()
goto(x, 0)
seth(0)
pd()
pencolor(____)
circle(ookisa[____])
pu()
goto(x - 6, -50)
pencolor("black")
write(youbi, font=("Arial", 15, "normal"))
x = x + 60
done()
18.8 絵辞書をつくる
いよいよ今回の目玉です. 入力された言葉に応じた絵を描くプログラムを作ります.
18.8.1 絵を描く関数を用意する
まず,果物を描く関数を 4 つと,「見つかりません」の絵を 1 つ用意します. どれも引数なし・画面の中央に描くという約束にそろえておきます. この「そろえておく」ことが,あとで効いてきます.
ringo()ichigo()suika()mikan()notfound()18.8.2 辞書に関数そのものを入れる
ここで,少し驚くことをします. 辞書の値に,関数そのものを入れます.
e = {"りんご": ringo, "いちご": ichigo, "すいか": suika, "みかん": mikan}
# ↑かっこを付けない
ringo() と書くと「関数を呼ぶ」ですが,
ringo とかっこを付けずに書くと,
「関数そのもの」という値になります.
辞書から取り出したあとでかっこを付ければ,そのとき呼ばれます.
f = e["りんご"] # 関数そのものを取り出す(まだ描かれない) f() # ここで呼ばれる → りんごが描かれる e["りんご"]() # 1 行にまとめてもよい
# 絵辞書:言葉を入れると,その絵が出てくる
from turtle import *
def ringo():
color("saddlebrown")
pensize(8)
pu()
goto(0, 40)
seth(90)
pd()
fd(35)
pensize(2)
color("crimson")
pu()
goto(0, -60)
seth(0)
pd()
begin_fill()
circle(55)
end_fill()
def ichigo():
color("crimson")
pu()
goto(-45, 40)
pd()
begin_fill()
goto(45, 40)
goto(0, -65)
goto(-45, 40)
end_fill()
color("seagreen")
pu()
goto(-52, 42)
pd()
begin_fill()
goto(52, 42)
goto(0, 72)
goto(-52, 42)
end_fill()
def mikan():
color("orange")
pu()
goto(0, -60)
seth(0)
pd()
begin_fill()
circle(58)
end_fill()
color("seagreen")
pu()
goto(0, 50)
pd()
begin_fill()
goto(42, 76)
goto(4, 66)
goto(0, 50)
end_fill()
def notfound():
color("gray")
pu()
goto(-22, -45)
pd()
write("?", font=("Arial", 110, "bold"))
ht()
speed(0)
pensize(2)
# 言葉 → 絵を描く関数 の辞書
e = {"りんご": ringo, "いちご": ichigo, "みかん": mikan}
kotoba = input("調べたい言葉:")
if kotoba in e:
e[kotoba]() # 辞書から関数を取り出して,呼ぶ
print(kotoba, "の絵を描きました")
else:
notfound()
print(kotoba, "は辞書にありません")
done()
右コンソールの入力行に いちご と入れてあります.
りんご や みかん,
辞書に無い言葉(ばなな など)に書き換えて,実行してみてください.
いちご と入力したときの結果.in と
e[kotoba] の 2 つに置き換わります.
道具が変わると,プログラムの形そのものが変わるという良い例です.
== で中身を比べられます.
"りんご" == "りんご" は True です.安心して使ってください.
(なお,Python には「同じ物体か」を調べる is もありますが,
文字列や数を比べるのに使ってはいけません.)
18.9 自分で調べる
ここまでで,データをまとめて扱う道具がリストと辞書の
2 つになりました.
Python には,あと 2 つよく使う入れ物があります.
重複を消せる集合(set)と,
あとから変えられない組のタプル(tuple)です
(タプルは 18.5 節のキー (1, 0) や,
第 14 章の return x, y で,すでに顔を出しています).
4 つの入れ物の違いと使い分けは,第 19 章でまとめて扱います. この節では,知らない命令に出会ったときの調べ方を身に付けておきます.
18.9.1 公式ドキュメントの読み方
ここまで習ってきた命令は,Python が用意してくれた道具のごく一部です. 「こんなことができる命令はないかな」と思ったら, 公式ドキュメントを見ます.日本語版があります.
| 場所 | 何が書いてあるか |
|---|---|
| Python チュートリアル docs.python.org/ja/3/tutorial/ | 読み物として最初から読めるもの.リスト・辞書の章がおすすめ |
| 組み込み型 docs.python.org/ja/3/library/stdtypes.html | list・dict・set・str に何ができるかの一覧 |
| 標準ライブラリ docs.python.org/ja/3/library/ | random・math・time などの道具箱の説明 |
| turtle docs.python.org/ja/3/library/turtle.html | タートルの命令の一覧(この教材で使えるのはその一部です) |
ドキュメントには,独特の書き方の約束があります.
たとえば get() の説明はこう書かれています.
get(key, default=None)
key が辞書にあれば key に対する値を,なければ default を返します.
key… 必ず書く引数default=None… 省略できる引数.省略するとNoneになる,という意味- だから
d.get(k)ともd.get(k, 0)とも書ける
この読み方が分かると,sorted(a, reverse=True) や
write(arg, align="center") のような書き方も,
自分でドキュメントから見つけられるようになります.
print(dir({})) と書けば辞書にできることの一覧が出ますし,
help(dict.get) と書けば説明が読めます
(この教材の画面でも動きます.試してみてください).
アンケートの回答から,それぞれ何人かと
何種類の答えがあったかを辞書で求め,円の大きさで描きます.
辞書のキーは重複しないので,組の数がそのまま種類の数になります.
色をまだ決めていない答えは,get() で灰色にします.
____ を埋めてください.
from turtle import *
ht()
speed(0)
pensize(2)
kotae = ["いぬ", "ねこ", "いぬ", "うさぎ", "ねこ", "いぬ", "ねこ", "いぬ"]
# それぞれ何人か
ninzu = {}
for k in kotae:
ninzu[k] = ninzu.get(k, ____) + 1
print(ninzu)
# 何種類あったか(キーは重複しないので,組の数=種類の数)
print("答えは", ____(ninzu), "種類ありました")
# 円の大きさで描く(うさぎの色はまだ決めていない)
iro = {"いぬ": "sienna", "ねこ": "dimgray"}
x = -140
for k, n in ninzu.____():
pu()
goto(x, 0)
pencolor(iro.get(k, ____)) # 色が決まっていなければ灰色
dot(n * 28)
pu()
goto(x - 20, -70)
pencolor("black")
write(k, font=("Arial", 14, "normal"))
goto(x - 12, -95)
write(str(____) + " 人", font=("Arial", 13, "normal"))
x = x + 140
done()
18.10 この章で出てきた命令
| 書き方 | 意味 |
|---|---|
d = {} | 空の辞書を作る |
d = {"a": 1, "b": 2} | キーと値の組を書いて辞書を作る |
d[k] | キー k の値を取り出す.無ければ KeyError |
d[k] = v | キー k の値を v にする.無ければ新しく作る |
k in d | キー k があれば True |
d.get(k) | 値.無ければ None |
d.get(k, x) | 値.無ければ x |
len(d) | 組の数 |
for k in d: | キーを 1 つずつ取り出す |
d.keys() | キーの一覧 |
d.values() | 値の一覧 |
d.items() | キーと値の組の一覧.for k, v in … で使う |
del d[k] | キー k の組を消す |
d.pop(k, x) | 消しながら値を取り出す.無ければ x |
d.clear() | 全部消す |
help(dict.get) | その命令の説明を表示する |
演習
出発点のプログラムを「▶ この演習を開く」で右側に読み込み,
それを発展させて作品にしてください.
input() を使う演習には,右コンソールの入力行に
あらかじめ値を入れてあります.書き換えて試してください.
入力に慣れるための演習です.次の順に作ってください.
- 入力された言葉を,そのまま画面に
writeで表示する(おうむ返し).位置は出発点と同じ (-150, 60) のあたりでよい. - ことばの辞典を辞書
imi(言葉 → 意味)で作り,入力された言葉の意味を,おうむ返しの下(例:(-150, 0))にwriteで表示する.出発点の 2 語に3 語以上足して 5 語以上にすること.例:たーとると入力すると「画面の上で線を引くかめ」と表示される. - 辞書に無い言葉が入力されたら,「その言葉は知りません」と画面に表示する(
inかget()). - これもやりましょう:知らない言葉が来たときは,続けて
input("意味を教えてください:")で意味を入力させ,imi[kotoba] = 意味でその場で辞書に登録する.そのあとfor k, v in imi.items():で,登録後の辞典を「言葉 … 意味」の形で 1 行に 1 語ずつコンソールに全部表示する.試すときは,入力行の 1 行目を辞書に無い言葉(例:ぱそこん)に書き換えてください.
input() を 2 回書けば,
input() が上から順に 1 行ずつ読み取ります.
必須(すべて満たして正しく動けば 8)
- 辞書
imiに5 語以上を登録し,入力された言葉の意味をimi[kotoba]で引いてwriteで表示している(if/elifの並びで答えていない). - 辞書に無い言葉が入力されても
KeyErrorで止まらず,inかget()で確かめて「その言葉は知りません」と表示する. - 知らない言葉のときは,意味を
input()で受け取り,imi[kotoba] = …で辞書に登録している. - 登録後の辞典を
for k, v in imi.items():でたどり,新しく登録した言葉も含めて「言葉 … 意味」の形で全部表示している.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 登録したあと,もう一度同じ言葉を引くと意味が表示されることを確かめる
- 登録の前後で
len(imi)を表示し,語数が 1 増えたことを確かめる(同じ言葉をもう一度登録しても増えないことも確かめる) - ひらがなとカタカナなど,表記の違う入力にも答えられるようにする
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
名詞と動詞を集めておいて, でたらめな文を作るプログラムです. 言葉は,実行するたびに入力して増やせるようにします.
- 辞書
tangoを作り,"めいし"と"どうし"をキーに,言葉のリストを値として入れる(18.6.1 節). - 品詞名と言葉を入力させて登録する.品詞名が
tangoのキーにあれば,その品詞のリストにappendする.無ければ「その品詞はありません」と表示する(inを使う). - 登録したあと,
for k, v in tango.items():で品詞ごとに何語あるかを表示する. random.choice(リスト)で 1 つずつ選び,「○○が△△」という文(例:ねこがわらう)を 5 つ作って,画面に縦に並べてwriteする.1 つめを (-230, 150) に書き,そこから 60 ずつ下へずらす.- これもやりましょう:
tangoに 3 つめのキー"けいようし"を足し,値に形容詞のリストを 3 語以上入れる(例:["おおきな", "ちいさな", "あかい"]).文は「けいようし+めいし+が+どうし」(例:あかいねこがわらう)の形にする.キーが 3 つになっても,(3) の語数の表示は書き直さずに 3 品詞ぶん出ることを確かめる.
random.choice を使うには,先頭に import random が要ります.
リストに言葉を足すのは tango["めいし"].append("ねこ") です.
右コンソールの入力行には,品詞名と言葉を 1 行ずつ入れてあります.
必須(すべて満たして正しく動けば 8)
tangoの値に言葉のリストを入れ,tango["めいし"]のようにキーで引いたリストから選んだ文を,5 つ縦にwriteしている.- 入力された品詞名を
inで確かめ,あればtango[品詞名].append(言葉)で登録し,無ければ「その品詞はありません」と表示する. for k, v in tango.items():で,品詞ごとの語数を表示している."けいようし"のキーを足し,「けいようし+めいし+が+どうし」の文を作っている(語数の表示にも 3 品詞が出る).
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 品詞名に無い入力(例:
ふくし)のとき,その品詞を新しく作って登録できるようにする - 登録した言葉が,作った文の中に必ず 1 回は出てくるようにする
- 品詞ごとに文字の色を変える(品詞 → 色 の辞書を使う)
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
絵と単語の辞書を使ったタイピングゲームを作ってください.
絵は出発点の みかん・りんご の 2 つのままでかまいません(新しい絵は描き足さない).
- 出発点の「単語 → その絵を描く関数」の辞書
e(18.8 節)で,問題の絵をe[mondai]()で描き,その名前を入力させる. - 答えが問題の単語と同じなら「せいかい」と表示する.違うときは,答えが辞書
eのキーにあるなら「ざんねん」と表示し,キーに無い(例:ばなな)なら「その名前の絵はありません」と表示する(inで確かめる). time.time()で入力にかかった秒数を測り,round(秒数, 2)で小数第 2 位まで表示する.- これもやりましょう:問題の単語のリスト
["みかん", "りんご", "みかん"]をforでたどり,3 問続けて出す.1 問ごとにclear()で前の絵を消してからe[mondai]()で描く.最後に「正解 2 / 3 問」「合計 4.21 秒/平均 1.4 秒」のような形で,正解数・合計時間・1 問あたりの平均時間(合計 ÷ 問題数)を表示する.入力行のみかん・りんご・ばなななら,3 問目は「その名前の絵はありません」になり,正解は 2 問.
time.time() は「いまの時刻(秒)」を返します.
入力の前後で 2 回呼び,引き算すればかかった時間が分かります.
round(x, 2) と書くと,小数第 2 位までに丸められます.
必須(すべて満たして正しく動けば 8)
- 絵は
e[mondai]()のように辞書から関数を取り出して描いている(if/elifの並びで描き分けていない). - 入力と問題の単語を比べて「せいかい」を表示し,違うときは
inで辞書eのキーにあるかを確かめて,「ざんねん」と「その名前の絵はありません」を出し分けている. time.time()の差で,入力にかかった秒数を表示している.- 問題の単語のリストを
forでたどって 3 問続けて出し,正解数・合計時間・1 問あたりの平均時間を表示している.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 出題の順番を
random.shuffleで毎回変える - 問題の単語のリストを
for k in e:で辞書eから作り,辞書に登録した単語がそのまま問題になるようにする - 間違えた単語と回数を辞書に記録して(
get()を使った数え方),最後に表示する
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
「学生番号 → 点数のリスト」という辞書から,成績表を作ってください.
- 1 人ずつ,3 回のテストの平均点を計算し,学生番号をキー,平均点を値とする辞書
heikinに入れる. - 平均点で評価(80 以上は A,70 以上は B,60 以上は C,それ未満は D)を付ける.評価は辞書ではなく
ifで決める.正しければ 2301 は A(平均 83.7),2303 は C(平均 69.7),2304 は D(平均 58.0)になる. - 平均点を棒グラフで描き,棒の下に学生番号,棒の上に評価を
writeする.棒は幅 48,高さは「平均点 × 2」,下の辺を y = -120 にそろえ,左はしの棒を x = -175 から始めて 72 ずつ右へずらす. - 評価ごとの人数を数えて表示する(
get()を使った数え方).正しければ A が 3 人,C が 1 人,D が 1 人になる. - これもやりましょう:平均点の高い順に並べ替えてから描く.18.6.2 節のように,学生番号を受け取って平均点を返す関数を
defで作り,sorted(heikin, key=その関数, reverse=True)に渡す.正しければ左から 2302,2301,2305,2303,2304 の順に並ぶ.
必須(すべて満たして正しく動けば 8)
- 学生番号をキー,3 回の平均点を値とする辞書
heikinを作っている. - 平均点から
ifで A〜D を決め,棒グラフの棒の上に評価をwriteしている. - 評価ごとの人数を
kazu[h] = kazu.get(h, 0) + 1の形で数え,A 3 人・C 1 人・D 1 人と表示している. - 平均点を返す関数を
key=に渡して高い順に並べ替え,2302,2301,2305,2303,2304 の順に棒を描いている.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 棒の上に平均点(小数第 1 位まで)も書き入れる
- 評価ごとに棒の色を変える(評価 → 色 の辞書を使う)
- 学生番号を
input()で受け取り,その人の成績を表示する(未登録の番号にも備える)
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
同じ目的でも,使う道具を変えるとプログラムの形が変わります. 次の「2 本のリスト版」のプログラムを,2 通りに書き直して 比べてください.
- 辞書
iro(色の名前 → 色コード)で書き直し,sagasuの色で (-140, 0) に直径 80 の点を打つ.探すforが要らなくなることを確かめる. - 辞書版で,登録されていない色(
"くろ")を探したときに,止まらずに「くろ は登録されていません」と表示する(inかget()). - 辞書が並んだリスト(
[{"namae": "あか", "code": "red"}, …]の 5 件)で書き直し,sagasuの色を探して (140, 0) に直径 80 の点を打つ. - どれがいちばん読みやすかったか,理由とあわせてコメントに書く.
- これもやりましょう:辞書版の
iroをfor k, v in iro.items():でたどり,登録されている 5 色を全部,画面の下に横 1 列の見本として並べる.点は直径 40 で,1 つめを (-200, -120) に打ち,100 ずつ右へずらす.各点の下(y = -170)に色の名前kをwriteする.
必須(すべて満たして正しく動けば 8)
- 辞書版で,
forによる探索を使わずにinとiro[sagasu]で色を引いて点を打っている. - 登録されていない色を探しても
KeyErrorで止まらず,「登録されていません」と表示する. - 辞書が並んだリスト版で
sagasuの色を探して点を打ち,3 つの版のどれが読みやすかったかを理由つきでコメントに書いている. iro.items()をforでたどり,5 色の見本の点と色の名前を横 1 列に並べて描いている.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 探して描く部分が何行から何行になったかを数えて書く
- 辞書が並んだリスト版の 1 件に説明文などの情報を足し,画面に表示する
input()で探す色を受け取れるようにする
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
自由制作です.辞書を中心に据えた作品を作ってください. 題材は何でもかまいません.アイデアの例を挙げておきます.
- 絵辞書の完成版.果物・動物・乗り物など,自分で絵を 5 つ以上描いて登録する.
- お絵かきコマンド.
"まる""しかく""ほし"など,言葉を入力すると,その図形が画面に足されていく. - 県庁所在地クイズ・英単語クイズ.「県名 → 都市名」の辞書を作り,出題して正解数を数える.
- モールス信号.「文字 → 記号」の辞書を作り,入力された言葉を点と線の図で描く.
- 顔ジェネレータ.「目 → 描き方」「口 → 描き方」の辞書を作り,入力(または
random)で組み合わせて顔を作る.
条件:辞書を2 つ以上使うこと.
in か get() で
知らない入力に備えること.
for で辞書の中身をたどる場面を1 つ以上入れること.
必須(すべて満たして正しく動けば 8)
- 辞書を2 つ以上使い,先頭のコメントに「キーは何で,値は何か」を書いている.
- 知らない入力が来ても,
inかget()で備えて止まらない. for k, v in d.items():などで,辞書の中身をたどる場面が 1 つ以上ある.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 値に関数を入れた辞書で,入力に応じて描き分ける
- 値がリストの辞書や,辞書が並んだリストを使う
- 入力で辞書に登録・削除できるようにする
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
追加実習:仕組みを確かめてから設計する
キーが存在することと,値が有効なこと
辞書を使うと,名前から値を取り出せます.
ただし,名前が存在することと,値が目的に合うことは別です.scoreというキーがあっても,値がNoneなら点数が未入力かもしれません.
0なら受験した結果が0点かもしれません.この2つを同じものとして扱うと,集計や表示で意図しない結果になります.
キーがあるかはinで調べ,値がNoneかはis Noneで調べます.
getでNoneを既定値にすると,キーがない場合と,キーがあって値がNoneの場合は同じ結果になります.区別したい場面では,まずキーの存在を確認してください.
便利な書き方でも,どの情報を区別できるかを理解して使う必要があります.
入力の形式を決める際,未入力を空文字列で表すのかNoneで表すのかもそろえます.
場所によって表現が違うと,すべての処理で複数の条件を調べることになります.読み込み時に決まった形式へそろえておくと,集計側は扱いやすくなります.
辞書は自由な入れ物ですが,作品やアプリの中ではキーと値の約束が必要です.
以下では,未登録,未入力,0点の3つを区別します.
文字として表示するだけでなく,どの分岐へ入ったかを予測してください.次の演習では,有効な点数だけを合計します.
0を除いてしまわないよう,真偽値としての判定と,データの意味としての判定を分けます.
records = {"a":None,"b":0,"c":80}
for name in ["a","b","missing"]:
if name not in records:
print(name,"未登録")
elif records[name] is None:
print(name,"未入力")
else:
print(name,records[name])
Noneを除いた有効な点数の人数と合計を求めてください.aがNone,bが0,cが80なら人数2,合計80です.0点の人も人数へ含めます.
必須(すべて満たして正しく動けば 8)
Noneの点数をis not Noneで除き,有効な人数と合計を求めている.- 0 点の人を人数に含め,人数 2・合計 80 になることを
assertで確かめている.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 未登録・未入力・0 点の 3 つを別々に数えて表示する
- 平均も求め,有効な人数が 0 のときの扱いを決める
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
同じキーを合わせるときの規則
2つの辞書を合わせる場合,同じキーが両方にあるとどうするかを決めます.
updateは,後から渡した値で置き換えます.設定の上書きには便利ですが,色ごとの個数を集計する場合には,置き換えではなく足し算が必要かもしれません.
辞書を合わせるという言葉だけでは,操作の意味は決まりません.
例えば,午前にblueが2個,午後にblueが3個なら,1日の合計は5個です.
updateで3へ置き換えると午前の分が失われます.一方,利用者が新しい表示色を指定した場合は,古い設定に足すのではなく置き換えるのが自然です.
同じキーの競合をどう解決するかを,用途に応じて選びます.
元の辞書を保つかどうかも決めます.
作業用にdict(first)で外側をコピーしてから更新すれば,元のキーと数値の対応は保てます.値が入れ子のリストなら,内部の共有に注意が必要ですが,この節では値を数値に限定して考えます.
一度にすべての型を扱うより,前提を明確にして操作の違いを確かめます.
以下ではupdateによる置き換えを観察します.
次の演習は,同じキーの値を加算する結合です.片方にしかないキー,両方にあるキー,空の辞書を含む場合を確認してください.
元データが変わっていないこともテストへ含めると,集計後に元の時間帯の結果を再利用できます.
first = {"blue":2,"red":1}
second = {"blue":3,"green":4}
merged = dict(first)
merged.update(second)
print(first,merged)
assert merged["blue"] == 3
同じキーは個数を足し,片方だけのキーはその値を使って結合してください.blueが5,redが1,greenが4になることと,元の2辞書が変わらないことを確認します.
必須(すべて満たして正しく動けば 8)
- 同じキーは個数を足し,片方だけのキーはその値を使って結合している(
get(key, 0)などを使う). - 結合の結果と,元の 2 つの辞書が変わっていないことを
assertで確かめている.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 空の辞書と結合しても正しく動くことを確かめる
- 結合を関数にして,3 つ以上の辞書を順に結合する
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
対応表のキーと,表示順を分ける
辞書はキーから値を引くための構造ですが,画面や表へ表示するときには順序が必要になります.
登録した順に見せたい場合も,キーの名前順,値の大きい順に見せたい場合もあります.どの順序が必要かを決め,表示する段階で並べ替えます.
辞書の対応関係を変更することと,表示順を変えることは別です.
キーの順ならsorted(records),値の順ならitemsから取り出した組をkeyで並べます.
どちらでも,表示する値が別のキーと入れ替わらないようにします.キーの一覧と値の一覧を別々に並べ替えると,対応が壊れる場合があります.
名前と点数のように対になっている情報は,組として扱うことが重要です.
同じ値がある場合の扱いも考えます.
点数の降順,同点なら名前の昇順という規則なら,(-score,name)をキーにできます.単にreverse=Trueを使うと,第2のキーの方向まで逆になる場合があるため注意してください.
具体的な期待順を先に書くと,並べ替えの式が仕様を表しているか確認しやすくなります.
以下では名前の順で表示します.
次の演習では点数の高い順,同点は名前の順へ変えます.点数が同じ2人を必ず含めてテストしてください.
異なる点数だけの入力では,第2のキーの規則が正しいか分かりません.辞書の学習でも,境界や同点を含む小さい例が役立ちます.
scores = {"b":80,"a":80,"c":90}
for name in sorted(scores):
print(name,scores[name])
点数の高い順,同点なら名前の昇順に並べて表示してください.例題ではc,a,bの順になります.名前と点数を別々に並べ替えず,itemsの組を使います.
必須(すべて満たして正しく動けば 8)
items()の組を,点数の高い順・同点なら名前の順に並べている(名前と点数を別々に並べ替えていない).- c・a・b の順になることと,元の辞書が変わっていないことを
assertで確かめている.
発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)
- 名前の順を逆にした並べ方も作り,結果の違いを説明する
- 同点が 3 人以上のデータでも確かめる
必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6
作品を確かめる観点
辞書を使う作品の説明には,キーの意味,値の意味,キーがない場合の扱い,同じキーが再び現れた場合の規則を書きます.
例えば色別個数なら,キーは色名,値は0以上の整数,未登録の色は0個,追加の集計は加算,という約束になります.設定情報なら同じキーへの新しい指定を上書きするなど,別の規則を選ぶでしょう.
構造の形が同じでも,操作の意味は用途によって変わります.テストには,登録済みと未登録,0とNone,同点の値,空の辞書を必要に応じて含めてください.
すべてを同じ既定値へ置き換える前に,区別すべき状態がないかを確認します.この整理は,第21章で図形のデータを設計し,第25章でJSONを読み込む際にも使います.
まとめ
- リストは番号で引く入れ物,辞書は名前(キー)で引く入れ物.
- 辞書は
{"キー": 値, …}で作り,d[k]で取り出す.d[k] = vは,キーが無ければ新しく作る. - 無いキーを
d[k]で引くとKeyError.inで確かめるか,get()を使う. kaisu[k] = kaisu.get(k, 0) + 1は「数える」の定石.for k in d:はキー,for k, v in d.items():はキーと値の両方を取り出す.- 値にはリストも辞書も関数も入れられる.長い
elifの列は,たいてい辞書にできる. - キーにできるのは,あとから変わらないもの(文字列・数・タプル).
- 集合(
set)とタプル(tuple)を加えた4 つの入れ物の使い分けは,第 19 章で扱う. - 知らない命令は公式ドキュメントで調べる.暗記より調べ方.
次の章では,リストと辞書にタプルと集合を加えた 4 つの入れ物を見わたし,場面に応じて使い分ける方法を学びます. スライスや内包表記など,プログラムを短く書く道具も加わります.
復習:虫食いで確かめる
コードの ____ を埋めてください.実行前に結果を予測し,実行後に問題文の条件と照合しましょう.assert は条件が成り立つかを調べ,成り立たないとエラーになります.
red がまだない辞書で,出現回数を1にします.初期値0で取り出すメソッドを補ってください.
counts = {}
counts["red"] = counts.____("red", ____) + 1
print(counts)
assert counts["red"] == 1