2012年10月31日水曜日

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

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

問62 「立方数になるような桁の置換をちょうど5つもつ最小の立方数を求めよ」
立方数 41063625 (3453) は, 桁の順番を入れ替えると2つの立方数になる:
56623104 (3843) と 66430125 (4053) である.
41063625は, 立方数になるような桁の置換をちょうど3つもつ最小の立方数である.
立方数になるような桁の置換をちょうど5つもつ最小の立方数を求めよ.


-----





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





私の解答例は以下です。畳んでいます。

def f(n):
	j = 0
	while True:
		j += 1
		L = [i*i*i for i in xrange(10**j)]
		M = [[str(s).count(str(t)) for t in xrange(10)] for s in L]
		for s in M:
			t = M.count(s)
			if t>=n:
				P = [i for i, u in enumerate(M) if s==u]
				N = [L[i] for i in P]
				return N[0], N, P

n = 5
print f(n)

最初は立方数の1つひとつの順列を作り、それが立方数かチェックしようとしましたが、1分では処理が終わりません。
そこで、立方数の1つひとつを構成する数字別個数リストを作って、同じリストになるものの数が指定個数になるかチェックするようにしました。

関数を1つ使っています。

1.関数f(n)
・j = 0
 変数jは10の累乗ごとに区切ってチェックするのでその累乗値カウンタです。初期クリアします。

・while True:
 無限ループです。

・j += 1
 累乗カウントをカウントアップします。

・L = [i*i*i for i in xrange(10**j)]
 Lは立方数リストです。10**jは10のj乗で、0からこの値までを1区切りにして処理を続けます。

・M = [[str(s).count(str(t)) for t in xrange(10)] for s in L]
 Mは数字別個数リストのリストです。二重の内包表記です。
 まず外側の内包表記の「for s in L」で先ほどの立方数リストLから1つずつ取り出しfor文の前の内側の内包表記に渡します。
 次に内側の内包表記の「for t in xrange(10)」で、ループ変数tに0以上10未満の整数が1つずつ設定されfor文の前に渡されます。
 「str(s).count(str(t))」で、立方数sに含まれる0以上10未満のいずれかのtの個数を数えます。
 例えば、s=1006012008の場合、内側の内包表記は[5,2,1,0,0,0,1,0,1,0]です。
 このような内側の内包表記が数字別個数リストで、外側の内包表記でそれをさらにリストにためています。

・for s in M:
 Mは数字別個数リストのリストから1つずつ取り出した数字別個数リストをループ変数sに設定します。

・t = M.count(s)
 リストMにある、ループ変数sの数字別個数リストと同じものの件数を数えtとします。

・if t>=n:
 上記tが指定個数n以上ならば、問題に合います。

・P = [i for i, u in enumerate(M) if s==u]
 Pは問題にあう立方数の元の数のリストです。
 enumerate関数で0からの連番を発生させます。このインデックス値が立方数の元の数です。

・N = [L[i] for i in P]
 リストPの値をインデックス値に立方数リストLから値を集め、リストNとします。

・return N[0], N, P
 問題に合う最小の立方数、桁を置換した立方数のリスト、その元の数のリストの3つを呼び出し元に返します。
 
2.関数の外
・n = 5
 print f(n)
 問題に合うように指定個数を5個として関数fで関連情報リストも含めて取得し、表示します。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。(127035954683L,
[127035954683L, 352045367981L, 373559126408L, 569310543872L, 589323567104L],
[5027, 7061, 7202, 8288, 8384])

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

2012年10月30日火曜日

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

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

問61 「6つの巡回する4桁の図形数となる唯一の集合の和を求めよ」
三角数, 四角数, 五角数, 六角数, 七角数, 八角数は多角数であり, それぞれ以下の式で生成される.

三角数P3,n=n(n+1)/2 1, 3, 6, 10, 15, ...
四角数P4,n=n2 1, 4, 9, 16, 25, ...
五角数P5,n=n(3n-1)/2 1, 5, 12, 22, 35, ...
六角数P6,n=n(2n-1) 1, 6, 15, 28, 45, ...
七角数P7,n=n(5n-3)/2 1, 7, 18, 34, 55, ...
八角数P8,n=n(3n-2) 1, 8, 21, 40, 65, ...

3つの4桁の数の順番付きの集合 (8128, 2882, 8281) は以下の面白い性質を持つ.
  1. この集合は巡回的である. 最後の数も含めて, 各数の後半2桁は次の数の前半2桁と一致する
  2. それぞれ多角数である: 三角数 (P3,127=8128), 四角数 (P4,91=8281), 五角数 (P5,44=2882) がそれぞれ別の数字で集合に含まれている
  3. 4桁の数の組で上の2つの性質をもつはこの組だけである.
同じように, 6つの4桁の数からなる順番付きの集合で,
  1. 集合は巡回的である
  2. 三角数, 四角数, 五角数, 六角数, 七角数, 八角数が全て表れる唯一の集合を求め, その和を求めよ.

-----

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






私の解答例は以下です。たたんでいます。
def p3(n): return n*(n+1)//2
def p4(n): return n*n
def p5(n): return n*(3*n-1)//2
def p6(n): return n*(2*n-1)
def p7(n): return n*(5*n-3)//2
def p8(n): return n*(3*n-2)

def h(f, a, b):
	m = __import__(__name__)
	L = []
	for i in xrange(b):
		s = getattr(m, f)(i)
		if a<=s<b: L.append(s)
		elif b<=s: break
	return L

def g(L, M=[], n=2):
	if len(L)<=len(M): return M
	for s in L[len(M)]:
		if M and str(M[-1])[-n:]!=str(s)[:n]: continue
		if len(L)-len(M)>1:
			M = g(L, M[:]+[s], n)
			if len(L)<=len(M): break
			if s==L[len(M)][-1]: break
		elif str(s)[-n:]==str(M[0])[:n]: return M+[s]	
	else:
		M.pop()
	return M

def f(a, b, n=4, m=2):
	import itertools
	P = [h("p"+str(i), 10**(n-1), 10**n) for i in xrange(a, b+1)]
	for t in itertools.permutations(xrange(b+1-a)):
		L = [P[int(i)] for i in t]
		M = g(L, n=m)
		if M: break
	return sum(M), M, [str(i+a)+"kakusu" for i in t]

if __name__=="__main__":
	s, M, t = f(a=3, b=8, n=4, m=2)
	print s, M
	print t

角数の関数6個と他3個の関数を使っています。

1.関数p3(n)~p8(n)
・三角数から八角数の定義式のとおりです。

2.関数h(f, a, b)
・当pythonソースファイル内の関数fにb未満の値を渡し、a以上b未満の戻り値のリストを返します。

・m = __import__(__name__)
 __import__関数は、指定したモジュールを実行時に動的にインポートする関数です。
 _name__変数には、このプログラムのpythonソースのモジュール名(ファイル名の拡張子よりも前の部分)が設定されています。
 通常のimport関数はインポートするモジュール名を事前に固定文字列で指定しておきますが、自分のファイル自身をインポートする場合はファイル名変更しても影響を受けないように、動的にインポートします。
 後で関数を動的にインポートするための準備です。
 変数mには動的にインポートしたモジュールオブジェクトが設定されます。
 
・for i in xrange(b):
 ループ変数iは0以上b未満の範囲で1ずつ増加します。

・s = getattr(m, f)(i)
 getattr(m, f)関数は、モジュールmの関数fを呼び出します。
 変数iは関数fの引数です。
 当pythonソースファイル中の関数fに先ほどのループ変数iを渡し、その戻り値を変数sとします。

・if a<=s<b: L.append(s)
 elif b<=s: break
 上記の値sが、a以上b未満ならリストLに追加し、b以上なら終了します。

3.関数g(L, M=[], n=2)
・リストLは三角数などの角数リストが格納されたリストです。
 リストMはリストLの要素である角数リストを順にn桁で巡回的連結した値を格納したのリストです。
 リストLの全部の要素リストをn桁で巡回的連結した場合、その連結した値のリストを返します。

・if len(L)<=len(M): return M
 自分自身を呼び出す再帰関数なので、まず再帰呼び出し終了条件です。
 リストLの要素リストの個数以上に、順に巡回的連結した値の個数があれば順に巡回的に連結できた値のリストMを返し処理終了です。

・for s in L[len(M)]:
 len(M)は巡回的連結済みの個数であり、リストLのインデックス値としては次の巡回的連結要素を探すリストのインデックス値です。
 リストLから次に巡回的に連結する値を探す対象の角数リストを選び、ループ変数sとします。

・if M and str(M[-1])[-n:]!=str(s)[:n]: continue
 リストMに要素があり、その最後の要素がループ変数sにn桁で巡回的連結しなければsは対象外なので、次のループ変数sにループを進めます。
 リストMの最後の要素とループ変数sがn桁で巡回的連結している場合は以下の処理を続けます。

・if len(L)-len(M)>1:
  M = g(L, M[:]+[s], n)
  if len(L)<=len(M): break
 巡回的連結値の個数とリストLの要素数の差が1より大きい、つまり、リストLの最後までいっていない場合、さらに自分自身である関数g()を再帰呼び出しして、巡回的連結可能な値を探します。
 この再帰呼び出しのときの引数はリストLと巡回判定桁数nはそのままの値で、巡回的連結値リストMにはsを追加した状態で次を探します。
 再帰呼び出しから戻ってきたら、最初の行と同じ終了判定をします。

・elif str(s)[-n:]==str(M[0])[:n]: return M+[s]
 巡回的連結値の個数よりも、リストLの要素数が多くないということは、これらの個数が等しく、リストLの要素である角数リストすべてから巡回的連結値を探し出した状態なので、最後のチェックとして先頭要素との巡回的連結で連結の輪が閉じれるかを判定します。まだリストMに追加前のループ変数sからリストMの最後の要素にn桁で巡回的連結できればリストMが完成しこれを呼び出し元に返します。
 M+[s]は、リストMと要素sだけのリストの連結です。appendして返すことと同じです。

・else:
 M.pop()
 このelseはループ変数sのfor文のelseです。
 for文が途中でbreakせず、ループ変数の最後の要素まで処理し終った後に行う処理です。
 ここでは呼び出し元に返すこともなくfor文が終わったということなので、現在にリストMの最後の要素が巡回的連結先が無いことが確定しているので、このリストMの最後の要素をpop()関数で取り除きます。
 
・return M
 最後まで処理が来た場合は、リストMをそのまま返します。

4.f(a, b, n=4, m=2)
・n桁のa角数からb角数を1つずつ選びm桁で巡回的につながる組を返します。

・import itertools
 標準モジュールitertoolsを使えるようにしておきます。

・P = [h("p"+str(i), 10**(n-1), 10**n) for i in xrange(a, b+1)]
 リストPは、関数h()による角数リストをためたリストです。内包表記で書いています。
 for文でループ変数iにa以上b以下の整数を1つずつ設定します。
 ループ変数iは前に送られ、関数h()を呼び出しその戻り値を設定します。
 関数hの第1引数で、"p"+str(i)、つまり三角数などの関数、「p3」などを指定します。第2引数と第3引数で戻り値がn桁になるようします。例えばn=3の場合、100、1000を引き渡します。
 
・for t in itertools.permutations(xrange(b+1-a)):
 ループ変数tは、後ででてくるリストLのindex番号の組合せに使います。
 permutations()は順列を返す関数です。
 例えばrange(3)ならば、012, 021, 102, 120, 201, 210と順に返します。
 xrange(b+1-a)で、角数リストの個数分だけ0からの連番を発生させます。
 
・L = [P[int(i)] for i in t]
 上記tで先ほどのリストPの中を組み替えて、6つの角数リストを任意の順に組み替えます。
・M = g(L, n=m)
 if M: break
 関数g()を呼び出し、空のリスト(論理的にFalse)ならば、ループを続け次の
 順列の角数リストで巡回的連結可能な値を探しにいきます。

・return sum(M), M, [str(i+a)+"kakusu" for i in t]
 巡回的連結がした値の合計値、その値リスト、およびどの角数リストからとってきたかを呼び出し元に返します。

5.関数の外
・s, M, t = f(a=3, b=8, n=4, m=2)
 print s, M
 print t
 三角数から八角数まで各角数のうち4桁の値を1つずつ2桁で循環的連結できる値の和、そのときの値のリスト、どの角数リストからとってきたかのリストの3つを受け取り表示します。

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
28684 [8256, 5625, 2512, 1281, 8128, 2882]
['3kakusu', '4kakusu', '7kakusu', '8kakusu', '6kakusu', '5kakusu']


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

2012年10月9日火曜日

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

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

問60 「任意の2つの素数を繋げたときに別の素数が生成される, 5つの素数の組を求めよ」
素数3, 7, 109, 673は非凡な性質を持っている.
任意の2つの素数を任意の順で繋げると, また素数になっている.
例えば, 7と109を用いると, 7109と1097の両方が素数である.
これら4つの素数の和は792である.
れは, このような性質をもつ4つの素数の組の和の中で最小である.

任意の2つの素数を繋げたときに別の素数が生成される, 5つの素数の組の和の中で最小のものを求めよ.


-----

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





最初にコーディングしたときにfor文の五重ループになってしまったので、どうしても再帰関数に改良したかったことと、処理速度の向上とで、1ヶ月近くかかってしまいました。
上限値を設けてやっと1分ルールを満たしているので、まだまだロジックが甘いのですが、行き詰まってしまったこともあり、現在のプログラムのまま提示しておきます。

答えは合っていますが、問題では「最小のもの」を求めるところ、題意を満たす「最初のもの」を求めるプログラムです。

私の解答例は以下です。畳んでいます。
def h(n):
	if n==2: return True
	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 e(n):
	i = n+1+int(n%2)
	while not h(i): i += 2
	return i

def g(L, t):
	t = str(t)
	for s in L:
		s = str(s)
		if not h(int(s+t)): return False
		if not h(int(t+s)): return False
	return True

def f(n, L=[], p=0):
	if len(L)>=n: return L
	while p<10**(n-1):
		p = e(p)
		if g(L, p):
			L = f(n, L+[p], p)
			if len(L)>=n: break
	else:
		L.pop()
	return L

n = 5
s = f(n)
print sum(s), s


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

1.関数h(n)
・素数判定です。問58で作成したものを流用しました。
 素数ならTrue、素数でなければFalseを返します。

2.関数e(n)
・引数を超える最小の素数を返します。n>1のとき有効です。

・i = n+1+int(n%2)
 変数iが素数候補です。
 %演算子は割り算の余りです。割り切れれば0、割り切れないと1なので、
 式全体では偶数なら1足し、奇数なら2足すことになります。
 後続処理の準備として、素数候補iを奇数にしておきます。

・while not h(i): i += 2
 関数h()で素数判定をして、素数でなければ2ずつ増やしてチェックします。
 前準備でこの処理にくるときには素数候補iは奇数にしているので、
 iが偶数で無限ループになることはありません。

・return i
 ここまでの処理でnを超える最初の素数としてiを返却します。

3.関数g(L, t)
・引数リストLの各要素と引数tを任意の順に2つ連結した値が、すべて素数かどうかを判定します。

・t = str(t)
 後で連結するので、引数tを文字型にします。

・for s in L:
 引数リストLから順にループ変数sに設定します。

・s = str(s)
 後で連結するので、ループ変数sを文字型にします。

・if not h(int(s+t)): return False
 if not h(int(t+s)): return False
 上記の文字列sと文字列tを前後とその入れ替えの2パターンで連結し、
 int関数で数値化して、関数hで素数判定します。
 素数でなければ即終了でFalseを返します。

・return True
 引数リストLと引数tでここまでで処理を抜けていなければ、そのすべての組合せが素数なので、Trueを返します。

4.関数f(n, L=[], p=0)
・問題に合う値のリストを返します。
 自分で自分を呼び出す記述のある再帰関数です。
 引数nは求める素数の個数です。
 引数Lは素数を格納するリストで初期値は空リストです。
 引数pは最後にチェックした素数で、初期値は0とします。

・if len(L)>=n: return L
 再帰関数の終了条件です。
 リストLに求める個数n個以上の要素がたまったら処理終了で、リストLを返します。

・while p<10**(n-1):
 対象とする素数が青天井では処理が終わらないので、とりあえず求める素数の個数分の桁数、10**(n-1)、未満の素数で、処理することにしました。
 求める素数が2個なら10以内の素数の組で最小組[3,7]が見つかり、
 同じく3個なら100以内の素数の組で最小組[3, 37, 67]が見つかり、
 同じく4個なら問題文にあるように1000以内の素数の組が見つかります。

・p = e(p)
 得られている最後の素数pを関数eで処理し、次に大きい素数を改めてpとします。

・if g(L, p):
 関数gで引数リストLの各要素と引数pを任意の順に2つ連結した値が、すべて素数かどうかを判定します。Falseの場合はwhileループの次の値へ進みます。

・L = f(n, L+[p], p)
 1つ前の関数g()ですべて素数の場合、次に大きい素数pを候補として、さらに題意にある素数を探して追加するため、当関数f()を再帰呼び出しします。
 なお、第2引数の「L+[p]」は、再帰呼び出しで引数Lで受けるので、お試しでリストLの末尾に追加することになります。

・if len(L)>=n: break
 再帰呼び出しから戻り、リストLの要素数がn以上ならば探索終了でwhileループから抜けます。

・else:
 L.pop()
 このelse節はインデント(行頭のずらし位置)から明らかなように、for文のelseです。直前のif文のelse節ではありません。
 for文のelse節の処理タイミングは、forループの最後の値まで処理が行われた場合のループ終了直後です。
 ここまでの処理でbreakで抜けない場合、再帰呼び出しの際にお試しで追加したpが求める値ではないということになります。
 このため、関数pop()で末尾の要素を削除しておきます。

・return L
 処理の最後にリストLを呼び出し元に戻します。

5.関数の外
・n = 5
 s = f(n)
 print sum(s), s
 問題に合うように引数の値を5として関数f()を呼び出します。
 返されたリストをsとして、そのリスト要素合計、及び参考としてリスト要素を表示します。


 
解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
26033 [13, 5197, 5701, 6733, 8389]


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

2012年9月12日水曜日

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

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

問59「XOR暗号化された暗号文を解読できるか?」
各文字はそれぞれ一意のコードに割り当てられている.よく使われる標準としてASCII (American Standard Code for Information Interchange) がある.ASCIIでは, 大文字A = 65, アスタリスク (*) = 42, 小文字k = 107というふうに割り当てられている.

モダンな暗号化の方法として, テキストファイルの各バイトをASCIIに変換し, 秘密鍵から計算された値とXORを取るという手法がある.
XOR関数の良い点は, 暗号化に用いたのと同じ暗号化鍵でXORを取ると平文を復号できる点である.
65 XOR 42 = 107であり, 107 XOR 42 = 65である.

破られない暗号化のためには, 鍵は平文と同じ長さのランダムなバイト列でなければならない.ユーザーは暗号文と暗号化鍵を別々の場所に保存する必要がある.また, もし一方が失われると, 暗号文を復号することは不可能になる.
悲しいかな, この手法はほとんどのユーザーにとって非現実的である.

そこで, 鍵の変わりにパスワードを用いる手法が用いられる.パスワードが平文より短ければ (よくあることだが), パスワードは鍵として繰り返し用いられる.
この手法では, 安全性を保つために十分長いパスワードを用いる必要があるが, 記憶するためにはある程度短くないといけない.

この問題での課題は簡単になっている.
暗号化鍵は3文字の小文字である.
cipher1.txtは暗号化されたASCIIのコードを含んでいる.
また, 平文はよく用いられる英単語を含んでいる.

この暗号文を復号し, 平文のASCIIでの値の和を求めよ.

-----


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






私の解答例は以下です。畳んでいます。
def g(s, n):
	import itertools
	for t in itertools.product(s, repeat=n): yield "".join(t)

def f(sIn, a, n):
	L = [int(t) for t in open(sIn).read().split(",")]
	for s in g(a, n):
		M = ([ord(t) for t in s]*len(L))[:len(L)]
		N = [i^j for i,j in zip(L, M)]
		R = [chr(i) for i in N]
		t = "".join(R)
		if " the " in t: return sum(N), s, t

import os
sIn = os.path.join(os.path.dirname(__file__), "cipher1.txt")
a = "abcdefghijklmnopqrstuvwxyz"
n = 3
print f(sIn, a, n)


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

1.関数g(s, n)
・文字列sからn個選んだ直積を連結した文字列を返します。
 直積とは、重複を許し順番が違えば別のものとして扱う組み合わせです。
 例えば、abcから2つ選んだ直積は、aa,ab,ac,ba,bb,bc,ca,cb,ccです。

・import itertools
 標準モジュールitertoolsを使えるようにしておきます。

・for t in itertools.product(s, repeat=n): yield "".join(t)
 標準モジュールitertoolsにあるproduct関数で、文字列sからをn個選んだ直積の構成要素のタプルを生成し、for文のループ変数tに設定します。
 forループの中で、タプルtを区切り文字無しで連結し、yield文で返します。
 yield文で返すと、返した時点で処理内容が一旦凍結され、再度呼び出されたときに処理を再開して続きの処理による値を返します。
 このような動きをするyield文で返す関数をジェネレータ関数といいます。
 なお、単発呼び出しで終了するreturn文で返す関数はイテレータ関数といいます。
 また、product関数はpython2.6以上です。
 python2.5にもitertoolsモジュールはありますが、product関数が含まれていません。

2.関数f(sIn, a, n)
・主処理です。
・入力ファイルsInの中の文字列を、指定した文字列aからn個選んだ秘密鍵sによって順にxor演算で復号した数値列N、及びこれをasciiコード扱いして得られる復号文字列tがあるとき、数値列Nの合計値、キー文字列s、復号文字列tを返します。

・引数sInは入力ファイルのフルパス、文字列aは秘密鍵の構成文字候補の文字列、nは秘密鍵の桁数です。

・L = [int(t) for t in open(sIn).read().split(",")]
 暗号文の各数値をもつリストLを作成します。
 入力ファイルsInを開き、その内容をすべて読み込んで、カンマで区切った文字列を
 ループ変数tに設定します。
 このtをfor文の前に送り、int関数で数値化してリストLとします。
 なお、開いたファイルオブジェクトはpythonがタイミングをみて閉じくれます。

・for s in g(a, n):
 関数gで文字列aからn個選んだ秘密鍵候補をループ変数sとします。

・M = ([ord(t) for t in s]*len(L))[:len(L)]
 Mは、秘密鍵候補sの構成要素の数値を暗号文の長さまで繰り返した復号用鍵のタプルです。
 内包表記のループ変数tとして秘密鍵候補sの文字1つずつを、for文の前に送り、ord関数でasciiコードとして対応する数値にしてリスト化します。
 このリストをlen(L)倍、つまり暗号文の長さ倍して、秘密鍵を順に繰り返し連結した状態にしてから、[:len(L)]で暗号文の長さの分だけ取り出しておきます。
 
・N = [i^j for i,j in zip(L, M)]
 Nは復号化した数値のリストです。
 zip関数で上記のリストLとリストMから1つずつ組み合わせて取り出し、ループ変数i,jとし、内包表記のfor文の前に送り、^演算子で数値j,jのxorを計算しリストにため、リストNとします。

・R = [chr(i) for i in N]
 復号数値リストNから1つずと取り出しchr関数でascii文字化してリストにため、リストRとします。
 
・t = "".join(R)
 上記の文字列リストRの要素を区切り文字無しで連結し、復号文tとします。

・if " the " in t: return sum(N), s, t
 復号が正しいか判定します。
 判定は「よく用いられる英単語」があるという問題文に従い、半角空白ではさんだ「 the 」としてみました。結果的にこれで判定できました。
「 the 」があれば、復号化した数値リストNの要素の合計、秘密鍵、復号文tを返します。

3.関数の外
・import os
 ファイルを扱うので、標準モジュールosを使えるように取り込んでおきます。

・sIn = os.path.join(os.path.dirname(__file__), "cipher1.txt")
 暗号文のフルパスです。
 「__file__」は実行するpythonファイルのフルパスです。
 os.path.dirname関数で親フォルダまで取得し、ファイル名とともに、
 os.path.joinでパス表示を連結しました。
 つまり、暗号文は実行するpythonファイルと同じフォルダに置いて実行するように記述しています。
 
・a = "abcdefghijklmnopqrstuvwxyz"
 秘密鍵の候補です。問題文から小文字のアルファベットです。

・n = 3
 秘密鍵の桁数です。問題文から3としました。

 

解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
107359
秘密鍵はgod。平文の内容は、聖書ヨハネによる福音書の第1章第1節から第14節。

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

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 ネタバレ注意の追記など

プロジェクトオイラー 問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 ネタバレ注意の追記など