Pythonには、for文またはwhlie分の直後にelseを書く構文が可能である。
for、whileはループするのだが、その終わり方に2種類ある。予定したループを正常に終える場合と、break文によりループを中断する2つの場合である。
まず、Pythonの例を見よう。
# [Python] リストを利用した素数の計算
def primes(p_num):
p_list = []
n = 2
while len(p_list) < p_num:
for p in p_list:
if n % p == 0:
break
else:
p_list.append(n)
n += 1
return p_list
elseはifに対応したものではなく、forに対応したものになっていることに注意しよう。
この場合、forの直後にelseがあり、else節には1つの文(nを素数リストに加える)がある。
このelse節は、forのループが正常終了した(breakしなかった)場合に実行される。
forループの素数pは、素数リストの素数を順番にセットし、素数候補の整数nが素数pの倍数だったらnは素数でないので、nは直ちにインクリメントする。
forループの最後までいってもループをbreakしなかった(素数リストのどの素数の倍数でもなかった)場合には、else節が実行され、nを素数リストに加える。
これを、C言語に書き換えてみよう。
前回のCython(9)でC言語のプログラムにしたけれど、そのときは次のように書いた。
// 素数の計算の本体
while( p_len < p_num ) {
for( idx=0; idx < p_len; ++idx ) {
p = p_array[idx];
if( n % p == 0 )
break;
}
if( idx >= p_len ) {
p_array[p_len++] = n;
}
++n;
}
forループの直後に、ループの抜け方を判定するためにif文があり、そのブロックにfor-elseのelse節の中を書いている。forループの直後の終了の再判定は無駄である。
ということで、書き直してみた。
// 素数の計算の本体
while( p_len < p_num ) {
for( idx=0; idx < p_len; ++idx ) {
p = p_array[idx];
if( n % p == 0 )
goto for_break;
}
p_array[p_len++] = n;
for_break:
++n;
}
forループの後ろに、breakしたときの飛び先となるところにラベルfor_breakを置き、素数で割り切れた時はbreakするのではなく、このラベルに飛ぶようにした。
forループを最後まで終えた時は、素数候補nが素数リストに追加され、for_breakに達してさらにnがインクリメントされる。
もちろん、gotoを使った方が若干早かった。しかし、やはりgotoは使いたくない。
では、CythonはどのようなCのコードに変換するのであろうか。
今回の最初のPythonのソースコードの拡張子をpyxに変更しただけの横着なCythonを用意し、コンパイルしてみると、次のようになった。
1993 /* "primes_list.pyx":9
1994 * for p in p_list:
1995 * if n % p == 0:
1996 * break # <<<<<<<<<<<<<<
1997 * else:
1998 * p_list.append(n)
1999 */
2000 goto __pyx_L6_break;
やはりgotoを使っていた。
速度面を考えると、gotoを使うことになる。
このような機械的なgotoの使い方は混乱を起こすこともないので、問題はないだろう。
ところで、for-else, while-else構文のあるプログラミング言語はPythonの他に存在するのだろうか?
ここでは詳細は書かないが、素数リストを最後まで調べるのはかなりの無駄であろう。nを割り切れる素数は、sqrt(n)以下で十分なので、上手に工夫すると速くなると思われるが、そこは自分でやってみよう。