2012年9月9日日曜日

プロジェクトオイラー 問58

プロジェクトオイラーの問題をpythonでやってみます。
日本語翻訳サイトは、プロジェクトオイラー日本語 でネット検索してください。

問58「渦巻きに配置された格子の対角線上にある素数の数を調べ上げよ」
1から始めて, 以下のように反時計回りに数字を並べていくと,
辺の長さが7の渦巻きが形成される.


37 36 35 34 33 32 31
38 17 16 15 14 13 30
39 18  5  4  3 12 29
40 19  6  1  2 11 28
41 20  7  8  9 10 27
42 21 22 23 24 25 26
43 44 45 46 47 48 49


面白いことに, 奇平方数が右下の対角線上に出現する.
もっと面白いことには, 対角線上の13個の数字のうち, 8個が素数である.
ここで割合は8/13 ≒ 62%である.


渦巻きに新しい層を付け加えよう. すると辺の長さが9の渦巻きが出来る.
以下, この操作を繰り返していく.


対角線上の素数の割合が10%未満に落ちる最初の辺の長さを求めよ.

-----


注意!!!
自力で解きたい人へ
以降の記述には解法に関するネタバレが含まれます。





私の解答例は以下です。畳んでいます。
def h(n):
	if n<2 or (not n%2): return False
	for i in xrange(3, n, 2):
		if i*i>n: break
		if not n%i: return False
	return True

def g(n):
	if n<1 or (not n%2): return []
	elif n==1: return [1]
	s, t = n-1, n-2
	u = t*t+s
	return [u, u+s, u+s*2, u+s*3]

def f(p):
	i, u = 1, 0
	while not (0<float(u)/(4*(i//2)+1)<p):
		i += 2
		u += len([j for j in g(i) if h(j)])
	return i

p = 0.1
print f(p)


3つの関数を使っています。

1.関数h(n)
・素数判定です。2と、3以上の奇数で割り切れるか判定します。

・if n<2 or (not n%2): return False
 2以下、または2で割り切れる場合、素数ではありません。
 %演算子は割り算の余り、余り0は論理的にFalseです。

・for i in xrange(3, n, 2):
 素因数候補として、3以上の奇数を順にループ変数iに設定します。
 割り切り判定の割る数として使用します。
 終了値をnにしていますが、後続ロジックでnになる前に必ずループを終了します。

・if i*i>n: break
 引数nを構成する素因数の限界値は√nです。これを超える素因数はあり得ません。そこで、素因数候補がこの限界値まで達したら、ループを抜けます。

・if not n%i: return False
 割り切り判定です。割り切れたら、素数でないのでFalseを返します。
 %演算子は割り算の余り、余り0は論理的にFalseです。

・return True
 割り切れる素因数候補が無く、素数なのでTrueを返します。

2.関数g(n)
 問28では同じ渦巻きの四隅和を返す関数を作りましたので、
 これの返り値を四隅和から四隅値のリストに修正しました。

3.関数f(n)
・問題文の渦巻きについて、対角線上の値のうち素数の割合が引数p未満になる最初の辺の長さを返します。

・i, u = 1, 0
 初期設定です。iは渦巻きの辺の長さ、uは対角線上の素数の個数です。

・while not (0<float(u)/(4*(i//2)+1)<p):
 「float(u)/(4*(i//2)+1)」は、対角線上の素数の割合です。
 この値が0と引数pの間に入ったら、ループ終了です。
 uは対角線上の素数の個数で、割り算で小数点以下まで求めるので、float関数で浮動小数点型にしています。
 「(4*(i//2)+1)」は対角線上の値の個数です。辺の長さiにより決まります。

・i += 2
 辺の長さは渦巻きを1つ巻くごとに、2ずつ増えます。

・u += len([j for j in g(i) if h(j)])
 内包表記です。
 for文で関数g(i)にて取得した辺の長さiの四隅値の1つひとつをループ変数jに設定し、後ろのif文に渡し、関数h(j)にて素数判定し素数だけをfor文の前に渡します。
 for文の前はjをリスト化し、len関数でその要素数を数えて、uに累積します。

・return i
 whileループ終了時点の辺の長さiを呼び出し元に返します。

4.関数の外
・p = 0.1
 print f(p)
 問題に合うように10%=0.1を関数fに渡し、その返り値を表示します。
 
解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
26241

(追記)
・20130129 ネタバレ注意の追記など

0 件のコメント:

コメントを投稿