4. リストと繰り返し¶
プログラミングの鍵となるリストと繰り返しについて説明します。
4.1. リストとは¶
プログラミングでは、複数の値をひとまとめのデータとして扱いたいケースが多くあります。
もしデータをひとつひとつ変数で識別すると:
a1 = 1
a2 = 2
a3 = 3
print(a1, a2, a3) #3つが限界だな..
リストは、複数個の値をひとまとまりとして扱うときの順序をもったデータ表現とする方法です。 リストを使えば、値にひとつずつ変数名をつける代わりに数列\(a_i\)のように、 リストの\(i\)番目の値として扱えます。
リストを用いて値をまとめて扱う場合:
a = [1, 2, 3] # 数値は並べてまとめる
print(a[0], a[1], a[2])
リストを使えば、値が100個に増えても、10,000個に増えても問題ありません。 リストは、アルゴリズムや来年以降、データサイエンスを学ぶときの基礎になります。 しっかり、基礎をマスターしていきましょう。
4.1.1. インデックス(添字)¶
リストは、数列\(a_i, ..., a_j\)に由来し、難しい概念ではありません。添字のことをインデックス(index)と呼びます。
注意すべき点
\(a_i\)は、
a[i]
と書き、0番目から数える要素には、数値以外の任意の値(文字列、論理値、さらにリストなど)が入る
数列(数学) |
リスト(Python) |
意味 |
---|---|---|
\(\|a\|\) |
|
個数 |
\(a_1\) |
|
先頭の値 |
\(a_2\) |
|
|
\(a_i\) |
|
i番目の値 |
\(a_{N-1}\) |
|
|
\(a_{N}\) |
|
末尾の値 |
\(N\)個数える
プログラミングでは、原則、「0から\(N-1\)まで」のように0から数えます。
例題(インデックス)
次のコードはエラーがあります。 リストa
の先頭と末尾の値の合計を求められるように 正しく修正してください。
a = [1, 2, 3, 5, 8]
a[1] + a[5]
まず、どんなエラーが発生する様子を確認しておきましょう。
[1]:
a = [1, 2, 3, 5, 8]
a[1] + a[5]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-1-b68a78bc2d4d> in <module>
1 a = [1, 2, 3, 5, 8]
----> 2 a[1] + a[5]
IndexError: list index out of range
IndexError: list index out of range
リストの要素数より大きなインデックスにをアクセスしたときに発生します。
インデックスは、0から数えますので、先頭はa[0]
、末尾はa[4]
になります。
[2]:
a = [1, 2, 3, 5, 8]
a[0] + a[4]
[2]:
9
Let’s try
リストの大きさが増減しても常に末尾から値が取れるように、 リストの要素数から末尾のインデックスを計算してみよう。
a = [1, 2, 3, 5, 8, 13]
[3]:
a = [1, 2, 3, 5, 8, 13]
a[0] + a[len(a)-1]
[3]:
14
Python 独自の記法として、a[-1]
のようにマイナスのインデックスを指定すると、後ろから数えてくれます。(ただし、このような記法はPythonだけの独自の記法なので、他のプログラミング言語では使えません。)
[4]:
a = [1, 2, 3, 5, 8, 13, 21]
a[0] + a[-1]
[4]:
22
4.1.2. リストの作り方¶
リストは、色々な方法で作ることができます。
基本:外延的記法¶
``[ ]`` で囲んで列挙する
[5]:
a = [1, 2, 3, 4]
a
[5]:
[1, 2, 3, 4]
リストのリストも定義できます
[6]:
b = [
[1, 0, 1],
[0, 1, 0],
[0, 0, 1],
]
b
[6]:
[[1, 0, 1], [0, 1, 0], [0, 0, 1]]
空のリストを作って、要素を追加する
[7]:
c = []
c.append(1)
c.append(9)
c
[7]:
[1, 9]
同じ要素の N個のリスト
[8]:
d = [0] * 10
d
[8]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
4.1.3. リストの変更(とタプル)¶
リストは、代入演算子を用いて、値を変更することができます。
[9]:
a = [1, 2, 3]
a[0] = 9
print(a)
[9, 2, 3]
リストによく似たデータ構造にタプル(組)があります。タプルは、( )
で値の列を囲んで作ります。
リストとタプルの違いは、(よく質問されますが)要素の値を変更できるかどうかです。
リスト: 変更可能
タプル: 変更不可能
タプル
要素の値を変更できない分、メモリ利用効率がよい
なお、タプルを変更しようとすると型エラー(TypeError)になります。
[10]:
a = (1, 2, 3)
a[0] = 9 # 変更しようとするとエラー
print(a)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-11b86ec22fde> in <module>
1 a = (1, 2, 3)
----> 2 a[0] = 9 # 変更しようとするとエラー
3 print(a)
TypeError: 'tuple' object does not support item assignment
4.1.4. リストと変数展開¶
例題(リスト)
空白区切りで与えられる2つの整数を入力から読み、 その和、差、積、商からなる新たなリストを出力せよ。
入力例:
21 17
出力例:
[38, 4, 351, 1]
文字通りにプログラムを書くと:
[11]:
# a = list(map(int, input().split()))
a = [21, 17]
[a[0] + a[1], a[0] - a[1], a[0] * a[1], a[0] // a[1]]
[11]:
[38, 4, 357, 1]
これは少し読みにくいですよね。 頻繁に使うリストの要素は、変数に展開した方が間違いが減ります。
[12]:
x = a[0]
y = a[1]
[x+y, x-y, x*y, x//y]
[12]:
[38, 4, 357, 1]
あ、いつも何気なく入力処理で使っている書き方をしても構いません。
x, y = list(map(int, input().split()))
[x+y, x-y, x*y, x//y]
デストラクタ代入(変数への展開)
要素の数がn個のとき、n個の変数に分割して代入する記法
x, y = [1, 2] # 2個のとき
x, y, z = [1, 2, 3] #3個のとき
ちなみに、リストを引数に展開するときは、*
をつけます。
[13]:
a = [1, 2, 3, 4]
print(a)
print(a[0], a[1], a[2], a[3])
print(*a)
[1, 2, 3, 4]
1 2 3 4
1 2 3 4
4.1.5. list()
関数¶
関数list()
は、リストではないけど、 値が並んでいる列(シーケンス)からリストに変換する関数です。
数列(連番)リスト
[14]:
list(range(1, 10)) # 1から10未満までの数列
[14]:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
range()
は、等差数列を生成する便利な関数です。
Python |
説明 |
---|---|
|
\(0\)から\(N-1\)までの数列 |
|
\(1\)から\(N\)までの数列 |
|
\(0\)から\(N-1\)までの\(2\)間隔ごとの数列 |
|
\(N\)から\(1\)までの数列(逆順) |
ポイント: リストもrange(N)
も0から数えます。
文字列
[15]:
list("abcdefg") # 文字列を分解した文字リスト
[15]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']
[16]:
input = lambda : "1 2 3"
list(map(int, input().split())) # おなじみのmap関数からlistに変換
[16]:
[1, 2, 3]
4.1.6. リスト内包記法★¶
リストの定義は、数学の集合記法に由来しています。
リストは、集合と異なり、順序が存在し重複が認められるので、 集合とは区別して{ }
でなく[ ]
で囲みます。
外延定義:
\(A = \{ 1, 2, 3, 5, 8, 13, 21, 34, 55 \}\)
A = [1, 2, 3, 5, 8, 13, 21, 34, 55]
集合記法と同じように、既に定義してある集合から内包的に定義することもできます。
内包定義:
\(B = \{ 2x | x \in A \}\)
B = [2*x for x in A]
\(C = \{ x | x \in A, \mbox{xは奇数} \}\)
C = [x for x in A if x % 2 == 1]
\(D = \{ 2x | x \in A, \mbox{xは奇数} \}\)
D = [2 * x for x in A if x % 2 == 1]
Pythonの集合(Set)
Python で集合を使いたいときは、set()
で変換します。
set(['1, 2, 3, 1, 2`])
順序も重複もない値の集合が得られます。
4.2. リストと繰り返し¶
前回、プログラミングの頻出する構造として、N回繰り返すパターンを練習しました。
決まり文句: N回繰り返す構造
for i in range(N):
# 繰り返す
# プログラム
Hello Worldを10回繰り返す
[17]:
for i in range(10):
print(f"{i} Hello World")
0 Hello World
1 Hello World
2 Hello World
3 Hello World
4 Hello World
5 Hello World
6 Hello World
7 Hello World
8 Hello World
9 Hello World
4.2.1. for文と列¶
for文は、 実はリストなどの列をの値を順番に取り出して、一つずつ処理する制御構造です。
for x in a
の意味
列a
の先頭から順番に値を取り出して、 変数 x に代入し、列a
の個数だけ繰り返す
つまり、range(10)
は0から9までの数列なので、range(N)
で数列を作らなくても、次のようにリストを処理するのと同じになります。
[18]:
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
print(f"{i} Hello World")
0 Hello World
1 Hello World
2 Hello World
3 Hello World
4 Hello World
5 Hello World
6 Hello World
7 Hello World
8 Hello World
9 Hello World
列とリスト
Pythonの理解しにくいところのひとつは、列とリストの違いです。 列は、値の並んだ抽象的な集まりです。リストは、代表的な列ですが、列はリストだけではありません。
列の例
リスト
[1, 2, 3]
タプル
(1, 2, 3)
文字列
"123"
配列(NumPy)
np.array([1, 2, 3])
イテレータ(数列):
range(1, 4)
列は、値が順番に並んでいるという共通した性質があるため、種類にかかわらず、 for 文で順番に処理することができます。
例題(リストと繰り返し)
3の倍数のリストa
が次のように与えられたとき、
a = [3, 6, 9, 12, 15, 18, 21, 24]
10以上20以下の3の倍数を順番に表示せよ。
出力例:
12
15
18
まず、オーソドックスな解き方から説明します。
リストの個数から繰り返す回数を決める
順番に条件を満たす
a[i]
を表示する
[19]:
a = [3, 6, 9, 12, 15, 18, 21, 24]
N = len(a)
for i in range(N):
if 10 <= a[i] and a[i] <= 20:
print(a[i])
12
15
18
[20]:
a = [3, 6, 9, 12, 15, 18, 21, 24]
for n in a:
if 10 <= n and n <= 20:
print(n)
12
15
18
enumerate(列)
enumerate
は、列に対して、0から番号をつけてくれます。 何番目の要素を処理しているか知りたいとき、重宝します。
a = [3, 6, 9, 12, 15, 18, 21, 24]
for i, n in enumerate(a):
if 10 <= n and n <= 20:
print(i, n)
4.2.2. break¶
最後に、繰り返しに慣れてきたら、 繰り返しを途中で抜ける方法も理解しておきましょう。
例題(最小の約数)
正の整数\(n~(n > 1)\)が与えられる。 \(n\)の\(2\)以上, \(n\)未満の最小の約数\(d\)を出力せよ。
入力例
2800733
出力例
13
小さい方から順に割って、約数かどうか判定しましょう。
[21]:
# n = int(input())
n = 2800733
for d in range(2, n):
if n % d == 0:
print(d)
13
17
19
23
29
221
247
299
323
377
391
437
493
551
667
4199
5083
5681
6409
7163
7429
8671
9367
11339
12673
96577
121771
147407
164749
215441
あらら、最小の約数だけでなく、2以上の全ての約数を表示してしまいます。
break
文を用いて、それ以上繰り返す必要がないときに繰り返しを抜け出してしまいましょう。
Let’s try
どこに break
を入れるかわかるかな?
[22]:
# n = int(input())
n = 2800733
for d in range(2, n):
if n % d == 0:
print(d)
break
13
そろそろ、break
は的確に使えるようにしていきましょう。
効率のよいプログラム
無駄な繰り返しはしない(さっさとbreakする)
4.3. リストの操作¶
リストは、Python のオブジェクト指向プログラミングの機能を使って提供されています。メソッドを通して、さまざまな操作ができます。
コード |
操作の説明 |
---|---|
|
リスト\(a\)の個数 |
|
\(i\) 番目の値 |
|
\(i\) 番目の値を\(x\)に置き換える |
|
\(i\) 番目から\(j\)番目までの部分リスト |
|
\(i\) 番目から最後尾までの部分リスト |
|
先頭から\(j\)番目までの部分リスト |
|
リスト\(a\)に\(x\)が含まれるかどうか |
|
リスト\(a\)内の最初の\(x\)の位置 |
|
リスト\(a\)内の最後の\(x\)の位置 |
4.3.1. スライス¶
スライスとは、部分列の始まり(start)と終わり(end)を指定して、部分列を取り出す操作です。
a[start:end]
a[start:end:step] # step は間隔
インデックスは、0から始まります。end
番目は、range()
関数と同じく、部分列に含まれません。
[23]:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a[2:5:1]
[23]:
[2, 3, 4]
開始(start), 終端(end), 間隔(step)は省略できます。
[24]:
print(a[2:5])
print(a[2:]) #省略すると最後まで
print(a[:5]) #省略すると先頭から
print(a[::-1]) #逆順に
[2, 3, 4]
[2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
4.3.2. 破壊的操作¶
リストの内容が変わる操作のことを破壊的操作、もしくはinplace操作と呼びます。
コード |
操作の説明 |
---|---|
|
リストの最後尾に x を追加する |
|
リストの最後尾にリストbの値をすべて追加する |
|
リストの最後尾から値を取り出す |
|
\(i\) 番目から値を取り出す |
|
リストを整列する |
|
リストを逆順にする |
4.3.3. リストを並び変える¶
リストは、順序をもった値の集まりなので、ときには並び変える操作が必要となります。
例題(リストの逆順)
入力から(空白区切りの)数列をリストに読み、 その数列を逆順に1行ずつ出力してみよう。
入力例
7 6 8
出力例
8
6
7
色々、解法があります。 あわせて、リストの破壊的操作がどのようなものかも確認しておきたいと思います。
まず、入力されたリストa
を作ってみます。
ここでは、同じ結果になるようにinput()
の代わりに、リスト内包記法で合成。
[25]:
# a = list(map(int, input().split()))
a = [x**2 for x in range(1, 6)] #入力されたリストの例
a
[25]:
[1, 4, 9, 16, 25]
(解法) a[len(a)-1]
, a[len(a)-2]
, .. と後ろから表示する
[26]:
for i in range(len(a), 0, -1):
print(a[i-1])
25
16
9
4
1
(解放): スライスで後ろから読む
[27]:
for n in a[::-1]:
print(n)
25
16
9
4
1
(解法) 後ろから順番にpop()
していく
[28]:
for _ in range(len(a)):
print(a.pop())
print(a) # 破壊的操作なので、最後は空になる
25
16
9
4
1
[]
破壊的操作
破壊的操作はリストの内容が変更されるため、もう一度リストを使うときは、 事前にコピーして複製を作るとよい。
リストの複製
[29]:
a = [x for x in range(1, 7)]
b = a[:] # リストの複製
b.reverse()
print('b =', b) # 逆順に
print('a =', a) # そのまま
b = [6, 5, 4, 3, 2, 1]
a = [1, 2, 3, 4, 5, 6]
4.3.4. リストの集約計算¶
リストが使えるようになると、データの集まりをまとめて処理できるようになります。
特に、リストの要素が数値、つまり数列の場合は、威力を発揮します。
コード |
説明 |
---|---|
|
数列 |
|
数列 |
|
数列 |
例題(最大値、最小値、合計値)
5人の得点が整数値として5行で与えられる。 その整数値の合計、最大値、最小値を各行に出力せよ
入力例
71 29 83 45 35
出力例
263 83 29
まず、成績の数列をa
に読み込みます。
[30]:
# a = list(map(int, input().split())
a = [71, 29, 83, 45, 35]
クラシックな(しかも原理を理解して書けるようになって欲しい)方法は、最大値、最小値を順番に更新していく方法です。
[31]:
sumScore = 0
maxScore = 0 # (原理的な)最小値に
minScore = 100 # (原理的な)最大値に
for s in a:
sumScore += s
if maxScore < s:
maxScore = s
if minScore > s:
minScore = s
print(sumScore, maxScore, minScore)
263 83 29
上の原理が理解できたら、これからは、ありがたく集約計算を使いましょう。
[32]:
print(sum(a), max(a), max(b))
263 83 6
4.3.5. 2次元リストとzip★¶
少し複雑なリスト操作にも慣れておきましょう。
例題(2次元リスト)
次の2次元リスト(リストのリスト)縦横の話を計算して表示してみよう。
入力例
a = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
]
出力例
1 2 3 4 5 15
6 7 8 9 10 40
11 12 13 14 15 65
16 17 18 19 20 90
21 22 23 24 25 115
55 60 65 70 75 325
まず、2次元リスト(リストのリスト)に慣れておきましょう。
[33]:
a = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
]
print(a[1]) # 1行目(0から数える)
print(a[1][2]) # 1行目の2列目の値
[6, 7, 8, 9, 10]
8
[34]:
for line in a:
print(*line, sum(line))
1 2 3 4 5 15
6 7 8 9 10 40
11 12 13 14 15 65
16 17 18 19 20 90
21 22 23 24 25 115
zip(a, b)
はふたつのリストをタプルのリストにして返してくれる関数です。 名前は、zipperに由来しています。
[35]:
list(zip(a[0], a[1]))
[35]:
[(1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]
これを5個同時にzipして縦方向もタプルを作り、集約します。
[36]:
line_sum = [sum(row) for row in zip(*a)]
print(*line_sum, sum(line_sum))
55 60 65 70 75 325
4.4. 演習問題¶
リストは、数値が複数、登場し、繰り返し処理するときに活用すると便利です。 ぜひ、積極的にリストを使って、プログラミングしていきましょう。