プログラムの基本的な流れは上から下へ一行ずつ実行していくというものです。単純なプログラムですと、テキストファイルに実施する処理を順番に羅列するだけで実現できます。いわゆる「バッチ処理」と呼ばれているやつです。
ただ、複雑なプログラムだと、このような「上から下に順番に実行していく」というスタイルだけでは処理を実現できなくなってきます。たとえば、天気予報を確認するアプリケーションでは、「今日が晴れなら晴れマークを表示、雨なら雨マークを表示」といった具合に「あるものがAならBをする。そうでないならCをする」という処理が必要になってきます。「条件」に応じて処理が「分岐」しているので、こういった処理のことを「条件分岐」といいます。
ほかには、同じ処理を何度も繰り返す「ループ処理」があります。たとえば、「クラス全員のテストの平均点を求め、その平均点と各生徒の点数の差分をチェックする」といった場合を考えてみましょう。平均を求めるには「生徒の点数の合計/人数」とする必要がありますが、この合計を求めるために「先頭の生徒から最後の生徒まで順番に点数を足していく」という「繰り返し(ループ)」が必要となります。平均との差分の算出も同様です。
今回はこのような条件分岐やループ処理といった「プログラムの制御構造」について取り扱います。これらの処理を使うこと自体はそれほど難しくないので、何度も書いて慣れてしまえば、簡単に使いこなせるようになるはずです。
なお、今までの記事ではプロンプトベースで説明を進めてきましたが、コードが長くなりはじめたのでプログラムファイルで実行することを前提に解説します。IDLEのエディタで書いてF5(MacはFn + F5)で実行するなり、pythonコマンドで実行するなりしてください。
条件分岐
さっそく、最も使われる制御構造のひとつである「条件分岐」について学んでいきましょう。条件分岐は、条件分岐の式を満たすか満たさないかで実行される処理が変わるという制御構造です。型について取り扱った際に紹介した「Bool型」が条件判定に利用され、その値がTrueかFalseかで実行するプログラムが変わります。
以下に条件分岐の仕組みを図で記します。
上記の図のうち、「elif」は任意の数(0も含む)繰り返すことができ、「else」も省略することができます。elseがないときは、どの条件にも合致しない場合は何もしないということです。Pythonのプログラムでは以下のように書きます。
if(条件A):
処理1-1 # 条件 A が True の時に実行される処理
処理1-2
elif(条件B):
処理2 # 条件 A が False で条件 B が True の時に実行される処理
elif(条件C):
処理3 # 条件 A,B が False で条件 C が True の時に実行される処理
else:
処理4 # 全ての条件が False の時に実行される処理
上記のifからelseの次の行までがひとつの「if文のカバー範囲」であり、そのなかにあるifやelif、elseが細かい処理の単位だと思っていただければ大丈夫です。
上記のプログラムには「if、else、elifのあとの処理が字下げ(インデント)されている」という規則性が見えますね。このインデントされている場所は「コードブロック」と呼ばれるもので、同じインデントのレベル(深さ)で揃えると同じコードブロックに属しているとみなされます。なんだか難しいようですが、ようするに上記のif文でいうと「処理1-1、1-2はif(条件A)のカバー範囲」であるということです。同様に処理2は「elif(条件B)」の範囲であり、処理3は「elif(条件C)」、処理4は「else」の範囲です。
実際に条件分岐を行うプログラムを書くことで、条件分岐の使い方をイメージしてみましょう。プログラムは非常に簡単で、変数xの値が0より大きければ「+」と出力し、ピッタリ0なら「0」と「Zero」、0未満なら「-」と出力するというものです。これは以下のようになります。
x = 5
if(x > 0):
print('+')
elif(x == 0):
print('0')
print('Zero')
else:
print('-')
繰り返しになりますが「print('0')」と「print('Zero')」は同じコードブロックです。上記プログラムをIDLEのエディタに書いて実行してみてください。xに5が代入されているので、「+」と出力されたはずです。これはx > 0の条件式が満たされ(Trueとなり)、「print('+')」が実行されたからです。
このxに代入する値をいろいろ変えて動かしてみると、どの条件式がチェックされ、「if、elif、else」のどの処理が実行されたのかイメージできるはずです。
コードブロック
条件分岐の話が終わったので、インデント(字下げ)についてもう少し詳しくお話しましょう。先ほどのプログラムは最初から最後までif文でしたが、実際には、if文は多くの処理のなかに埋もれるかたちで処理します。
すると、ifなどの制御構造が「どこからどこまでをカバーしているか」をどのような形で表現するかが問題になってきます。たとえば、処理1、2、3、4、5とあるなかで条件Aを満たす場合のみ処理2、3を実行し、満たさない場合は4を実行するとした場合、どのように表現すればよいでしょうか。
勘のいいかたなら気が付かれたかもしれませんが、インデント(字下げ)をすることでこれを実現しています。
処理1
if(条件A):
# ここから
処理2
処理3
# ここまでがコードブロック
else:
# ここから
処理4
# ここまでがコードブロック
処理5
字下げをすることでコードブロックを表現する。簡単ですね。
なお、CやJavaにもコードブロックはありますが、その書き方は異なっています。たとえばJavaだと上記のサンプルコードは以下のようなものとなります。
処理1
if(条件A){
// ここから
処理2 // 字下げは必須ではない
処理3
// ここまでがコードブロック
}else{
// ここから
処理4
// ここまでがコードブロック
}
処理5
{}で囲むことでコードブロックを表しています。たいていは読みやすいように上記のようにインデントをしますが、プログラムとしてはインデントをする必要性はありません。
コードブロックはifやループなどの制御構造だけではなく、関数やクラスでも利用されます。なお、Pythonのインデントの仕方は「半角空白を2つまたは4つ」が普通だと思います。自分や属するプロジェクトのコーディング規約次第があればそれに従ってください。
コードブロックのネスト(入れ子)
コードブロックの中にコードブロックを作ることも可能です。たとえば条件分岐の中に、さらに条件分岐を作ったりすることもできます。書き方は簡単で、コードブロックの内側にさらにコードブロックを作るというものです。その際、内側のコードブロックは外側のコードブロックに属しています。
サンプルコードをあげてみます。
if(条件A):
処理1 # "if(条件1)"のコードブロックに属する
if(条件B):
処理2 # "if(条件1)" と "if(条件2)" の両方法のコードブロックに属する
処理3
else:
処理4
処理1、2、3はすべて「if(条件1)」のコードブロックに属していますが、処理2だけではそれに加えて 「if(条件B):」にも属しています。そのため、処理2が実行されるのは条件A、Bが共にTrueのときのみです。たとえ条件BがTrueであっても、条件AがFalseなら処理2は実行されません。
なお、コードブロックに限らず、プログラミングで「入れ子」構造にすることを一般的に「ネストする」と言いますので覚えておいてください。ネストすること自体には問題はありませんが、その深さが増えてくるとプログラムが非常に読みにくくなります。深いレベルのネストが必要な状況になってきたら、アルゴリズムそのものを見直すか、後の連載で扱う「関数」に処理を分割することで読みやすくすることが多いです。
ループ処理
次に、別の制御構造であるループ処理について扱います。ループ処理はその名前からわかるように「同じ処理を何度も繰り返す」という処理です。
ループ処理の制御構造にはforとwhileの2つがあり、両者の使うべきポイントは若干異なっています。そのため、それぞれ別に説明します。
for
「for」は「グループにある要素すべてを処理する」といったときに使われるループ構造です。一番よく使われるのが、前回お話したリスト(配列)に格納されている要素すべてをチェックするような処理です。JavaやCで使われるfor文と書き方はかなり異なるものの、ほとんど同じような場面で使います。
Pythonのfor文のイメージを以下の図に書きます。
難しい用語でいうと「イテレーター」と呼ばれる処理方式なのですが、ようするに「たくさんある集合の先頭ひとつを取り出して、それを処理する。それが終わったら、次を取り出して処理をする」ということを、集合が空になるまで繰り返すというイメージです。
それほど難しくないので例で示しましょう。1、2、3、4、5という数字が格納されているリストの中身を一つひとつすべてprint出力する処理をforで書くと以下のようになります。
a = [1,2,3,4,5]
for i in a:
print(i)
1、2、3、4、5という集合から、
- リスト a から 1 を取り出して i に格納。それをprint出力
- リスト a から 2 を取り出して i に格納。それをprint出力
- …(中略)…
- リスト a から 5 を取り出して i に格納。それをprint出力
- リスト a からすべてを取り出したのでforのコードブロックを終了
という動きをします。すでに想像はついているかと思いますが、出力は以下のようになります。
1
2
3
4
5
イテレーターを使っているので、Javaのfor文で使うような「インデックス(配列の何番目か)による制御」に比べて、間違った要素を指定するリスクが減っています。
while
whileもforと同じくループ処理のための制御構造です。ただ、whileは「ループを何周すればいいかわからない処理」に利用されます。
先ほどのforの例を思い出して下さい。forでのループ回数は「リストaに格納されている要素の数」と明確にわかりますよね。このような場合はforで処理すべきです。一方、たとえば「123456789という数字を2進数で表現するのに必要な桁数を求める処理」が必要だとした場合、これをどうfor文で処理すればいいか、想像できますか。私はシンプルでスマートな実装は思いつかないです。
解き方はいろいろあると思いますが、一番簡単な解法の一つとして、以下のようなものが考えられます。
- 2 の 1 乗は 123456789 より大きいか -> No
- 2 の 2 乗は 123456789 より大きいか -> No
- ..
- 2 の N 乗は 123456789 より大きいか -> No
- 2 の N+1 乗は 123456789 より大きいか -> YES
- N+1桁あれば 123456789 を表現可能だとわかる
この処理では2を1乗、2乗とループ処理でどんどん大きくしていきますが、最終的に2の何乗になるかがわかりませんよね。このようなときに「特定の条件をクリアするまでループを回す」ためにwhileを使うと便利です。
以下にwhile文の使い方のイメージ図をのせます。
上記の図を見てもらうとわかるように、while文はループを回るごとに条件式をチェックして、それがTrueならループを継続して、Falseならループを抜けるという処理をします。これはJavaやCのwhileとまったく同じです。
先ほどの2進数の桁数を求めるプログラムをwhileで書いてみます。
a = 123456789
i = 1
while(2**i < a):
i+=1
print(i)
すでに扱った内容ですが、上記のプログラムを補足すると、2**iは「2のi乗」を計算していて、i+=1はiをインクリメント(i = i + 1)しています。2**iが123456789より小さい間はiをインクリメントしていき、2**iが123456789より大きくなったらループを抜けるという動きをします。ループを抜けた際iに入っている値が必要な桁数を表しています。
break と continue
制御そのものの打ち切りや「ループのその回だけ」の打ち切りが必要な場面があります。たとえば以下のようなプログラムがあるとします。
a = [1,3,5,7,9,10,11,13,15]
has_even = False
for i in a:
if(i%2 == 0):
has_even = True
print("List has even: " + str(has_even))
偶数がリストの中にあるかどうかをチェックしていますね。リストの中に10があるので、当然Trueとなります。ただ、よく考えてみてください。なにか無駄な処理があると思いませんか。そう、ループが10になった回で偶数があることがわかったのに、さらにチェックを繰り返しています。10が現れた時点で偶数があることはわかりきっているので、ループを回し続けるのは無駄なのです。
「break」を使って処理を打ち切ることで、この問題を解決できます。
a = [1,3,5,7,9,10,11,13,15]
has_even = False
for i in a:
print(i) # NEW CODE
if(i%2 == 0):
has_even = True
break # NEW CODE
print("List has even: " + str(has_even))
確認のためにbreakだけでなく、print文も追加しています。これを実行すると以下のようになります。
1
3
5
7
9
10
List has even: True
どうです、11以降のチェックをしなくなりましたよね。このようにbreakはかなり使える処理なので覚えておく必要があります。
一方「continue」ですが、正直こちらはbreakほど頻繁に利用されない気がします。ただ、ループで「特定の条件の場合だけ処理をしたい」というときに利用されることが多いです。
たとえば、数値1から99のリストのうち、3でも5でも割り切れるものだけを画面出力する必要があるとします。リストを使わないで愚直な書き方をすると以下のようになります(実際はcontinueを使わなくとも、もっとスマートに書けます)。
a = []
for i in range(1,100):
if(i%3 == 0):
if(i%5 == 0):
print(i)
range関数は第一引数(1)から第二引数(100)のひとつ前までの数値のリストを作成する関数です。もし、iが3で割り切れたら、もしiが5で割り切れたら……などというように条件分岐がどんどん深くなってしまいます。これをcontinueを使って書き直すと、次のようになります。
a = []
for i in range(1,100):
if(i%3 != 0):
continue
if(i%5 != 0):
continue
print(i)
行数は増えてしまいましたが、プログラムの見渡しはよくなりましたね。このように使いようによっては、breakとcontinueは便利です。個人的に私がよく使うのは「whileの条件判定にTrueをいれた無限ループ」をbreakで抜けるというものです。たとえば以下のような構造です。
while(True):
処理
if(条件):
処理
break
処理
気をつけないと無限ループから抜けられなくなりますが、適切に使えば、きれいなコードが書けます。
演習1
[[1,5,3], [2,6,4]] は、リストにリストが入っています。内側のリストの最大値をそれぞれ求めるプログラムを書いて下さい。
演習2
1から100までの整数で
- 3の倍数の時は Fizz
- 5の倍数の時は Buzz
- 3の倍数でもあり5の倍数でもあるときは FizzBuzz
と表示するプログラムを書いて下さい。
※解答はこちらをご覧ください。
次回はモジュールや関数について扱います。よろしくお願いします。
執筆者紹介伊藤裕一(ITO Yuichi)シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。 もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。 Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択 詳細(英語)はこちら |
---|