第6回:繰り返し処理(講義)#
担当:相澤宏旭
内容#
第6回 繰り返し処理では,ある処理を条件式に基づき繰り返す処理について学ぶ.プログラムにおいて繰り返し処理はリストなど複数のデータ値を持つ変数に対する演算処理,ユーザ入力の待機時間の実装など様々な場面で利用される.さらに,第4回で学んだ条件分岐と組み合わせることで,指定した条件が満たされたとき繰り返し処理を停止させたりするなど,処理をより複雑に制御できるようになる.このような処理を実現するために,Pythonでは for
文やwhile
文がサポートされている.具体的に,これらの文法について以下を学ぶ.
一定回数繰り返すまたは条件に応じて処理を反復するための,
for
文とwhile
文の基本文法を理解する.繰り返し処理を強制的に制御する
break
とcontinue
の文法を学び,その動作を確認する.Pythonに実装されているより実用的な繰り返し処理を学ぶ.
繰り返し処理とは?#
繰り返し処理とは,言葉の通り,ある処理を複数回繰り返す処理である.前述したように,繰り返し処理によって,特定の処理を複数回反復することができ,また条件分岐と繰り返し処理を組み合わせることで,より複雑なプログラムを実装することができる.何より,繰り返し処理によって,複数回同じプログラムを記述する必要がなくなるため,コードの可読性を改善し,コード量も減らすことができる.
では,以降で繰り返し処理の問題場面を確認しよう.
繰り返し処理の利用場面#
第2回の講義で学んだリストの各要素に対して何らかの処理を加えてprint
で出力する場面を考える.例えば,以下のような曜日の文字列が格納されたリストを与えられ,今日は...です
と要素を出力することを考える.このとき繰り返し処理を利用しなければ,以下のようなインデックスによる出力方法が考えられる.
days = ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日']
print(f'今日は{days[0]}です')
print(f'今日は{days[1]}です')
print(f'今日は{days[2]}です')
print(f'今日は{days[3]}です')
print(f'今日は{days[4]}です')
print(f'今日は{days[5]}です')
print(f'今日は{days[6]}です')
今日は月曜日です
今日は火曜日です
今日は水曜日です
今日は木曜日です
今日は金曜日です
今日は土曜日です
今日は日曜日です
このように愚直にprint
文を要素数だけ記述すると各要素に対して何らかの演算と出力できる.
続いて,平日の場合は...は平日です
,休日の場合は...は休日です
と出力することを考える.同様にprint
文を使って以下のように書ける.
print(f'{days[0]}は平日です')
print(f'{days[1]}は平日です')
print(f'{days[2]}は平日です')
print(f'{days[3]}は平日です')
print(f'{days[4]}は平日です')
print(f'{days[5]}は休日です')
print(f'{days[6]}は休日です')
月曜日は平日です
火曜日は平日です
水曜日は平日です
木曜日は平日です
金曜日は平日です
土曜日は休日です
日曜日は休日です
しかしながら,このようなprint
文による処理は問題がある.わかりやすい問題点をいくつか述べると,
要素数が増えると
print
文の行数が増えること要素の順番が可変の場合は
print
文を修正する必要があること要素の数が可変の場合は
print
文では対処しきれないこと
が挙げられる.このような問題をスマートに解決する方法が繰り返し処理である.
for
文#
前述した例のように,リストの各要素に対して順番に処理を適用したいとき,そのような場面で利用されるのが for
文である.一般的な for
文の構文は以下である.
for 要素を保存する仮変数 in リスト:
繰り返される処理
ここで,for
文では繰り返される処理をインデントによるコードブロックで記述している.インデントの詳細については第4回 条件分岐を参照されたい.
もちろん,リストだけでなく,辞書やタプルといったデータ型も繰り返すことができる.
では,使用例として曜日を要素として格納したdays
リストの各要素を出力する.
for day in days:
print(day)
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日
出力結果からもわかるように,リストの各要素に対してprint
文を書くことなく,すべての要素を出力できている.
続いて,各要素に処理を加えて出力する場合を確認する.前述のように,今日は...です
と文字列を加える処理をfor
文で記述する.
for day in days:
print(f'今日は{day}です')
今日は月曜日です
今日は火曜日です
今日は水曜日です
今日は木曜日です
今日は金曜日です
今日は土曜日です
今日は日曜日です
このようにコードの行数を削減できるだけでなく,リストの要素数が可変であっても処理できることが繰り返し処理の利点である.
最後に,平日の場合は...は平日です
,休日の場合は...は休日です
と出力するプログラムを条件分岐と組み合わせて作成する.
for day in days:
if day in ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日']:
print(f'{day}は平日です')
elif day in ['土曜日', '日曜日']:
print(f'{day}は休日です')
月曜日は平日です
火曜日は平日です
水曜日は平日です
木曜日は平日です
金曜日は平日です
土曜日は休日です
日曜日は休日です
このプログラムでは days
から各要素を順番に day
に取り出し,day
の中身が平日の要素 ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日']
か休日の要素 ['土曜日', '日曜日']
かを in
演算子で条件分岐を行い,その結果に応じた文字列を出力している.
このように,繰り返し処理は条件分岐と組み合わせることで非常に柔軟な処理を実現できる.
Caution
利用場面として紹介した特定の処理を予め予想し固定の処理としてコーディングすることはハードコーディングと呼ばれバグの温床となる.今回の場合では悪い例として要素数を固定してprint
文で出力したが,for
文を使って要素数や順番が可変であっても対応できるコーディングをすることが望まれる.
Tip
発展的な内容であるがfor
文などの繰り返し処理に利用できるオブジェクトはイテラブルオブジェクト(itarable object)と呼ばれる.イテラブルオブジェクトとして代表的なデータ型はリスト,タプル,辞書,文字列である.これらのオブジェクトはfor
文で上記のように利用できる.一方で,数値データや論理値はイテラブルでないオブジェクトである.
range
関数#
前述のfor
文の例では,繰り返し処理のために曜日や集合の要素が格納されたリストを予め作成する必要があった.
しかしながら,例えば,\(1000\)回繰り返し処理をしたいとき,\(1000\)個の値が格納されたリスト
num_list = [0, 1, ..., 999]
を定義しなければならない.説明のため,...
としたが,実際は,前述までの例のように愚直にリストに値を格納する必要があり,非常に手間がかかる.
そこで,このような一定回数だけ処理を繰り返したい場面のために,Pythonではrange
関数がサポートされている.このrange
関数は任意の回数反復可能な整数列を作成できる.
range
関数は以下のように利用できる.
for 仮変数 in range(start, end, step):
繰り返される処理
ここで,start
は開始時の数,end
は反復回数,step
は一定回数ごとにスキップする数である.
Tip
range
文ではデフォルト引数としてstart=0
とstep=1
が指定されているため,start
とstep
は省略できる.そのため,range(0,n,1)
はrange(n)
と書くことができる.range
関数はfor
やwhile
と同様に組み込み関数と呼ばれる.デフォルト引数や関数は第8回 関数とスコープで紹介するので,print
文やif
文のようにまずは使い方だけを習得してほしい.
以下,range
関数のサンプルコードである.例えば,range(0,10,1)
は0から9までの整数列を返す処理となる.
range
関数のstart
,end
,step
を変更し,挙動を確認されたい.
for i in range(0,10,1):
print(i)
0
1
2
3
4
5
6
7
8
9
for i in range(10):
print(i)
0
1
2
3
4
5
6
7
8
9
for i in range(5,10):
print(i)
5
6
7
8
9
for i in range(0,10,2):
print(i)
0
2
4
6
8
よく利用されるテクニックとしてrange
関数によるリストのインデキシングがある.例えば,len
関数でリストの要素数を取得できるので,以下のようにrange
関数を定義すると,インデックスによるリストの走査ができる.
for i in range(len(days)):
print(days[i])
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日
前述の例であるがリストは反復可能なデータ型なので以下のコードのようにも書ける.上記のコードは以下と同じである.
for i in days:
print(i)
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日
for文のまとめ
for
文はリストなどの複数要素を繰り返すときに便利である.for
文の処理はインデントを使って記述する必要がある.range
関数を利用することで一定回数だけ繰り返す処理を実装できる.
while
文#
Pythonではfor
文以外にもいくつかの繰り返し処理が実装されている.その中でも代表的な繰り返し処理が条件式がTrue
である間,処理を繰り返すwhile
文である.条件式については第4回 条件分岐を参照されたい.
while
文の構文は以下である.
while 条件式:
条件式がTrueである間, 繰り返される処理
for
文との違いは,for
文はあるオブジェクトの要素数分またはrange
関数による一定回数の繰り返し処理に対して,while
文はある条件がTrue
の間処理を繰り返す.ただし,利用場面の違いはあるがfor
文であっても条件式に基づいた繰り返し処理を実装でき,while
文についても一定回数後に繰り返しを終了するよう実装できる.このような書き換えは,後述するbreak
文や条件分岐処理を使って実現できる.
while
文の挙動を実例から確認しよう.
以下のwhile
文は変数count
が\(0\)以上のときTrue
となる条件式count >= 0
に基づいて繰り返される.条件式がTrue
の間はcount
の値の出力と繰り返されるたびにcount
の変数を\(-1\)する処理が実行される.count
の値が繰り返すたびに減少するため,処理がcount
\(+1\)回分行われるとcount
の中身は負の値となり繰り返し処理が停止する.
count = 10
while count >= 0:
print(count)
count -= 1
10
9
8
7
6
5
4
3
2
1
0
Important
上記のコードにおいてcount
を処理のたびに\(-1\)しない処理を考える.このとき,count
の値が減少しないため,while
文は強制的にプログラムを停止しない限り繰り返し処理が続く.このような意図せず実行された,終わらない繰り返しは無限ループと呼ばれる.プログラミングのテクニックとして,無限ループはユーザ入力の待機待ちの実装等で利用されることもあるが,繰り返し(ループ)が終了する条件もプログラムされている.Google ColabではCtrl+CやInterruptボタンを押さない限り動作し続けるため注意されたい.
break
文による繰り返しの終了#
for/while
文で,繰り返し(ループ)を中断したいとき,break
文を使用する.これによって,前述の無限ループのように「Ctrl+C」や「Interrupt」ボタンを押すことなく,無限ループを強制的に終了させることができる.
break
文は,例えば,何回繰り返すか決まっていない繰り返しで,ある条件式が満たされたら繰り返しを終了させたい場面で有効である.一般的に,以下のように利用される.
while 繰り返しの条件式:
繰り返しの条件式がTrueの間, 繰り返される処理
if 繰り返しを終了させる条件式:
break
以下のwhile
文による繰り返しに与えられる条件式はTrue
の条件式であり,中断処理をしない限り繰り返しが続く.ここでは break
文の使用例として,繰り返しの外で変数 i
を定義し,繰り返すたびに繰り返し内の処理として条件式の判定を行う.内部の条件式は i
が \(0\) 未満となったとき繰り返しを強制的に抜ける break
文を実行し,i
が \(0\) 以上のとき i
を \(-1\) する処理である.
i = 10
while True:
if i < 0:
break
else:
print(i)
i -= 1
10
9
8
7
6
5
4
3
2
1
0
このように繰り返し内部に条件式とbreak
文を設けることで無限繰り返しによる処理を実装できる.
Important
break
文はwhile
文内だけでなくfor
文でも利用できる.
continue
文による次の繰り返しの開始#
continue
文は,繰り返しを終了することなく,その後の処理をスキップして次の繰り返しに進むことができる.条件式を満たすとき,処理1を実行するが,処理2は実行しない繰り返し文を実行したいとき以下のようにcontinue
を利用する.
while 繰り返しの条件式:
処理1
if 処理2をスキップさせる条件式:
continue
処理2
continue
文の利用場面として,例えば,整数のリストを与えられ,偶数の要素だけ表示するプログラムを考える.以下がそのコードとなるので確認されたい.
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
i = 0
while i < len(nums):
if nums[i] % 2 != 0:
i += 1
continue
print(nums[i])
i += 1
2
4
6
8
10
上記のコードのwhile
文は繰り返し回数i
が整数リストnum
の要素数len(nums)
より小さいときTrue
となる条件式
i < len(nums)
に基づいて繰り返される.つまりはリストの要素数だけ繰り返す処理となっている.
そして,繰り返し内ではi
番目のリストの要素nums[i]
について\(2\)で割り切れるかを判定する.これは以下のコードである
if nums[i] % 2 != 0:
ここで,\(2\)で割り切れないとき,つまり,奇数であるとき,i
を\(+1\)してcontinue
する.このとき,continue
後の処理print
が実行されていないことを確認されたい.
一方で,\(2\)で割り切れるとき,つまり,偶数であるとき,print
文でi
番目のリストの要素nums[i]
を出力し,i
を\(+1\)して次の繰り返し処理に進む.
Important
continue
文はwhile
文内だけでなくfor
文でも利用できる.
while文のまとめ
while
文は条件式がTrue
のとき処理を繰り返す.break
文やcontinue
文を組み合わせると複雑な繰り返し制御を実装できる.常に
True
状態となる無限ループとなる場合があるので注意する.
for
文によるリストの作成#
繰り返し処理はリストの作成にも利用できる.
具体的に,空のリストにappend
関数を使ってfor
文とrange
関数による繰り返し処理から得られる数値を追加することでリストを作成できる.
以下に\(0\)から\(9\)までの計10個の要素を持つリストをfor
文を利用するコードを示す.
l = []
for i in range(10):
l.append(i)
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
上記のコードでは,要素が空のリスト l=[]
を作成し,繰り返し処理で得られる \(i=0,1,...,9\) の値をリストにappend
している.リストを出力した結果が [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
となっていることを確認されたい.
また,特定の条件を満たすリストや数値以外のリストを作成する際に便利である.例えば,\(3\)で割り切れない数値のみを持つリストを作成してみよう.これは前述のコードに条件分岐を組み合わせることで実現できる.
以下にfor
文による\(3\)で割り切れない数値のみを持つリストの作成コードを示す.
l = []
for i in range(10):
if i % 3 == 0:
continue
l.append(i)
print(l)
[1, 2, 4, 5, 7, 8]
上記のコードは,\(3\) で割り切れないときのみ値を append
し,割り切れる場合は continue
文を用いて以降の処理をスキップして次の繰り返しに移行する.結果を出力すると,\(3\)の倍数を除いたリスト [1, 2, 4, 5, 7, 8]
が作成されていることがわかる.
【発展】多重ループ#
for
文の応用として,複数のオブジェクトの要素を繰り返す多重ループがある.多重ループはネストループとも呼ばれ,2重だけでなく3重以上のループも実装できる.このような多重ループは,例えば,2つの要素集合のリスト間の組み合わせを出力する場面で利用できる.
以下は集合\(A=\{a,b,c\}\)と集合\(B=\{x,y,z\}\)間の組み合わせ出力する多重ループのサンプルコードである.各集合はリストとして実装し,2重ループとして,各組み合わせをprint
文で出力する.
A = ['a', 'b', 'c']
B = ['x', 'y', 'z']
for i in A:
for j in B:
print(i, j)
a x
a y
a z
b x
b y
b z
c x
c y
c z
Tip
多重ループは後述のwhile
文,break
文,continue
文と組み合わせると便利である.しかしながら,多重ループはコードの可読性を下げるため多用は厳禁である.Pythonには itertools
を代表とするループ処理のライブラリも提供されており,複雑なループを実装したいときに便利である.
【発展】イテレータ#
厳密にはPythonにおいてrange
関数は イテレータ と呼ばれる仕組みである.イテレータは次の要素を順番に取り出し,読み出し可能な要素がなくなったときに終了変数を返すような機能を持つ.
range
関数をprint
してみるとリストのように中身の要素が出力されないことに注意されたい.
print(range(3))
range(0, 3)
print(type(range(3)))
<class 'range'>
range
関数から直接にリストやタプルが作成できると便利である.データ型の変換を利用するとrange
関数からリストやタプルが作成できる.
x = list(range(3))
print(x)
[0, 1, 2]
x = tuple(range(3))
print(x)
(0, 1, 2)
リストをイテレータ化して,順番に取り出すこともできる.具体的に,
iter()
にリストを渡しイテレータ化返ってきたイテレータに対して
next()
を使って呼び出し終了変数
StopIteration
が発生するまで繰り返し となる
以下のコードで確認されたい.
l = [3, 2, 1, 0]
l = iter(l)
print(next(l))
print(next(l))
print(next(l))
print(next(l))
# print(next(l)) # StopIterationのエラーが発生する
3
2
1
0
【発展】enumerate
関数#
enumerate
関数はfor
文やwhile
文と同様に繰り返し処理のためのPythonの組み込み関数である.enumerate
関数はループの回数(=要素の数)をカウントする利用場面で便利である.その具体例を以下のセルに示す.
i = 0
for v in ['a', 'b', 'c']:
print(i, v)
i += 1
0 a
1 b
2 c
ループと共にその回数をカウントしたいとき,上のコードのようにカウント用の変数i
を定義して,ループ処理のたびにi
のカウント数を\(+1\)する必要がある.このようなカウントは以下のようにrange
関数でも実現できるが,多重ループでのカウントなどでコードの可読性が下がる可能性がある.
l = ['a', 'b', 'c']
for i in range(len(l)):
print(i, l[i])
0 a
1 b
2 c
enumerate
関数は以下のように定義することで要素のカウントとループを同時に行うことができる.
for カウント, 要素 in enumerate(リスト):
処理
for
文でリストやrange
関数をenumerate
関数に置き換えるだけで実装でき,多重ループであっても可読性は高い.以下のセルに前述のfor
文でのループ数のカウントをenumerate
関数で置き換えたコードを示す.
for i, v in enumerate(['a', 'b', 'c']):
print(i, v)
0 a
1 b
2 c
【発展】zip
関数#
zip
関数は複数の反復可能なオブジェクトの繰り返し処理をしたいときに便利である.多重ループでの例のように,集合 \(A=\{a,b,c\}\) と集合 \(B=\{x,y,z\}\) について各要素を順番に取り出す処理を考える.望まれる出力は以下である.
a x
b y
c z
このような出力をfor
文で実現したいとき要素のインデックスを要素数分だけ繰り返すrange
関数を作成すれば良い.以下が参考例である.
A = ['a', 'b', 'c']
B = ['x', 'y', 'z']
for i in range(len(A)):
print(A[i], B[i])
a x
b y
c z
enumerate
関数の利用例とは異なり,既に可読性の高い繰り返し処理がfor
文で実現できている.シンプルな処理であれば十分であるが,要素数が異なる複数のオブジェクトを反復させたい場合や機械学習など大規模なプログラムを作成時に非効率的になる可能性がある.実際に,要素数が異なる複数オブジェクトを上記のようにfor
文で繰り返し処理をしようとすると,少ない要素数のオブジェクトで参照エラーが発生する.
# A = ['a', 'b', 'c', 'd']
# B = ['x', 'y', 'z']
# for i in range(len(A)):
# print(A[i], B[i])
上記の例では集合\(A\)と\(B\)の要素数を予め取得し,少ない要素数を持つ集合に対してrange
関数を作成すれば良いが,Pythonにはこのような処理をすることなく複数オブジェクトを同時に反復する効率的な組み込み関数が提供されている.リストの場合,zip
関数は以下のように定義できる.
for リストAの要素, リストBの要素, ... in zip(リストA, リストB, ...):
処理
zip
関数ではオブジェクトの要素数が異なる場合はもっとも少ない要素数を持つオブジェクトを基準に繰り返し処理がなされる.以降のセルに,等しい要素数を持つ反復と異なる要素数を持つ反復の例を示す.
for u, v in zip(['a', 'b', 'c'], ['x', 'y', 'z']):
print(u, v)
a x
b y
c z
for u, v in zip(['a', 'b', 'c', 'd'], ['x', 'y', 'z']):
print(u, v)
a x
b y
c z
【発展】辞書とfor
文#
辞書を用いた繰り返し処理を紹介する.これまではリスト型を中心に繰り返し処理を説明したが,ここでは,辞書を用いた繰り返し処理を紹介する.第2回の講義で説明したように,辞書は変数key
に基づいて値value
を参照するデータ型である.辞書は反復可能なイテラブルなデータ型であるため,len
関数がサポートされているが,リストのように要素の順番を示すインデックス変数i
を利用した参照ができない.このような辞書型であってもシンプルかつ効果的に繰り返し処理をするための関数が提供されている.
以降のセルでは第2回の講義で紹介した辞書の値のみ,キーワードのみ,またはその両方を取得する関数を復習し,for
文による繰り返し処理を紹介する.
d = {'key1': 1, 'key2': 2, 'key3': 3}
print(d)
{'key1': 1, 'key2': 2, 'key3': 3}
辞書の要素にアクセスするためのキーワードは辞書.keys()
で取得できる.以下のように記述することで辞書のキーワードを繰り返すことができる.
for key in d.keys():
print(key, d[key])
key1 1
key2 2
key3 3
辞書のキーワードによって参照される値は辞書.values()
で取得できる.以下のように記述することで辞書の値を繰り返すことができる.
for value in d.values():
print(value)
1
2
3
辞書.items()
で辞書のキーワードと値を同時に取得できる.同様にfor
文と組み合わせることで辞書のキーワードと値を同時に繰り返すことができる.
for key, value in d.items():
print(key, value)
key1 1
key2 2
key3 3
【発展】内包表記#
前述したfor
文によるリストの作成は便利であるが,コードはやや煩雑となる.Pythonでは内包表記と呼ばれる文法が定義されており,非常にシンプルかつ高速にリストの作成ができる.以下に,\(0\)から\(9\)までの計10個の要素を持つリストを内包表記で作成するコードを示す.
l = [i for i in range(10)]
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
出力結果とコードからわかるように,3行の処理が1行でリストを作成できる.続いて,for
文による\(3\)で割り切れない数値のみを持つリストの作成コードを示す.ここでは条件分岐を行うために,以下のように3項演算子と同様の表記の条件式を繰り返し処理の後ろに記述する.
l = [i for i in range(10) if i % 3 != 0]
print(l)
[1, 2, 4, 5, 7, 8]
上記の内包表記は条件式i % 3 != 0
がTrue
のときに\(i\)を追加する処理となっている.内包表記は多重ループに対しても利用できるが可読性が著しく下がるため推奨されない.また,内包表記はリストだけでなく辞書も作成できる.以下に,zip
文を応用した辞書の内包表記の例を示す.
d = {k: v for k, v in zip(['key1', 'key2', 'key3'], [1, 2, 3])}
print(d)
{'key1': 1, 'key2': 2, 'key3': 3}