2012年9月9日日曜日

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

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

問57「2の平方根の連分数展開を調べあげよ」
2の平方根は無限に続く連分数で表すことができる.
 √ 2 = 1 + 1/(2 + 1/(2 + 1/(2 + ... ))) = 1.414213...
最初の4回の繰り返しを展開すると以下が得られる.
1 + 1/2 = 3/2 = 1.5
1 + 1/(2 + 1/2) = 7/5 = 1.4
1 + 1/(2 + 1/(2 + 1/2)) = 17/12 = 1.41666...
1 + 1/(2 + 1/(2 + 1/(2 + 1/2))) = 41/29 = 1.41379...

次の3つの項は99/70, 239/169, 577/408である.
第8項は1393/985である.
これは分子の桁数が分母の桁数を超える最初の例である.

最初の1000項を考えたとき, 分子の桁数が分母の桁数を超える項はいくつか?


-----


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






私の解答例は以下です。畳んでいます。
def g(n):
	if n<1: return 1, 1
	a, b = g(n-1)
	return a+b*2, a+b

def f(n):
	import sys
	sys.setrecursionlimit(n+10)
	u = 0
	for i in xrange(n+1):
		a, b = g(i)
		if len(str(a))>len(str(b)): u += 1
	return u

n =1000
print f(n)


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

1.関数g(n)
・問題文の第n項の値を、分子と分母で返します。
・問題文から、
 L1 = 1 + 1/2
 L2 = 1 + 1/(2 + 1/2)
 L3 = 1 + 1/(2 + 1/(2 + 1/2))
 L4 = 1 + 1/(2 + 1/(2 + 1/(2 + 1/2)))
 ・・・

「1+(1/x)」のxの部分に1世代前の値が入るようにして再帰関数を作ります。
 L0 = 1
 L1 = 1 + 1/2 ... (A)
   = 1 + 1/(1 + L0)
  
 L2 = 1 + 1/(2      + 1/2)
   = 1 + 1/(1 + (1 + 1/2)) ...(B)
   = 1 + 1/(1 +  L1)
  
 L3 = 1 + 1/(2      + 1/(2      + 1/2))
   = 1 + 1/(1 + (1 + 1/(1 + (1 + 1/2))))...(C)
   = 1 + 1/(1 + L2)

・・・というようになります。
ポイントは、各世代の第1項が1なので入れ子になる「1+(1/x)」のx自身も第1項は1からはじめることです。
問題文ではこの部分は2なので1+1に分けて式変形します。
(B)全体を「1+(1/x)」と見ると、そのxの部分が「1+(A)」です。
(C)全体を「1+(1/x)」と見ると、そのxの部分が「1+(B)」です。

次に、1世代前の値の分母子から、当世代の値の分母子を求めておきます。
1世代前の値がa/b、つまり分子a、分母bだとすると、当世代の値は、
  1 + 1 / (1 + a/b)  かっこ内を分母bで通分して、
 = 1 + 1 / ((a+b) / b) 第2項の分母子をb倍して、
 = 1 + (b / (a+b))   全体を分母(a+b)で通分して、
 = ((a+b) + b) / (a+b) 整理して
 = (a+b*2) / (a+b)   以上より、分子a+b*2、分母a+b になります。
 
以上をふまえて、
・if n<1: return 1, 1
 g(n)は1世代前の自分自身を呼び出す再帰関数なので、
 世代さかのぼりの終了条件として、nが1より小さくなったら、
 L0 = 1/1 で、分子1、分母1を返し、さかのぼりは終了します。

・a, b = g(n-1)
 1世代前の分子a、分母bを受け取ります。
 
・return a+b*2, a+b
 当世代の分子a+b*2、分母a+bを返します。

2.関数f(n)
・問題文に合う最初のn項までのうち、
 分子の桁数が分母の桁数を超える場合の件数を返します。

・import sys
 sys.setrecursionlimit(n+10)
 再帰呼び出しの深さの限界値を設定します。
 初期値は1000なので当問題では足りません。
 限界を超えたときのエラーメッセージは以下のとおりです。
 「RuntimeError: maximum recursion depth exceeded」
 ここでは上記エラーが発生しないように、
 引数nに余裕をもたせた値を再帰呼び出し深さ限界値として設定しています。
 
・u = 0
 求める件数のカウンターです。0クリアしておきます。

・for i in xrange(n+1):
 問題文にある項番号をループ変数iとして設定します。

・a, b = g(i)
 当世代の分子、分母をそれぞれa、bとして取得します。

・if len(str(a))>len(str(b)): u += 1
 分子a、分母bをそれぞれstr関数で文字型にしてlen関数で文字列長(桁数)を取得し、
 分子の桁数が分母の桁数を超える場合に、カウントアップします。

3.関数の外
・n =1000
 print f(n)
 問題に合うように引数1000を関数fに渡し、戻り値を表示します。
 

なお、関数g(n)の返り値の分子分母はその桁数違いを判定するので、
公約数が存在すのであれば約分して最も簡単な分数にしておく必要があります。
そこでこの分子分母に公約数が無いことを確認をしておきます。

まず、第1項のa=3,b=2には明らかに公約数が存在しません。
次に、
第n-1項の分子分母a,bに公約数が存在しない条件のもと、
第n項の分子分母a+b*2, a+bに公約数pが存在すると仮定すると、
公約数pを使用して、それぞれ以下のように表せます。
a+b*2 = pm ...(1)
a+b = pn ...(2)
ここで、
(1)-(2)より、
(a+b*2) - (a+b) = pm - pn
 b = p(m-n) ...(3)
(2)*2-(1)より、
(a+b)*2 - (a+b*2) = 2*pn - pm
 a = p(2*n-m) ...(4)
となって、(3)(4)よりa,bにも公約数pが存在することになり、
a,bに公約数が存在しないという条件と矛盾します。
以上より、a+b*2,a+bには公約数が存在しません。

なので、関数g(n)の返り値はそのまま桁数判定に使っています。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
153

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

2012年9月2日日曜日

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

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

問56「aのb乗の形の自然数について桁の和の最大値を求めよ」
Googol (10100)は非常に大きな数である: 1の後に0が100個続く.
100100は想像を絶する. 1の後に0が200回続く.
その大きさにも関わらず, 両者とも桁の和は1である.

a, b < 100について自然数abを考える. 桁の和の最大を答えよ.

-----


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






私の解答例は以下です。畳んでいます。
def f(n):
	L = [0, 0, 0]
	for i in xrange(n):
		for j in xrange(n):
			s = sum([int(k) for k in list(str(i**j))])
			if L[2]<s: L = [i, j, s]
	return L

n = 100
i,j,t = f(n)
print t," = ", i, "**",j


1.関数f(n):
・n未満の整数i,jについて、iのj乗の各桁の合計値が最大になるときの
 i,j,およびiのj乗の各桁の合計値を返します。

・for i in xrange(n):
  for j in xrange(n):
 ループ変数i,jにはn未満の整数を1つずつ設定します。

・s = sum([int(k) for k in list(str(i**j))])
 sはiのj乗の各桁の合計値です。
 **演算子は累乗です。i**jでiのj乗です。
 これをstr関数で各桁ごとの文字のリストにします。
 内包表記で、このリストから1つずつループ変数kに設定しint関数で数値に変換した値のリストを作ります。
 そしてsum関数でこのリストの合計値を計算します。

・if L[2]<s: L = [i, j, s]
 iのj乗とその桁和値sについて、リストLにこの順でとっておきます。
 L[2]は桁和値です。
 この桁和値についてリストL保存値よりも計算値sが大きい場合、
 その計算値sとこのときのi,jをリストLに保存します。

2.関数の外
・n = 100
 i,j,t = f(n)
 print t," = ", i, "**",j
 問題に合うように100未満の数値を対象にして、
 関数fで問題に合う数値のリストを取得し、表示します。
 
解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
972  =  99 ** 95


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

Project Euler - Problem 55

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 55
47とその反転を足し合わせると, 47 + 74 = 121となり, 回文数になる.

全ての数が素早く回文数になるわけではない. 349を考えよう,
 1.349 + 943 = 1292
 2.1292 + 2921 = 4213
 3.4213 + 3124 = 7337
349は 3回の操作を経て回文数になる.

まだ証明はされていないが,

196のようないくつかの数字は回文数にならないと考えられている.
反転したものを足すという操作を経ても回文数にならないものを

Lychrel数と呼ぶ.
先のような数の理論的な性質により, またこの問題の目的のために,
Lychrel数で無いと証明されていない数はLychrel数だと仮定する.

更に, 10000未満の数については,常に以下のどちらか一方が成り立つと仮定してよい.
 1.50回未満の操作で回文数になる
 2.まだ誰も回文数まで到達していない

実際, 10677が50回以上の操作を必要とする最初の数である:
4668731596684224866951378664 (53回の操作で28桁のこの回文数になる).

驚くべきことに, 回文数かつLychrel数であるものが存在する. 最初の数は4994である.
10000未満のLychrel数の個数を答えよ.

注: 2007/04/24にLychrel数の理論的な性質を強調するために文面が修正された.


-----


私の解答例は以下です。畳んでいます。
def g(n):
	s = str(n)
	for i in xrange(len(s)//2):
		if s[i]!=s[-(i+1)]: return False
	return True

def f(n):
	L = []
	for i in xrange(n):
		s = i
		for j in xrange(50):
			s += int("".join(list(str(s))[::-1]))
			if g(s): break
		else: L.append(i)
	return L

n = 10000
L = f(n)
print len(L)


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

1.関数g(n):
・回文数かどうかチェックします。
・s = str(n)
 引数nを文字型に変換し、文字列sとします。

・for i in xrange(len(s)//2):
 ループ変数iは、文字列sの真ん中まで、位置番号を1つずつ設定します。
 文字列sが奇数桁の場合、真ん中の文字はチェック対象外です。
 len関数で文字列sの長さを取得し、//演算子で割り算の商を求めています。

・if s[i]!=s[-(i+1)]: return False
 文字列sの前からi番目の文字と後ろからi番目の文字を比べ、
 違っていたらFalseを返し、同じならば位置iを1つ進めます。

・return True
 上記でFalseでループを抜けなかったので、前からと後ろからの同じ位置の文字がすべて同じで回文数なので、Trueを返します。

2.関数f(n)
 n未満のLychrel数のリストを返します。
・for i in xrange(n):
 ループ変数iには0から引数nまでの値を1つずつ設定します。

・for j in xrange(50):
 50回を限度に、以下の反転数累積とその回文数チェックをします。

・s += int("".join(list(str(s))[::-1]))
 数値sをstr関数で文字型にして、list関数で1文字ずつのリストにして、[::-1]で反転します。
 "".joinでその反転リストを結合して反転数文字列にして、int関数で数値に直して元の反転前の値に累積していきます。
リスト[開始位置:終了位置:刻み]なので、リスト[::-1]で反転リストになります。
・if g(s): break
 上記で計算した反転数累積値について、関数gにて回文数チェックをします。
 回文数ならば、問題に合わないのでループ変数iを次に進めます。

・else: L.append(i)
 for文のelse節は、forループが途中抜けせず最後まで回って要素が尽きた場合に処理します。
 ここでelse節に入るには、反転数累積を50回やっても1つも回文数にならなかったということで、まさにLychrel数なので、このようなiはリストLに追加します。

3.関数の外
・n=10000
 L = f(n)
 print len(L)
 問題に合うように10000未満の数値を対象にして、
 関数fで問題に合う数値のリストを取得し、len関数でその件数を取得し、表示します。
 

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
249

Project Euler - Problem 54

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 54
カードゲームのポーカーでは, 手札は5枚のカードからなりランク付けされている.
役を低い方から高い方へ順に並べると以下である.

0.役無し: 一番値が大きいカード
1.ワン・ペア: 同じ値のカードが2枚
2.ツー・ペア: 2つの異なる値のペア
3.スリーカード: 同じ値のカードが3枚
4.ストレート: 5枚の連続する値のカード
5.フラッシュ: 全てのカードが同じスート
 (注: スートとはダイヤ・ハート・クラブ/スペードというカードの絵柄のこと)
6.フルハウス: スリーカードとペア
7.フォーカード: 同じ値のカードが4枚
8.ストレートフラッシュ: ストレートかつフラッシュ
9.ロイヤルフラッシュ: 同じスートの10, J, Q, K, A

ここでカードの値は小さい方から
2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, Aである.
(訳注:データ中で10は'T'と表される)

もし2人のプレイヤーが同じ役の場合には, 役を構成する中で値が最も大きいカードによってランクが決まる:
例えば, 8のペアは5のペアより強い (下の例1を見よ).
それでも同じランクの場合には (例えば, 両者ともQのペアの場合),
一番値が大きいカードによってランクが決まる (下の例4を見よ).
一番値が大きいカードが同じ場合には, 次に値が大きいカードが比べれられ, 以下同様にランクを決定する.

例:
プレイヤー1プレイヤー2勝者
15H 5C 6S 7S KD
(5のペア)
2C 3S 8S 8D TD
(8のペア)
プレイヤー2
25D 8C 9S JS AC
(役無し, A)
2C 5C 7D 8S QH
(役無し, Q)
プレイヤー1
32D 9C AS AH AC
(Aのスリーカード)
3D 6D 7D TD QD
(ダイヤのフラッシュ)
プレイヤー2
44D 6S 9H QH QC
(Qのペア, 9)
3D 6D 7H QD QS
(Qのペア, 7)
プレイヤー1
52H 2D 4C 4D 4S
(4-2のフルハウス)
3C 3D 3S 9S 9D
(3-9のフルハウス)
プレイヤー1

poker.txtには1000個のランダムな手札の組が含まれている.
各行は10枚のカードからなる(スペースで区切られている):
最初の5枚がプレイヤー1の手札であり, 残りの5枚がプレイヤー2の手札である.
以下のことを仮定してよい

  • 全ての手札は正しい (使われない文字が出現しない. 同じカードは繰り返されない)
  • 各プレイヤーの手札は特に決まった順に並んでいるわけではない
  • 各勝負で勝敗は必ず決まる
1000回中プレイヤー1が勝つのは何回か?

-----

私の解答例は以下です。畳んでいます。
def L2dic(L):
	d = {"T":10,"J":11,"Q":12,"K":13,"A":14}
	dN, dM = {}, {}
	for s in L:
		if s[0]>"9": t = d[s[0]]
		else: t = int(s[0])
		dN[t] = dN.get(t, 0)+1
		dM[s[1]] = dM.get(s[1], 0)+1
	return dN, dM

def g(L):
	dN, dM = L2dic(L)
	maxN, minN, nN, maxNV = max(dN), min(dN), len(dN), max(dN.values())
	nM = len(dM)
	difN = maxN-minN
	if nN==5:
		if difN>4:
			if nM>1: t = 0	#0:High Card
			else: t = 5		#5:Flush
		elif nM>1: t = 4		#4:Straight
		elif minN<10: t = 8	#8:Straight Flush
		else: t = 9			#9:Royal Straight Flush
	elif nN==4: t = 1		#1:One Pair
	elif nN==3:
		if maxNV==3: t = 3	#3:Three cards
		else: t = 2			#2:Two pairs
	elif nN==2:
		if maxNV==4: t = 7	#7:Four in a kind
		else: t = 6			#6:Full House

	s = str(t)
	for k,v in sorted(dN.items(), key=lambda (k,v):(v,k), reverse=True):
		s += str(k).zfill(2) * v
	return s

def f(sIn):
	t = 0
	for s in open(sIn).readlines():
		L = s.split()
		p1, p2 = g(L[:5]), g(L[5:])
		t += int(p1>p2)
	return t

import os, sys
sIn = os.path.join(os.path.dirname(sys.argv[0]), "poker.txt")
print f(sIn)


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

1.関数L2dic(L):
・カードのリストから、番号辞書とマーク辞書を返します。
 番号辞書dNは、カードの番号をキーにその枚数を値に持つ辞書です。
 マーク辞書dMは、カードのマークをキーにその枚数を値に持つ辞書です。
・9を超える番号でも並べ替え可能にするため、
 T,J,Q,K,Aを順に10から14までの連番に置き換えます。

・d = {"T":10,"J":11,"Q":12,"K":13,"A":14}
 10以上の値のための変換辞書です。

・for s in L:
 カードのリストから1枚ずつ取り出し、ループ変数sとします。
 sは2桁の文字列で、位置0の値s[0]が番号で、位置1の値s[1]が絵柄です。

・if s[0]>"9": t = d[s[0]]
 else: t = int(s[0])
 番号が9を超える場合、変換辞書で数値にします。
 番号が9を超えない場合、int関数でそのまま数値にします。

・dN[t] = dN.get(t, 0)+1
 番号辞書では、上記で数値化した値tをキーにカウントアップします。
 辞書[キー]ではキー未設定の場合エラーになるので、
 get関数を使い、引数1未設定の場合に引数2、この場合0を返すようにします。
 こうして得た値に、+1します。

・dM[s[1]] = dM.get(s[1], 0)+1
 マーク辞書は、マークのアルファベット値をキーに件数カウントアップします。

2.関数g(L)
・カードの強さ(役+すべての番号)の文字列を返します。
 ただし、すべての番号の並び順は、
 役を構成している番号群を優先し、番号の大きい順です。

・dN, dM = L2dic(L)
 番号辞書dN、マーク辞書dMを取得しておきます。

・maxN, minN, nN, maxNV = max(dN), min(dN), len(dN), max(dN.values())
 nM = len(dM)
 difN = maxN-minN
 役の判定で必要な情報を以下のように前もって準備しておきます。
 最大番号maxN、最小番号minN、番号の種類数nN、番号別件数の最大値maxNV、
 マークの種類数nM、最大番号と最小番号の差difN。
 difNは連番判定で使います。
 
・if nN==5:
 数字が5種類の場合の判定です。

・if difN>4:
  if nM>1: t = 0 #0:High Card
  else: t = 5  #5:Flush
 数字が5種類で、連番以外の場合のチェックです。
 最大番号と最小番号の差が4より大きいということが連番ではないということです。
 この場合はさらにマークの種類がいくつあるかみて、
 マークが複数あれば、役無しで、強さtは0です。
 マークが1種類であれば、フラッシュで、強さtは5です。
 
・elif nM>1: t = 4 #4:Straight
 数字が5種類で、連番の場合、
 マークが複数であれば、ストレートで、強さt=4です。

・elif minN<10: t = 8 #8:Straight Flush
 数字が5種類で、連番で、マークが1種類の場合、
 最小番号が10未満の場合、ストレートフラッシュで、強さt=8です。

・else: t = 9   #9:Royal Straight Flush
 数字が5種類で、連番で、マークが1種類で、最小番号が10以上の場合、
 ストレートフラッシュで、強さt=8です。

・elif nN==4: t = 1  #1:One Pair
 数字が4種類の場合はワンペアしかなく、強さt=1。

・elif nN==3:
  if maxNV==3: t = 3 #3:Three cards
  else: t = 2   #2:Two pairs
 数字が3種類の場合、番号別の件数は(3,1,1)(2,2,1)のどちらかです。
 そこで番号別件数の最大値maxNVが3の場合、スリーカードで、強さt=3です。
 それ以外はツーペアで、強さt=2です。

・elif nN==2:
  if maxNV==4: t = 7 #7:Four in a kind
  else: t = 6   #6:Full House
 数字が2種類の場合、番号別の件数は(4,1)(3,2)のどちらかです。
 そこで番号別件数の最大値maxNVが4の場合、フォーカードで、強さt=7です。
 それ以外はフルハウスで、強さt=6

・s = str(t)
 上記で判定した役の強さをstr関数で文字列にしてsとします。

・for k,v in sorted(dN.items(), key=lambda (k,v):(v,k), reverse=True):
 ループ処理で1ずつ値を取り出せるものをイテレータといいます。
 sorted関数は並べ替えたイテレータを返します。
 最初の引数は並べ替え対象のイテレータです。
 dN.items()は、番号辞書dNのキーと値の組のタプルを返すイテレータです。
 つまり番号8が2枚のときに(8,2)のように組み合わせて返すイテレータです。
 これが並べ替え元です。

 引数keyは並べ替えキーの指定関数です。
 lambda関数で指定しています。lambda関数は無名関数といって、
 関数名を付けられないだけで通常の関数と同様に数式を指定できます。
 これはdef文による名前付き関数を別途定義して、その関数名を指定しても同じです。
 書式は「lambda 引数:数式」です。
 ここでは「lambda (k,v):(v,k)」なので、引数として
 番号辞書dNのキーと値の組み合わせタプル(k,v)を受け取って、(v,k)を返します。
 役を構成する番号はフラッシュ以外すべて複数枚であり、
 しかもその枚数の多い方が判定で優先使用されるため、
 ここでは番号辞書dNの(枚数, 番号)の順に並べ替えます。
 なおフラッシュは番号順に優先使用なのでこの並べ替え優先順位はそのまま使えます。

 引数reverseは逆順指定です。
 初期値はFalseで小さい順です。ここでは大きい順にしたいのでTrueとします。

・s += str(k).zfill(2) * v
 zfillプロパティは文字列を頭0埋めして指定桁数にした値を返します。
 上記で並べ替えた順に番号kと枚数vが設定されるので、
 頭0埋め文字列に変換して枚数の分だけ、順に連結します。

・return s
 上記で設定した、カードの強さ、つまり役+すべての番号の文字列sを呼び出し元に返します。
 例えば、カードが「TC TD QC 6D TS」の場合、10のスリーカードで、
 s=031010101206となります。

3.関数f(sIn)
・問題にあるポーカー用テキストファイルのフルパスを受け取り、プレーヤー1が勝つ回数を返します。

・for s in open(sIn).readlines():
 入力テキストファイルの全行を読み込んで、1行ずつループ変数sとします。

・L = s.split()
 半角空白区切りでリストにします。

・p1, p2 = g(L[:5]), g(L[5:])
 p1,p2はプレーヤー1,2の手札、前半5枚と後半5枚を関数gで変換した強さ文字列です。

・t += int(p1>p2)
 プレーヤー1の方が強い場合、カウンターtを1つカウントアップします。

4.関数の外
・import os, sys
 pythonの標準モジュールであるosモジュールとsysモジュールを使えるように準備します。

・sIn = os.path.join(os.path.dirname(sys.argv[0]), "poker.txt")
 ポーカー用テキストファイルのフルパスです。
 当pyソースと同じフォルダに置いていることを想定しています。
 sys.argv[0]は当pyソースのフルパスです。
 os.path.dirnameでその親フォルダ名を取得し、
 os.path.joinで親フォルダ名とファイル名を結合します。
 
・print f(sIn)
 上記関数fにポーカー用テキストファイルのフルパスを渡し、戻り値を表示します。
 

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
376



2012年8月26日日曜日

Project Euler - Problem 53

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 53
12345から3つ選ぶ選び方は10通りである.
123, 124, 125, 134, 135, 145, 234, 235, 245, 345.
組み合わせでは, 以下の記法を用いてこのことを表す: 5C3 = 10.

一般に, r≦n についてnCr = n!/(r!(n-r)!) である.
ここで, n! = n×(n-1)×...×3×2×1, 0! = 1と階乗を定義する.

n = 23になるまで, これらの値が100万を超えることはない:
23C10 = 1144066.
1≦n≦100について, 100万を超えるnCrは何通りか?

-----


私の解答例は以下です。畳んでいます。
def h(n):
	if n<=1: return 1
	else: return h(n-1)*n

def g(n, m): return h(n)//h(n-m)//h(m)

def f(n, m):
	s = 0
	for i in xrange(1, n+1):
		for j in xrange(1, i+1):
			if g(i, j)>m: s += 1
	return s

print f(100, 1000000)


1.関数h(n)
・nの階乗を返します。problem20と同じです。


2.関数g(n, r)
・順列nCrの値を返します。problem15と同じです。


3.関数f(m):
・順列nC*のすべての組み合わせ値のうちmを超える組の件数を返します。


・s = 0
 sは件数カウンターです。0クリアしておきます。


・for i in xrange(1, n+1):
 ループ関数iは順列nCr、n個のうちr個選ぶ組み合わせのnです。


・for j in xrange(1, i+1):
 ループ関数jは順列nCr、n個のうちr個選ぶ組み合わせのrです。


・if g(i, j)>m: s += 1
 関数g(i,j)で順列値を求めその値がmを超える場合、件数カウンターsをカウントアップします。


・return s
 iとjのループが終わったら、件数カウンターsを返します。

4.関数の外
 関数f(100,1000000)を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
4075

Project Euler - Problem 52

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 52
125874を2倍すると251748となる. これは元の数125874と同じ数を含む.

2x, 3x, 4x, 5x, 6xがxと同じ数を含むような最小の正整数xを求めよ.

-----

私の解答例は以下です。畳んでいます。
def f():
	for i in xrange(1, 10000000):
		s = set(str(i))
		for j in xrange(2, 7):
			t = set(str(i*j))
			if s!=t: break
		else: return i

print f()


1.関数f(n)
・for i in xrange(1, 10000000):
 iは1からの連番です。10000000は、とりあえずの終了値です。
 結果的に6桁で問題に合う値に到達します。

・s = set(str(i))
 連番iをstr関数で文字型にして、set関数で集合型にします。
 集合型にすることで、1文字ずつの重複要素を削除し、構成要素だけの集合にします。

・for j in xrange(2, 7):
 jは掛ける数です。範囲は2から6までです。

・t = set(str(i*j))
 連番iと掛ける数jの積について、上記iと同様に構成要素だけの集合にします。

・if s!=t: break
 元の連番とj倍した値のそれぞれの構成要素が異なれば、ループ変数iの次の値へ処理を進めます。

・else: return i
 ループ変数jのfor文のelseです。
 for文のelseは、for文を全部最後の要素まで回り終わった場合に実行します。
 途中でループを抜けた場合には実行されません。
 ここでは上記if文ででbreakぜずに、2倍値から6倍値までのすべてで、その構成要素が元の数iの構成要素と同じ場合にだけ、そのiを呼び出し元に返します。

2.関数の外
 関数f()を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
142857

Project Euler - Problem 51

Project Euler(プロジェクト オイラー)の問題をpythonでやってみます。

出典: Project Euler(日本語 翻訳サイト)
   ProjectEuler.net(英語サイト)

Problem 51
*3の第1桁を置き換えることで,
13, 23, 43, 53, 73, 83という6つの素数が得られる.

56**3の第3桁と第4桁を同じ数で置き換ることを考えよう.
この5桁の数は7つの素数をもつ最初の例である:
56003, 56113, 56333, 56443, 56663, 56773, 56993.
よって, この族の最初の数である56003は, このような性質を持つ最小の素数である.

桁を同じ数で置き換えることで8つの素数が得られる最小の素数を求めよ.
(注:連続した桁でなくても良い)

-----


私の解答例は以下です。畳んでいます。
def p(n):
	L = [0,0]+[1]*(n-1)
	i = 2
	while i*i<=n:
		while not L[i]: i += 1
		for j in xrange(i+i, n+1, i): L[j] = 0
		i += 1
	return [i for i in xrange(n+1) if L[i]]

def h(n, L):
	for i in xrange(10):
		t = ""
		for j, k in enumerate(str(n)):
			if j in L: t += str(i)
			else: t += k
		yield int(t)

def g(m, n):
	d = {}
	for i, t in enumerate(str(m)):
		d[t] = d.get(t, [])+[i]
	for k,v in sorted(d.items()):
		if len(v)==n: yield int(k),v

def f():
	for i in xrange(3, 100, 3):
		for n in xrange(i+1, 100):
			P = [s for s in p(10**n) if s>10**(n-1)]
			for j in P:
				for k,L in g(j, i):
					if k>2: break
					if L[-1]>=n-1: break
					t = 0
					for u in h(j, L):
						if u not in P: t += 1
						if t>2: break
					if t==2: return j

print f()

候補を絞り込みます。

桁を同じ数で置き換えることで8つの素数が得られる最小の素数とのことなので、
置換後値が素数になる場合の置換数字には0~9うちの8個選ばれるので、
置換数字の最小値には0,1,2のどれかが含まれます。...(A)

素数でない数として、まず2の倍数をはずします。
置換数字には、0~9の10種類の数字のうちの8個選ばれるので、必ず偶数を含みます。
1の位に偶数がきたら2の倍数となり素数ではないので、
置換数字は1の位にきません。...(B)

つまり、置換数字は10の位よりも上の桁になるので、
全体桁数は置換数字の桁数よりも1桁以上多いです。...(C)

次に3の倍数をはずします。
置換数字が1桁の連番の場合、
3で割るとその余りは、0,1,2が繰り返しでてきます。
すると余り0,1,2が最低各3つずつあり、
3の倍数以外が8個あることはあり得ず問題に合いません。

置換数字が2桁の11,22,...の場合も同様で
余り0,1,2が最低各3つずつあり、
3の倍数以外が8個あることはあり得ず問題に合いません。

置換数字が3桁の111,222,...の場合は
置換数字そのものの各桁和が3の倍数なので、置換数字はすべて3の倍数なので、
3で割るとその余りはすべて同じで、0,1,2のどれかです。
この場合は問題に合うことがあり得ます。

置換数字が4桁以上の場合も上記と同様なので、
置換数字の桁数は3の倍数です。...(D)

上記の4条件を使って絞り込みます。
4つの関数を使っています。

1.関数p(n)
・n以下の素数リストを返します。problem37と同じです。

2.関数h(n, L):
・数値nの、位置リストLの位置を0-9に置換した数値を返します。
 指定位置の桁の値を置換数字で置換した値を返します。

・for i in xrange(10):
 ループ変数iは置換数字です。0~9の値を順に取ります。

・t = ""
 置換後数字を1桁ずつためていく変数です。初期値は""です。

・for j, k in enumerate(str(n)):
 ループ変数kは数字nを上から1文字ずつ取り出した値で、jはそのときの0からの連番です。

・if j in L: t += str(i)
 else: t += k
 連番jがリストLに含まれていれば現在位置が置換位置なので、置換数字iをstr関数で文字型変換した値を付け足します。
 そうでなければ数字nを上から1文字ずつ取り出した値kを付け足します。

・yield int(t)
 yield文で値が1つ決まるごとに処理を一旦凍結して呼び出し元に値を返却します。そして再度呼び出されたら処理を再開し続行、次の値を処理します。
 yield文では返却値が1つ決まるごとに処理凍結、値返却、処理再開を繰り返します。

このようにyield文で値を返す関数を「ジェネレータ関数」と呼びます。
これに対してreturn文で値を返し処理を終了してしまう関数は「イテレータ関数」といいます。
1つの関数の中にyield文とreturn文を両方含めることはできません。

3.関数g(m, n):
・数字mにn個含まれる構成文字とその位置リストの組を返します。
 置換数字を置換する位置を求めるときに使います。

・d = {}
 辞書dは数字mに含まれる各桁の値をキーに、その位置のリストを値として持ちます。初期値は空です。

・for i, t in enumerate(str(m)):
  d[t] = d.get(t, [])+[i]
 ループ変数iはenumerate関数による0からの連番で、数字mの中での左からの位置を示します。
 ループ変数tは数字mの上から1文字ずつの値です。
 get関数で、辞書dに数字mの上から1文字ずつの値tがキーとして存在すればtに対応する値を返し、存在しなければ空のリスト[]を返します。
 これに0からの連番iだけをを要素に持つリストを連結することで、
 数字mを構成する文字をキーとする、位置リストの辞書を作成します。

・for k,v in sorted(d.items()):
 items関数で辞書dのキーと値の組をループ変数k,vとして取得します。
 sorted関数でキー順に並べ替えます。
 これは1122のように同じ件数の構成文字が複数種類ある場合、小さい順に処理するためです。

・if len(v)==n: yield int(k),v
 ループ変数vは数字mの構成文字kの位置リストなので、len関数でその個数をみて、指定個数n個と等しければ、int関数で数値にした構成文字kとその位置リストを返します。

4.f():
・主処理です。

・for i in xrange(3, 100, 3):
 ループ変数iは置換文字の桁数です。
 絞り込み検討(D)より、置換数字の桁数は3の倍数なので、
 3から100まで3飛びの値をとります。100というのはとりあえずの終了値です。
 結果的にi=3のときに求める値に到達しました。

・for n in xrange(i+1, 100):
 ループ変数nは全体桁数です。
 絞り込み検討(C)より、全体桁数は置換数字の桁数よりも1以上多いので、
 i+1から100までの値をとります。100というのはとりあえずの終了値です。
 結果的にn=6のときに求める値に到達しました。

・P = [s for s in p(10**n) if s>10**(n-1)]
 Pはn桁の素数リストです。
 10**nは10のn乗、つまりn桁最大値+1なので、p(10**n)はn桁以下の素数リストです。
 内包表記で、for文でn桁以下の素数リストから順番に1つずつ取得してsとして、後ろのif文に送り、n桁の最小値を超える値をforの前に送り、そのまま要素にします。
 なお、n桁の最小値は1000などで決して素数ではありません。
 例えばn=4の場合、p(10000)は9999以下の素数で、Pはこのうち1000を超える素数です。

・for j in P:
 ループ変数jはn桁の素数リストから順に1つずつ取得します。
 このjが求める値の候補です。

・for k,L in g(j, i):
 ループ変数kとLには、関数gで算出した、
 候補jの置換数字候補とその位置リストの組をそれぞれ設定します。

・if k>2: break
 絞り込み検討(A)より、置換数字の最小値には0,1,2のどれかが含まれますので、置換数字候補が2を超えたら、次の候補jに進みます。

・if L[-1]>=n-1: break
 絞り込み検討(B)より、置換数字は1の位にきませんので、
 置換位置リストLの最後の値が全体桁数n-1以上ならば、次の候補jに進みます。

・t = 0
 候補jの置換後数値についての、素数以外カウンターです。
 素数判定の処理が重いで、10個中素数8個ということを素数以外が2個ということに代えて処理を軽くします。

・for u in h(j, L):
 ループ変数uは置換後数値です。
 関数hを使って、候補jの、位置リストLの位置の桁の値を0-9に置換した数値を1つずつ設定します。

・if u not in P: t += 1
 置換後数値uが素数でなければ、素数以外カウンターをカウントアップします。

・if t>2: break
 素数以外が2個以上(素数8個未満)が判明したら、次の値に処理を進めます。

・if t==2: return j
 素数以外が2個(素数8個)で問題の条件に合いますので、候補jを返し処理を終了します。

5.関数の外
 関数f()を呼び出し、その戻り値を出力します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
121313