Python のデータ構造
入れ物を選ぶ.切り出す.重ねる.短く書く.
第 16 章でリストを,第 18 章で辞書を習いました. どちらも「たくさんのデータをまとめて持つ入れ物」です. Python には,この仲間があと 2 つあります. タプル(tuple)と集合(set)です.
今回は,4 つの入れ物を一度に見わたして使い分けができるようにします.
そのうえで,これまで説明しそびれていた便利な書き方—
スライス(一部を切り出す),
リストのリスト(マス目のデータ),
内包表記(1 行でリストを作る),
key= を使った並べ替え—をまとめて身に付けます.
どれも「知らなくても書ける」ものばかりです. しかし知っていると,5 行かかっていたものが 1 行になります. 書く量が減れば,それだけ間違える場所も減ります. ここから先,自分のプログラムが急に短くなるのを実感できるはずです.
list・tuple・dict・setの違いを説明でき,場面に応じて選べる- 他の言語の配列と Python の
listの違いがわかる - よく使うメソッド(
append・pop・items・addなど)を一覧から選んで使える len・sum・max・min・any・all・enumerate・zipを使える- スライス
a[1:4]・a[::2]・a[::-1]で一部を切り出せる - リストのリストでマス目のデータを表し,二重の
forで絵にできる - リストのリストを表として使い,行ごと・列ごとに集計できる
- 内包表記
[f(x) for x in xs]でリストを 1 行で作れる sorted(a, key=…, reverse=True)で,好きな基準で並べ替えられるinの速さが入れ物によって違う理由を,感覚として説明できる
19.1 4 つの入れ物を見わたす
まず 4 つを並べて,違いを一度に見てしまいましょう. この表が今回のいちばん大事なページです.
リスト list | タプル tuple | 辞書 dict | 集合 set | |
|---|---|---|---|---|
| 書き方 | [3, 1, 4] | (3, 1) | {"a": 1} | {3, 1, 4} |
| 空のもの | [] | () | {} | set() |
| 順番 | ある | ある | 入れた順に並ぶ | 無い |
| 同じ値 | 何個でも入る | 何個でも入る | キーは 1 つだけ | 1 個に消える |
| あとから変える | できる | できない | できる | できる(出し入れ) |
| 取り出し方 | 番号 a[0] | 番号 t[0] | キー d["a"] | 取り出せない(有無だけ) |
| 得意なこと | 順番が意味を持つデータ.あとから増やす | あとから変わらない組.辞書のキーになれる | 名前から値を素早く引く | 重複を消す・入っているか調べる・共通部分 |
{} は空の辞書であって,空の集合ではありません.
空の集合が欲しいときは set() と書きます.
{1, 2} と書けば集合,{"a": 1} と書けば辞書です.
同じ { } を使い回しているので,ここだけは覚えるしかありません.
19.1.1 タプル — あとから変えられない組
タプルは,丸かっこで書く入れ物です. リストとほとんど同じように使えますが,作ったあとで中身を変えられません. 足すことも,減らすことも,書き換えることもできません.
「変えられない」のは不便に聞こえますが,これは長所です.
「この 2 つは組で,ばらばらにならない」ということをプログラムの上で宣言できます.
座標 (x, y),日付 (2026, 9, 20),
赤緑青 (255, 128, 0) のように,
まとまりに意味がある組によく使われます.
# タプル:あとから変えられない組
hito = (170, 62) # 身長と体重を 1 組にする
print(hito)
print(hito[0], hito[1]) # 取り出し方はリストと同じ
print(len(hito))
shincho, taiju = hito # 2 つの変数へ一度に取り出せる
print(shincho, "cm", taiju, "kg")
# hito[0] = 180 # ← # を外すと TypeError(変えられない)
# hito.append(3) # ← これもできない
# 変わらないので,辞書のキーにできる
basho = {(0, 0): "まんなか", (100, 50): "右上"}
print(basho[(0, 0)])
print(basho[(100, 50)])
# dame = {[0, 0]: "むり"} # ← リストはキーにできない(変わってしまうから)
# リストとタプルは行き来できる
print(list(hito))
print(tuple([1, 2, 3]))
for k, v in d.items(): の items() が返しているのは,
(キー, 値) というタプルの並びです.
pos() が返す座標もタプルですし,
第 14 章で習った「関数から 2 つの値を返す」書き方
(return x, y)も,中身はタプルです.
19.1.2 集合 — 重複が消えて,順番が無い
集合は,数学で習う集合とそっくりです. 同じものは 1 つしか入らず,順番を持ちません. 「何が入っているか」だけを覚えている入れ物だと思ってください.
# 集合:重複が消えて,順番が無い
kotae = ["いぬ", "ねこ", "いぬ", "うさぎ", "ねこ", "いぬ"]
shurui = set(kotae) # リストから集合を作る
print(len(kotae), "個の答えのうち,種類は", len(shurui), "種類")
print(sorted(shurui)) # 順番が無いので,並べて表示すると読みやすい
print("ねこ" in shurui) # 入っているかを調べるのが得意
print("とり" in shurui)
a = {1, 2, 3, 4}
b = {3, 4, 5}
print(sorted(a | b)) # 和集合:どちらかにあるもの
print(sorted(a & b)) # 積集合:両方にあるもの
print(sorted(a - b)) # 差集合:a にだけあるもの
s[0] のように番号で取り出すことはできませんし,
print(s) したときの並び順も,あてにしてはいけません.
順番が要るときは sorted(s) でリストに直してから使います.
19.1.3 どれを使うか,迷ったときの決め方
やりたいことを日本語で言ってみると,たいてい決まります.
- 「1 番目,2 番目……と順に並べたい」「あとから足したり減らしたりする」 → リスト
- 「2 つ(3 つ)をひとまとめにして渡したい」「もう変わらない」 → タプル
- 「名前で引きたい」「○○の△△を覚えておきたい」 → 辞書
- 「ダブっているものを消したい」「入っているかだけ知りたい」「共通しているものを出したい」 → 集合
set(a) や tuple(a) で作り直せます.
最初から正解を選ぼうとして手が止まるより,
動くものを書いてから直すほうが,ずっと早く進みます.
19.1.4 配列とリスト — 他の言語の本を読むときに
| 他の言語の配列 | Python の list | |
|---|---|---|
| 大きさ | 作るときに個数を決める.int a[5]; と書いたら 5 個しか入らない | 決めない.append すればいくらでも伸びる |
| あふれたら | 6 個目を入れようとするとエラー(または,もっと悪いことが起きる) | 起きない.勝手に伸びる |
| 中身の種類 | ふつうは全部同じ種類(整数なら整数だけ) | 混ぜてよい.[1, "ねこ", 3.5] も作れる |
| 作り直し | 大きくしたいときは,新しく作って中身を移し替える | 要らない.Python が中でやってくれている |
array という名前の道具はありますが,ふつうは使いません.
リストのほうが便利だからです.
「Python にはあの不便さが無い」ということを知っていれば,
他の言語の本を読んでも混乱しません.
同じデータを 4 つの入れ物で扱います. ____ を埋めてください. 空の集合の書き方,重複の消し方,タプルの取り出し方がポイントです.
# 4 つの入れ物を使い分ける
kudamono = ["りんご", "みかん", "りんご", "すいか", "みかん", "りんご"]
# (1) リスト:順番どおりに 3 番目を表示する
print("3 番目は", kudamono[____])
# (2) 集合:何種類あるか
shurui = ____(kudamono)
print("種類の数は", len(shurui))
# (3) 辞書:それぞれ何個あるか
kazu = {}
for k in kudamono:
kazu[k] = kazu.____(k, 0) + 1
print(kazu)
# (4) タプル:名前と個数を組にして,2 つの変数へ取り出す
ooi = ("りんご", kazu["りんご"])
namae, n = ____
print(namae, "が", n, "個でいちばん多い")
# (5) 空の集合を作る(空の辞書ではない書き方)
karappo = ____
print(len(karappo), type(karappo))
19.2 よく使うメソッドの一覧
a.append(5) の append のように,
入れ物のうしろにドットを付けて呼ぶ命令を
メソッドといいます.
「その入れ物に対してできること」の一覧だと思ってください.
ここで一度まとめておきます.覚えなくてかまいません.必要になったら見に来てください.
19.2.1 リストのメソッド
| 書き方 | すること | 元のリスト |
|---|---|---|
a.append(x) | 末尾に x を 1 つ足す | 変わる |
a.insert(i, x) | i 番目に x を割り込ませる.後ろはずれる | 変わる |
a.pop() | 末尾を取り出して取り除く | 変わる |
a.pop(i) | i 番目を取り出して取り除く | 変わる |
a.remove(x) | 値が x のものを先頭から探して 1 つ取り除く | 変わる |
a.sort() | 小さい順に並べ替える | 変わる |
a.reverse() | 前後をひっくり返す | 変わる |
a.extend(b) | リスト b の中身をまとめて足す(a + b でも同じ) | 変わる |
a.index(x) | x が何番目にあるか.無ければエラー | 変わらない |
a.count(x) | x がいくつ入っているか | 変わらない |
a.pop(i) は番号で取り除き,
a.remove(x) は値で取り除きます.
a = [10, 20, 30] のとき,a.pop(0) は 10 を取り除きますが,
a.remove(0) は「値が 0 のもの」を探して見つからずエラーになります.
よく取り違えるところです.
a = [3, 1, 4]
a.append(9) # 末尾に足す
a.insert(1, 7) # 1 番目に割り込ませる
print(a)
x = a.pop() # 末尾を取り出して取り除く
y = a.pop(0) # 0 番目を取り出して取り除く
print("取り出した:", x, y)
print(a)
a.remove(7) # 「値が 7 のもの」を取り除く
print(a)
a.extend([8, 8, 2]) # まとめて足す
print(a)
print("8 は", a.index(8), "番目")
print("8 は", a.count(8), "個")
a.sort()
print("小さい順:", a)
a.reverse()
print("逆順 :", a)
19.2.2 辞書のメソッド
| 書き方 | すること |
|---|---|
d.keys() | キーの一覧 |
d.values() | 値の一覧 |
d.items() | (キー, 値) の組の一覧.for k, v in d.items(): で使う |
d.get(k) | 値.キーが無ければ None(エラーにならない) |
d.get(k, x) | 値.キーが無ければ x.数えあげの定番 |
d.pop(k) | 組を取り除きながら,その値を返す |
d.clear() | 全部消す |
k in d | キー k があれば True(値ではなくキーを見る) |
ten = {"あかり": 78, "けんた": 92, "ゆうき": 61}
print(list(ten.keys())) # キーだけ
print(list(ten.values())) # 値だけ
print(list(ten.items())) # 組(タプル)の並び
for namae, t in ten.items():
print(namae, "は", t, "点")
print(ten.get("あかり")) # ある → 値
print(ten.get("みさき")) # 無い → None
print(ten.get("みさき", 0)) # 無い → 0
print("合計", sum(ten.values()))
print("平均", sum(ten.values()) / len(ten))
print("最高", max(ten.values()))
ten.keys() を print すると
dict_keys([...]) という見慣れない表示になります.
これは「キーの一覧を必要になったら順に出すもの」で,
リストそのものではありません.
リストとして見たいときは list(ten.keys()) と包みます.
for で回すだけなら,包まなくてもそのまま使えます.
19.2.3 集合のメソッド
| 書き方 | 記号で書くと | すること |
|---|---|---|
s.add(x) | — | x を 1 つ入れる.すでにあれば何も起きない |
s.discard(x) | — | x を取り除く.無くてもエラーにならない |
s.remove(x) | — | x を取り除く.無ければエラー |
a.union(b) | a | b | 和集合.どちらかにあるもの |
a.intersection(b) | a & b | 積集合.両方にあるもの |
a.difference(b) | a - b | 差集合.a にだけあるもの |
x in s | — | 入っていれば True.とても速い |
s = {"りんご", "みかん"}
s.add("すいか")
s.add("りんご") # すでにある.増えない
print(sorted(s), len(s))
s.discard("なし") # 無い.エラーにもならない
s.discard("みかん")
print(sorted(s))
a = {"りんご", "みかん", "すいか"}
b = {"みかん", "いちご"}
print(sorted(a.union(b))) # a | b と同じ
print(sorted(a.intersection(b))) # a & b と同じ
print(sorted(a.difference(b))) # a - b と同じ
19.2.4 自分で調べる — dir() と help()
上の表に載っていないメソッドもたくさんあります. 全部を覚える必要はありません. 動いている Python に直接たずねられるからです.
| 書き方 | すること |
|---|---|
dir(もの) | そのものにできることの名前の一覧.「何ができるんだっけ」のとき |
help(命令) | その命令の説明.「どう書くんだっけ」のとき |
type(もの) | そのものが何の仲間か.list・dict など |
# 「何ができるの?」「どう書くの?」をプログラムに聞く
a = [1, 2, 3]
print(type(a))
# dir() … できることの一覧._ で始まるものは内部用なので読み飛ばす
for namae in dir(a):
if not namae.startswith("_"):
print(namae, end=" ")
print()
print("---")
help(list.insert)
dir("") で文字列,dir({}) で辞書,
dir(set()) で集合にできることが出ます.
知らない名前を見つけたら help(str.upper) のように
「仲間の名前.メソッド名」で説明を読めます.
もっとくわしい説明は,日本語の公式ドキュメント
docs.python.org/ja/3/ にあります.
2 つのクラスでアンケートを取りました. ____ を埋めて, 両方のクラスに出てきた色と どちらかに出てきた色と 1 組にだけ出てきた色を求めてください.
# 2 つのクラスの好きな色をくらべる
kumi1 = ["あか", "あお", "きいろ", "あか", "みどり"]
kumi2 = ["あお", "むらさき", "あか", "あお"]
# 重複を消して集合にする
s1 = ____(kumi1)
s2 = ____(kumi2)
print("1 組:", sorted(s1))
print("2 組:", sorted(s2))
# 両方に出てきた色
print("両方 :", sorted(s1.____(s2)))
# どちらかに出てきた色
print("どちらか :", sorted(s1.____(s2)))
# 1 組にだけ出てきた色
print("1 組だけ :", sorted(s1 ____ s2))
# 2 組に「きいろ」はあるか
print("2 組にきいろ:", "きいろ" ____ s2)
19.3 全体をまとめて調べる関数
入れ物の全体について何かを知りたいとき,
わざわざ for で回さなくてよい命令があります.
これらは入れ物の種類を選びません.
リストでも,タプルでも,集合でも,辞書でも同じように使えます.
| 書き方 | 返すもの | 自分で書くと |
|---|---|---|
len(a) | 個数 | カウンタを 1 ずつ増やす |
sum(a) | 合計(数のとき) | s = 0 から足していく |
max(a) / min(a) | いちばん大きい/小さい値 | 「いまのところの最大値」を覚えながら比べる |
any(a) | 1 つでも成り立てば True | for で回して,見つかったら break |
all(a) | 全部成り立てば True | for で回して,1 つでも外れたら False |
sorted(a) | 並べ替えた新しいリスト | 選択ソート・挿入ソート(第 17 章) |
enumerate(a) | (番号, 中身) の組を順に出す | 自分で i を用意して増やす |
zip(a, b) | 2 つのリストから同じ位置どうしの組を出す | for i in range(len(a)) で両方を添字で引く |
ten = [72, 95, 61, 88, 40]
print("個数", len(ten))
print("合計", sum(ten))
print("平均", sum(ten) / len(ten))
print("最高", max(ten), " 最低", min(ten))
# any / all は「条件のリスト」に対して使う
gokaku = []
for t in ten:
gokaku.append(t >= 60)
print(gokaku)
print("1 人でも合格した?", any(gokaku))
print("全員合格した? ", all(gokaku))
print("---")
# enumerate:番号と中身を同時に取り出す
for i, t in enumerate(ten):
print(i, "番目は", t, "点")
print("---")
# zip:2 つのリストを同じ位置どうしで組にする
namae = ["あかり", "けんた", "ゆうき", "みさき", "そうた"]
for n, t in zip(namae, ten):
print(n, t)
# zip で辞書を作ることもできる
print(dict(zip(namae, ten)))
for i in range(len(a)): と書いていました.
enumerate を使うと
for i, x in enumerate(a): と書けて,
番号と中身の両方が一度に手に入ります.
a[i] と書かなくてよくなるので,書き間違いも減ります.
zip は,長さの違うリストを渡すと
短いほうに合わせて止まります.
余ったぶんは黙って捨てられ,エラーにはなりません.
2 つのリストの長さがそろっているかは,自分で気をつけてください.
19.4 スライス — 一部を切り出す
a[2] と書けば 1 つだけ取り出せます.
続きの何個かをまとめて取り出したいときは,
a[1:4] のようにコロンを使います.これをスライスといいます.
第 16 章で軽く触れましたが,ここで正面から扱います.
| 書き方 | 意味 |
|---|---|
a[1:4] | 1 番目から,4 番目の手前まで(1・2・3 番目の 3 個) |
a[:3] | 先頭から 3 番目の手前まで.始まりを省くと先頭から |
a[3:] | 3 番目から最後まで.終わりを省くと最後まで |
a[-2:] | うしろから 2 個.マイナスは「うしろから数えて」 |
a[::2] | 1 つおき.3 つめの数は「とびとびの幅」 |
a[::-1] | 逆順.幅がマイナスだと後ろから進む |
a[:] | 全部.コピーを作るときの決まり文句 |
a[1:4] に 4 番目は入りません.
「1 番目から,4 番目の手前まで」です.
range(1, 4) が 1・2・3 だったのと同じ決まりです.
この「手前まで」は Python 中で一貫しているので,
終わりの数は入らないと覚えてしまってください.
a = [10, 20, 30, 40, 50, 60]
print(a[1:4]) # 1 番目から 4 番目の手前まで
print(a[:3]) # 先頭から
print(a[3:]) # 最後まで
print(a[-2:]) # うしろから 2 個
print(a[::2]) # 1 つおき
print(a[::-1]) # 逆順
print(a[:]) # 全部
print("---")
# 文字列にも同じように使える
s = "たーとるぐらふぃっくす"
print(s[0:3])
print(s[-4:])
print(s[::-1])
print("---")
# コピーと「同じものの別名」の違い
b = a[:] # コピー.b を変えても a は変わらない
b[0] = 999
print("a[0] =", a[0], " b[0] =", b[0])
c = a # これは同じものに別の名前を付けただけ
c[0] = 777
print("a[0] =", a[0], " c[0] =", c[0])
b = a と書いても,リストはコピーされません.
同じ 1 つのリストに a と b という
2 つの名札が付くだけです.
だから b を変えると a も変わります.
これは初めのうち必ずはまる落とし穴です.
中身を別に持ちたいときは b = a[:](または b = list(a))と書きます.
sorted() は新しいリストを返し,
sort() はその場で並べ替える」と習いましたが,それと同じ区別です.
1 年ぶんの月別データから,いろいろな切り出し方をします. ____ を埋めてください. 「終わりの数は入らない」ことに気をつけてください.
# スライスで切り分ける
tsuki = ["1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"]
# (1) 前半 6 か月
print("前半:", tsuki[____])
# (2) 後半 6 か月
print("後半:", tsuki[____])
# (3) 4 月から 6 月まで(3 つ)
print("春 :", tsuki[3:____])
# (4) うしろから 3 か月
print("年末:", tsuki[____])
# (5) 1 つおき(1月,3月,5月,…)
print("奇数月:", tsuki[____])
# (6) 逆順
print("逆順:", tsuki[____])
# (7) コピーを作って,1 つだけ書き換える(元は変わらないこと)
copy = tsuki[____]
copy[0] = "睦月"
print(copy[0], tsuki[0])
19.5 リストのリスト — マス目のデータ
リストの中身は,何でも構いません.リストを入れることもできます. リストのリストを作ると,マス目(表)を表せます. 他の言語では二次元配列と呼ばれるものです.
masu = [
[1, 2, 3],
[4, 5, 6],
]
| 書き方 | 意味 |
|---|---|
masu[0] | 1 行目まるごと.[1, 2, 3] |
masu[0][2] | 1 行目の 2 番目.3.行が先,列が後ろ |
len(masu) | 行の数.この例では 2 |
len(masu[0]) | 列の数.この例では 3 |
masu[1][0] = 9 | 2 行目の 0 番目を書き換える |
masu[行][列] の順です.
タートルの座標は (x, y)(横が先)でしたが,
マス目はたてが先になります.
y で行を,x で列を指す,と決めておくと混乱しません.
マス目を全部たどるには,for を二重にします
(第 7 章の入れ子です).
masu = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
print("行の数", len(masu))
print("列の数", len(masu[0]))
print("2 行目 3 列目は", masu[1][2])
# 行ごとに表示する
for gyo in masu:
print(gyo)
print("---")
# 1 マスずつたどる
gokei = 0
for y in range(len(masu)):
for x in range(len(masu[y])):
gokei = gokei + masu[y][x]
print("全部の合計は", gokei)
# 行ごとの合計・列ごとの最大値
for y in range(len(masu)):
print(y, "行目の合計", sum(masu[y]))
19.5.1 マス目を絵にする — ドット絵
マス目のデータは,そのまま絵になります.
1 のところに点を打つだけです.
データを書き換えれば絵が変わる,というのが気持ちのよいところです.
from turtle import *
ht()
speed(0)
# 0 は何も描かない,1 は色を塗る
e = [
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 0],
[1, 1, 0, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1],
[0, 1, 0, 0, 0, 1, 0],
]
print("たて", len(e), "行 / よこ", len(e[0]), "列")
masu = 40
hidari = -(len(e[0]) - 1) * masu / 2
ue = (len(e) - 1) * masu / 2
for y in range(len(e)):
for x in range(len(e[y])):
if e[y][x] == 1:
pu()
goto(hidari + x * masu, ue - y * masu)
pencolor("darkorange")
dot(masu - 4)
done()
0 と 1 を書き換えると絵が変わる.0 と 1 の 2 種類にしましたが,
0・1・2…と増やして,
数と色を対応させた辞書を用意すれば,
何色でも使えます(第 18 章の辞書の出番です).
演習 19-1 でやってみましょう.
マス目のデータをプログラムで作って描きます. たてと横の番号を足して 2 で割った余りで色を決めると,市松模様になります. ____ を埋めてください.
from turtle import *
ht()
speed(0)
n = 8 # 8 行 8 列
# (1) マス目のデータを作る
masu = []
for y in range(n):
gyo = []
for x in range(n):
gyo.append((x + y) % ____) # 0 か 1 が交互に入る
masu.____(gyo) # 1 行ぶんを足す
print(masu[0])
print(masu[1])
# (2) 絵にする
haba = 44
hidari = -(n - 1) * haba / 2
ue = (n - 1) * haba / 2
for y in range(____(masu)):
for x in range(len(masu[y])):
pu()
goto(hidari + x * haba, ue - y * haba)
if masu[y][x] == 0:
pencolor("darkslateblue")
else:
pencolor("lightsteelblue")
dot(haba - 4)
done()
19.5.2 表としてのリストのリスト — 1 行が 1 件
リストのリストは,マス目だけのものではありません. 1 行に 1 件ぶんのデータを並べると,そのまま表になります. 名簿,成績表,商品の一覧 — 世の中のデータの多くはこの形をしています.
このとき,1 行の中には種類の違うものを混ぜてよいのがリストの気楽なところです. 名前は文字列,点数は整数のまま並べられます.
seiseki = [
["あかり", 78, 92, 61],
["けんた", 55, 70, 88],
["ゆうき", 90, 64, 73],
]
| 書き方 | 取り出せるもの |
|---|---|
seiseki[0] | 1 人ぶんまるごと.["あかり", 78, 92, 61] |
seiseki[0][0] | その人の名前 |
seiseki[0][1] | その人の1 科目めの点数 |
seiseki[0][1:] | 名前をとばして点数だけ(19.4 節のスライス) |
len(seiseki) | 人数(行の数) |
行ごとの集計は簡単です.1 行が 1 人ぶんなので,
for gyo in seiseki: と回せばそのまま 1 人ずつになります.
やっかいなのは列ごと(科目ごと)の集計で,
こちらは行を縦に渡り歩くことになるので,列の番号を添字で指す必要があります.
kamoku = ["国語", "数学", "英語"]
seiseki = [
["あかり", 78, 92, 61],
["けんた", 55, 70, 88],
["ゆうき", 90, 64, 73],
]
print(seiseki[0]) # 1 人ぶんまるごと
print(seiseki[0][0]) # 名前
print(seiseki[0][1:]) # 点数だけ
print("---")
# 行ごと(人ごと)の合計と平均
for gyo in seiseki:
namae = gyo[0]
ten = gyo[1:]
print(namae, " 合計", sum(ten), " 平均", sum(ten) / len(ten))
print("---")
# 列ごと(科目ごと)の合計 … 行を縦に渡り歩く
for x in range(len(kamoku)):
gokei = 0
for gyo in seiseki:
gokei = gokei + gyo[1 + x] # 名前のぶん 1 つずらす
print(kamoku[x], "の合計", gokei)
print("---")
# 表の形に並べて表示する
print("名前 国語 数学 英語")
for gyo in seiseki:
for atai in gyo:
print(atai, end=" ") # 改行しないで,つづけて出す
print() # 1 行ぶん終わったら改行する
print() は,ふつう表示のあとに改行します.
print(atai, end=" ") と書くと,
改行のかわりに何を出すかを指定できます.
ここでは全角の空白を出しているので,横に並んでいきます.
最後に print() だけを書くと,そこで改行されます.
表を画面に出すときの定番の書き方です.
マス目のときと同じく,for を二重にすれば 1 マスずつたどれます.
for gyo in seiseki: の中で for atai in gyo: と書けば,
添字をまったく使わずに全部のマスを回れます.
添字が要るのは,「何行目・何列目か」を知りたいときと,
上の科目ごとの合計のように縦に渡り歩くときだけです.
seiseki = [
{"名前": "あかり", "国語": 78, "数学": 92, "英語": 61},
{"名前": "けんた", "国語": 55, "数学": 70, "英語": 88},
]
gyo[2] は短いけれど「2 は数学だったかな」と迷いますし,
列の順番を入れ替えたとたんに壊れます.
gyo["数学"] は長いけれど,読んだ瞬間に意味が分かります.
項目に名前があるなら辞書,
同じ種類の値がただ並ぶだけならリスト.19.1.3 節の決め方のとおりです.
19.6 内包表記 — リストを 1 行で作る
「空のリストを作って,for で回して,append する」.
この 4 行の型は,これまで何度も書いてきました.
Python には,これを1 行で書く書き方があります.
内包表記(ないほうひょうき)といいます.
# これまでの書き方(4 行)
a = []
for x in range(1, 6):
a.append(x * x)
# 内包表記(1 行)
a = [x * x for x in range(1, 6)]
読み方はうしろからです.
「range(1, 6) の x それぞれについて,
x * x を並べたリスト」.
英語の語順のままなので,慣れると英文として読めるようになります.
| 書き方 | 意味 |
|---|---|
[f(x) for x in a] | a の中身それぞれを f で変えたリスト |
[x for x in a if 条件] | 条件に合うものだけを選んだリスト |
[f(x) for x in a if 条件] | 選んでから変える |
{f(x) for x in a} | 角かっこを波かっこにすると集合になる |
# 変える
print([x * x for x in range(1, 11)])
print([x * 2 for x in [3, 1, 4]])
# 選ぶ
print([x for x in range(1, 21) if x % 3 == 0])
ten = [72, 95, 61, 88, 40, 77]
print([t for t in ten if t >= 60]) # 合格者だけ
print(len([t for t in ten if t >= 60]), "人合格")
# 選んでから変える
print([t + 5 for t in ten if t < 60]) # 不合格者だけ 5 点おまけ
# 文字列にも
w = ["ringo", "ichigo", "suika"]
print([s.upper() for s in w])
print([len(s) for s in w])
# 辞書から
tensu = {"あかり": 78, "けんた": 92, "ゆうき": 61}
print([namae for namae in tensu if tensu[namae] >= 70])
# 集合の内包表記
print(sorted({len(s) for s in w}))
[(x, 0) for x in range(-200, 201, 50)] と書けば,
横一列に並んだ座標のリストが 1 行でできます.
for で回して描けば,等間隔に並んだ図形になります.
for で書いたほうが読みやすいです.
短く書くことが目的ではありません.読みやすいことが目的です.
19.7 並べ替え — 好きな基準で並べる
第 17 章で sorted() と sort() を習いました.
おさらいすると,違いは「新しいリストを返すか,その場で並べ替えるか」です.
| 書き方 | 元のリスト | 返すもの | 使いどころ |
|---|---|---|---|
sorted(a) | 変わらない | 新しい並べ替え済みリスト | 元の順番も残しておきたいとき.辞書や集合にも使える |
a.sort() | 並べ替わる | None(無し) | 元の順番はもう要らないとき.リスト専用 |
ここから先が新しい話です.
sorted には省略できる引数が 2 つあり,
これを使うと好きな基準で並べ替えられます.
| 引数 | すること | 例 |
|---|---|---|
reverse=True | 大きい順(逆順)にする | sorted(a, reverse=True) |
key=関数 | 何で比べるかを決める.中身をその関数に通した結果で比べる | sorted(w, key=len)(文字数の順) |
key= には関数の名前を書きます.
かっこは付けません(第 18 章で,辞書に関数そのものを入れたときと同じです).
自分で作った関数も渡せます.
# (名前, 身長) のタプルのリスト
hito = [("あかり", 158), ("けんた", 172), ("ゆうき", 165),
("みさき", 149), ("そうた", 180)]
# そのまま並べると「名前の順」になる(タプルは先頭から比べられる)
print(sorted(hito))
print("---")
# 身長で比べたい → 「組から身長を取り出す関数」を渡す
def shincho(t):
return t[1]
print(sorted(hito, key=shincho))
print(sorted(hito, key=shincho, reverse=True))
print("---")
# 文字数の順に並べる
w = ["ringo", "ichigo", "suika", "momo"]
print(sorted(w, key=len))
# 辞書を「値の大きい順」に並べる
ten = {"あかり": 78, "けんた": 92, "ゆうき": 61}
def atai(kv):
return kv[1]
print(sorted(ten.items(), key=atai, reverse=True))
sorted(ten.items(), key=lambda kv: kv[1]) という書き方を見ることがあります.
lambda(ラムダ)はその場かぎりの小さな関数を書く書き方で,
上の def atai(kv): return kv[1] と同じ意味です.
この教材では使いませんが,見たときに読めれば十分です.
19.7.1 並べ替えてから描く
並べ替えは,絵にする直前にやると効きます. データの並び順をいじらずに,見せ方だけ変えられるからです.
from turtle import *
ht()
speed(0)
pensize(2)
hito = [("あかり", 158), ("けんた", 172), ("ゆうき", 165),
("みさき", 149), ("そうた", 180)]
def shincho(t):
return t[1]
narabi = sorted(hito, key=shincho, reverse=True)
print(narabi)
print("いちばん高い人は", narabi[0][0])
x = -245
for namae, h in narabi:
pu()
goto(x, -150)
seth(90)
pd()
pencolor("dimgray")
fillcolor("steelblue")
begin_fill()
fd((h - 140) * 3)
rt(90)
fd(64)
rt(90)
fd((h - 140) * 3)
rt(90)
fd(64)
rt(90)
end_fill()
pu()
goto(x + 4, -178)
pencolor("black")
write(namae, font=("Arial", 13, "normal"))
goto(x + 10, -142 + (h - 140) * 3)
write(h, font=("Arial", 14, "normal"))
x = x + 100
done()
key= で身長の高い順に並べ替えてから描いたもの.(名前, 点数) のタプルのリストを点数の高い順に並べ替え, 順位と点数を表示します. ____ を埋めてください. 合格者だけを選ぶところは,内包表記を使います.
from turtle import *
ht()
speed(0)
seiseki = [("あかり", 78), ("けんた", 92), ("ゆうき", 61),
("みさき", 55), ("そうた", 88)]
# (1) 点数を取り出す関数
def tensu(t):
return t[____]
# (2) 点数の高い順に並べ替える
narabi = sorted(seiseki, key=____, reverse=____)
print(narabi)
# (3) 合格者(60 点以上)だけを内包表記で選ぶ
gokaku = [t for t in seiseki if t[1] >= ____]
print(len(gokaku), "人合格")
# (4) 順位をつけて描く(番号は enumerate で取る)
y = 150
for jun, kumi in ____(narabi):
namae = kumi[0]
ten = kumi[1]
pu()
goto(-240, y)
if ten >= 60:
pencolor("navy")
else:
pencolor("crimson")
write(str(jun + 1) + " 位 " + namae + " " + str(ten) + " 点",
font=("Arial", 20, "normal"))
y = y - 60
done()
19.8 in の速さは入れ物で違う
最後に,知っているかどうかで差が出る話をします.
x in a という同じ書き方でも,
a が何かによって速さがまるで違います.
| 入れ物 | どうやって探しているか | データが 100 倍になると |
|---|---|---|
| リスト | 先頭から1 つずつ順に比べる.第 17 章で自分で書いた線形探索そのもの | 時間も約 100 倍 |
| タプル | リストと同じ.先頭から順に比べる | 時間も約 100 倍 |
| 辞書 | 値から置き場所を計算して,そこを直接見る | ほとんど変わらない |
| 集合 | 辞書と同じ仕組み.そこを直接見る | ほとんど変わらない |
たとえ話にするとこうです. リストは本を 1 冊ずつ手に取って表紙を見るやり方. 辞書と集合は背表紙の番号から棚の場所がわかっていて,そこへ一直線に行くやり方です. 本が 10 冊なら大差ありませんが,10 万冊になると決定的に違います.
本当に違うのか,測ってみましょう. 5 万個のデータを持つリストと集合に対して,「入っていない値」を 2000 回探します.
# リストと集合で,in の速さをくらべる
import time
n = 50000
nakami_list = list(range(n)) # 0 から 49999 までのリスト
nakami_set = set(nakami_list) # 同じ中身の集合
kaisu = 2000
sagasu = -1 # わざと「入っていない」値を探す
t0 = time.time()
for i in range(kaisu):
x = sagasu in nakami_list
t1 = time.time()
for i in range(kaisu):
x = sagasu in nakami_set
t2 = time.time()
print("リストで", kaisu, "回調べる:", round((t1 - t0) * 1000), "ミリ秒")
print("集合で ", kaisu, "回調べる:", round((t2 - t1) * 1000), "ミリ秒")
何百倍という差が出たはずです(数字はパソコンによって変わります). リストのほうは,入っていない値を探すたびに5 万個すべてと比べているからです.
in で調べる」
という場面になったときに,
「集合に入れ直せば速くなるかもしれない」と思い出せればじゅうぶんです.
print(hash("ねこ")) で覗けます.
タプルが辞書のキーにできてリストができないのは,この計算のためです.
中身が変わってしまうと住所も変わってしまうので,
変えられないタプルだけがキーになれるのです.
くわしくは 2 年次の「データ構造とアルゴリズム」で習います.
19.9 この章で出てきた命令
| 書き方 | 意味 |
|---|---|
(1, 2) | タプル.あとから変えられない組 |
x, y = t | タプルの中身を 2 つの変数に取り出す |
{1, 2, 3} | 集合.重複が消え,順番は無い |
set() | 空の集合({} は空の辞書) |
set(a) | リスト a から重複を消した集合を作る |
list(t) / tuple(a) | 入れ物どうしを作り直す |
s.add(x) / s.discard(x) | 集合に入れる/取り除く |
a.union(b) / a | b | 和集合.どちらかにあるもの |
a.intersection(b) / a & b | 積集合.両方にあるもの |
a.difference(b) / a - b | 差集合.a にだけあるもの |
a.append(x) / a.insert(i, x) | 足す/割り込ませる |
a.pop() / a.pop(i) | 番号で取り出して取り除く |
a.remove(x) | 値で探して取り除く |
a.extend(b) | リスト b の中身をまとめて足す |
a.index(x) / a.count(x) | 何番目か/いくつあるか |
d.keys() / d.values() / d.items() | キー/値/組の一覧 |
d.get(k, x) | 値.無ければ x |
len(a) / sum(a) / max(a) / min(a) | 個数/合計/最大/最小 |
any(a) / all(a) | 1 つでも成り立つ/全部成り立つ |
enumerate(a) | (番号, 中身) の組を順に出す |
zip(a, b) | 2 つのリストから同じ位置どうしの組を出す |
a[1:4] | 1 番目から 4 番目の手前まで(スライス) |
a[:3] / a[3:] / a[-2:] | 先頭から/最後まで/うしろから |
a[::2] / a[::-1] | 1 つおき/逆順 |
b = a[:] | リストのコピーを作る(b = a はコピーではない) |
masu[y][x] | リストのリスト.行が先,列が後ろ |
hyo[i][0] / hyo[i][1:] | 表としてのリストのリスト.1 件ぶんの 1 つめ/2 つめから先 |
print(x, end=" ") | 改行しないで続けて表示する |
[f(x) for x in a] | 内包表記.変えたものを並べたリスト |
[x for x in a if 条件] | 内包表記.条件に合うものだけ |
sorted(a, key=f) | f に通した結果で比べて並べ替える |
sorted(a, reverse=True) | 大きい順に並べ替える |
dir(もの) / help(命令) | できることの一覧/説明 |
type(もの) | そのものが何の仲間か |
演習
出発点のプログラムを「▶ この演習を開く」で右側に読み込み, それを発展させて作品にしてください. できあがったら「⤓ 保存」でファイルに保存して,手元に残しておきましょう.
help(…) や dir(…) を実行して確かめられます.
調べながら書くという動きに慣れてください.
リストのリストで自分のドット絵を作ります. 出発点には,白黒 2 色のマス目が入っています.
- マス目の中身を書き換えて,自分のドット絵にする(顔,文字,記号,キャラクターなど何でもよい).
- 色を3 色以上に増やす.
0・1・2… と数を増やし,数と色を対応させた辞書を用意する. - マス目の大きさ
masuを変えても,絵が画面の中央に来るようにする(出発点のhidari・ueの計算が参考になります). - 余裕があれば,
dotではなく四角い塗りつぶし(begin_fill/end_fill)にして,すきまのない絵にする.
クラスのアンケートを集計して,結果を絵にします. 集合・辞書・並べ替えを全部使う演習です.
- 何種類の答えがあったかを集合で求めて表示する.
- 答えごとの人数を辞書で数える.
- 人数の多い順に
key=で並べ替え,enumerateで順位を付けてコンソールに表示する. - 結果を棒グラフか円の大きさでタートルに描かせる.答えの名前と人数も
writeで書き入れること. - 余裕があれば,2 票以上のものだけを内包表記で選んで描く.
key= に渡します(19.7 節).
棒の描き方は 19.7.1 節の例がそのまま使えます.
長く書いたプログラムを,今回習った書き方で短くする演習です. 出発点には,わざと長く書いたプログラムが入っています.
#の下に,スライスや内包表記を使った短い版を書く.- 結果が同じになることを
printで確かめる(print(a == b)と書けばTrueが出ます). - 最後に,何行から何行に減ったかをコメントで書く.
- 余裕があれば,
enumerateやzipを使って書き直せるところも探す.
(名前, 点数, 身長) のタプルのリストを, いろいろな基準で並べ替えて絵にします.
- 点数の高い順に並べ替えて棒グラフにする.
- 名前の順(あいうえお順)に並べ替えたものも描く.並べ替える基準を変えるだけで済むことを確かめる.
- 60 点未満の棒だけ色を変える.
enumerateで順位を,sum・max・minで平均・最高・最低を画面に書き入れる.- 余裕があれば,身長の順にも並べ替えられるようにして,どの基準で並べるかを
input()で選べるようにする.
def tensu(t): return t[1] と
def shincho(t): return t[2] の 2 つを用意して,
key= に渡し分けてください.
動きは変えずに,書き方だけをよくすることを リファクタリングといいます. プロの現場でも日常的におこなわれている作業で, 新しい機能を足す前に,まず読みやすく整えるのがふつうです. 今回はそれを自分のプログラムでやってみます.
出発点には,「書き直す前」のプログラムが入っています. 同じことを何度も書き,変数を 5 個も並べた,いかにも長いプログラムです. 動かすと,星が 5 つ描かれ,点数の合計・平均・最高が表示されます.
- まず動かして,どんな結果になるかを覚える.書き直したあとで,同じ結果になることを確かめるためです.
- 星の部分をリストと
forで書き直す.座標・色・大きさをタプルのリストにまとめると,1 つのforで済みます. - 点数の部分をリストにして,
sum・max・lenで書き直す. - 名前のリストと点数のリストを
zipで組にし,sorted(…, key=…, reverse=True)で高い順に並べて,enumerateで順位を付けて表示する. - 合格者(60 点以上)の名前を,内包表記で取り出して表示する.
- 何行から何行に減ったか数えて,コメントに書く.空行とコメントを除いた行数で数えてください.
自分でデータを決めて,それを絵にしてください.題材は自由です.
- 1 週間の勉強時間,好きな音楽の再生回数,科目ごとの点数,日ごとの気温,ゲームのスコア,都道府県の面積……何でもかまいません
- リスト・タプル・辞書・集合のうち 3 つ以上を使うこと
- 並べ替え(
sorted)を必ず使うこと - スライス・内包表記・リストのリストのうち,少なくとも 1 つを使うこと
- 数だけでなく,名前や単位も画面に書くこと(
write).絵だけでは読み手に伝わりません - 先頭にコメントで,題名・氏名・日付・何を表した絵かを書くこと
まとめ
- 入れ物は 4 つ.リスト(順番がある・増やせる)・タプル(変えられない・キーになれる)・辞書(名前で引く)・集合(重複が消えて順番が無い).
- 他の言語の配列は「大きさが決まっているリスト」.Python のリストには,その不便さが無い.
- 空の集合は
set().{}は空の辞書. len・sum・max・min・any・all・enumerate・zipは,入れ物の種類を選ばずに使える.- スライス
a[1:4]は「4 番目の手前まで」.a[::2]は 1 つおき,a[::-1]は逆順,a[:]はコピー.b = aはコピーではない. - リストのリストでマス目を表せる.
masu[行][列]の順.二重のforでたどり,そのまま絵にできる. - 内包表記
[f(x) for x in a]で 4 行が 1 行になる.ただし短ければよいわけではない. sorted(a, key=関数, reverse=True)で,好きな基準で並べ替えられる.inは,リストでは端から順に探し,辞書と集合では直接見る.データが多いときに大きく差がつく.
次の回は最終回です.ここまでに拾いきれなかった Python の文法—
break・continue,and・or・
not,文字列の扱いと f-string,
try による入力の失敗への備え—を,総まとめとして見わたします.
そして,この教材では扱わなかったことと,
その先に何があるのかもお話しします.