Cython(10) for-else, while-else構文


2022年 09月 29日

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)以下で十分なので、上手に工夫すると速くなると思われるが、そこは自分でやってみよう。