Python AtCoder入門 第6講 タプルと内包表記 第5講でリストを一通り扱えるようになりました。 この講では、リストの兄弟にあたる タプル と、リストを短く作る 内包表記 を学びます。 1
今回のテーマ タプルと内包表記は、どちらも データをまとめて扱うための道具 です。 この先の講で繰り返し必要になる前提知識です。 2
この講のゴール この講では、次を身につけます。 タプルの作り方 アンパック タプルが必要になる場面 リスト内包表記 入力処理での内包表記 ジェネレータ式 3
コードファイル名の方針 この講でも、コード例ごとにファイル名を付けます。 tuple_basic.py points_set.py list_comprehension.py grid_init.py generator_sum.py answer_6_1.py 4
6-1 タプルの基本 タプルは、丸括弧 ( ) で作ります。 t = (3, 7) print(t) print(t[0]) print(t[1]) リストと同じように、インデックスでアクセスできます。 5
tuple_basic_access.py t = (3, 7) print(t) print(t[0]) print(t[1]) 出力: (3, 7) 3 7 6
リストと同じように使える部分 タプルでも、次の操作はリストと同じです。 インデックスでアクセス len() で長さを取る for で順に取り出す t = (3, 7) print(len(t)) 7
括弧は省略できる 実は、タプルの括弧は省略できます。 t = 3, 7 これもタプルです。 カンマがあればタプルになる と覚えてください。 8
one_tuple.py 要素が1つのタプルを作るときは、末尾のカンマが必要です。 a = (3) b = (3,) print(type(a)) print(type(b)) 出力: <class 'int'> <class 'tuple'> 9
tuple変換 tuple() を使うと、リストなどをタプルに変換できます。 A = [3, 1, 4] t = tuple(A) print(t) 出力: (3, 1, 4) 10
list変換 逆に、 list() を使えばタプルからリストに戻せます。 t = (3, 1, 4) A = list(t) print(A) 出力: [3, 1, 4] 11
入力をタプルで受け取る 入力をそのままタプルにすることもできます。 A = tuple(map(int, input().split())) 第1講で覚えた、 list(map(int, input().split())) の list を tuple に変えただけです。 12
listとtupleの使い分け 判断は単純です。 あとで書き換えるなら list 書き換えず、1件分のデータとして扱うなら tuple 通常の数列は list 。 座標やレコードは tuple が便利です。 13
N行のデータをタプルで保存する # Store each line as one immutable record N = int(input()) data = [tuple(map(int, input().split())) for _ in range(N)] N行ぶんのデータが、 タプルのリスト になります。 14
タプルは変更できない リストは変更できます。 A = [3, 7] A[0] = 100 しかし、タプルは変更できません。 t = (3, 7) t[0] = 100 これは TypeError になります。 15
immutable.py A = [3, 7] A[0] = 100 print(A) t = (3, 7) t[0] = 100 タプルは作ったあとで中身を書き換えられません。 この性質を イミュータブル と呼びます。 16
アンパック代入 タプルの中身は、変数を並べて一度に取り出せます。 t = (3, 7) a, b = t print(a) print(b) これを アンパック と言います。 17
unpack.py t = (3, 7) a, b = t print(a) print(b) 出力: 3 7 左辺の変数の個数と、タプルの要素数が一致している必要があります。 18
変数の値を交換する アンパックを使うと、2つの変数の値を1行で交換できます。 a = 1 b = 2 a, b = b, a print(a, b) 出力: 2 1 19
実は最初から使っていた 第1講で使ったこの書き方もアンパックです。 A, B = map(int, input().split()) 右辺から2つの値が出てきて、 A と B に順番に入ります。 20
enumerateもアンパック 第4講のこの書き方も同じです。 for i, x in enumerate(A): print(i, x) が、インデックスと中身の組を返します。 それを i と x にアンパックしています。 enumerate(A) 21
tuple_basic.py # Create a tuple t = (3, 7) print(t[0], t[1], len(t)) # Parentheses can be omitted u = 10, 20 print(u) # Unpacking a, b = u print(a, b) # Swapping two values a, b = b, a print(a, b) 22
tuple_basic.py の出力 3 7 2 (10, 20) 10 20 20 10 タプル、括弧の省略、アンパック、交換を確認できます。 23
関数から複数の値を返す タプルのおかげで、関数から複数の値をまとめて返せます。 def divide(a, b): return a // b, a % b return の右側にカンマがあるので、タプルを返します。 24
return_tuple.py # Return the quotient and the remainder at once def divide(a, b): return a // b, a % b # The returned tuple is unpacked into two variables q, r = divide(17, 5) print(q, r) 出力: 3 2 25
6-2 タプルが必要になる場面 リストがあるのに、なぜタプルが必要なのでしょうか。 大きな理由は2つです。 1. 辞書のキーや集合の要素にできる 2. 1件分のデータをひとまとめにできる 26
① 集合の要素にできる 第10講で学ぶ set には、 中身が変わらないもの しか入れられません。 リストは変更できるので入れられません。 タプルは変更できないので入れられます。 27
set_tuple.py s = set() s.add((1, 2)) s.add([1, 2]) # OK # TypeError タプルは集合に入れられます。 リストは集合に入れられません。 28
座標の管理 この性質が効くのが、座標の管理です。 (x, y) のような座標をタプルにして、集合に入れます。 重複した座標を1つとして扱えます。 29
points_set.py # Read the number of points N = int(input()) # Store each point as a tuple in a set points = set() for _ in range(N): x, y = map(int, input().split()) points.add((x, y)) # Duplicated points are counted only once print(len(points)) 30
points_set.py の実行例 入力例: 5 1 2 3 4 1 2 5 6 3 4 出力例: 3 重複を除くと、3種類の点です。 31
tupleで直接追加する アンパックを経由せず、直接タプルにして追加できます。 for _ in range(N): points.add(tuple(map(int, input().split()))) 読み取った1行を、そのまま1つの座標として集合に入れます。 32
② 1件分のデータをまとめる 別々の値でも、1組で意味を持つデータがあります。 例: 名前と点数 日付と気温 x座標とy座標 このようなものは、タプルにまとめると扱いやすくなります。 33
レコードとしてのタプル data = [("Sato", 80), ("Suzuki", 45), ("Takahashi", 72)] この形にしておけば、並べ替えても名前と点数がずれません。 第13講のソートで特に便利です。 34
student_records.py # Read the number of students N = int(input()) # Store each student as a (name, score) tuple data = [] for _ in range(N): name, score = input().split() data.append((name, int(score))) # Unpack each tuple in the loop for name, score in data: if score >= 60: print(name, score) 35
student_records.py の実行例 入力例: 3 Sato 80 Suzuki 45 Takahashi 72 出力例: Sato 80 Takahashi 72 点数だけ int() で変換しています。 36
リストとタプルの比較 リスト タプル 中身の変更 できる できない 辞書のキー・集合の要素 使えない 使える 主な用途 同じ種類のデータの並び 1件分のデータの組 37
使い分けの目安 競技プログラミングでは、だいたい次の使い分けです。 N個の数列 → リスト 座標 → タプル 名前と点数のような1件分のデータ → タプル あとで書き換えるならリストです。 38
6-3 リスト内包表記 ここから話題が変わります。 リストを短く作るための書き方 を学びます。 39
appendでリストを作る 第5講では、次のように書きました。 A = [] for i in range(5): A.append(i * i) これは、二乗のリストを作るコードです。 40
内包表記で1行にする 同じことを1行で書けます。 A = [i * i for i in range(5)] これが リスト内包表記 です。 41
内包表記の基本形 [ 式 for 変数 in 範囲 ] 読むときは、後ろから読むのがコツです。 range(5) の各 i について、i * i を並べたリスト です。 42
list_comprehension_basic.py A = [i * i for i in range(5)] print(*A) 出力: 0 1 4 9 16 43
条件付き内包表記 末尾に if を付けると、条件を満たすものだけを集められます。 [式 for 変数 in 範囲 if 条件] 条件で絞り込んでから、リストにします。 44
even_list.py A = [3, 1, 4, 1, 5, 9, 2, 6] even = [x for x in A if x % 2 == 0] print(*even) 出力: 4 2 6 偶数だけを取り出しています。 45
list_comprehension.py # Squares of 0 to 4 squares = [i * i for i in range(5)] print(*squares) # Extract even numbers A = [3, 1, 4, 1, 5, 9, 2, 6] even = [x for x in A if x % 2 == 0] print(*even) # Combine both doubled_even = [x * 2 for x in A if x % 2 == 0] print(*doubled_even) 46
list_comprehension.py の出力 0 1 4 9 16 4 2 6 8 4 12 3つ目は、 偶数だけを2倍したリスト です。 47
for文との対応 result = [] for x in A: if 条件: result.append(式) これは、内包表記でこう書けます。 result = [式 for x in A if 条件] 48
comprehension_compare.py A = [3, 1, 4, 1, 5, 9, 2, 6] # Using a for loop result1 = [] for x in A: if x >= 4: result1.append(x * 10) # Using a list comprehension result2 = [x * 10 for x in A if x >= 4] print(*result1) print(*result2) 49
comprehension_compare.py の出力 40 50 90 60 40 50 90 60 同じ結果です。 読みにくいと感じるうちは、 for 文で書いても構いません。 50
6-4 内包表記と入力処理 内包表記が最も威力を発揮するのが、 入力処理 です。 特に、N行入力を受け取るときに便利です。 51
N行の入力を1行で受け取る A = [int(input()) for _ in range(N)] これは、 N行読み取って整数のリストにする という意味です。 52
for文で書くと A = [] for _ in range(N): A.append(int(input())) これと同じことを、内包表記なら1行で書けます。 A = [int(input()) for _ in range(N)] 53
input_lines_comprehension.py # Read N and then N integers, one per line N = int(input()) A = [int(input()) for _ in range(N)] print(sum(A), max(A)) 入力例: 4 10 25 18 7 出力例: 60 25 54
複数値のN行入力 N行にわたって「名前 点数」のような値が並ぶ場合も同じです。 data = [input().split() for _ in range(N)] 各行がリストになり、それがN行ぶん集まります。 55
2次元リストの初期化 もう1つの重要な用途が、 2次元リスト の初期化です。 H行W列のマス目を、すべて0で埋めたい場合を考えます。 56
正しい2次元リスト grid = [[0] * W for _ in range(H)] これは、 長さWのリストを、H個作る という意味です。 57
危険な書き方 これはやってはいけません。 grid = [[0] * W] * H 一見同じに見えますが、壊れます。 H個の行が、すべて同じ1つのリストを指してしまいます。 58
grid_init.py H = 3 W = 4 # Create an H x W grid filled with zeros grid = [[0] * W for _ in range(H)] # Modify one cell grid[1][2] = 9 # Print row by row for row in grid: print(*row) 59
grid_init.py の出力 0 0 0 0 0 0 9 0 0 0 0 0 書き換えた1か所だけが変わっています。 60
6-5 その他の内包表記 角括弧を波括弧に変えると、集合や辞書も作れます。 ここでは、形だけ知っておけば十分です。 集合と辞書は第10講で扱います。 61
集合内包表記 A = [3, 1, 4, 1, 5] s = {x for x in A} print(s) 重複が自動的に消えます。 62
辞書内包表記 A = [3, 1, 4, 1, 5] d = {x: x * x for x in A} print(d) キーと値を : で結びます。 63
set_dict_comprehension.py A = [3, 1, 4, 1, 5] # Set comprehension removes duplicates s = {x for x in A} print(len(s)) # Dictionary comprehension maps each value to its square d = {x: x * x for x in A} print(d[4]) 出力: 4 16 64
ジェネレータ式 のような関数に内包表記を渡すとき、 角括弧を省略できます。 sum() sum(x for x in A if x % 2 == 0) これをジェネレータ式と呼びます。 65
リストを作る場合 A = [3, 1, 4, 1, 5, 9, 2, 6] print(sum([x for x in A if x % 2 == 0])) これは、偶数だけのリストを作ってから合計しています。 66
リストを作らない場合 A = [3, 1, 4, 1, 5, 9, 2, 6] print(sum(x for x in A if x % 2 == 0)) 途中のリストを実際には作りません。 1つずつ値を生み出して足していきます。 67
generator_sum.py # Read N and the list of N integers N = int(input()) A = list(map(int, input().split())) # Sum of even numbers print(sum(x for x in A if x % 2 == 0)) # Count of even numbers print(sum(1 for x in A if x % 2 == 0)) 68
generator_sum.py の実行例 入力例: 6 3 1 4 1 5 6 出力例: 10 2 偶数の合計は 10 、個数は 2 です。 69
sum(1 for ...) sum(1 for x in A if x % 2 == 0) これは、条件を満たすたびに 1 を足す書き方です。 つまり、 条件を満たす個数を数える ことになります。 70
内包表記を使いすぎない 内包表記は強力ですが、詰め込みすぎると読みにくくなります。 result = [x * y for x in A for y in B if x % 2 == 0 and y > 3] このくらい複雑なら、 for 文に戻したほうが安全です。 71
for文で書くと result = [] for x in A: for y in B: if x % 2 == 0 and y > 3: result.append(x * y) このほうが読みやすいなら、無理に内包表記にしなくてよいです。 72
目安 内包表記は、読みやすくなるときに使います。 目安として、 forが2つ以上入るなら、for文に戻す ことをおすすめします。 短さよりも、5秒で意味がわかることを優先しましょう。 73
章末まとめ:タプル タプルは (3, 7) で作る 括弧は省略できる カンマがあればタプル インデックス、 len 、 for はリストと同じ タプルは変更できない 74
章末まとめ:アンパック タプルは、アンパックできます。 a, b = t 値の交換もできます。 a, b = b, a A, B = map(...) もアンパックです。 75
章末まとめ:タプルの使い所 タプルは変更できないので、 辞書のキー 集合の要素 として使えます。 また、座標や1件分のデータをまとめるのに向いています。 76
章末まとめ:内包表記 リスト内包表記は、 [式 for 変数 in 範囲] です。 条件で絞るなら、 [式 for 変数 in 範囲 if 条件] です。 77
章末まとめ:入力処理 N行入力は、内包表記で短く書けます。 A = [int(input()) for _ in range(N)] 2次元リストは、次の形で作ります。 grid = [[0] * W for _ in range(H)] 78
章末まとめ:ジェネレータ式 sum() などに渡すときは、角括弧を省略できます。 sum(x for x in A if 条件) 個数を数えるなら、 sum(1 for x in A if 条件) です。 79
練習問題 6-1 二乗して出力 個の整数 A_1, A_2, ..., A_N が与えられます。 それぞれを2乗した値を、空白区切りで1行に出力してください。 N 80
練習問題 6-1:入力と出力 入力: N A_1 A_2 ... A_N 入力例: 5 3 1 4 1 5 出力例: 9 1 16 1 25 81
answer_6_1.py # Read N and the list of N integers N = int(input()) A = list(map(int, input().split())) # Square every element B = [x * x for x in A] print(*B) 内包表記で新しいリストを作ります。 82
answer_6_1_direct.py # Read N and the list of N integers N = int(input()) A = list(map(int, input().split())) print(*[x * x for x in A]) リストを変数に入れず、直接出力することもできます。 83
練習問題 6-2 合格者の名前 人の生徒の名前と点数が、1行に1人ずつ与えられます。 点数が60以上の生徒の名前を、入力された順に1行ずつ出力してください。 該当者がいない場合は、何も出力しません。 N 84
練習問題 6-2:入力例 入力例: 4 Sato 80 Suzuki 45 Takahashi 72 Tanaka 60 出力例: Sato Takahashi Tanaka 60 ちょうども含みます。 85
answer_6_2.py # Read the number of students N = int(input()) # Read each line and print the name if the score is high enough for _ in range(N): name, score = input().split() if int(score) >= 60: print(name) 点数だけ int() で変換します。 86
answer_6_2_data.py # Read the number of students N = int(input()) # Store each student as a (name, score) tuple data = [input().split() for _ in range(N)] for name, score in data: if int(score) >= 60: print(name) いったんデータとして保存してから処理する書き方です。 87
練習問題 6-3 偶数の合計と個数 個の整数 A_1, A_2, ..., A_N が与えられます。 このうち偶数であるものについて、 合計 と 個数 を、この順に空白区切りで1行に出力してください。 N 88
練習問題 6-3:入力と出力 入力例: 6 3 1 4 1 5 6 出力例: 10 2 偶数が1つもない場合は、 0 0 と出力します。 89
answer_6_3.py # Read N and the list of N integers N = int(input()) A = list(map(int, input().split())) # Sum and count of even numbers print(sum(x for x in A if x % 2 == 0), sum(1 for x in A if x % 2 == 0)) ジェネレータ式で合計と個数を求めます。 90
answer_6_3_even_list.py # Read N and the list of N integers N = int(input()) A = list(map(int, input().split())) # Extract even numbers first even = [x for x in A if x % 2 == 0] print(sum(even), len(even)) 同じ条件を2回書くのが気になる場合はこちらが読みやすいです。 91
第6講まとめ この講では、 タプルと内包表記 を学びました。 タプルは1件分のデータをまとめる道具。 内包表記はリストを短く作る道具です。 92
次回予告 次の第7講では、 文字列 を扱います。 第5講のスライスや、この講の内包表記が、そのまま文字列にも使えることがわかります。 93