作者: 機械伯爵
日時: 2004/2/13(15:27)
 そもそも機械式/電子式計算機は、階乗計算をこなす
ために生まれました。

 では、階乗計算は、プログラミング言語でどう表現さ
れるか。

 階乗用の演算子 n! をサポートしている言語(あるの
かな?)ならともかく、そうでなければいくつかの式を
書く必要がでてきます。

 Python では、桁数無限 (半無限?) のロング整数があ
るので、桁溢れに関しては問題ありません。

 それでは、実際に 100 の階乗を計算して見ましょう。


■正統ループ式

>>> # Factorial
...
>>> n = 100
>>> f = 1
>>> while n:
...   f *= n
...   n -= 1
...
>>> f
93326215443944152681699238856266700490715968264381
62146859296389521759999322991560894146397615651828
62536979208272237582511852109168640000000000000000
00000000L
>>>

 カウンタを利用した以外は、全く以って平凡な式です。

 多分他言語では while 文は使わないと思いますが、for
文の位置づけが他言語と微妙に異なる Python で、組み
込み関数/オブジェクトを使わない方法に限定すると、
このような形になります。


■Pythonでのスタンダード

>>> f = 1
>>> for n in xrange(1,101):
...   f *= n
...
>>> f
93326215443944152681699238856266700490715968264381
62146859296389521759999322991560894146397615651828
62536979208272237582511852109168640000000000000000
00000000L
>>>

 C 言語の for 文に相当する文法は、Python では以上
のように書きます。

 組み込みオブジェクトの xrange および range 関数は、
カウンタとして利用可能ですが、文法というより定石に
近い使い方になります。


■関数を使った再帰

 ここからは、再帰を使った例を挙げます。

>>> def fac(n):
...   if n == 0:
...     return 1
...   else:
...     return n * fac(n-1)
...
>>> f = fac(100)
>>> f
93326215443944152681699238856266700490715968264381
62146859296389521759999322991560894146397615651828
62536979208272237582511852109168640000000000000000
00000000L
>>>

 関数にまとめると、グローバル変数を使わなくて良い
反面、式の形式も処理も複雑になります(Lisp や Scheme
ではないので、再帰はスタックに詰まれるため)


■lambda式を使った再帰

 Python には Lisp の lambda に似た、関数を返すキー
ワードがあります。

 これを利用すると以下のようになります。


>>> fac = lambda x:(x and fac(x-1)*x)or 1
>>> f = fac(100)
>>> f
93326215443944152681699238856266700490715968264381
62146859296389521759999322991560894146397615651828
62536979208272237582511852109168640000000000000000
00000000L
>>>

 lambda 式には文を含めることができませんので、and
演算子の短絡処理を利用して if 条件節を再現する、や
や裏技的な手法です。

 可読性にこだわる Python 言語のコミュニティーとし
ては、あまり推奨されないやりかたのようです。


■内包表記とlambda

 Python で階乗計算を一発で計算する方法は無いのか?

 そりゃ、f = 100 * 99 * 98 * ... * 3 * 2 * 1 とか
書けばできますが、あまりに頭悪げなので、色々考えた
末に、「偶然」見つけました。

>>> f = [
...   x for x in[
...     lambda k:(k and x(k-1)*k)or 1 
...   ]
... ][0](100)
>>> f
93326215443944152681699238856266700490715968264381
62146859296389521759999322991560894146397615651828
62536979208272237582511852109168640000000000000000
00000000L
>>>

 リストの内包表記の for で使用される変数は、for 文
の変数と同じくグローバル空間に作成されます。

 この変数は、lambda 式の中から参照できるため、それ
を用いて再帰ループを作るわけです。

 内包表現の構文を悪用した裏技です。

 事実、発展性も応用性もまず皆無なので、「こうすれ
ばできる」というパズル的な楽しみ以外の何者でもあり
ません。

 もっとも、スマートでさえ無いので、マネする方もい
ないでしょうが・・・

   /機械伯爵/