Pythonで素数生成

  • November 17, 2007 08:04

記録: [メモ]数学の記述ではやはりHaskellがPythonよりも上手ですが、変態的Pythonなら肉薄できそう4 TopCoder: Python - generatorで素数生成 が面白かった。

これらをみると僕はitertoolsのcountもifilterも知ってることは知ってるがうまく使えてないのが良く分かる。

itertools.count()は

>>> a=count(1)
>>> a.next()
1
>>> a.next()
2
>>> a.next()
3

のように次の整数を返すgeneratorです。

ちなみにgeneratorとはイテレータになってる関数で例のようにnextメソッドで次々に新しい値を返します。

ちなみにcountを自分で実装すると以下のようになります。return の代わりにyieldで値を返すのがポイントです。

>>> def mycount(i=0):
....     if not isinstance(i, int):
....         raise TypeError, 'an integer is required'
....     while True:
....         yield i #nextメソッドが呼ばれたときに返す値
....         i+=1
....
>>> m = mycount()
>>> m.next()
0
>>> m.next()
1
>>> m.next()
2
>>> m.next()
3
>>> m.next()
4

イテレータなのでfor構文でまわすことも出来ます。

#例
>>> for i in mycount():
....     if i>10:break
....     print i,
....
....
0 1 2 3 4 5 6 7 8 9 10

countにifilterをかけることによって特定の数字を抜かすことができるgeneratorを試しに作ってみます。

#例
>>> a = count(1)
>>> a = ifilter(lambda x:x!=4, a) #4以外を通すフィルター
>>> a.next()
1
>>> a.next()
2
>>> a.next()
3
>>> a.next() #4はフィルターを通らなかったので次の5を返す
5

ifilterは遅延評価なので 2行目で4以外の整数のリストを作っているのではなく、nextメソッドが呼ばれるたびに4じゃないかどうかを評価しています。

つまり、 4 TopCoder: Python - generatorで素数生成 の中にある以下は

>>> def sieve():
...     g = count(2)
...     while True:
...         prime = g.next()
...         yield prime
...         g = ifilter(lambda x, prime=prime: x%prime, g)
...

これは

2行目で2,3,4,5,6...とnextメソッドで次々と整数を返すgeneratorを生成してgとする。

3行目で無限ループ

4行目でgの次をprimeという変数に入れる

5行目yieldでprimeを返す

6行目gをprimeで割ったら0にならないフィルターを追加したgeneratorに変更する。

となっています。つまり

1週目

primeは2

gは2で割ったら0にならない整数を返すgenerator

2週目

primeはg.next()で2で割っても0にならない2の次の整数ということで3

gは2で割っても、3で割っても0にならない3の次の整数を生成するgenerator

3週目

primeはg.next()で2でも3で割っても0にならない3の次の整数ということで4じゃなくて5

gは2,3,5で割っても0にならない5の次の整数を生成するgenerator

4週目

primeはg.next()で2,3,5で割っても0にならない5の次の整数ということで6じゃなくて7

gは2,3,5,7で割っても0にならない7の次の整数を生成するgenerator

と一巡するたびに増えていくフィルターを通るかどうかをg.next()するたびに計算しているわけですね。

再帰を避ける方法も含めて実にエレガンス。すばらしい。