素数を表示してみる_プログラミング2
さて、ここからは、プログラム主体でいこうと思います。
当初の予定では、
- 100までの数を並べる
- 他の数でわる
- 割り切れたら消す
- 割り切れなかったら残す
- 残った数を表示する
を作るはずでした。そうなんですね。前章までで、やっと「100までの数を並べる」ができました。同じように(同じプロセスを踏んで)他の処理も書いていきます。
太字になっている箇所が今回追加したところです。
- 100までの数を、一時保存できる変数(NaturalNumber)を用意する
- 100までの素数を、一時保存できる変数(PrimeNumber)を用意する
- 反復処理の回数を、一時保存できる変数(Counter)を用意する
- 変数(Counter)に1を代入する
- 変数(Counter)が100になるまで、以下の処理を繰り返す
- 変数(NaturalNumber)に、変数(Counter)の値を代入する
- 変数(Counter)の値に1を足す
- 変数(Counter)に変数(Counter)の値を代入する
- 反復処理の回数を、一時保存できる変数(Counter2)を用意する
- 変数(Counter)に0を代入する
- 変数(Counter)が100になるまで、以下の処理を繰り返す
- 変数(Counter2)に2を代入する
- 変数(Counter2)が(NaturalNumber@Counter)未満の間、以下の処理を繰り返す
- 変数(NaturalNumber@Counter)を変数(Counter2)で割った余りを出す1)
- 余りが0なら
- breakする
- 余りが0以外なら
- 変数(Counter2)の値に1を足す
- 変数(Counter2)に変数(Counter2)の値を代入する
- breakしなかったら
- 変数(PrimeNumber)に変数(Counter)の値を代入する
- 変数(Counter)の値に1を足す
- 変数(Counter)に変数(Counter)の値を代入する
- 変数(PrimeNumber)を表示する
このように、言語仕様を加味した上で設計することをプログラム設計と言います。プログラム設計書におこすことによって、実行すべきテストが明確になります。
では、これをpythonで書いてみましょう。解説はその後です。
- #100までの数を、一時保存できる変数(NaturalNumber)を用意する
- NaturalNumber = []
- #100までの素数を、一時保存できる変数(PrimeNumber)を用意する
- PrimeNumber = []
- #反復処理の回数を、一時保存できる変数(Counter)を用意する
- Counter = 0
- #変数(Counter)に1を代入する
- Counter = 1
- #変数(Counter)が100になるまで、以下の処理を繰り返す
- while Counter <= 100:
- #変数(NaturalNumber)に、変数(Counter)の値を代入する
- NaturalNumber.append(Counter)
- #変数(Counter)の値に1を足す
- #変数(Counter)に変数(Counter)の値を代入する
- Counter = Counter + 1
- #反復処理の回数を、一時保存できる変数(Counter2)を用意する
- Counter2 = 0
- #変数(Counter)に0を代入する
- Counter = 0
- #変数(Counter)が100になるまで、以下の処理を繰り返す
- while Counter < 100:
- #変数(Counter2)に2を代入する
- Counter2 = 2
- #変数(Counter2)が(NaturalNumber@Counter)未満の間、以下の処理を繰り返す
- while Counter2 < NaturalNumber[Counter]:
- #変数(NaturalNumber@Counter)を変数(Counter2)で割った余りを出す
- #余りが0なら
- if NaturalNumber[Counter] % Counter2 == 0 :
- #breakする
- break
- #余りが0以外なら
- else :
- #変数(Counter2)の値に1を足す
- #変数(Counter2)に変数(Counter2)の値を代入する
- Counter2 = Counter2 + 1
- #breakしなかったら
- else :
- #変数(PrimeNumber)に代入する
- PrimeNumber.append(NaturalNumber[Counter])
- #変数(Counter)の値に1を足す
- #変数(Counter)に変数(Counter)の値を代入する
- Counter = Counter + 1
- #変数(PrimeNumber)を表示する
- print (PrimeNumber)
解説
5行目
PrimeNumber = []
では、素数を保存しておくための変数を宣言します。ここで、大事なことは、目的別に変数を作るということです。最初に宣言した「NaturalNumber」は100までの数字を表示するための変数で、「PrimeNumber」は素数を保管しておくための変数ですので、目的が違います。もちろん、同じように数字を保管しておくためのものですから、「NaturalNumber」を流用して、素数まで保管しようと考える人も居るかも知れませんが、それはセンスがありません。
目的の違う変数を別の変数として定義するということはとっても大事なことなのです。たまに、少ない変数でコーディングした方が効率が良いと勘違いしてる人がいますが、そんなもんは言語道断です。
最後に素数を表示しなければいけないのですから、素数を一時保管しておくための変数を新しく定義しておきましょう。
14行目~21行目では、NaturalNumberに1~100までの整数を追加しています。これは前章で行った内容です。
24行目の
Counter2 = 0
では、この後、繰り返し処理を2回行うので、繰り返し処理用の変数を1つ追加しています。
ここからが鬼門ですね。一発理解は難しいところです。
まずは大枠を見ていきましょう。29行目~57行目では、繰り返し処理が入れ子2)になっています。つまり、繰り返し処理中に別の繰り返し処理が発生しています。
文字で書くのが面倒なので絵でいきます。
出でよ!mspaint!3)
繰り返し処理の入れ子というのはこんな感じ。外側の繰り返し処理が実行されると、中の繰り返し処理が実行されます。このとき、中の繰り返し処理は終了条件を満たすまで何度も実行されます。で、中の繰り返し処理が終了すると、赤い線のように中の繰り返し処理から外側に出て行きます。でも、まだ、外側の繰り返し処理の終了条件は満たしていませんから、外側の繰り返し処理は自分の中身の処理を実行しようとします。中身の処理には、中の繰り返し処理がありますので、、中の繰り返し処理は終了条件を満たすまで何度も実行されます。で、中の繰り返し処理が終了すると、赤い線のように中の繰り返し処理から外側に出て行きます。でも、まだ、外側の繰り返し処理の終了条件は満たしていませんから、外側の繰り返し処理は自分の中身の処理を実行しようとします。中身の処理には、中の繰り返し処理がありますので、、中の繰り返し処理は終了条件を満たすまで何度も実行されます。で、中の繰り返し処理が終了すると、赤い線のように中の繰り返し処理から外側に出て行きます。でも、まだ、外側の繰り返し処理の終了条件は満たしていませんから、外側の繰り返し処理は自分の中身の処理を実行しようとします。中身の処理には、中の繰り返し処理がありますので、、中の繰り返し処理は終了条件を満たすまで何度も実行されます。で、中の繰り返し処理が終了すると、赤い線のように中の繰り返し処理から外側に出て行きます。でも、まだ、外側の繰り返し処理の終了条件は満たしていませんから、外側の繰り返し処理は自分の中身の処理を実行しようとします。・・・・で、外側の繰り返し処理の終了条件を満たすと青い線のように繰り返し処理の外側に出て行きます。
たぶん、設計するときに一番頭を使うところがこの繰り返し処理の入れ子なのではないでしょうか。
では、なぜ繰り返し処理の入れ子が必要なのでしょうか。答えは難しそうで簡単なんです。
まず、「NaturalNumber」から整数1~100までを取り出している間に、そのそれぞれの整数について割り切れる整数があるかどうかを調べないといけません。これは「場合の数の積の法則」と同じですね。それぞれの場合について別の事象を(1つずつ)調べないといけないのです。積の法則なので「かけ算」でいいのでは?と思うかも知れませんが、プログラムで扱える数値は単一の値が基本です。そして、「場合の数」というのは集合演算なのですね。
ここの分別はとっても大事なことなのです。プログラムで演算できるデータは、1件ごと(つまり集合の要素1つ1つ)だけです。ですから、配列「NaturalNumber」という集合の要素1つ1つをわざわざ取り出して(外側の繰り返し処理)、要素に対して割り切れるかどうかを調べないといけません。割り切れる整数があるかどうかを調べるには、2~自身の数未満で割り切れるかどうかを1件ずつ調べる必要があります。例えば、NaturalNumberが9のときは、2~8で割り切れるかどうかを調べる必要がありますね。
このように、1件1件の要素それぞれに対して、別の1件1件と照らし合わせるという行為はプログラムではよくあることなのです。
これに対して、集合を集合のまま扱えるようにしようとしたのがデータベース。また、データベースに格納されたデータを集合演算できるようにするのが「SQL」と呼ばれる言語です。
では、繰り返し処理のもう少し細かいところを見ていきましょう。
27行目の
Counter = 0
では、Counterに0を代入しています。CounterはNaturalNumberの添え字としても使います。配列の添え字は0から始まります。つまり、配列の中に100個のデータが入っていると、添え字は0~99となります。いま、NaturalNumberには1~
~100までのデータが入っていますので、NaturalNumber[0]~NaturalNumber[99]まであるということです。
配列の要素を1件ずつ処理する場合は、繰り返し処理の条件式に使用する変数を、配列の添え字に流用します。これは、配列1件ごとに1つの繰り返しになりますので、目的が同じとなります。
30行目
while Counter < 100:
は0~99までを繰り返したいので、「⇐」ではなく、「<」とします。こうすることで、Counterが0~99までの間、繰り返し条件を満たすことになります。
33行目
Counter2 = 2
Counter2は割る数を繰り返すためのものです。ですから、1から始めるのではなく、素数の2から始めます。また、このタイミングでCounter2に2を代入しておかないと、外側の繰り返し処理2度目のときにCounter2が2から始まりません。変数の値をリセットするタイミングというのはとっても大事なことなのです。繰り返し処理であれば、基本的には、繰り返し処理の直前にリセット(初期化)することになります。
36行目
while Counter2 < NaturalNumber[Counter]:
これが入れ子の繰り返し処理となります。Counter2は2から始まりますが、Counterは0から始まります。外側1回目の繰り返し処理のときは、Counterが0なので、
2 < NaturalNumber[0]
が評価されます。で、NaturalNumber[0]は配列の1番目ですので、値は1ですね。つまり
2 < 1
が評価されることになります。これは評価の結果がFalseですので、繰り返し処理は行われません。続いて外側2回目の繰り返し処理にうつり、Counterが1になりますので、
2 < NaturalNumber[1]
が評価されます。同様に、
2 < 2
が評価されることになります。これは評価の結果がFalseですので、繰り返し処理は行われません。続いて外側3回目の繰り返し処理にうつり、Counterが2になりますので、
2 < NaturalNumber[2]
が評価されます。同様に、
2 < 3
が評価されることになります。このとき、評価の結果がTrueとなるので、入れ子になった繰り返し処理を実行されることになります。
このように、繰り返し処理の入れ子には、無駄な作業(実行はされないが、評価はする必要がある)が対象なりともともないます。この評価の回数を少しでも減らしたりすると、実行速度が速くなります。実行速度を速くすることを目的にコードを修正することをコードチューニング4)と言うこともあります。プログラムの世界でも無駄を省くというのはとっても大事なことなのです。逆に言えば、無駄な処理や評価をしていることを知っておくことが大事なのです。
では次の40行目
if NaturalNumber[Counter] % Counter2 == 0 :
これは、IF文などとよく言われます。条件によって処理をわけたいときに使います。一般的にはこのように書きます。
if (条件式)
条件式は繰り返し処理と同じで、boolean型の戻りである必要があります。条件式がtureであれば、直後に書いた処理が実行されます。では、40行目のif文では余りを出すという演算の「%」と、比較演算子の「==」が書かれています。
NaturalNumber[Counter] % Counter2 #ここでは、余りを出す演算をする == #ここでは 左辺と右辺が等価であることを評価する
という形になっています。実は、演算子には優先順位というもんが設けられています。四則演算が先に処理され、その後に比較演算子が処理されます。つまり、余りを算出した後で、評価されるという流れになります。
大事なことがもう一つ、比較演算子以外の処理が記載されている場合は、ちゃんと処理されるということです。このことは、今後、関数を条件式に使うときに1つのポイントになってきます。一応、例だけ出しておきます。
- def test_function():
- print ("実行される")
- return 1
- print("ここから処理が始まります")
- if test_function() == 1 :
- print ("終了")
実行結果
- ここから処理が始まります
- 実行される
- 終了
今は、上から順番に実行されるのに、なぜかprintの実行順序が違うね~ぐらいに思って下さい。
では次に、44行目の
else :
ですが、これはif文のセットです。
if (条件式) 条件式がtrueだったときの処理 else 条件式がfalseだったときの処理
という形で書くことが多いです。falseの時に何も処理する必要がなければ、書く必要がありません。if文が条件分岐が基本ですので、条件が合致すれば処理A、合致しなければ処理Bというように処理を分岐させるのが一般的です。
- #変数(NaturalNumber@Counter)を変数(Counter2)で割った余りを出す
- #余りが0なら
- if NaturalNumber[Counter] % Counter2 == 0 :
- #breakする
- break
- #余りが0以外なら
- else :
- #変数(Counter2)の値に1を足す
- #変数(Counter2)に変数(Counter2)の値を代入する
- Counter2 = Counter2 + 1
では、余りが0であれば、breakしています。breakというのは、繰り返し処理を終了するという意味です。なので、1つでも割り切れる数が見つかったら、繰り返す必要はありませんので、breakしています。ですが、Counter2で割り切ることができなかったら、次の値を検証する必要があるので、elseの処理で、Counter2をインクリメントしています。
51行目の
else :
はwhileのセットのelseです。pythonでは、ブロックをインデントで表現しますので、51行目と同じインデントの36行目のwhile文とセットだということがわかります。実は、while文のセットのelse文というのはpython独特の言語仕様です。他の言語ではあまり見られません。while文のセットのelse文というのは、breakしなかったらという意味です。つまり、繰り返し処理の途中で、1度もbreakされなかった場合の処理を書くことができます。このプログラムでは、1度もbreakされない場合は、1度も余りが0にならなかったという意味ですので、素数ということになります。素数であれば、「PrimeNumber」に一時保管する必要がありますので、else文に
PrimeNumber.append(NaturalNumber[Counter])
と記述しています。
だいたいこんな感じですね。
さて、今のプログラムでは、変数「counter」を2つのwhile文で使用しています。これは、効率が悪いのです。いま繰り替え処理が全部で3つありますが、同じCounterを使用している繰り返し処理を1つにまとめられないかを考えていきます。
先ほども言ったとおり、while文はどうしても無駄な処理や評価が実行されることがあります。なので、while文を少なくするだけで、それだけ実行速度は速くなります。そこで、同じCounterを使用しているwhile文に着目しチューニングをしていきます。
では、次回は、素数を表示してみる(チューニング)です。